familia 2.0.0.pre4 → 2.0.0.pre6
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 +11 -8
- data/Gemfile +5 -1
- data/Gemfile.lock +19 -3
- data/README.md +36 -157
- data/docs/overview.md +359 -0
- data/docs/wiki/API-Reference.md +347 -0
- data/docs/wiki/Connection-Pooling-Guide.md +437 -0
- data/docs/wiki/Encrypted-Fields-Overview.md +101 -0
- data/docs/wiki/Expiration-Feature-Guide.md +596 -0
- data/docs/wiki/Feature-System-Guide.md +600 -0
- data/docs/wiki/Features-System-Developer-Guide.md +892 -0
- data/docs/wiki/Field-System-Guide.md +784 -0
- data/docs/wiki/Home.md +106 -0
- data/docs/wiki/Implementation-Guide.md +276 -0
- data/docs/wiki/Quantization-Feature-Guide.md +721 -0
- data/docs/wiki/RelatableObjects-Guide.md +563 -0
- data/docs/wiki/Security-Model.md +183 -0
- data/docs/wiki/Transient-Fields-Guide.md +280 -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/data_type/types/counter.rb +38 -0
- data/lib/familia/{datatype → data_type}/types/hashkey.rb +20 -2
- data/lib/familia/{datatype → data_type}/types/list.rb +17 -18
- data/lib/familia/data_type/types/lock.rb +43 -0
- data/lib/familia/{datatype → data_type}/types/sorted_set.rb +17 -17
- data/lib/familia/{datatype → data_type}/types/string.rb +11 -3
- data/lib/familia/{datatype → data_type}/types/unsorted_set.rb +17 -18
- data/lib/familia/{datatype.rb → data_type.rb} +12 -14
- data/lib/familia/encryption/encrypted_data.rb +137 -0
- data/lib/familia/encryption/manager.rb +119 -0
- data/lib/familia/encryption/provider.rb +49 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +123 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +184 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +138 -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/errors.rb +17 -3
- data/lib/familia/features/encrypted_fields/concealed_string.rb +295 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +221 -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 +273 -0
- data/lib/familia/horreum/{connection.rb → core/connection.rb} +6 -15
- data/lib/familia/horreum/{commands.rb → core/database_commands.rb} +20 -21
- data/lib/familia/horreum/core/serialization.rb +535 -0
- data/lib/familia/horreum/{utils.rb → core/utils.rb} +9 -12
- data/lib/familia/horreum/core.rb +21 -0
- data/lib/familia/horreum/{settings.rb → shared/settings.rb} +10 -4
- data/lib/familia/horreum/subclass/definition.rb +469 -0
- data/lib/familia/horreum/{class_methods.rb → subclass/management.rb} +27 -250
- data/lib/familia/horreum/{related_fields_management.rb → subclass/related_fields_management.rb} +15 -10
- data/lib/familia/horreum.rb +30 -22
- 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 +3 -2
- data/try/core/base_enhancements_try.rb +115 -0
- data/try/core/connection_try.rb +0 -1
- data/try/core/create_method_try.rb +240 -0
- data/try/core/database_consistency_try.rb +299 -0
- data/try/core/errors_try.rb +25 -5
- data/try/core/familia_extended_try.rb +3 -4
- data/try/core/familia_try.rb +1 -2
- data/try/core/persistence_operations_try.rb +297 -0
- 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/data_types/counter_try.rb +93 -0
- 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/data_types/lock_try.rb +133 -0
- 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/debug_aad_process.rb +82 -0
- data/try/debugging/debug_concealed_internal.rb +59 -0
- data/try/debugging/debug_concealed_reveal.rb +61 -0
- data/try/debugging/debug_context_aad.rb +68 -0
- data/try/debugging/debug_context_simple.rb +80 -0
- data/try/debugging/debug_cross_context.rb +62 -0
- data/try/debugging/debug_database_load.rb +64 -0
- data/try/debugging/debug_encrypted_json_check.rb +53 -0
- data/try/debugging/debug_encrypted_json_step_by_step.rb +62 -0
- data/try/debugging/debug_exists_lifecycle.rb +54 -0
- data/try/debugging/debug_field_decrypt.rb +74 -0
- data/try/debugging/debug_fresh_cross_context.rb +73 -0
- data/try/debugging/debug_load_path.rb +66 -0
- data/try/debugging/debug_method_definition.rb +46 -0
- data/try/debugging/debug_method_resolution.rb +41 -0
- data/try/debugging/debug_minimal.rb +24 -0
- data/try/debugging/debug_provider.rb +68 -0
- data/try/debugging/debug_secure_behavior.rb +73 -0
- data/try/debugging/debug_string_class.rb +46 -0
- data/try/debugging/debug_test.rb +46 -0
- data/try/debugging/debug_test_design.rb +80 -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 +125 -0
- data/try/features/encrypted_fields_integration_try.rb +216 -0
- data/try/features/encrypted_fields_no_cache_security_try.rb +219 -0
- data/try/features/encrypted_fields_security_try.rb +377 -0
- data/try/features/encryption_fields/aad_protection_try.rb +138 -0
- data/try/features/encryption_fields/concealed_string_core_try.rb +250 -0
- data/try/features/encryption_fields/context_isolation_try.rb +141 -0
- data/try/features/encryption_fields/error_conditions_try.rb +116 -0
- data/try/features/encryption_fields/fresh_key_derivation_try.rb +128 -0
- data/try/features/encryption_fields/fresh_key_try.rb +168 -0
- data/try/features/encryption_fields/key_rotation_try.rb +123 -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 +56 -0
- data/try/features/encryption_fields/secure_by_default_behavior_try.rb +310 -0
- data/try/features/encryption_fields/thread_safety_try.rb +199 -0
- data/try/features/encryption_fields/universal_serialization_safety_try.rb +174 -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 +67 -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 +1 -2
- data/try/horreum/relations_try.rb +1 -2
- data/try/horreum/serialization_persistent_fields_try.rb +165 -0
- data/try/horreum/serialization_try.rb +41 -7
- 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 +1 -2
- data/try/models/customer_try.rb +1 -2
- data/try/models/datatype_base_try.rb +1 -2
- data/try/models/familia_object_try.rb +0 -1
- metadata +131 -23
- data/lib/familia/horreum/serialization.rb +0 -445
@@ -0,0 +1,32 @@
|
|
1
|
+
# Familia Encryption Debugging Tools
|
2
|
+
|
3
|
+
Debug scripts for encryption issues. All scripts run safely without modifying data.
|
4
|
+
|
5
|
+
## Quick Reference
|
6
|
+
|
7
|
+
| Issue | Script | Purpose |
|
8
|
+
|-------|--------|---------|
|
9
|
+
| Provider problems | `provider_diagnostics.rb` | Test provider registration, availability, compatibility |
|
10
|
+
| Performance issues | `cache_behavior_tracer.rb` | Analyze key derivation cache efficiency |
|
11
|
+
| Field operations | `encryption_method_tracer.rb` | Trace encrypt/decrypt calls with timing |
|
12
|
+
|
13
|
+
```bash
|
14
|
+
# Run any script
|
15
|
+
ruby try/debugging/[script_name].rb
|
16
|
+
```
|
17
|
+
|
18
|
+
## Usage Guide
|
19
|
+
|
20
|
+
**Provider Issues**: Algorithm not found, registration failures
|
21
|
+
→ `provider_diagnostics.rb`
|
22
|
+
|
23
|
+
**Slow Performance**: Excessive key derivations, cache misses
|
24
|
+
→ `cache_behavior_tracer.rb` then `encryption_method_tracer.rb`
|
25
|
+
|
26
|
+
**Field Bugs**: AAD problems, context issues, decryption failures
|
27
|
+
→ `encryption_method_tracer.rb`
|
28
|
+
|
29
|
+
## Output Key
|
30
|
+
- **SUCCESS/FAILED/ERROR** = test results
|
31
|
+
- `version:context => [bytes]` = cache entries
|
32
|
+
- Timing in milliseconds = performance data
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Debug script to trace encryption cache behavior and key derivation
|
3
|
+
|
4
|
+
require 'base64'
|
5
|
+
require 'bundler/setup'
|
6
|
+
require_relative '../helpers/test_helpers'
|
7
|
+
|
8
|
+
puts "=== Encryption Cache Behavior Tracer ==="
|
9
|
+
puts
|
10
|
+
|
11
|
+
# Setup test configuration
|
12
|
+
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
13
|
+
Familia.config.encryption_keys = test_keys
|
14
|
+
Familia.config.current_key_version = :v1
|
15
|
+
|
16
|
+
# Clear any existing cache
|
17
|
+
Thread.current[:familia_key_cache] = nil
|
18
|
+
puts "1. Initial Cache State:"
|
19
|
+
puts " Cache: #{Thread.current[:familia_key_cache].inspect}"
|
20
|
+
puts
|
21
|
+
|
22
|
+
# Define test model with multiple encrypted fields
|
23
|
+
class CacheTraceModel < Familia::Horreum
|
24
|
+
feature :encrypted_fields
|
25
|
+
identifier_field :user_id
|
26
|
+
|
27
|
+
field :user_id
|
28
|
+
encrypted_field :field_a
|
29
|
+
encrypted_field :field_b
|
30
|
+
encrypted_field :field_c
|
31
|
+
end
|
32
|
+
|
33
|
+
puts "2. Model Creation:"
|
34
|
+
user = CacheTraceModel.new(user_id: 'cache-user-1')
|
35
|
+
puts " After model creation: #{Thread.current[:familia_key_cache].inspect}"
|
36
|
+
puts
|
37
|
+
|
38
|
+
puts "3. Setting Encrypted Fields:"
|
39
|
+
['field_a', 'field_b', 'field_c'].each_with_index do |field, i|
|
40
|
+
puts " Setting #{field}..."
|
41
|
+
user.public_send("#{field}=", "test-value-#{i+1}")
|
42
|
+
cache = Thread.current[:familia_key_cache]
|
43
|
+
if cache
|
44
|
+
puts " Cache size: #{cache.size}"
|
45
|
+
puts " Cache keys: #{cache.keys.inspect}"
|
46
|
+
else
|
47
|
+
puts " Cache: nil"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
puts
|
51
|
+
|
52
|
+
puts "4. Reading Encrypted Fields:"
|
53
|
+
['field_a', 'field_b', 'field_c'].each do |field|
|
54
|
+
puts " Reading #{field}..."
|
55
|
+
value = user.public_send(field)
|
56
|
+
puts " Retrieved: #{value}"
|
57
|
+
cache = Thread.current[:familia_key_cache]
|
58
|
+
if cache
|
59
|
+
puts " Cache size: #{cache.size}"
|
60
|
+
puts " Cache keys: #{cache.keys.inspect}"
|
61
|
+
else
|
62
|
+
puts " Cache: nil"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
puts
|
66
|
+
|
67
|
+
puts "5. Final Cache Analysis:"
|
68
|
+
cache = Thread.current[:familia_key_cache]
|
69
|
+
if cache && !cache.empty?
|
70
|
+
puts " Cache size: #{cache.size} entries"
|
71
|
+
cache.each_with_index do |(key, value), i|
|
72
|
+
puts " Entry #{i+1}: #{key} => [#{value.bytesize} bytes]"
|
73
|
+
end
|
74
|
+
else
|
75
|
+
puts " Cache is empty or nil"
|
76
|
+
end
|
77
|
+
puts
|
78
|
+
|
79
|
+
# Test cache behavior with multiple models
|
80
|
+
puts "6. Multiple Models Cache Test:"
|
81
|
+
user2 = CacheTraceModel.new(user_id: 'cache-user-2')
|
82
|
+
user2.field_a = 'different-value'
|
83
|
+
puts " After second model field set:"
|
84
|
+
cache = Thread.current[:familia_key_cache]
|
85
|
+
if cache
|
86
|
+
puts " Cache size: #{cache.size}"
|
87
|
+
puts " Unique contexts: #{cache.keys.map { |k| k.split(':').last }.uniq.size}"
|
88
|
+
end
|
89
|
+
|
90
|
+
puts
|
91
|
+
puts "=== Cache Tracing Complete ==="
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('lib', __dir__))
|
4
|
+
ENV['TEST'] = 'true'
|
5
|
+
require 'familia'
|
6
|
+
require_relative 'try/helpers/test_helpers'
|
7
|
+
|
8
|
+
puts "Debugging AAD during encrypt/decrypt process..."
|
9
|
+
|
10
|
+
# Setup encryption keys
|
11
|
+
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
12
|
+
Familia.config.encryption_keys = test_keys
|
13
|
+
Familia.config.current_key_version = :v1
|
14
|
+
|
15
|
+
class DebugModelA < Familia::Horreum
|
16
|
+
feature :encrypted_fields
|
17
|
+
identifier_field :id
|
18
|
+
field :id
|
19
|
+
encrypted_field :api_key
|
20
|
+
end
|
21
|
+
|
22
|
+
class DebugModelB < Familia::Horreum
|
23
|
+
feature :encrypted_fields
|
24
|
+
identifier_field :id
|
25
|
+
field :id
|
26
|
+
encrypted_field :api_key
|
27
|
+
end
|
28
|
+
|
29
|
+
# Patch the EncryptedFieldType to add debug output
|
30
|
+
class Familia::EncryptedFieldType
|
31
|
+
alias_method :original_encrypt_value, :encrypt_value
|
32
|
+
alias_method :original_decrypt_value, :decrypt_value
|
33
|
+
alias_method :original_build_aad, :build_aad
|
34
|
+
|
35
|
+
def encrypt_value(record, value)
|
36
|
+
context = build_context(record)
|
37
|
+
aad = build_aad(record)
|
38
|
+
puts "[ENCRYPT] Class: #{record.class}, ID: #{record.identifier}, Context: #{context}, AAD: #{aad}"
|
39
|
+
original_encrypt_value(record, value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def decrypt_value(record, encrypted)
|
43
|
+
context = build_context(record)
|
44
|
+
aad = build_aad(record)
|
45
|
+
puts "[DECRYPT] Class: #{record.class}, ID: #{record.identifier}, Context: #{context}, AAD: #{aad}"
|
46
|
+
original_decrypt_value(record, encrypted)
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_aad(record)
|
50
|
+
aad = original_build_aad(record)
|
51
|
+
puts "[BUILD_AAD] Class: #{record.class}, ID: #{record.identifier}, AAD: #{aad}"
|
52
|
+
aad
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Clean database
|
57
|
+
Familia.dbclient.flushdb
|
58
|
+
|
59
|
+
model_a = DebugModelA.new(id: 'same-id')
|
60
|
+
model_b = DebugModelB.new(id: 'same-id')
|
61
|
+
|
62
|
+
puts "\n=== ENCRYPTION PHASE ==="
|
63
|
+
puts "Encrypting for ModelA:"
|
64
|
+
model_a.api_key = 'secret-key'
|
65
|
+
cipher_a = model_a.instance_variable_get(:@api_key)
|
66
|
+
|
67
|
+
puts "\nEncrypting for ModelB:"
|
68
|
+
model_b.api_key = 'secret-key'
|
69
|
+
cipher_b = model_b.instance_variable_get(:@api_key)
|
70
|
+
|
71
|
+
puts "\n=== DECRYPTION PHASE - Same Context ==="
|
72
|
+
puts "Decrypting ModelA with ModelA context:"
|
73
|
+
model_a.api_key.reveal { |plain| puts "Result: #{plain}" }
|
74
|
+
|
75
|
+
puts "\n=== DECRYPTION PHASE - Cross Context ==="
|
76
|
+
puts "Setting ModelB cipher into ModelA and trying to decrypt:"
|
77
|
+
model_a.instance_variable_set(:@api_key, cipher_b)
|
78
|
+
begin
|
79
|
+
model_a.api_key.reveal { |plain| puts "ERROR: Cross-context worked: #{plain}" }
|
80
|
+
rescue => e
|
81
|
+
puts "SUCCESS: Cross-context failed as expected: #{e.class}: #{e.message}"
|
82
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('lib', __dir__))
|
4
|
+
ENV['TEST'] = 'true' # Mark as test environment
|
5
|
+
require 'familia'
|
6
|
+
require_relative 'try/helpers/test_helpers'
|
7
|
+
|
8
|
+
puts "Testing ConcealedString internal call chain..."
|
9
|
+
|
10
|
+
class TestModel < Familia::Horreum
|
11
|
+
feature :encrypted_fields
|
12
|
+
identifier_field :id
|
13
|
+
field :id
|
14
|
+
encrypted_field :secret
|
15
|
+
end
|
16
|
+
|
17
|
+
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
18
|
+
Familia.config.encryption_keys = test_keys
|
19
|
+
Familia.config.current_key_version = :v1
|
20
|
+
|
21
|
+
# Clean database
|
22
|
+
Familia.dbclient.flushdb
|
23
|
+
|
24
|
+
puts "\n=== CONCEALED STRING INTERNAL DEBUGGING ==="
|
25
|
+
|
26
|
+
# Create and save model
|
27
|
+
model = TestModel.new(id: 'test1')
|
28
|
+
model.secret = 'plaintext-secret'
|
29
|
+
model.save
|
30
|
+
|
31
|
+
# Load model from database
|
32
|
+
loaded_model = TestModel.load('test1')
|
33
|
+
concealed_string = loaded_model.secret
|
34
|
+
|
35
|
+
# Access internal ConcealedString components
|
36
|
+
puts "ConcealedString @encrypted_data: #{concealed_string.instance_variable_get(:@encrypted_data)}"
|
37
|
+
puts "ConcealedString @record: #{concealed_string.instance_variable_get(:@record)}"
|
38
|
+
puts "ConcealedString @field_type: #{concealed_string.instance_variable_get(:@field_type)}"
|
39
|
+
|
40
|
+
record = concealed_string.instance_variable_get(:@record)
|
41
|
+
field_type = concealed_string.instance_variable_get(:@field_type)
|
42
|
+
encrypted_data = concealed_string.instance_variable_get(:@encrypted_data)
|
43
|
+
|
44
|
+
puts "\n=== STEP BY STEP DECRYPTION ==="
|
45
|
+
puts "Record class: #{record.class}"
|
46
|
+
puts "Record identifier: #{record.identifier}"
|
47
|
+
puts "Record exists?: #{record.exists?}"
|
48
|
+
puts "Field type class: #{field_type.class}"
|
49
|
+
|
50
|
+
# Test the exact same call that ConcealedString.reveal makes
|
51
|
+
puts "\nCalling field_type.decrypt_value(record, encrypted_data)..."
|
52
|
+
begin
|
53
|
+
result = field_type.decrypt_value(record, encrypted_data)
|
54
|
+
puts "decrypt_value result: #{result}"
|
55
|
+
puts "Result class: #{result.class}"
|
56
|
+
rescue => e
|
57
|
+
puts "decrypt_value ERROR: #{e.class}: #{e.message}"
|
58
|
+
puts e.backtrace.first(10)
|
59
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('lib', __dir__))
|
4
|
+
ENV['TEST'] = 'true' # Mark as test environment
|
5
|
+
require 'familia'
|
6
|
+
require_relative 'try/helpers/test_helpers'
|
7
|
+
|
8
|
+
puts "Testing ConcealedString.reveal error handling..."
|
9
|
+
|
10
|
+
class TestModel < Familia::Horreum
|
11
|
+
feature :encrypted_fields
|
12
|
+
identifier_field :id
|
13
|
+
field :id
|
14
|
+
encrypted_field :secret
|
15
|
+
end
|
16
|
+
|
17
|
+
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
18
|
+
Familia.config.encryption_keys = test_keys
|
19
|
+
Familia.config.current_key_version = :v1
|
20
|
+
|
21
|
+
# Clean database
|
22
|
+
Familia.dbclient.flushdb
|
23
|
+
|
24
|
+
puts "\n=== CONCEALED STRING REVEAL ERROR PATH ==="
|
25
|
+
|
26
|
+
# Create and save model (encrypt with AAD = nil)
|
27
|
+
model = TestModel.new(id: 'test1')
|
28
|
+
model.secret = 'plaintext-secret'
|
29
|
+
model.save
|
30
|
+
|
31
|
+
# Load model from database (decrypt with AAD = "test1")
|
32
|
+
loaded_model = TestModel.load('test1')
|
33
|
+
|
34
|
+
puts "Loaded model secret class: #{loaded_model.secret.class}"
|
35
|
+
|
36
|
+
# Test ConcealedString.reveal directly
|
37
|
+
concealed_string = loaded_model.secret
|
38
|
+
puts "ConcealedString object: #{concealed_string.inspect}"
|
39
|
+
|
40
|
+
puts "\nTesting ConcealedString.reveal..."
|
41
|
+
begin
|
42
|
+
result = concealed_string.reveal do |plaintext|
|
43
|
+
puts "Inside reveal block, plaintext: #{plaintext}"
|
44
|
+
plaintext # Return the plaintext
|
45
|
+
end
|
46
|
+
puts "Reveal result: #{result}"
|
47
|
+
puts "Result class: #{result.class}"
|
48
|
+
rescue => e
|
49
|
+
puts "Reveal ERROR: #{e.class}: #{e.message}"
|
50
|
+
puts e.backtrace.first(5)
|
51
|
+
end
|
52
|
+
|
53
|
+
puts "\nTesting ConcealedString.reveal_for_testing..."
|
54
|
+
begin
|
55
|
+
result = concealed_string.reveal_for_testing
|
56
|
+
puts "reveal_for_testing result: #{result}"
|
57
|
+
puts "Result class: #{result.class}"
|
58
|
+
rescue => e
|
59
|
+
puts "reveal_for_testing ERROR: #{e.class}: #{e.message}"
|
60
|
+
puts e.backtrace.first(5)
|
61
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('lib', __dir__))
|
4
|
+
ENV['TEST'] = 'true'
|
5
|
+
require 'familia'
|
6
|
+
require_relative 'try/helpers/test_helpers'
|
7
|
+
|
8
|
+
puts "Debugging context and AAD generation..."
|
9
|
+
|
10
|
+
# Setup encryption keys
|
11
|
+
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
12
|
+
Familia.config.encryption_keys = test_keys
|
13
|
+
Familia.config.current_key_version = :v1
|
14
|
+
|
15
|
+
class ModelA < Familia::Horreum
|
16
|
+
feature :encrypted_fields
|
17
|
+
identifier_field :id
|
18
|
+
field :id
|
19
|
+
encrypted_field :api_key
|
20
|
+
end
|
21
|
+
|
22
|
+
class ModelB < Familia::Horreum
|
23
|
+
feature :encrypted_fields
|
24
|
+
identifier_field :id
|
25
|
+
field :id
|
26
|
+
encrypted_field :api_key
|
27
|
+
end
|
28
|
+
|
29
|
+
model_a = ModelA.new(id: 'same-id')
|
30
|
+
model_b = ModelB.new(id: 'same-id')
|
31
|
+
|
32
|
+
# Get the field type for each model
|
33
|
+
field_type_a = ModelA.fields[:api_key]
|
34
|
+
field_type_b = ModelB.fields[:api_key]
|
35
|
+
|
36
|
+
puts "=== Context and AAD Analysis ==="
|
37
|
+
|
38
|
+
# Test context generation
|
39
|
+
context_a = field_type_a.send(:build_context, model_a)
|
40
|
+
context_b = field_type_b.send(:build_context, model_b)
|
41
|
+
|
42
|
+
puts "ModelA context: #{context_a}"
|
43
|
+
puts "ModelB context: #{context_b}"
|
44
|
+
puts "Contexts match: #{context_a == context_b}"
|
45
|
+
|
46
|
+
# Test AAD generation
|
47
|
+
aad_a = field_type_a.send(:build_aad, model_a)
|
48
|
+
aad_b = field_type_b.send(:build_aad, model_b)
|
49
|
+
|
50
|
+
puts "\nModelA AAD: #{aad_a}"
|
51
|
+
puts "ModelB AAD: #{aad_b}"
|
52
|
+
puts "AADs match: #{aad_a == aad_b}"
|
53
|
+
|
54
|
+
puts "\n=== Testing different identifiers ==="
|
55
|
+
model_a2 = ModelA.new(id: 'different-id')
|
56
|
+
model_b2 = ModelB.new(id: 'different-id')
|
57
|
+
|
58
|
+
context_a2 = field_type_a.send(:build_context, model_a2)
|
59
|
+
context_b2 = field_type_b.send(:build_context, model_b2)
|
60
|
+
aad_a2 = field_type_a.send(:build_aad, model_a2)
|
61
|
+
aad_b2 = field_type_b.send(:build_aad, model_b2)
|
62
|
+
|
63
|
+
puts "ModelA2 context: #{context_a2}"
|
64
|
+
puts "ModelB2 context: #{context_b2}"
|
65
|
+
puts "A2-B2 contexts match: #{context_a2 == context_b2}"
|
66
|
+
puts "ModelA2 AAD: #{aad_a2}"
|
67
|
+
puts "ModelB2 AAD: #{aad_b2}"
|
68
|
+
puts "A2-B2 AADs match: #{aad_a2 == aad_b2}"
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('lib', __dir__))
|
4
|
+
ENV['TEST'] = 'true'
|
5
|
+
require 'familia'
|
6
|
+
|
7
|
+
puts "Understanding the issue with cross-context decryption..."
|
8
|
+
|
9
|
+
# Setup encryption keys
|
10
|
+
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
11
|
+
Familia.config.encryption_keys = test_keys
|
12
|
+
Familia.config.current_key_version = :v1
|
13
|
+
|
14
|
+
class ModelA < Familia::Horreum
|
15
|
+
feature :encrypted_fields
|
16
|
+
identifier_field :id
|
17
|
+
field :id
|
18
|
+
encrypted_field :api_key
|
19
|
+
end
|
20
|
+
|
21
|
+
class ModelB < Familia::Horreum
|
22
|
+
feature :encrypted_fields
|
23
|
+
identifier_field :id
|
24
|
+
field :id
|
25
|
+
encrypted_field :api_key
|
26
|
+
end
|
27
|
+
|
28
|
+
model_a = ModelA.new(id: 'same-id')
|
29
|
+
model_b = ModelB.new(id: 'same-id')
|
30
|
+
|
31
|
+
puts "ModelA class: #{model_a.class}"
|
32
|
+
puts "ModelB class: #{model_b.class}"
|
33
|
+
puts "Same class?: #{model_a.class == model_b.class}"
|
34
|
+
|
35
|
+
# Inspect what context would be built
|
36
|
+
puts "\nContext analysis:"
|
37
|
+
puts "ModelA identifier: #{model_a.identifier}"
|
38
|
+
puts "ModelB identifier: #{model_b.identifier}"
|
39
|
+
|
40
|
+
# Let's see what happens with the field types
|
41
|
+
modelA_api_key_type = nil
|
42
|
+
modelB_api_key_type = nil
|
43
|
+
|
44
|
+
ModelA.field_types.each do |name, type|
|
45
|
+
if name == :api_key
|
46
|
+
modelA_api_key_type = type
|
47
|
+
break
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
ModelB.field_types.each do |name, type|
|
52
|
+
if name == :api_key
|
53
|
+
modelB_api_key_type = type
|
54
|
+
break
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
puts "ModelA api_key type: #{modelA_api_key_type}"
|
59
|
+
puts "ModelB api_key type: #{modelB_api_key_type}"
|
60
|
+
puts "Same type object?: #{modelA_api_key_type.object_id == modelB_api_key_type.object_id}"
|
61
|
+
|
62
|
+
# Check what context and AAD would be generated
|
63
|
+
if modelA_api_key_type && modelB_api_key_type
|
64
|
+
context_a = "#{model_a.class.name}:api_key:#{model_a.identifier}"
|
65
|
+
context_b = "#{model_b.class.name}:api_key:#{model_b.identifier}"
|
66
|
+
|
67
|
+
puts "\nExpected contexts:"
|
68
|
+
puts "ModelA context: #{context_a}"
|
69
|
+
puts "ModelB context: #{context_b}"
|
70
|
+
puts "Contexts should be different: #{context_a != context_b}"
|
71
|
+
|
72
|
+
# AAD should be identifier when no aad_fields
|
73
|
+
aad_a = model_a.identifier
|
74
|
+
aad_b = model_b.identifier
|
75
|
+
|
76
|
+
puts "\nExpected AADs:"
|
77
|
+
puts "ModelA AAD: #{aad_a}"
|
78
|
+
puts "ModelB AAD: #{aad_b}"
|
79
|
+
puts "AADs are same: #{aad_a == aad_b}"
|
80
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('lib', __dir__))
|
4
|
+
ENV['TEST'] = 'true'
|
5
|
+
require 'familia'
|
6
|
+
require_relative 'try/helpers/test_helpers'
|
7
|
+
|
8
|
+
puts "Debugging cross-context validation..."
|
9
|
+
|
10
|
+
# Setup encryption keys
|
11
|
+
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
12
|
+
Familia.config.encryption_keys = test_keys
|
13
|
+
Familia.config.current_key_version = :v1
|
14
|
+
|
15
|
+
class ModelA < Familia::Horreum
|
16
|
+
feature :encrypted_fields
|
17
|
+
identifier_field :id
|
18
|
+
field :id
|
19
|
+
encrypted_field :api_key
|
20
|
+
end
|
21
|
+
|
22
|
+
class ModelB < Familia::Horreum
|
23
|
+
feature :encrypted_fields
|
24
|
+
identifier_field :id
|
25
|
+
field :id
|
26
|
+
encrypted_field :api_key
|
27
|
+
end
|
28
|
+
|
29
|
+
# Clean database
|
30
|
+
Familia.dbclient.flushdb
|
31
|
+
|
32
|
+
model_a = ModelA.new(id: 'same-id')
|
33
|
+
model_b = ModelB.new(id: 'same-id')
|
34
|
+
|
35
|
+
model_a.api_key = 'secret-key'
|
36
|
+
model_b.api_key = 'secret-key'
|
37
|
+
|
38
|
+
cipher_a = model_a.instance_variable_get(:@api_key)
|
39
|
+
cipher_b = model_b.instance_variable_get(:@api_key)
|
40
|
+
|
41
|
+
puts "cipher_a: #{cipher_a.class} - #{cipher_a.encrypted_value}"
|
42
|
+
puts "cipher_b: #{cipher_b.class} - #{cipher_b.encrypted_value}"
|
43
|
+
|
44
|
+
# Now try cross-context access
|
45
|
+
puts "\n=== Cross-context test ==="
|
46
|
+
model_a.instance_variable_set(:@api_key, cipher_b)
|
47
|
+
puts "Set cipher_b into model_a"
|
48
|
+
|
49
|
+
begin
|
50
|
+
result = model_a.api_key
|
51
|
+
puts "Got result: #{result.class} - should be ConcealedString"
|
52
|
+
|
53
|
+
# Try to reveal it
|
54
|
+
result.reveal do |plaintext|
|
55
|
+
puts "Successfully revealed: #{plaintext}"
|
56
|
+
end
|
57
|
+
puts "ERROR: Cross-context decryption should have failed!"
|
58
|
+
rescue Familia::EncryptionError => e
|
59
|
+
puts "SUCCESS: Got expected encryption error: #{e.message}"
|
60
|
+
rescue => e
|
61
|
+
puts "Got unexpected error: #{e.class}: #{e.message}"
|
62
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('lib', __dir__))
|
4
|
+
ENV['TEST'] = 'true' # Mark as test environment
|
5
|
+
require 'familia'
|
6
|
+
require_relative 'try/helpers/test_helpers'
|
7
|
+
|
8
|
+
puts "Testing database load vs in-memory encryption..."
|
9
|
+
|
10
|
+
class TestModel < Familia::Horreum
|
11
|
+
feature :encrypted_fields
|
12
|
+
identifier_field :id
|
13
|
+
field :id
|
14
|
+
field :title
|
15
|
+
encrypted_field :secret
|
16
|
+
end
|
17
|
+
|
18
|
+
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
19
|
+
Familia.config.encryption_keys = test_keys
|
20
|
+
Familia.config.current_key_version = :v1
|
21
|
+
|
22
|
+
# Clean database
|
23
|
+
Familia.dbclient.flushdb
|
24
|
+
|
25
|
+
puts "\n=== PHASE 1: In-memory object ==="
|
26
|
+
model = TestModel.new(id: 'test1')
|
27
|
+
model.title = 'Test Title'
|
28
|
+
puts "PRE-ENCRYPT: model.exists? = #{model.exists?}"
|
29
|
+
model.secret = 'plaintext-secret'
|
30
|
+
|
31
|
+
puts "In-memory secret class: #{model.secret.class}"
|
32
|
+
puts "In-memory secret value: #{model.secret}"
|
33
|
+
|
34
|
+
model.secret.reveal do |plaintext|
|
35
|
+
puts "In-memory reveal: #{plaintext}"
|
36
|
+
end
|
37
|
+
|
38
|
+
puts "\n=== PHASE 2: Save to database ==="
|
39
|
+
model.save
|
40
|
+
|
41
|
+
puts "Keys in database: #{Familia.dbclient.keys('*')}"
|
42
|
+
puts "Hash contents: #{Familia.dbclient.hgetall('testmodel:test1:object')}"
|
43
|
+
|
44
|
+
raw_secret = Familia.dbclient.hget('testmodel:test1:object', 'secret')
|
45
|
+
puts "Raw secret from DB: #{raw_secret}"
|
46
|
+
|
47
|
+
puts "\n=== PHASE 3: Load from database ==="
|
48
|
+
loaded_model = TestModel.load('test1')
|
49
|
+
|
50
|
+
if loaded_model
|
51
|
+
puts "Loaded model exists: #{loaded_model.exists?}"
|
52
|
+
puts "Loaded secret class: #{loaded_model.secret.class}"
|
53
|
+
puts "Loaded secret value: #{loaded_model.secret}"
|
54
|
+
|
55
|
+
begin
|
56
|
+
loaded_model.secret.reveal do |plaintext|
|
57
|
+
puts "Loaded reveal: #{plaintext}"
|
58
|
+
end
|
59
|
+
rescue => e
|
60
|
+
puts "Loaded reveal ERROR: #{e.class}: #{e.message}"
|
61
|
+
end
|
62
|
+
else
|
63
|
+
puts "Failed to load model"
|
64
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('lib', __dir__))
|
4
|
+
ENV['TEST'] = 'true' # Mark as test environment
|
5
|
+
require 'familia'
|
6
|
+
require_relative 'try/helpers/test_helpers'
|
7
|
+
|
8
|
+
puts "Testing encrypted_json? method..."
|
9
|
+
|
10
|
+
class TestModel < Familia::Horreum
|
11
|
+
feature :encrypted_fields
|
12
|
+
identifier_field :id
|
13
|
+
field :id
|
14
|
+
encrypted_field :secret
|
15
|
+
end
|
16
|
+
|
17
|
+
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
18
|
+
Familia.config.encryption_keys = test_keys
|
19
|
+
Familia.config.current_key_version = :v1
|
20
|
+
|
21
|
+
field_type = TestModel.field_types[:secret]
|
22
|
+
|
23
|
+
# Test with actual encrypted JSON from the load path
|
24
|
+
encrypted_json = '{"algorithm":"xchacha20poly1305","nonce":"RDK0GSY3Vbrbv7OAgol10bHOmderAExt","ciphertext":"uo8j6Pm6tV68BcvqK5maXQ==","auth_tag":"5Cr1QgTnajnWIji0fsQP0g==","key_version":"v1"}'
|
25
|
+
|
26
|
+
puts "Testing encrypted JSON: #{encrypted_json[0..80]}..."
|
27
|
+
puts "String class: #{encrypted_json.class}"
|
28
|
+
puts "Is string?: #{encrypted_json.is_a?(String)}"
|
29
|
+
|
30
|
+
puts "\nManual JSON parsing:"
|
31
|
+
begin
|
32
|
+
parsed = JSON.parse(encrypted_json)
|
33
|
+
puts "Parsed successfully: #{parsed.class}"
|
34
|
+
puts "Is hash?: #{parsed.is_a?(Hash)}"
|
35
|
+
puts "Has algorithm key?: #{parsed.key?('algorithm')}"
|
36
|
+
puts "Algorithm value: #{parsed['algorithm']}"
|
37
|
+
rescue => e
|
38
|
+
puts "JSON parse error: #{e.class}: #{e.message}"
|
39
|
+
end
|
40
|
+
|
41
|
+
puts "\nTesting field_type.encrypted_json? method:"
|
42
|
+
result = field_type.encrypted_json?(encrypted_json)
|
43
|
+
puts "encrypted_json? result: #{result}"
|
44
|
+
|
45
|
+
puts "\nTesting with symbol keys:"
|
46
|
+
begin
|
47
|
+
parsed_sym = JSON.parse(encrypted_json, symbolize_names: true)
|
48
|
+
puts "Parsed with symbols: #{parsed_sym.class}"
|
49
|
+
puts "Has :algorithm key?: #{parsed_sym.key?(:algorithm)}"
|
50
|
+
puts "Has 'algorithm' key?: #{parsed_sym.key?('algorithm')}"
|
51
|
+
rescue => e
|
52
|
+
puts "Symbol parse error: #{e.class}: #{e.message}"
|
53
|
+
end
|