familia 2.0.0.pre4 → 2.0.0.pre5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop_todo.yml +17 -17
- data/CLAUDE.md +3 -3
- data/Gemfile +5 -1
- data/Gemfile.lock +18 -3
- data/README.md +36 -157
- data/TEST_COVERAGE.md +40 -0
- data/docs/overview.md +359 -0
- data/docs/wiki/API-Reference.md +270 -0
- data/docs/wiki/Encrypted-Fields-Overview.md +64 -0
- data/docs/wiki/Home.md +49 -0
- data/docs/wiki/Implementation-Guide.md +183 -0
- data/docs/wiki/Security-Model.md +143 -0
- data/lib/familia/base.rb +18 -27
- data/lib/familia/connection.rb +6 -5
- data/lib/familia/{datatype → data_type}/commands.rb +2 -5
- data/lib/familia/{datatype → data_type}/serialization.rb +8 -10
- data/lib/familia/{datatype → data_type}/types/hashkey.rb +2 -2
- data/lib/familia/{datatype → data_type}/types/list.rb +17 -18
- data/lib/familia/{datatype → data_type}/types/sorted_set.rb +17 -17
- data/lib/familia/{datatype → data_type}/types/string.rb +2 -1
- data/lib/familia/{datatype → data_type}/types/unsorted_set.rb +17 -18
- data/lib/familia/{datatype.rb → data_type.rb} +10 -12
- data/lib/familia/encryption/manager.rb +102 -0
- data/lib/familia/encryption/provider.rb +49 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +103 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +184 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +118 -0
- data/lib/familia/encryption/registry.rb +50 -0
- data/lib/familia/encryption.rb +178 -0
- data/lib/familia/encryption_request_cache.rb +68 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +153 -0
- data/lib/familia/features/encrypted_fields.rb +28 -0
- data/lib/familia/features/expiration.rb +107 -77
- data/lib/familia/features/quantization.rb +5 -9
- data/lib/familia/features/relatable_objects.rb +2 -4
- data/lib/familia/features/safe_dump.rb +14 -17
- data/lib/familia/features/transient_fields/redacted_string.rb +159 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +62 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +139 -0
- data/lib/familia/features/transient_fields.rb +47 -0
- data/lib/familia/features.rb +40 -24
- data/lib/familia/field_type.rb +270 -0
- data/lib/familia/horreum/connection.rb +8 -11
- data/lib/familia/horreum/{commands.rb → database_commands.rb} +7 -19
- data/lib/familia/horreum/definition_methods.rb +453 -0
- data/lib/familia/horreum/{class_methods.rb → management_methods.rb} +19 -243
- data/lib/familia/horreum/serialization.rb +46 -18
- data/lib/familia/horreum/settings.rb +10 -2
- data/lib/familia/horreum/utils.rb +9 -10
- data/lib/familia/horreum.rb +18 -10
- data/lib/familia/logging.rb +14 -14
- data/lib/familia/settings.rb +39 -3
- data/lib/familia/utils.rb +45 -0
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +2 -1
- data/try/core/base_enhancements_try.rb +115 -0
- data/try/core/connection_try.rb +0 -1
- data/try/core/errors_try.rb +0 -1
- data/try/core/familia_extended_try.rb +3 -4
- data/try/core/familia_try.rb +0 -1
- data/try/core/pools_try.rb +2 -2
- data/try/core/secure_identifier_try.rb +0 -1
- data/try/core/settings_try.rb +0 -1
- data/try/core/utils_try.rb +0 -1
- data/try/{datatypes → data_types}/boolean_try.rb +1 -2
- data/try/{datatypes → data_types}/datatype_base_try.rb +2 -3
- data/try/{datatypes → data_types}/hash_try.rb +1 -2
- data/try/{datatypes → data_types}/list_try.rb +1 -2
- data/try/{datatypes → data_types}/set_try.rb +1 -2
- data/try/{datatypes → data_types}/sorted_set_try.rb +1 -2
- data/try/{datatypes → data_types}/string_try.rb +1 -2
- data/try/debugging/README.md +32 -0
- data/try/debugging/cache_behavior_tracer.rb +91 -0
- data/try/debugging/encryption_method_tracer.rb +138 -0
- data/try/debugging/provider_diagnostics.rb +110 -0
- data/try/edge_cases/hash_symbolization_try.rb +0 -1
- data/try/edge_cases/json_serialization_try.rb +0 -1
- data/try/edge_cases/reserved_keywords_try.rb +42 -11
- data/try/encryption/config_persistence_try.rb +192 -0
- data/try/encryption/encryption_core_try.rb +328 -0
- data/try/encryption/instance_variable_scope_try.rb +31 -0
- data/try/encryption/module_loading_try.rb +28 -0
- data/try/encryption/providers/aes_gcm_provider_try.rb +178 -0
- data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +169 -0
- data/try/encryption/roundtrip_validation_try.rb +28 -0
- data/try/encryption/secure_memory_handling_try.rb +125 -0
- data/try/features/encrypted_fields_core_try.rb +117 -0
- data/try/features/encrypted_fields_integration_try.rb +220 -0
- data/try/features/encrypted_fields_no_cache_security_try.rb +205 -0
- data/try/features/encrypted_fields_security_try.rb +370 -0
- data/try/features/encryption_fields/aad_protection_try.rb +53 -0
- data/try/features/encryption_fields/context_isolation_try.rb +120 -0
- data/try/features/encryption_fields/error_conditions_try.rb +116 -0
- data/try/features/encryption_fields/fresh_key_derivation_try.rb +122 -0
- data/try/features/encryption_fields/fresh_key_try.rb +163 -0
- data/try/features/encryption_fields/key_rotation_try.rb +117 -0
- data/try/features/encryption_fields/memory_security_try.rb +37 -0
- data/try/features/encryption_fields/missing_current_key_version_try.rb +23 -0
- data/try/features/encryption_fields/nonce_uniqueness_try.rb +54 -0
- data/try/features/encryption_fields/thread_safety_try.rb +199 -0
- data/try/features/expiration_try.rb +0 -1
- data/try/features/feature_dependencies_try.rb +159 -0
- data/try/features/quantization_try.rb +0 -1
- data/try/features/real_feature_integration_try.rb +148 -0
- data/try/features/relatable_objects_try.rb +0 -1
- data/try/features/safe_dump_advanced_try.rb +0 -1
- data/try/features/safe_dump_try.rb +0 -1
- data/try/features/transient_fields/redacted_string_try.rb +248 -0
- data/try/features/transient_fields/refresh_reset_try.rb +164 -0
- data/try/features/transient_fields/simple_refresh_test.rb +50 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +310 -0
- data/try/features/transient_fields_core_try.rb +181 -0
- data/try/features/transient_fields_integration_try.rb +260 -0
- data/try/helpers/test_helpers.rb +42 -0
- data/try/horreum/base_try.rb +157 -3
- data/try/horreum/enhanced_conflict_handling_try.rb +176 -0
- data/try/horreum/field_categories_try.rb +118 -0
- data/try/horreum/field_definition_try.rb +96 -0
- data/try/horreum/initialization_try.rb +0 -1
- data/try/horreum/relations_try.rb +0 -1
- data/try/horreum/serialization_persistent_fields_try.rb +165 -0
- data/try/horreum/serialization_try.rb +2 -3
- data/try/memory/memory_basic_test.rb +73 -0
- data/try/memory/memory_detailed_test.rb +121 -0
- data/try/memory/memory_docker_ruby_dump.sh +80 -0
- data/try/memory/memory_search_for_string.rb +83 -0
- data/try/memory/test_actual_redactedstring_protection.rb +38 -0
- data/try/models/customer_safe_dump_try.rb +0 -1
- data/try/models/customer_try.rb +0 -1
- data/try/models/datatype_base_try.rb +1 -2
- data/try/models/familia_object_try.rb +0 -1
- metadata +85 -18
@@ -0,0 +1,110 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Debug script for testing encryption providers and diagnosing issues
|
3
|
+
|
4
|
+
require 'base64'
|
5
|
+
require_relative '../helpers/test_helpers'
|
6
|
+
|
7
|
+
puts "=== Familia Encryption Provider Diagnostics ==="
|
8
|
+
puts
|
9
|
+
|
10
|
+
# Check encryption system status
|
11
|
+
puts "1. Encryption System Status:"
|
12
|
+
puts " Status: #{Familia::Encryption.status.inspect}"
|
13
|
+
puts
|
14
|
+
|
15
|
+
# Check registry setup
|
16
|
+
puts "2. Registry Providers:"
|
17
|
+
require_relative '../../lib/familia/encryption/registry'
|
18
|
+
Familia::Encryption::Registry.setup!
|
19
|
+
Familia::Encryption::Registry.providers.each do |algo, provider_class|
|
20
|
+
puts " #{algo}: #{provider_class.name}"
|
21
|
+
puts " Available: #{provider_class.available?}"
|
22
|
+
puts " Priority: #{provider_class.priority}"
|
23
|
+
end
|
24
|
+
puts
|
25
|
+
|
26
|
+
# Setup test keys
|
27
|
+
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
28
|
+
Familia.config.encryption_keys = test_keys
|
29
|
+
Familia.config.current_key_version = :v1
|
30
|
+
|
31
|
+
# Test each provider individually
|
32
|
+
['xchacha20poly1305', 'aes-256-gcm'].each do |algorithm|
|
33
|
+
puts "3. Testing #{algorithm.upcase} Provider:"
|
34
|
+
|
35
|
+
begin
|
36
|
+
manager = Familia::Encryption::Manager.new(algorithm: algorithm)
|
37
|
+
provider = manager.provider
|
38
|
+
|
39
|
+
puts " Provider class: #{provider.class.name}"
|
40
|
+
puts " Algorithm: #{provider.algorithm}"
|
41
|
+
puts " Nonce size: #{provider.nonce_size} bytes"
|
42
|
+
puts " Auth tag size: #{provider.auth_tag_size} bytes"
|
43
|
+
|
44
|
+
# Test encryption/decryption
|
45
|
+
test_data = "diagnostic test data for #{algorithm}"
|
46
|
+
encrypted = manager.encrypt(test_data, context: 'diagnostics')
|
47
|
+
puts " Encryption: SUCCESS (#{encrypted.length} chars)"
|
48
|
+
|
49
|
+
decrypted = manager.decrypt(encrypted, context: 'diagnostics')
|
50
|
+
success = decrypted == test_data
|
51
|
+
puts " Decryption: #{success ? 'SUCCESS' : 'FAILED'}"
|
52
|
+
|
53
|
+
if !success
|
54
|
+
puts " Expected: #{test_data.inspect}"
|
55
|
+
puts " Got: #{decrypted.inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
rescue => e
|
59
|
+
puts " ERROR: #{e.class}: #{e.message}"
|
60
|
+
puts " Backtrace: #{e.backtrace.first(3).join(', ')}"
|
61
|
+
end
|
62
|
+
|
63
|
+
puts
|
64
|
+
end
|
65
|
+
|
66
|
+
# Test cross-algorithm compatibility
|
67
|
+
puts "4. Cross-Algorithm Compatibility Test:"
|
68
|
+
begin
|
69
|
+
xchacha_manager = Familia::Encryption::Manager.new(algorithm: 'xchacha20poly1305')
|
70
|
+
aes_manager = Familia::Encryption::Manager.new(algorithm: 'aes-256-gcm')
|
71
|
+
default_manager = Familia::Encryption::Manager.new
|
72
|
+
|
73
|
+
test_data = "cross-algorithm test"
|
74
|
+
|
75
|
+
# Encrypt with XChaCha20Poly1305
|
76
|
+
xchacha_encrypted = xchacha_manager.encrypt(test_data, context: 'cross-test')
|
77
|
+
xchacha_decrypted = default_manager.decrypt(xchacha_encrypted, context: 'cross-test')
|
78
|
+
puts " XChaCha20Poly1305 -> Default: #{xchacha_decrypted == test_data ? 'SUCCESS' : 'FAILED'}"
|
79
|
+
|
80
|
+
# Encrypt with AES-GCM
|
81
|
+
aes_encrypted = aes_manager.encrypt(test_data, context: 'cross-test')
|
82
|
+
aes_decrypted = default_manager.decrypt(aes_encrypted, context: 'cross-test')
|
83
|
+
puts " AES-GCM -> Default: #{aes_decrypted == test_data ? 'SUCCESS' : 'FAILED'}"
|
84
|
+
|
85
|
+
rescue => e
|
86
|
+
puts " ERROR: #{e.class}: #{e.message}"
|
87
|
+
end
|
88
|
+
puts
|
89
|
+
|
90
|
+
# Test high-level API
|
91
|
+
puts "5. High-Level API Test:"
|
92
|
+
begin
|
93
|
+
encrypted_high = Familia::Encryption.encrypt_with('aes-256-gcm', 'high-level test', context: 'api-test')
|
94
|
+
puts " encrypt_with: SUCCESS"
|
95
|
+
|
96
|
+
# Parse encrypted data to verify structure
|
97
|
+
require 'json'
|
98
|
+
parsed = JSON.parse(encrypted_high, symbolize_names: true)
|
99
|
+
puts " Algorithm stored: #{parsed[:algorithm]}"
|
100
|
+
puts " Key version: #{parsed[:key_version]}"
|
101
|
+
|
102
|
+
decrypted_high = Familia::Encryption.decrypt(encrypted_high, context: 'api-test')
|
103
|
+
puts " decrypt: #{decrypted_high == 'high-level test' ? 'SUCCESS' : 'FAILED'}"
|
104
|
+
|
105
|
+
rescue => e
|
106
|
+
puts " ERROR: #{e.class}: #{e.message}"
|
107
|
+
end
|
108
|
+
|
109
|
+
puts
|
110
|
+
puts "=== Diagnostics Complete ==="
|
@@ -17,7 +17,7 @@ result
|
|
17
17
|
#=!> StandardError
|
18
18
|
|
19
19
|
## prefixed field names work as expected
|
20
|
-
|
20
|
+
ExampleTestClass = Class.new(Familia::Horreum) do
|
21
21
|
identifier_field :email
|
22
22
|
field :email
|
23
23
|
field :secret_ttl
|
@@ -25,7 +25,7 @@ TestClass2 = Class.new(Familia::Horreum) do
|
|
25
25
|
field :dbclient_config
|
26
26
|
end
|
27
27
|
|
28
|
-
user =
|
28
|
+
user = ExampleTestClass.new(email: 'test@example.com')
|
29
29
|
user.secret_ttl = 3600
|
30
30
|
user.user_db = 5
|
31
31
|
user.dbclient_config = { host: 'localhost' }
|
@@ -39,7 +39,7 @@ user.delete!
|
|
39
39
|
result
|
40
40
|
#=> true
|
41
41
|
|
42
|
-
##
|
42
|
+
## Reserved methods still work normally
|
43
43
|
TestClass3 = Class.new(Familia::Horreum) do
|
44
44
|
# Note: Does not enable expiration feature
|
45
45
|
identifier_field :email
|
@@ -55,20 +55,51 @@ user
|
|
55
55
|
#==> _.respond_to?(:logical_database)
|
56
56
|
#==> _.respond_to?(:dbclient)
|
57
57
|
|
58
|
+
## Attempting to pass default_expiration as a field value when instantiating,
|
59
|
+
## when expiration feature is enabled. It doesn't actually change the default
|
60
|
+
## expiration for the instance b/c "default_expiration" is not a regular field.
|
61
|
+
TestClassWithExpirationEnabled1 = Class.new(Familia::Horreum) do
|
62
|
+
feature :expiration
|
63
|
+
identifier_field :email
|
64
|
+
field :email
|
65
|
+
end
|
66
|
+
|
67
|
+
user = TestClassWithExpirationEnabled1.new(email: 'test@example.com', default_expiration: 3600)
|
68
|
+
user.default_expiration
|
69
|
+
#=> 0
|
58
70
|
|
59
|
-
## Attempting to set default_expiration
|
60
|
-
|
71
|
+
## Attempting to set default_expiration for an instance when
|
72
|
+
## the feature is enabled should work
|
73
|
+
TestClassWithExpirationEnabled2 = Class.new(Familia::Horreum) do
|
61
74
|
feature :expiration
|
62
75
|
identifier_field :email
|
63
76
|
field :email
|
64
|
-
field :default_expiration
|
65
77
|
end
|
66
78
|
|
67
|
-
user =
|
68
|
-
user.
|
69
|
-
user.
|
70
|
-
|
71
|
-
|
79
|
+
user = TestClassWithExpirationEnabled2.new(email: 'test@example.com')
|
80
|
+
user.default_expiration = 3601
|
81
|
+
user.default_expiration
|
82
|
+
#=> 3601
|
83
|
+
|
84
|
+
## Attempting to pass default_expiration as a field value when instantiating,
|
85
|
+
## when expiration feature is disabled and then trying to access that value
|
86
|
+
## simply raises a NoMethodError error.
|
87
|
+
TestClassWithExpirationDisabled = Class.new(Familia::Horreum) do
|
88
|
+
identifier_field :email
|
89
|
+
field :email
|
90
|
+
end
|
91
|
+
|
92
|
+
user = TestClassWithExpirationDisabled.new(email: 'test@example.com', default_expiration: 3600)
|
93
|
+
user.default_expiration
|
94
|
+
#=!> NoMethodError
|
95
|
+
|
96
|
+
## Attempting to add a field with a reserved name should raise an error
|
97
|
+
TestClassWithExpirationDisabled = Class.new(Familia::Horreum) do
|
98
|
+
identifier_field :email
|
99
|
+
field :email
|
100
|
+
field :default_expiration
|
101
|
+
end
|
102
|
+
##=!> NoMethodError
|
72
103
|
|
73
104
|
## prefixed field names work as expected
|
74
105
|
TestClass5 = Class.new(Familia::Horreum) do
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# try/encryption/debug2_try.rb
|
2
|
+
|
3
|
+
# - Tests configuration persistence between test sections
|
4
|
+
# - Validates that config can be set and accessed in tryouts
|
5
|
+
|
6
|
+
require 'base64'
|
7
|
+
|
8
|
+
require_relative '../helpers/test_helpers'
|
9
|
+
require 'familia/encryption/providers/xchacha20_poly1305_provider'
|
10
|
+
|
11
|
+
# SETUP
|
12
|
+
Familia.config.encryption_keys = {
|
13
|
+
v1: Base64.strict_encode64('a' * 32)
|
14
|
+
}
|
15
|
+
Familia.config.current_key_version = :v1
|
16
|
+
|
17
|
+
## Check config in test
|
18
|
+
keys = Familia.config.encryption_keys
|
19
|
+
version = Familia.config.current_key_version
|
20
|
+
[keys.nil?, version.nil?]
|
21
|
+
#=> [false, false]
|
22
|
+
|
23
|
+
## Try basic encryption in test
|
24
|
+
Familia.config.encryption_keys = {v1: Base64.strict_encode64('a' * 32)}
|
25
|
+
Familia.config.current_key_version = :v1
|
26
|
+
result = Familia::Encryption.encrypt('test', context: 'test')
|
27
|
+
result.nil?
|
28
|
+
#=> false
|
29
|
+
|
30
|
+
## XChaCha20Poly1305Provider is available when RbNaCl is loaded
|
31
|
+
@provider_class = Familia::Encryption::Providers::XChaCha20Poly1305Provider
|
32
|
+
@provider_class.available?
|
33
|
+
#=> true
|
34
|
+
|
35
|
+
## Provider has highest priority
|
36
|
+
@provider_class.priority
|
37
|
+
#=> 100
|
38
|
+
|
39
|
+
## derive_key generates 32-byte key from master key and context
|
40
|
+
provider = @provider_class.new
|
41
|
+
master_key = 'a' * 32
|
42
|
+
context = 'TestModel:field:user123'
|
43
|
+
derived_key = provider.derive_key(master_key, context)
|
44
|
+
derived_key.bytesize
|
45
|
+
#=> 32
|
46
|
+
|
47
|
+
## derive_key with same inputs produces same output
|
48
|
+
provider = @provider_class.new
|
49
|
+
master_key = 'a' * 32
|
50
|
+
context = 'TestModel:field:user123'
|
51
|
+
key1 = provider.derive_key(master_key, context)
|
52
|
+
key2 = provider.derive_key(master_key, context)
|
53
|
+
key1 == key2
|
54
|
+
#=> true
|
55
|
+
|
56
|
+
## derive_key with different contexts produces different keys
|
57
|
+
provider = @provider_class.new
|
58
|
+
master_key = 'a' * 32
|
59
|
+
context1 = 'TestModel:field:user123'
|
60
|
+
context2 = 'TestModel:field:user456'
|
61
|
+
key1 = provider.derive_key(master_key, context1)
|
62
|
+
key2 =
|
63
|
+
provider.derive_key(master_key, context2)
|
64
|
+
key1 != key2
|
65
|
+
#=> true
|
66
|
+
|
67
|
+
## derive_key with custom personalization works
|
68
|
+
provider = @provider_class.new
|
69
|
+
master_key = 'a' * 32
|
70
|
+
context = 'TestModel:field:user123'
|
71
|
+
personal = 'custom_app_v2'
|
72
|
+
derived_key = provider.derive_key(master_key, context, personal: personal)
|
73
|
+
derived_key.bytesize
|
74
|
+
#=> 32
|
75
|
+
|
76
|
+
## derive_key rejects personalization string with null bytes
|
77
|
+
provider = @provider_class.new
|
78
|
+
master_key = 'a' * 32
|
79
|
+
context = 'TestModel:field:user123'
|
80
|
+
personal_with_null = "app\0version"
|
81
|
+
begin
|
82
|
+
provider.derive_key(master_key, context, personal: personal_with_null)
|
83
|
+
"should_not_reach_here"
|
84
|
+
rescue Familia::EncryptionError => e
|
85
|
+
e.message
|
86
|
+
end
|
87
|
+
#=> "Personalization string must not contain null bytes"
|
88
|
+
|
89
|
+
## derive_key rejects config personalization with null bytes
|
90
|
+
provider = @provider_class.new
|
91
|
+
master_key = 'a' * 32
|
92
|
+
context = 'TestModel:field:user123'
|
93
|
+
# Set config with null byte
|
94
|
+
original_personal = Familia.config.encryption_personalization
|
95
|
+
Familia.config.encryption_personalization = "bad\0config"
|
96
|
+
begin
|
97
|
+
provider.derive_key(master_key, context)
|
98
|
+
"should_not_reach_here"
|
99
|
+
rescue Familia::EncryptionError => e
|
100
|
+
e.message
|
101
|
+
ensure
|
102
|
+
Familia.config.encryption_personalization = original_personal
|
103
|
+
end
|
104
|
+
#=> "Personalization string must not contain null bytes"
|
105
|
+
|
106
|
+
## derive_key validates master key length
|
107
|
+
provider = @provider_class.new
|
108
|
+
short_key = 'a' * 16 # Too short
|
109
|
+
context = 'TestModel:field:user123'
|
110
|
+
begin
|
111
|
+
provider.derive_key(short_key, context)
|
112
|
+
"should_not_reach_here"
|
113
|
+
rescue Familia::EncryptionError => e
|
114
|
+
e.message
|
115
|
+
end
|
116
|
+
#=> "Key must be at least 32 bytes"
|
117
|
+
|
118
|
+
## derive_key rejects nil master key
|
119
|
+
provider = @provider_class.new
|
120
|
+
context = 'TestModel:field:user123'
|
121
|
+
begin
|
122
|
+
provider.derive_key(nil, context)
|
123
|
+
"should_not_reach_here"
|
124
|
+
rescue Familia::EncryptionError => e
|
125
|
+
e.message
|
126
|
+
end
|
127
|
+
#=> "Key cannot be nil"
|
128
|
+
|
129
|
+
## encrypt/decrypt round trip with derived key works
|
130
|
+
provider = @provider_class.new
|
131
|
+
master_key = 'a' * 32
|
132
|
+
context = 'TestModel:field:user123'
|
133
|
+
derived_key = provider.derive_key(master_key, context)
|
134
|
+
plaintext = 'sensitive data'
|
135
|
+
encrypted_data = provider.encrypt(plaintext, derived_key)
|
136
|
+
decrypted = provider.decrypt(
|
137
|
+
encrypted_data[:ciphertext],
|
138
|
+
derived_key,
|
139
|
+
encrypted_data[:nonce],
|
140
|
+
encrypted_data[:auth_tag]
|
141
|
+
)
|
142
|
+
decrypted
|
143
|
+
#=> "sensitive data"
|
144
|
+
|
145
|
+
## encrypt with additional data and derived key
|
146
|
+
provider = @provider_class.new
|
147
|
+
master_key = 'a' * 32
|
148
|
+
context = 'TestModel:field:user123'
|
149
|
+
derived_key = provider.derive_key(master_key, context)
|
150
|
+
plaintext = 'sensitive data'
|
151
|
+
additional_data = 'user_id:123'
|
152
|
+
encrypted_data = provider.encrypt(plaintext, derived_key, additional_data)
|
153
|
+
decrypted = provider.decrypt(
|
154
|
+
encrypted_data[:ciphertext],
|
155
|
+
derived_key,
|
156
|
+
encrypted_data[:nonce],
|
157
|
+
encrypted_data[:auth_tag],
|
158
|
+
additional_data
|
159
|
+
)
|
160
|
+
decrypted
|
161
|
+
#=> "sensitive data"
|
162
|
+
|
163
|
+
## generate_nonce produces correct size
|
164
|
+
provider = @provider_class.new
|
165
|
+
nonce = provider.generate_nonce
|
166
|
+
nonce.bytesize
|
167
|
+
#=> 24
|
168
|
+
|
169
|
+
## generate_nonce produces unique values
|
170
|
+
provider = @provider_class.new
|
171
|
+
nonce1 = provider.generate_nonce
|
172
|
+
nonce2 = provider.generate_nonce
|
173
|
+
nonce1 != nonce2
|
174
|
+
#=> true
|
175
|
+
|
176
|
+
## secure_wipe works with valid key
|
177
|
+
provider = @provider_class.new
|
178
|
+
key = 'a' * 32
|
179
|
+
provider.secure_wipe(key)
|
180
|
+
# Should not raise error
|
181
|
+
true
|
182
|
+
#=> true
|
183
|
+
|
184
|
+
## secure_wipe handles nil key gracefully
|
185
|
+
provider = @provider_class.new
|
186
|
+
provider.secure_wipe(nil)
|
187
|
+
# Should not raise error
|
188
|
+
true
|
189
|
+
#=> true
|
190
|
+
|
191
|
+
# TEARDOWN
|
192
|
+
Thread.current[:familia_key_cache]&.clear if Thread.current[:familia_key_cache]
|