Class: Familia::Encryption::Manager

Inherits:
Object
  • Object
show all
Defined in:
lib/familia/encryption/manager.rb

Overview

High-level encryption manager - replaces monolithic Encryption module

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(algorithm: nil) ⇒ Manager

Returns a new instance of Manager.

Raises:



11
12
13
14
15
# File 'lib/familia/encryption/manager.rb', line 11

def initialize(algorithm: nil)
  Registry.setup! if Registry.providers.empty?
  @provider = algorithm ? Registry.get(algorithm) : Registry.default_provider
  raise EncryptionError, 'No encryption provider available' unless @provider
end

Instance Attribute Details

#providerObject (readonly)

Returns the value of attribute provider.



9
10
11
# File 'lib/familia/encryption/manager.rb', line 9

def provider
  @provider
end

Instance Method Details

#decrypt(encrypted_json_or_hash, context:, additional_data: nil) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/familia/encryption/manager.rb', line 37

def decrypt(encrypted_json_or_hash, context:, additional_data: nil)
  return nil if encrypted_json_or_hash.nil? || (encrypted_json_or_hash.respond_to?(:empty?) && encrypted_json_or_hash.empty?)

  # Increment counter immediately to track all decryption attempts, even failed ones
  Familia::Encryption.derivation_count.increment

  begin
    # Delegate parsing and instantiation to EncryptedData.from_json
    # Wrap validation errors for security (don't expose internal structure details)
    begin
      data = Familia::Encryption::EncryptedData.from_json(encrypted_json_or_hash)
      raise EncryptionError, 'Failed to parse encrypted data' unless data
    rescue EncryptionError => e
      # Re-wrap validation errors with generic message for security
      raise EncryptionError, "Decryption failed: #{e.message}"
    end

    # Validate algorithm support
    provider = Registry.get(data.algorithm)
    key = derive_key_without_increment(context, version: data.key_version, provider: provider)

    # Safely decode and validate sizes
    nonce = decode_and_validate(data.nonce, provider.nonce_size, 'nonce')
    ciphertext = decode_and_validate_ciphertext(data.ciphertext)
    auth_tag = decode_and_validate(data.auth_tag, provider.auth_tag_size, 'auth_tag')

    provider.decrypt(ciphertext, key, nonce, auth_tag, additional_data)
  rescue EncryptionError
    raise
  rescue Familia::SerializerError => e
    raise EncryptionError, "Invalid JSON structure: #{e.message}"
  rescue StandardError => e
    raise EncryptionError, "Decryption failed: #{e.message}"
  end
ensure
  Familia::Encryption.secure_wipe(key) if key
end

#encrypt(plaintext, context:, additional_data: nil) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/familia/encryption/manager.rb', line 17

def encrypt(plaintext, context:, additional_data: nil)
  return nil if plaintext.to_s.empty?

  key = derive_key(context)

  result = @provider.encrypt(plaintext, key, additional_data)

  encrypted_data = Familia::Encryption::EncryptedData.new(
    algorithm: @provider.algorithm,
    nonce: Base64.strict_encode64(result[:nonce]),
    ciphertext: Base64.strict_encode64(result[:ciphertext]),
    auth_tag: Base64.strict_encode64(result[:auth_tag]),
    key_version: current_key_version
  ).to_h

  Familia::JsonSerializer.dump(encrypted_data)
ensure
  Familia::Encryption.secure_wipe(key) if key
end