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.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop_todo.yml +17 -17
  4. data/CLAUDE.md +11 -8
  5. data/Gemfile +5 -1
  6. data/Gemfile.lock +19 -3
  7. data/README.md +36 -157
  8. data/docs/overview.md +359 -0
  9. data/docs/wiki/API-Reference.md +347 -0
  10. data/docs/wiki/Connection-Pooling-Guide.md +437 -0
  11. data/docs/wiki/Encrypted-Fields-Overview.md +101 -0
  12. data/docs/wiki/Expiration-Feature-Guide.md +596 -0
  13. data/docs/wiki/Feature-System-Guide.md +600 -0
  14. data/docs/wiki/Features-System-Developer-Guide.md +892 -0
  15. data/docs/wiki/Field-System-Guide.md +784 -0
  16. data/docs/wiki/Home.md +106 -0
  17. data/docs/wiki/Implementation-Guide.md +276 -0
  18. data/docs/wiki/Quantization-Feature-Guide.md +721 -0
  19. data/docs/wiki/RelatableObjects-Guide.md +563 -0
  20. data/docs/wiki/Security-Model.md +183 -0
  21. data/docs/wiki/Transient-Fields-Guide.md +280 -0
  22. data/lib/familia/base.rb +18 -27
  23. data/lib/familia/connection.rb +6 -5
  24. data/lib/familia/{datatype → data_type}/commands.rb +2 -5
  25. data/lib/familia/{datatype → data_type}/serialization.rb +8 -10
  26. data/lib/familia/data_type/types/counter.rb +38 -0
  27. data/lib/familia/{datatype → data_type}/types/hashkey.rb +20 -2
  28. data/lib/familia/{datatype → data_type}/types/list.rb +17 -18
  29. data/lib/familia/data_type/types/lock.rb +43 -0
  30. data/lib/familia/{datatype → data_type}/types/sorted_set.rb +17 -17
  31. data/lib/familia/{datatype → data_type}/types/string.rb +11 -3
  32. data/lib/familia/{datatype → data_type}/types/unsorted_set.rb +17 -18
  33. data/lib/familia/{datatype.rb → data_type.rb} +12 -14
  34. data/lib/familia/encryption/encrypted_data.rb +137 -0
  35. data/lib/familia/encryption/manager.rb +119 -0
  36. data/lib/familia/encryption/provider.rb +49 -0
  37. data/lib/familia/encryption/providers/aes_gcm_provider.rb +123 -0
  38. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +184 -0
  39. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +138 -0
  40. data/lib/familia/encryption/registry.rb +50 -0
  41. data/lib/familia/encryption.rb +178 -0
  42. data/lib/familia/encryption_request_cache.rb +68 -0
  43. data/lib/familia/errors.rb +17 -3
  44. data/lib/familia/features/encrypted_fields/concealed_string.rb +295 -0
  45. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +221 -0
  46. data/lib/familia/features/encrypted_fields.rb +28 -0
  47. data/lib/familia/features/expiration.rb +107 -77
  48. data/lib/familia/features/quantization.rb +5 -9
  49. data/lib/familia/features/relatable_objects.rb +2 -4
  50. data/lib/familia/features/safe_dump.rb +14 -17
  51. data/lib/familia/features/transient_fields/redacted_string.rb +159 -0
  52. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +62 -0
  53. data/lib/familia/features/transient_fields/transient_field_type.rb +139 -0
  54. data/lib/familia/features/transient_fields.rb +47 -0
  55. data/lib/familia/features.rb +40 -24
  56. data/lib/familia/field_type.rb +273 -0
  57. data/lib/familia/horreum/{connection.rb → core/connection.rb} +6 -15
  58. data/lib/familia/horreum/{commands.rb → core/database_commands.rb} +20 -21
  59. data/lib/familia/horreum/core/serialization.rb +535 -0
  60. data/lib/familia/horreum/{utils.rb → core/utils.rb} +9 -12
  61. data/lib/familia/horreum/core.rb +21 -0
  62. data/lib/familia/horreum/{settings.rb → shared/settings.rb} +10 -4
  63. data/lib/familia/horreum/subclass/definition.rb +469 -0
  64. data/lib/familia/horreum/{class_methods.rb → subclass/management.rb} +27 -250
  65. data/lib/familia/horreum/{related_fields_management.rb → subclass/related_fields_management.rb} +15 -10
  66. data/lib/familia/horreum.rb +30 -22
  67. data/lib/familia/logging.rb +14 -14
  68. data/lib/familia/settings.rb +39 -3
  69. data/lib/familia/utils.rb +45 -0
  70. data/lib/familia/version.rb +1 -1
  71. data/lib/familia.rb +3 -2
  72. data/try/core/base_enhancements_try.rb +115 -0
  73. data/try/core/connection_try.rb +0 -1
  74. data/try/core/create_method_try.rb +240 -0
  75. data/try/core/database_consistency_try.rb +299 -0
  76. data/try/core/errors_try.rb +25 -5
  77. data/try/core/familia_extended_try.rb +3 -4
  78. data/try/core/familia_try.rb +1 -2
  79. data/try/core/persistence_operations_try.rb +297 -0
  80. data/try/core/pools_try.rb +2 -2
  81. data/try/core/secure_identifier_try.rb +0 -1
  82. data/try/core/settings_try.rb +0 -1
  83. data/try/core/utils_try.rb +0 -1
  84. data/try/{datatypes → data_types}/boolean_try.rb +1 -2
  85. data/try/data_types/counter_try.rb +93 -0
  86. data/try/{datatypes → data_types}/datatype_base_try.rb +2 -3
  87. data/try/{datatypes → data_types}/hash_try.rb +1 -2
  88. data/try/{datatypes → data_types}/list_try.rb +1 -2
  89. data/try/data_types/lock_try.rb +133 -0
  90. data/try/{datatypes → data_types}/set_try.rb +1 -2
  91. data/try/{datatypes → data_types}/sorted_set_try.rb +1 -2
  92. data/try/{datatypes → data_types}/string_try.rb +1 -2
  93. data/try/debugging/README.md +32 -0
  94. data/try/debugging/cache_behavior_tracer.rb +91 -0
  95. data/try/debugging/debug_aad_process.rb +82 -0
  96. data/try/debugging/debug_concealed_internal.rb +59 -0
  97. data/try/debugging/debug_concealed_reveal.rb +61 -0
  98. data/try/debugging/debug_context_aad.rb +68 -0
  99. data/try/debugging/debug_context_simple.rb +80 -0
  100. data/try/debugging/debug_cross_context.rb +62 -0
  101. data/try/debugging/debug_database_load.rb +64 -0
  102. data/try/debugging/debug_encrypted_json_check.rb +53 -0
  103. data/try/debugging/debug_encrypted_json_step_by_step.rb +62 -0
  104. data/try/debugging/debug_exists_lifecycle.rb +54 -0
  105. data/try/debugging/debug_field_decrypt.rb +74 -0
  106. data/try/debugging/debug_fresh_cross_context.rb +73 -0
  107. data/try/debugging/debug_load_path.rb +66 -0
  108. data/try/debugging/debug_method_definition.rb +46 -0
  109. data/try/debugging/debug_method_resolution.rb +41 -0
  110. data/try/debugging/debug_minimal.rb +24 -0
  111. data/try/debugging/debug_provider.rb +68 -0
  112. data/try/debugging/debug_secure_behavior.rb +73 -0
  113. data/try/debugging/debug_string_class.rb +46 -0
  114. data/try/debugging/debug_test.rb +46 -0
  115. data/try/debugging/debug_test_design.rb +80 -0
  116. data/try/debugging/encryption_method_tracer.rb +138 -0
  117. data/try/debugging/provider_diagnostics.rb +110 -0
  118. data/try/edge_cases/hash_symbolization_try.rb +0 -1
  119. data/try/edge_cases/json_serialization_try.rb +0 -1
  120. data/try/edge_cases/reserved_keywords_try.rb +42 -11
  121. data/try/encryption/config_persistence_try.rb +192 -0
  122. data/try/encryption/encryption_core_try.rb +328 -0
  123. data/try/encryption/instance_variable_scope_try.rb +31 -0
  124. data/try/encryption/module_loading_try.rb +28 -0
  125. data/try/encryption/providers/aes_gcm_provider_try.rb +178 -0
  126. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +169 -0
  127. data/try/encryption/roundtrip_validation_try.rb +28 -0
  128. data/try/encryption/secure_memory_handling_try.rb +125 -0
  129. data/try/features/encrypted_fields_core_try.rb +125 -0
  130. data/try/features/encrypted_fields_integration_try.rb +216 -0
  131. data/try/features/encrypted_fields_no_cache_security_try.rb +219 -0
  132. data/try/features/encrypted_fields_security_try.rb +377 -0
  133. data/try/features/encryption_fields/aad_protection_try.rb +138 -0
  134. data/try/features/encryption_fields/concealed_string_core_try.rb +250 -0
  135. data/try/features/encryption_fields/context_isolation_try.rb +141 -0
  136. data/try/features/encryption_fields/error_conditions_try.rb +116 -0
  137. data/try/features/encryption_fields/fresh_key_derivation_try.rb +128 -0
  138. data/try/features/encryption_fields/fresh_key_try.rb +168 -0
  139. data/try/features/encryption_fields/key_rotation_try.rb +123 -0
  140. data/try/features/encryption_fields/memory_security_try.rb +37 -0
  141. data/try/features/encryption_fields/missing_current_key_version_try.rb +23 -0
  142. data/try/features/encryption_fields/nonce_uniqueness_try.rb +56 -0
  143. data/try/features/encryption_fields/secure_by_default_behavior_try.rb +310 -0
  144. data/try/features/encryption_fields/thread_safety_try.rb +199 -0
  145. data/try/features/encryption_fields/universal_serialization_safety_try.rb +174 -0
  146. data/try/features/expiration_try.rb +0 -1
  147. data/try/features/feature_dependencies_try.rb +159 -0
  148. data/try/features/quantization_try.rb +0 -1
  149. data/try/features/real_feature_integration_try.rb +148 -0
  150. data/try/features/relatable_objects_try.rb +0 -1
  151. data/try/features/safe_dump_advanced_try.rb +0 -1
  152. data/try/features/safe_dump_try.rb +0 -1
  153. data/try/features/transient_fields/redacted_string_try.rb +248 -0
  154. data/try/features/transient_fields/refresh_reset_try.rb +164 -0
  155. data/try/features/transient_fields/simple_refresh_test.rb +50 -0
  156. data/try/features/transient_fields/single_use_redacted_string_try.rb +310 -0
  157. data/try/features/transient_fields_core_try.rb +181 -0
  158. data/try/features/transient_fields_integration_try.rb +260 -0
  159. data/try/helpers/test_helpers.rb +67 -0
  160. data/try/horreum/base_try.rb +157 -3
  161. data/try/horreum/enhanced_conflict_handling_try.rb +176 -0
  162. data/try/horreum/field_categories_try.rb +118 -0
  163. data/try/horreum/field_definition_try.rb +96 -0
  164. data/try/horreum/initialization_try.rb +1 -2
  165. data/try/horreum/relations_try.rb +1 -2
  166. data/try/horreum/serialization_persistent_fields_try.rb +165 -0
  167. data/try/horreum/serialization_try.rb +41 -7
  168. data/try/memory/memory_basic_test.rb +73 -0
  169. data/try/memory/memory_detailed_test.rb +121 -0
  170. data/try/memory/memory_docker_ruby_dump.sh +80 -0
  171. data/try/memory/memory_search_for_string.rb +83 -0
  172. data/try/memory/test_actual_redactedstring_protection.rb +38 -0
  173. data/try/models/customer_safe_dump_try.rb +1 -2
  174. data/try/models/customer_try.rb +1 -2
  175. data/try/models/datatype_base_try.rb +1 -2
  176. data/try/models/familia_object_try.rb +0 -1
  177. metadata +131 -23
  178. data/lib/familia/horreum/serialization.rb +0 -445
@@ -1,6 +1,5 @@
1
- # try/datatypes/set_try.rb
1
+ # try/data_types/set_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  @a = Bone.new 'atoken'
@@ -1,6 +1,5 @@
1
- # try/datatypes/sorted_set_try.rb
1
+ # try/data_types/sorted_set_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  @a = Bone.new 'atoken'
@@ -1,6 +1,5 @@
1
- # try/datatypes/string_try.rb
1
+ # try/data_types/string_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  @a = Bone.new(token: 'atoken2')
@@ -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