Class: Familia::Encryption::EncryptedData

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#algorithmObject (readonly)

Returns the value of attribute algorithm

Returns:

  • (Object)

    the current value of algorithm



7
8
9
# File 'lib/familia/encryption/encrypted_data.rb', line 7

def algorithm
  @algorithm
end

#auth_tagObject (readonly)

Returns the value of attribute auth_tag

Returns:

  • (Object)

    the current value of auth_tag



7
8
9
# File 'lib/familia/encryption/encrypted_data.rb', line 7

def auth_tag
  @auth_tag
end

#ciphertextObject (readonly)

Returns the value of attribute ciphertext

Returns:

  • (Object)

    the current value of ciphertext



7
8
9
# File 'lib/familia/encryption/encrypted_data.rb', line 7

def ciphertext
  @ciphertext
end

#key_versionObject (readonly)

Returns the value of attribute key_version

Returns:

  • (Object)

    the current value of key_version



7
8
9
# File 'lib/familia/encryption/encrypted_data.rb', line 7

def key_version
  @key_version
end

#nonceObject (readonly)

Returns the value of attribute nonce

Returns:

  • (Object)

    the current value of nonce



7
8
9
# File 'lib/familia/encryption/encrypted_data.rb', line 7

def nonce
  @nonce
end

Class Method Details

.from_json(json_string_or_hash) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/familia/encryption/encrypted_data.rb', line 49

def self.from_json(json_string_or_hash)
  # Support both JSON strings (legacy) and already-parsed Hashes (v2.0 deserialization)
  if json_string_or_hash.is_a?(Hash)
    # Already parsed - use directly
    parsed = json_string_or_hash
    # Symbolize keys if they're strings
    parsed = parsed.transform_keys(&:to_sym) if parsed.keys.first.is_a?(String)
    new(**parsed)
  else
    # JSON string - validate and parse
    validate!(json_string_or_hash)
  end
end

.valid?(json_string) ⇒ Boolean

Class methods for parsing and validation

Returns:

  • (Boolean)


9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/familia/encryption/encrypted_data.rb', line 9

def self.valid?(json_string)
  return true if json_string.nil? # Allow nil values
  return false unless json_string.is_a?(::String)

  begin
    parsed = Familia::JsonSerializer.parse(json_string, symbolize_names: true)
    return false unless parsed.is_a?(Hash)

    # Check for required fields
    required_fields = %i[algorithm nonce ciphertext auth_tag key_version]
    result = required_fields.all? { |field| parsed.key?(field) }
    Familia.debug "[valid?] result: #{result}, parsed: #{parsed}, required: #{required_fields}"
    result
  rescue Familia::SerializerError => e
    Familia.debug "[valid?] JSON error: #{e.message}"
    false
  end
end

.validate!(json_string) ⇒ Object

Raises:



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/familia/encryption/encrypted_data.rb', line 28

def self.validate!(json_string)
  return nil if json_string.nil?

  raise EncryptionError, "Expected JSON string, got #{json_string.class}" unless json_string.is_a?(::String)

  begin
    parsed = Familia::JsonSerializer.parse(json_string, symbolize_names: true)
  rescue Familia::SerializerError => e
    raise EncryptionError, "Invalid JSON structure: #{e.message}"
  end

  raise EncryptionError, "Expected JSON object, got #{parsed.class}" unless parsed.is_a?(Hash)

  required_fields = %i[algorithm nonce ciphertext auth_tag key_version]
  missing_fields = required_fields.reject { |field| parsed.key?(field) }

  raise EncryptionError, "Missing required fields: #{missing_fields.join(', ')}" unless missing_fields.empty?

  new(**parsed)
end

Instance Method Details

#decryptable?Boolean

Instance methods for decryptability validation

Returns:

  • (Boolean)


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/familia/encryption/encrypted_data.rb', line 64

def decryptable?
  return false unless algorithm && nonce && ciphertext && auth_tag && key_version

  # Ensure Registry is set up before checking algorithms
  Registry.setup! if Registry.providers.empty?

  # Check if algorithm is supported
  return false unless Registry.providers.key?(algorithm)

  # Validate Base64 encoding of binary fields
  begin
    Base64.strict_decode64(nonce)
    Base64.strict_decode64(ciphertext)
    Base64.strict_decode64(auth_tag)
  rescue ArgumentError
    return false
  end

  true
end

#validate_decryptable!Object

Raises:



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/familia/encryption/encrypted_data.rb', line 85

def validate_decryptable!
  raise EncryptionError, 'Missing algorithm field' unless algorithm

  # Ensure Registry is set up before checking algorithms
  Registry.setup! if Registry.providers.empty?

  raise EncryptionError, "Unsupported algorithm: #{algorithm}" unless Registry.providers.key?(algorithm)

  unless nonce && ciphertext && auth_tag && key_version
    missing = []
    missing << 'nonce' unless nonce
    missing << 'ciphertext' unless ciphertext
    missing << 'auth_tag' unless auth_tag
    missing << 'key_version' unless key_version
    raise EncryptionError, "Missing required fields: #{missing.join(', ')}"
  end

  # Get the provider for size validation
  provider = Registry.providers[algorithm]

  # Validate Base64 encoding and sizes
  begin
    decoded_nonce = Base64.strict_decode64(nonce)
    if decoded_nonce.bytesize != provider.nonce_size
      raise EncryptionError, "Invalid nonce size: expected #{provider.nonce_size}, got #{decoded_nonce.bytesize}"
    end
  rescue ArgumentError
    raise EncryptionError, 'Invalid Base64 encoding in nonce field'
  end

  begin
    Base64.strict_decode64(ciphertext) # ciphertext can be variable size
  rescue ArgumentError
    raise EncryptionError, 'Invalid Base64 encoding in ciphertext field'
  end

  begin
    decoded_auth_tag = Base64.strict_decode64(auth_tag)
    if decoded_auth_tag.bytesize != provider.auth_tag_size
      raise EncryptionError,
            "Invalid auth_tag size: expected #{provider.auth_tag_size}, got #{decoded_auth_tag.bytesize}"
    end
  rescue ArgumentError
    raise EncryptionError, 'Invalid Base64 encoding in auth_tag field'
  end

  # Validate that the key version exists
  unless Familia.config.encryption_keys&.key?(key_version.to_sym)
    raise EncryptionError, "No key for version: #{key_version}"
  end

  self
end