familia 2.0.0.pre3 → 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 -229
- 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/class_methods_try.rb +27 -36
- 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
data/lib/familia/settings.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
# lib/familia/settings.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
|
5
4
|
@delim = ':'
|
6
5
|
@prefix = nil
|
7
6
|
@suffix = :object
|
8
7
|
@default_expiration = 0 # see update_expiration. Zero is skip. nil is an exception.
|
9
8
|
@logical_database = nil
|
9
|
+
@encryption_keys = nil
|
10
|
+
@current_key_version = nil
|
11
|
+
@encryption_personalization = 'FamilialMatters'
|
10
12
|
|
11
13
|
module Settings
|
12
|
-
|
13
|
-
|
14
|
+
attr_writer :delim, :suffix, :default_expiration, :logical_database, :prefix, :encryption_keys,
|
15
|
+
:current_key_version, :encryption_personalization
|
14
16
|
|
15
17
|
def delim(val = nil)
|
16
18
|
@delim = val if val
|
@@ -44,5 +46,39 @@ module Familia
|
|
44
46
|
suffix
|
45
47
|
end
|
46
48
|
|
49
|
+
def encryption_keys(val = nil)
|
50
|
+
@encryption_keys = val if val
|
51
|
+
@encryption_keys
|
52
|
+
end
|
53
|
+
|
54
|
+
def current_key_version(val = nil)
|
55
|
+
@current_key_version = val if val
|
56
|
+
@current_key_version
|
57
|
+
end
|
58
|
+
|
59
|
+
# Personalization string for BLAKE2b key derivation in XChaCha20Poly1305.
|
60
|
+
# This provides cryptographic domain separation, ensuring derived keys are
|
61
|
+
# unique per application even with identical master keys and contexts.
|
62
|
+
# Must be 16 bytes or less (automatically padded with null bytes).
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# Familia.configure do |config|
|
66
|
+
# config.encryption_personalization = 'MyApp1.0'
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# @param val [String, nil] The personalization string, or nil to get current value
|
70
|
+
# @return [String] Current personalization string
|
71
|
+
def encryption_personalization(val = nil)
|
72
|
+
if val
|
73
|
+
raise ArgumentError, 'Personalization string cannot exceed 16 bytes' if val.bytesize > 16
|
74
|
+
|
75
|
+
@encryption_personalization = val
|
76
|
+
end
|
77
|
+
@encryption_personalization
|
78
|
+
end
|
79
|
+
|
80
|
+
def config
|
81
|
+
self
|
82
|
+
end
|
47
83
|
end
|
48
84
|
end
|
data/lib/familia/utils.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# lib/familia/utils.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
+
|
5
|
+
# Family-related utility methods
|
6
|
+
#
|
4
7
|
module Utils
|
5
8
|
|
6
9
|
# Joins array elements with Familia delimiter
|
@@ -145,5 +148,47 @@ module Familia
|
|
145
148
|
end
|
146
149
|
end
|
147
150
|
|
151
|
+
# Converts an absolute file path to a path relative to the current working
|
152
|
+
# directory. This simplifies logging and error reporting by showing
|
153
|
+
# only the relevant parts of file paths instead of lengthy absolute paths.
|
154
|
+
#
|
155
|
+
# @param filepath [String, Pathname] The file path to convert
|
156
|
+
# @return [Pathname, String, nil] A relative path from current directory,
|
157
|
+
# basename if path goes outside current directory, or nil if filepath is nil
|
158
|
+
#
|
159
|
+
# @example Using current directory as base
|
160
|
+
# Utils.pretty_path("/home/dev/project/lib/config.rb") # => "lib/config.rb"
|
161
|
+
#
|
162
|
+
# @example Path outside current directory
|
163
|
+
# Utils.pretty_path("/etc/hosts") # => "hosts"
|
164
|
+
#
|
165
|
+
# @example Nil input
|
166
|
+
# Utils.pretty_path(nil) # => nil
|
167
|
+
#
|
168
|
+
# @see Pathname#relative_path_from Ruby standard library documentation
|
169
|
+
def pretty_path(filepath)
|
170
|
+
return nil if filepath.nil?
|
171
|
+
|
172
|
+
basepath = Dir.pwd
|
173
|
+
relative_path = Pathname.new(filepath).relative_path_from(basepath)
|
174
|
+
if relative_path.to_s.start_with?('..')
|
175
|
+
File.basename(filepath)
|
176
|
+
else
|
177
|
+
relative_path
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Formats a stack trace with pretty file paths for improved readability
|
182
|
+
#
|
183
|
+
# @param limit [Integer] Maximum number of stack frames to include (default: 3)
|
184
|
+
# @return [String] Formatted stack trace with relative paths joined by newlines
|
185
|
+
#
|
186
|
+
# @example
|
187
|
+
# Utils.pretty_stack(limit: 10)
|
188
|
+
# # => "lib/models/user.rb:25:in `save'\n lib/controllers/app.rb:45:in `create'"
|
189
|
+
def pretty_stack(skip: 1, limit: 5)
|
190
|
+
caller(skip..(skip + limit + 1)).first(limit).map { |frame| pretty_path(frame) }.join("\n")
|
191
|
+
end
|
192
|
+
|
148
193
|
end
|
149
194
|
end
|
data/lib/familia/version.rb
CHANGED
data/lib/familia.rb
CHANGED
@@ -0,0 +1,115 @@
|
|
1
|
+
# try/core/base_enhancements_try.rb
|
2
|
+
|
3
|
+
require_relative '../helpers/test_helpers'
|
4
|
+
|
5
|
+
Familia.debug = false
|
6
|
+
|
7
|
+
# Base class provides default UUID generation
|
8
|
+
class BaseUuidTest < Familia::Horreum
|
9
|
+
identifier_field :id
|
10
|
+
field :id
|
11
|
+
end
|
12
|
+
|
13
|
+
# Empty class still has base functionality
|
14
|
+
class EmptyBaseTest < Familia::Horreum
|
15
|
+
end
|
16
|
+
|
17
|
+
@base_uuid = BaseUuidTest.new(id: 'uuid_test_1')
|
18
|
+
|
19
|
+
## UUID generation creates unique identifiers
|
20
|
+
@uuid1 = @base_uuid.uuid
|
21
|
+
@uuid1
|
22
|
+
#=:> String
|
23
|
+
|
24
|
+
## UUID is properly formatted
|
25
|
+
@uuid1
|
26
|
+
#=~>/\A[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
|
27
|
+
|
28
|
+
## UUID is memoized (same value on repeated calls)
|
29
|
+
@uuid2 = @base_uuid.uuid
|
30
|
+
@uuid1 == @uuid2
|
31
|
+
#=> true
|
32
|
+
|
33
|
+
## Different instances get different UUIDs
|
34
|
+
@base_uuid2 = BaseUuidTest.new(id: 'uuid_test_2')
|
35
|
+
@base_uuid.uuid != @base_uuid2.uuid
|
36
|
+
#=> true
|
37
|
+
|
38
|
+
## Base class provides ID generation
|
39
|
+
@generated_id1 = @base_uuid.generate_id
|
40
|
+
@generated_id1
|
41
|
+
#=:> String
|
42
|
+
|
43
|
+
## Generated ID is memoized
|
44
|
+
@generated_id2 = @base_uuid.generate_id
|
45
|
+
@generated_id1 == @generated_id2
|
46
|
+
#=> true
|
47
|
+
|
48
|
+
## Base class to_s method returns identifier
|
49
|
+
@base_uuid.to_s
|
50
|
+
#=> "uuid_test_1"
|
51
|
+
|
52
|
+
## Feature registry is accessible
|
53
|
+
Familia::Base.features_available
|
54
|
+
#=:> Hash
|
55
|
+
|
56
|
+
## Feature definitions registry is accessible
|
57
|
+
Familia::Base.feature_definitions
|
58
|
+
#=:> Hash
|
59
|
+
|
60
|
+
## Base class includes proper modules
|
61
|
+
BaseUuidTest.ancestors.include?(Familia::Base)
|
62
|
+
#=> true
|
63
|
+
|
64
|
+
## Feature methods are accessible through the class
|
65
|
+
BaseUuidTest.respond_to?(:feature)
|
66
|
+
#=> true
|
67
|
+
|
68
|
+
## Class instance variables are properly initialized
|
69
|
+
BaseUuidTest.instance_variable_get(:@fields)
|
70
|
+
#=:> Array
|
71
|
+
|
72
|
+
## Field definitions are properly initialized
|
73
|
+
BaseUuidTest.instance_variable_get(:@field_types)
|
74
|
+
#=:> Hash
|
75
|
+
|
76
|
+
## Feature system is properly integrated
|
77
|
+
BaseUuidTest.respond_to?(:feature)
|
78
|
+
#=> true
|
79
|
+
|
80
|
+
## Feature registration methods are available
|
81
|
+
Familia::Base.respond_to?(:add_feature)
|
82
|
+
#=> true
|
83
|
+
|
84
|
+
## Valid identifiers work correctly
|
85
|
+
@base_uuid.identifier
|
86
|
+
#=> "uuid_test_1"
|
87
|
+
|
88
|
+
## Empty class has base methods available
|
89
|
+
EmptyBaseTest.ancestors.include?(Familia::Base)
|
90
|
+
#=> true
|
91
|
+
|
92
|
+
## Empty class can use feature system
|
93
|
+
EmptyBaseTest.respond_to?(:feature)
|
94
|
+
#=> true
|
95
|
+
|
96
|
+
## Test Base module constants are defined
|
97
|
+
Familia::Base.features_available
|
98
|
+
#=:> Hash
|
99
|
+
|
100
|
+
## Dump and load methods are set
|
101
|
+
Familia::Base.dump_method
|
102
|
+
#=> :to_json
|
103
|
+
|
104
|
+
## Load method is set correctly
|
105
|
+
Familia::Base.load_method
|
106
|
+
#=> :from_json
|
107
|
+
|
108
|
+
## Base module provides inspect with class name
|
109
|
+
@base_uuid.inspect.include?('BaseUuidTest')
|
110
|
+
#=> true
|
111
|
+
|
112
|
+
@base_uuid.destroy! rescue nil
|
113
|
+
@base_uuid2.destroy! rescue nil
|
114
|
+
@base_uuid = nil
|
115
|
+
@base_uuid2 = nil
|
data/try/core/connection_try.rb
CHANGED
data/try/core/errors_try.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'time'
|
4
4
|
|
5
|
-
require_relative '../../lib/familia'
|
6
5
|
require_relative '../helpers/test_helpers'
|
7
6
|
|
8
7
|
## Has all datatype relativess
|
@@ -11,15 +10,15 @@ registered_types.collect(&:to_s).sort
|
|
11
10
|
#=> ["counter", "hash", "hashkey", "list", "lock", "set", "sorted_set", "string", "zset"]
|
12
11
|
|
13
12
|
## Familia created class methods for datatype list class
|
14
|
-
Familia::Horreum::
|
13
|
+
Familia::Horreum::DefinitionMethods.public_method_defined? :list?
|
15
14
|
#=> true
|
16
15
|
|
17
16
|
## Familia created class methods for datatype list class
|
18
|
-
Familia::Horreum::
|
17
|
+
Familia::Horreum::DefinitionMethods.public_method_defined? :list
|
19
18
|
#=> true
|
20
19
|
|
21
20
|
## Familia created class methods for datatype list class
|
22
|
-
Familia::Horreum::
|
21
|
+
Familia::Horreum::DefinitionMethods.public_method_defined? :lists
|
23
22
|
#=> true
|
24
23
|
|
25
24
|
## A Familia object knows its datatype relatives
|
data/try/core/familia_try.rb
CHANGED
data/try/core/pools_try.rb
CHANGED
@@ -30,7 +30,7 @@ end
|
|
30
30
|
class PoolTestAccount < Familia::Horreum
|
31
31
|
identifier_field :account_id
|
32
32
|
field :account_id
|
33
|
-
field :balance
|
33
|
+
field :balance, on_conflict: :skip
|
34
34
|
field :holder_name
|
35
35
|
|
36
36
|
def init
|
@@ -83,7 +83,7 @@ class PoolTestAccountDB1 < Familia::Horreum
|
|
83
83
|
self.logical_database = 1
|
84
84
|
identifier_field :account_id
|
85
85
|
field :account_id
|
86
|
-
field :balance
|
86
|
+
field :balance, on_conflict: :skip
|
87
87
|
field :holder_name
|
88
88
|
|
89
89
|
def init
|
data/try/core/settings_try.rb
CHANGED
data/try/core/utils_try.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
# try/
|
1
|
+
# try/data_types/base_try.rb
|
2
2
|
|
3
|
-
require_relative '../../lib/familia'
|
4
3
|
require_relative '../helpers/test_helpers'
|
5
4
|
|
6
5
|
@limiter1 = Limiter.new :requests
|
@@ -64,6 +63,6 @@ p [@limiter1.counter.parent.default_expiration, @limiter2.counter.parent.default
|
|
64
63
|
#=> 3600.0
|
65
64
|
|
66
65
|
## Check current_expiration
|
67
|
-
sleep 1 #
|
66
|
+
sleep 1 # NOTE: Mocking time would be foolish in life, but helpful here
|
68
67
|
@limiter1.counter.current_expiration
|
69
68
|
#=> 3600-1
|
@@ -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,138 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Debug script to trace encryption method calls and data flow
|
3
|
+
|
4
|
+
require 'base64'
|
5
|
+
require 'bundler/setup'
|
6
|
+
require_relative '../helpers/test_helpers'
|
7
|
+
|
8
|
+
puts "=== Encryption Method Call Tracer ==="
|
9
|
+
puts
|
10
|
+
|
11
|
+
# Monkey patch to add detailed tracing
|
12
|
+
module Familia
|
13
|
+
module Encryption
|
14
|
+
class << self
|
15
|
+
# Store original methods
|
16
|
+
alias_method :orig_encrypt, :encrypt
|
17
|
+
alias_method :orig_decrypt, :decrypt
|
18
|
+
|
19
|
+
def encrypt(plaintext, context:, additional_data: nil)
|
20
|
+
puts "📤 ENCRYPT called:"
|
21
|
+
puts " Context: #{context}"
|
22
|
+
puts " Plaintext length: #{plaintext&.length} chars"
|
23
|
+
puts " AAD: #{additional_data ? 'present' : 'none'}"
|
24
|
+
|
25
|
+
start_time = Time.now
|
26
|
+
result = orig_encrypt(plaintext, context: context, additional_data: additional_data)
|
27
|
+
elapsed = ((Time.now - start_time) * 1000).round(2)
|
28
|
+
|
29
|
+
puts " Result length: #{result ? result.length : 'nil'} chars"
|
30
|
+
puts " Elapsed: #{elapsed}ms"
|
31
|
+
puts
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def decrypt(encrypted_json, context:, additional_data: nil)
|
36
|
+
puts "📥 DECRYPT called:"
|
37
|
+
puts " Context: #{context}"
|
38
|
+
puts " Encrypted length: #{encrypted_json&.length} chars"
|
39
|
+
puts " AAD: #{additional_data ? 'present' : 'none'}"
|
40
|
+
|
41
|
+
start_time = Time.now
|
42
|
+
begin
|
43
|
+
result = orig_decrypt(encrypted_json, context: context, additional_data: additional_data)
|
44
|
+
elapsed = ((Time.now - start_time) * 1000).round(2)
|
45
|
+
|
46
|
+
puts " Result length: #{result ? result.length : 'nil'} chars"
|
47
|
+
puts " Elapsed: #{elapsed}ms"
|
48
|
+
puts
|
49
|
+
result
|
50
|
+
rescue => e
|
51
|
+
elapsed = ((Time.now - start_time) * 1000).round(2)
|
52
|
+
puts " ERROR: #{e.class}: #{e.message}"
|
53
|
+
puts " Elapsed: #{elapsed}ms"
|
54
|
+
puts
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Manager
|
61
|
+
alias_method :orig_derive_key_with_provider, :derive_key_with_provider
|
62
|
+
|
63
|
+
def derive_key_with_provider(provider, context, version: nil)
|
64
|
+
puts "🔑 DERIVE_KEY called:"
|
65
|
+
puts " Provider: #{provider.class.name}"
|
66
|
+
puts " Context: #{context}"
|
67
|
+
puts " Version: #{version || 'current'}"
|
68
|
+
|
69
|
+
cache = Thread.current[:familia_key_cache] ||= {}
|
70
|
+
cache_key = "#{version || current_key_version}:#{context}"
|
71
|
+
puts " Cache key: #{cache_key}"
|
72
|
+
puts " Cache before: #{cache.keys.inspect}"
|
73
|
+
|
74
|
+
start_time = Time.now
|
75
|
+
result = orig_derive_key_with_provider(provider, context, version: version)
|
76
|
+
elapsed = ((Time.now - start_time) * 1000).round(2)
|
77
|
+
|
78
|
+
cache_after = Thread.current[:familia_key_cache] || {}
|
79
|
+
puts " Cache after: #{cache_after.keys.inspect}"
|
80
|
+
puts " Derived key: [#{result.bytesize} bytes]"
|
81
|
+
puts " Elapsed: #{elapsed}ms"
|
82
|
+
puts
|
83
|
+
result
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Setup test configuration
|
90
|
+
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
91
|
+
Familia.config.encryption_keys = test_keys
|
92
|
+
Familia.config.current_key_version = :v1
|
93
|
+
|
94
|
+
# Clear cache
|
95
|
+
Thread.current[:familia_key_cache] = nil
|
96
|
+
|
97
|
+
# Define test model
|
98
|
+
class TraceTestModel < Familia::Horreum
|
99
|
+
feature :encrypted_fields
|
100
|
+
identifier_field :user_id
|
101
|
+
|
102
|
+
field :user_id
|
103
|
+
encrypted_field :password
|
104
|
+
encrypted_field :api_key, aad_fields: [:user_id]
|
105
|
+
end
|
106
|
+
|
107
|
+
puts "=== Test Scenario: Field Operations ==="
|
108
|
+
puts
|
109
|
+
|
110
|
+
puts "Creating model and setting encrypted fields..."
|
111
|
+
user = TraceTestModel.new(user_id: 'trace-user-1')
|
112
|
+
|
113
|
+
puts "Setting password (no AAD)..."
|
114
|
+
user.password = 'secret-password-123'
|
115
|
+
|
116
|
+
puts "Setting api_key (with AAD)..."
|
117
|
+
user.api_key = 'api-key-xyz-789'
|
118
|
+
|
119
|
+
puts "Reading password..."
|
120
|
+
retrieved_password = user.password
|
121
|
+
puts "Final password value: #{retrieved_password}"
|
122
|
+
|
123
|
+
puts "Reading api_key..."
|
124
|
+
retrieved_api_key = user.api_key
|
125
|
+
puts "Final api_key value: #{retrieved_api_key}"
|
126
|
+
|
127
|
+
puts
|
128
|
+
puts "=== Test Scenario: Cross-Algorithm Decryption ==="
|
129
|
+
puts
|
130
|
+
|
131
|
+
# Test cross-algorithm compatibility
|
132
|
+
aes_encrypted = Familia::Encryption.encrypt_with('aes-256-gcm', 'cross-test-data', context: 'cross-test')
|
133
|
+
puts "Decrypting AES-GCM data with default manager..."
|
134
|
+
cross_decrypted = Familia::Encryption.decrypt(aes_encrypted, context: 'cross-test')
|
135
|
+
puts "Cross-algorithm result: #{cross_decrypted}"
|
136
|
+
|
137
|
+
puts
|
138
|
+
puts "=== Method Tracing Complete ==="
|