Class: Familia::Encryption::EncryptedData
- Inherits:
-
Data
- Object
- Data
- Familia::Encryption::EncryptedData
- Defined in:
- lib/familia/encryption/encrypted_data.rb
Instance Attribute Summary collapse
-
#aad_fields ⇒ Object
readonly
Returns the value of attribute aad_fields.
-
#algorithm ⇒ Object
readonly
Returns the value of attribute algorithm.
-
#auth_tag ⇒ Object
readonly
Returns the value of attribute auth_tag.
-
#ciphertext ⇒ Object
readonly
Returns the value of attribute ciphertext.
-
#encoding ⇒ Object
readonly
Returns the value of attribute encoding.
-
#envelope_version ⇒ Object
readonly
Returns the value of attribute envelope_version.
-
#key_material_fields ⇒ Object
readonly
Returns the value of attribute key_material_fields.
-
#key_version ⇒ Object
readonly
Returns the value of attribute key_version.
-
#nonce ⇒ Object
readonly
Returns the value of attribute nonce.
Class Method Summary collapse
- .from_json(json_string_or_hash) ⇒ Object
-
.valid?(json_string) ⇒ Boolean
Class methods for parsing and validation.
- .validate!(json_string) ⇒ Object
Instance Method Summary collapse
-
#decryptable? ⇒ Boolean
Instance methods for decryptability validation.
-
#encoding_valid? ⇒ Boolean
The encoding field is optional (pre-#229 envelopes omit it and decrypt as UTF-8).
- #has_key_material? ⇒ Boolean
-
#initialize(algorithm:, nonce:, ciphertext:, auth_tag:, key_version:, encoding: nil, envelope_version: nil, aad_fields: nil, key_material_fields: nil) ⇒ EncryptedData
constructor
A new instance of EncryptedData.
- #stored_aad_fields ⇒ Object
-
#to_h ⇒ Object
Omit nil-valued keys from the hash representation so that the encrypted envelope stays backward-compatible (no :encoding key unless explicitly set).
- #to_json(*_args) ⇒ Object
- #validate_decryptable! ⇒ Object
- #validate_encoding! ⇒ Object
- #with_metadata(envelope_version: self.envelope_version, aad_fields: self.aad_fields, key_material_fields: self.key_material_fields) ⇒ Object
Constructor Details
#initialize(algorithm:, nonce:, ciphertext:, auth_tag:, key_version:, encoding: nil, envelope_version: nil, aad_fields: nil, key_material_fields: nil) ⇒ EncryptedData
Returns a new instance of EncryptedData.
21 22 23 24 |
# File 'lib/familia/encryption/encrypted_data.rb', line 21 def initialize(algorithm:, nonce:, ciphertext:, auth_tag:, key_version:, encoding: nil, envelope_version: nil, aad_fields: nil, key_material_fields: nil) super end |
Instance Attribute Details
#aad_fields ⇒ Object (readonly)
Returns the value of attribute aad_fields
19 20 21 |
# File 'lib/familia/encryption/encrypted_data.rb', line 19 def aad_fields @aad_fields end |
#algorithm ⇒ Object (readonly)
Returns the value of attribute algorithm
19 20 21 |
# File 'lib/familia/encryption/encrypted_data.rb', line 19 def algorithm @algorithm end |
#auth_tag ⇒ Object (readonly)
Returns the value of attribute auth_tag
19 20 21 |
# File 'lib/familia/encryption/encrypted_data.rb', line 19 def auth_tag @auth_tag end |
#ciphertext ⇒ Object (readonly)
Returns the value of attribute ciphertext
19 20 21 |
# File 'lib/familia/encryption/encrypted_data.rb', line 19 def ciphertext @ciphertext end |
#encoding ⇒ Object (readonly)
Returns the value of attribute encoding
19 20 21 |
# File 'lib/familia/encryption/encrypted_data.rb', line 19 def encoding @encoding end |
#envelope_version ⇒ Object (readonly)
Returns the value of attribute envelope_version
19 20 21 |
# File 'lib/familia/encryption/encrypted_data.rb', line 19 def envelope_version @envelope_version end |
#key_material_fields ⇒ Object (readonly)
Returns the value of attribute key_material_fields
19 20 21 |
# File 'lib/familia/encryption/encrypted_data.rb', line 19 def key_material_fields @key_material_fields end |
#key_version ⇒ Object (readonly)
Returns the value of attribute key_version
19 20 21 |
# File 'lib/familia/encryption/encrypted_data.rb', line 19 def key_version @key_version end |
#nonce ⇒ Object (readonly)
Returns the value of attribute nonce
19 20 21 |
# File 'lib/familia/encryption/encrypted_data.rb', line 19 def nonce @nonce end |
Class Method Details
.from_json(json_string_or_hash) ⇒ Object
97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/familia/encryption/encrypted_data.rb', line 97 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.slice(*members)) else # JSON string - validate and parse validate!(json_string_or_hash) end end |
.valid?(json_string) ⇒ Boolean
Class methods for parsing and validation
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/familia/encryption/encrypted_data.rb', line 57 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}, missing: #{(required_fields - parsed.keys).inspect}" result rescue Familia::SerializerError => e Familia.debug "[valid?] JSON error: #{e.class}" false end end |
.validate!(json_string) ⇒ Object
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/familia/encryption/encrypted_data.rb', line 76 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.class}" 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.slice(*members)) end |
Instance Method Details
#decryptable? ⇒ Boolean
Instance methods for decryptability validation
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/familia/encryption/encrypted_data.rb', line 112 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) return false unless encoding_valid? # 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 |
#encoding_valid? ⇒ Boolean
The encoding field is optional (pre-#229 envelopes omit it and decrypt as UTF-8). When present it must be a String naming an encoding this Ruby knows, see ENVELOPE_ENCODINGS.
192 193 194 |
# File 'lib/familia/encryption/encrypted_data.rb', line 192 def encoding_valid? encoding.nil? || (encoding.is_a?(::String) && ENVELOPE_ENCODINGS.include?(encoding)) end |
#has_key_material? ⇒ Boolean
48 49 50 |
# File 'lib/familia/encryption/encrypted_data.rb', line 48 def has_key_material? !key_material_fields.nil? && !key_material_fields.empty? end |
#stored_aad_fields ⇒ Object
52 53 54 |
# File 'lib/familia/encryption/encrypted_data.rb', line 52 def stored_aad_fields aad_fields&.map(&:to_sym) end |
#to_h ⇒ Object
Omit nil-valued keys from the hash representation so that the encrypted envelope stays backward-compatible (no :encoding key unless explicitly set).
29 30 31 |
# File 'lib/familia/encryption/encrypted_data.rb', line 29 def to_h super.compact end |
#to_json(*_args) ⇒ Object
33 34 35 |
# File 'lib/familia/encryption/encrypted_data.rb', line 33 def to_json(*_args) Familia::JsonSerializer.dump(to_h) end |
#validate_decryptable! ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/familia/encryption/encrypted_data.rb', line 135 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 validate_encoding! end |
#validate_encoding! ⇒ Object
199 200 201 202 203 |
# File 'lib/familia/encryption/encrypted_data.rb', line 199 def validate_encoding! return self if encoding_valid? raise EncryptionError, "Unsupported encoding: #{encoding.inspect}" end |
#with_metadata(envelope_version: self.envelope_version, aad_fields: self.aad_fields, key_material_fields: self.key_material_fields) ⇒ Object
37 38 39 40 41 42 43 44 45 46 |
# File 'lib/familia/encryption/encrypted_data.rb', line 37 def (envelope_version: self.envelope_version, aad_fields: self.aad_fields, key_material_fields: self.key_material_fields) # EncryptedData is a Data.define, so #with copies all other members # for us; only the envelope metadata is overridden here. with( envelope_version: envelope_version, aad_fields: aad_fields, key_material_fields: key_material_fields ) end |