keeper_secrets_manager 17.0.4 → 17.2.0
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/CHANGELOG.md +100 -16
- data/Gemfile +6 -3
- data/README.md +75 -270
- data/Rakefile +1 -1
- data/bin/console +47 -0
- data/keeper_secrets_manager.gemspec +36 -0
- data/lib/keeper_secrets_manager/cache.rb +139 -0
- data/lib/keeper_secrets_manager/config_keys.rb +4 -2
- data/lib/keeper_secrets_manager/core.rb +993 -452
- data/lib/keeper_secrets_manager/crypto.rb +101 -116
- data/lib/keeper_secrets_manager/dto/payload.rb +7 -6
- data/lib/keeper_secrets_manager/dto.rb +374 -38
- data/lib/keeper_secrets_manager/errors.rb +13 -2
- data/lib/keeper_secrets_manager/field_types.rb +3 -3
- data/lib/keeper_secrets_manager/folder_manager.rb +25 -29
- data/lib/keeper_secrets_manager/keeper_globals.rb +11 -17
- data/lib/keeper_secrets_manager/notation.rb +202 -93
- data/lib/keeper_secrets_manager/notation_enhancements.rb +24 -24
- data/lib/keeper_secrets_manager/storage.rb +38 -38
- data/lib/keeper_secrets_manager/totp.rb +27 -27
- data/lib/keeper_secrets_manager/utils.rb +85 -18
- data/lib/keeper_secrets_manager/version.rb +2 -2
- data/lib/keeper_secrets_manager.rb +11 -3
- metadata +39 -22
- data/DEVELOPER_SETUP.md +0 -0
- data/MANUAL_TESTING_GUIDE.md +0 -332
- data/RUBY_SDK_COMPLETE_DOCUMENTATION.md +0 -354
- data/RUBY_SDK_COMPREHENSIVE_SUMMARY.md +0 -192
- data/examples/01_quick_start.rb +0 -45
- data/examples/02_authentication.rb +0 -82
- data/examples/03_retrieve_secrets.rb +0 -81
- data/examples/04_create_update_delete.rb +0 -104
- data/examples/05_field_types.rb +0 -135
- data/examples/06_files.rb +0 -137
- data/examples/07_folders.rb +0 -145
- data/examples/08_notation.rb +0 -103
- data/examples/09_totp.rb +0 -100
- data/examples/README.md +0 -89
|
@@ -8,7 +8,7 @@ module KeeperSecretsManager
|
|
|
8
8
|
GCM_IV_LENGTH = 12
|
|
9
9
|
GCM_TAG_LENGTH = 16
|
|
10
10
|
AES_KEY_LENGTH = 32
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
# Block size for padding
|
|
13
13
|
BLOCK_SIZE = 16
|
|
14
14
|
|
|
@@ -30,7 +30,7 @@ module KeeperSecretsManager
|
|
|
30
30
|
|
|
31
31
|
# Convert URL-safe base64 string to bytes
|
|
32
32
|
def url_safe_str_to_bytes(str)
|
|
33
|
-
|
|
33
|
+
raise CryptoError, 'url_safe_str_to_bytes: received nil' if str.nil?
|
|
34
34
|
str += '=' * (4 - str.length % 4) if str.length % 4 != 0
|
|
35
35
|
Base64.urlsafe_decode64(str)
|
|
36
36
|
end
|
|
@@ -42,6 +42,7 @@ module KeeperSecretsManager
|
|
|
42
42
|
|
|
43
43
|
# Convert base64 to bytes
|
|
44
44
|
def base64_to_bytes(str)
|
|
45
|
+
raise CryptoError, 'base64_to_bytes: received nil' if str.nil?
|
|
45
46
|
Base64.strict_decode64(str)
|
|
46
47
|
end
|
|
47
48
|
|
|
@@ -50,38 +51,40 @@ module KeeperSecretsManager
|
|
|
50
51
|
# Generate private key bytes
|
|
51
52
|
private_key_bytes = generate_encryption_key_bytes
|
|
52
53
|
private_key_str = bytes_to_url_safe_str(private_key_bytes)
|
|
53
|
-
|
|
54
|
+
|
|
54
55
|
# Create EC key from private key bytes
|
|
55
56
|
private_key_bn = OpenSSL::BN.new(private_key_bytes, 2)
|
|
56
|
-
|
|
57
|
+
|
|
57
58
|
# OpenSSL 3.0 compatibility - use ASN1 sequence to create key
|
|
58
59
|
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
|
59
|
-
|
|
60
|
+
|
|
60
61
|
# Generate public key point
|
|
61
62
|
public_key_point = group.generator.mul(private_key_bn)
|
|
62
|
-
|
|
63
|
+
|
|
63
64
|
# Create ASN1 sequence for the key
|
|
64
65
|
asn1 = OpenSSL::ASN1::Sequence([
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
OpenSSL::ASN1::Integer(1),
|
|
67
|
+
OpenSSL::ASN1::OctetString(private_key_bytes),
|
|
68
|
+
OpenSSL::ASN1::ObjectId('prime256v1', 0, :EXPLICIT),
|
|
69
|
+
OpenSSL::ASN1::BitString(public_key_point.to_octet_string(:uncompressed), 1,
|
|
70
|
+
:EXPLICIT)
|
|
71
|
+
])
|
|
72
|
+
|
|
71
73
|
# Create key from DER
|
|
72
74
|
key = OpenSSL::PKey::EC.new(asn1.to_der)
|
|
73
|
-
|
|
75
|
+
|
|
74
76
|
# Get public key bytes (uncompressed format)
|
|
75
77
|
public_key_bytes = key.public_key.to_octet_string(:uncompressed)
|
|
76
78
|
public_key_str = bytes_to_url_safe_str(public_key_bytes)
|
|
77
|
-
|
|
79
|
+
|
|
78
80
|
# Also store the EC key in DER format for compatibility
|
|
79
81
|
private_key_der = key.to_der
|
|
80
|
-
|
|
82
|
+
|
|
81
83
|
{
|
|
82
84
|
private_key_str: private_key_str,
|
|
83
85
|
public_key_str: public_key_str,
|
|
84
|
-
private_key_bytes:
|
|
86
|
+
private_key_bytes: private_key_bytes, # Use raw 32 bytes
|
|
87
|
+
private_key_der: private_key_der, # Also provide DER format
|
|
85
88
|
public_key_bytes: public_key_bytes,
|
|
86
89
|
private_key_obj: key
|
|
87
90
|
}
|
|
@@ -89,79 +92,65 @@ module KeeperSecretsManager
|
|
|
89
92
|
|
|
90
93
|
# Encrypt with AES-GCM or fallback to CBC
|
|
91
94
|
def encrypt_aes_gcm(data, key)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
raise e
|
|
115
|
-
end
|
|
95
|
+
cipher = OpenSSL::Cipher.new('AES-256-GCM')
|
|
96
|
+
cipher.encrypt
|
|
97
|
+
|
|
98
|
+
# Generate random IV
|
|
99
|
+
iv = generate_random_bytes(GCM_IV_LENGTH)
|
|
100
|
+
cipher.iv = iv
|
|
101
|
+
cipher.key = key
|
|
102
|
+
|
|
103
|
+
# Encrypt data
|
|
104
|
+
encrypted = cipher.update(data) + cipher.final
|
|
105
|
+
|
|
106
|
+
# Get authentication tag
|
|
107
|
+
tag = cipher.auth_tag(GCM_TAG_LENGTH)
|
|
108
|
+
|
|
109
|
+
# Combine IV + encrypted + tag
|
|
110
|
+
iv + encrypted + tag
|
|
111
|
+
rescue RuntimeError => e
|
|
112
|
+
if e.message.include?('unsupported cipher')
|
|
113
|
+
# Fallback to AES-CBC for older Ruby/OpenSSL
|
|
114
|
+
encrypt_aes_cbc(data, key)
|
|
115
|
+
else
|
|
116
|
+
raise e
|
|
116
117
|
end
|
|
117
118
|
end
|
|
118
119
|
|
|
119
|
-
# Decrypt with AES-GCM or fallback to CBC
|
|
120
120
|
def decrypt_aes_gcm(encrypted_data, key)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
# Fallback to AES-CBC
|
|
138
|
-
decrypt_aes_cbc(encrypted_data, key)
|
|
139
|
-
else
|
|
140
|
-
raise e
|
|
141
|
-
end
|
|
142
|
-
rescue OpenSSL::Cipher::CipherError => e
|
|
143
|
-
# Maybe it's CBC encrypted?
|
|
144
|
-
begin
|
|
145
|
-
decrypt_aes_cbc(encrypted_data, key)
|
|
146
|
-
rescue
|
|
147
|
-
raise DecryptionError, "Failed to decrypt data: #{e.message}"
|
|
148
|
-
end
|
|
121
|
+
iv = encrypted_data[0...GCM_IV_LENGTH]
|
|
122
|
+
tag = encrypted_data[-GCM_TAG_LENGTH..]
|
|
123
|
+
ciphertext = encrypted_data[GCM_IV_LENGTH...-GCM_TAG_LENGTH]
|
|
124
|
+
|
|
125
|
+
cipher = OpenSSL::Cipher.new('AES-256-GCM')
|
|
126
|
+
cipher.decrypt
|
|
127
|
+
cipher.iv = iv
|
|
128
|
+
cipher.key = key
|
|
129
|
+
cipher.auth_tag = tag
|
|
130
|
+
|
|
131
|
+
cipher.update(ciphertext) + cipher.final
|
|
132
|
+
rescue RuntimeError => e
|
|
133
|
+
if e.message.include?('unsupported cipher')
|
|
134
|
+
decrypt_aes_cbc(encrypted_data, key)
|
|
135
|
+
else
|
|
136
|
+
raise e
|
|
149
137
|
end
|
|
138
|
+
rescue OpenSSL::Cipher::CipherError => e
|
|
139
|
+
raise DecryptionError, "Failed to decrypt data: #{e.message}"
|
|
150
140
|
end
|
|
151
141
|
|
|
152
142
|
# Legacy AES-CBC encryption (for compatibility)
|
|
153
143
|
def encrypt_aes_cbc(data, key, iv = nil)
|
|
154
144
|
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
|
155
145
|
cipher.encrypt
|
|
156
|
-
|
|
146
|
+
|
|
157
147
|
iv ||= generate_random_bytes(BLOCK_SIZE)
|
|
158
148
|
cipher.iv = iv
|
|
159
149
|
cipher.key = key
|
|
160
|
-
|
|
161
|
-
#
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
150
|
+
|
|
151
|
+
# OpenSSL handles PKCS7 padding automatically in cipher.final
|
|
152
|
+
encrypted = cipher.update(data) + cipher.final
|
|
153
|
+
|
|
165
154
|
# Return IV + encrypted
|
|
166
155
|
iv + encrypted
|
|
167
156
|
end
|
|
@@ -171,16 +160,16 @@ module KeeperSecretsManager
|
|
|
171
160
|
# Extract IV
|
|
172
161
|
iv = encrypted_data[0...BLOCK_SIZE]
|
|
173
162
|
ciphertext = encrypted_data[BLOCK_SIZE..]
|
|
174
|
-
|
|
163
|
+
|
|
175
164
|
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
|
176
165
|
cipher.decrypt
|
|
177
166
|
cipher.iv = iv
|
|
178
167
|
cipher.key = key
|
|
179
|
-
|
|
168
|
+
|
|
169
|
+
# OpenSSL handles PKCS7 padding removal automatically in cipher.final
|
|
180
170
|
decrypted = cipher.update(ciphertext) + cipher.final
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
unpad_data(decrypted)
|
|
171
|
+
|
|
172
|
+
decrypted
|
|
184
173
|
rescue OpenSSL::Cipher::CipherError => e
|
|
185
174
|
raise DecryptionError, "Failed to decrypt data: #{e.message}"
|
|
186
175
|
end
|
|
@@ -195,18 +184,16 @@ module KeeperSecretsManager
|
|
|
195
184
|
# Remove PKCS7 padding
|
|
196
185
|
def unpad_data(data)
|
|
197
186
|
return data if data.empty?
|
|
198
|
-
|
|
187
|
+
|
|
199
188
|
pad_len = data[-1].ord
|
|
200
|
-
|
|
189
|
+
|
|
201
190
|
# Validate padding
|
|
202
191
|
if pad_len > 0 && pad_len <= BLOCK_SIZE && pad_len <= data.length
|
|
203
192
|
# Check if all padding bytes are the same
|
|
204
193
|
padding = data[-pad_len..]
|
|
205
|
-
if padding.bytes.all? { |b| b == pad_len }
|
|
206
|
-
return data[0...-pad_len]
|
|
207
|
-
end
|
|
194
|
+
return data[0...-pad_len] if padding.bytes.all? { |b| b == pad_len }
|
|
208
195
|
end
|
|
209
|
-
|
|
196
|
+
|
|
210
197
|
data
|
|
211
198
|
end
|
|
212
199
|
|
|
@@ -214,21 +201,21 @@ module KeeperSecretsManager
|
|
|
214
201
|
def generate_hmac(key, data)
|
|
215
202
|
OpenSSL::HMAC.digest('SHA512', key, data)
|
|
216
203
|
end
|
|
217
|
-
|
|
204
|
+
|
|
218
205
|
# Generate ECDSA signature
|
|
219
206
|
def sign_ec(data, private_key)
|
|
220
207
|
# Use SHA256 for ECDSA signature
|
|
221
|
-
digest = OpenSSL::Digest
|
|
208
|
+
digest = OpenSSL::Digest.new('SHA256')
|
|
222
209
|
private_key.sign(digest, data)
|
|
223
210
|
end
|
|
224
211
|
|
|
225
212
|
# Verify HMAC signature
|
|
226
213
|
def verify_hmac(key, data, signature)
|
|
227
214
|
expected = generate_hmac(key, data)
|
|
228
|
-
|
|
215
|
+
|
|
229
216
|
# Constant time comparison
|
|
230
217
|
return false unless expected.bytesize == signature.bytesize
|
|
231
|
-
|
|
218
|
+
|
|
232
219
|
result = 0
|
|
233
220
|
expected.bytes.zip(signature.bytes) { |a, b| result |= a ^ b }
|
|
234
221
|
result == 0
|
|
@@ -237,14 +224,14 @@ module KeeperSecretsManager
|
|
|
237
224
|
# Load private key from DER format
|
|
238
225
|
def load_private_key_der(der_bytes, password = nil)
|
|
239
226
|
OpenSSL::PKey.read(der_bytes, password)
|
|
240
|
-
rescue => e
|
|
227
|
+
rescue StandardError => e
|
|
241
228
|
raise CryptoError, "Failed to load private key: #{e.message}"
|
|
242
229
|
end
|
|
243
230
|
|
|
244
231
|
# Load public key from DER format
|
|
245
232
|
def load_public_key_der(der_bytes)
|
|
246
233
|
OpenSSL::PKey.read(der_bytes)
|
|
247
|
-
rescue => e
|
|
234
|
+
rescue StandardError => e
|
|
248
235
|
raise CryptoError, "Failed to load public key: #{e.message}"
|
|
249
236
|
end
|
|
250
237
|
|
|
@@ -262,20 +249,20 @@ module KeeperSecretsManager
|
|
|
262
249
|
def encrypt_ec(data, public_key_bytes)
|
|
263
250
|
# Load public key
|
|
264
251
|
public_key = load_ec_public_key(public_key_bytes)
|
|
265
|
-
|
|
252
|
+
|
|
266
253
|
# Generate ephemeral key pair
|
|
267
254
|
ephemeral = OpenSSL::PKey::EC.generate('prime256v1')
|
|
268
|
-
|
|
255
|
+
|
|
269
256
|
# Perform ECDH to get shared secret
|
|
270
257
|
# The shared secret is computed using ECDH between ephemeral private key and server public key
|
|
271
258
|
shared_secret = ephemeral.dh_compute_key(public_key.public_key)
|
|
272
|
-
|
|
259
|
+
|
|
273
260
|
# Derive encryption key using SHA256
|
|
274
261
|
encryption_key = OpenSSL::Digest::SHA256.digest(shared_secret)
|
|
275
|
-
|
|
262
|
+
|
|
276
263
|
# Encrypt data with AES-GCM
|
|
277
264
|
encrypted_data = encrypt_aes_gcm(data, encryption_key)
|
|
278
|
-
|
|
265
|
+
|
|
279
266
|
# Return ephemeral public key + encrypted data
|
|
280
267
|
ephemeral_public = ephemeral.public_key.to_octet_string(:uncompressed)
|
|
281
268
|
ephemeral_public + encrypted_data
|
|
@@ -286,17 +273,17 @@ module KeeperSecretsManager
|
|
|
286
273
|
# Extract ephemeral public key (65 bytes for uncompressed)
|
|
287
274
|
ephemeral_public_bytes = encrypted_data[0...65]
|
|
288
275
|
ciphertext = encrypted_data[65..]
|
|
289
|
-
|
|
276
|
+
|
|
290
277
|
# Create EC key with ephemeral public key
|
|
291
278
|
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
|
292
279
|
ephemeral_point = OpenSSL::PKey::EC::Point.new(group, ephemeral_public_bytes)
|
|
293
|
-
|
|
280
|
+
|
|
294
281
|
# Compute shared secret using ECDH
|
|
295
282
|
shared_secret = private_key.dh_compute_key(ephemeral_point)
|
|
296
|
-
|
|
283
|
+
|
|
297
284
|
# Derive decryption key
|
|
298
285
|
decryption_key = OpenSSL::Digest::SHA256.digest(shared_secret)
|
|
299
|
-
|
|
286
|
+
|
|
300
287
|
# Decrypt data
|
|
301
288
|
decrypt_aes_gcm(ciphertext, decryption_key)
|
|
302
289
|
end
|
|
@@ -307,31 +294,29 @@ module KeeperSecretsManager
|
|
|
307
294
|
def load_ec_public_key(public_key_bytes)
|
|
308
295
|
# If the bytes are longer than 65, it might be DER encoded
|
|
309
296
|
# Extract the raw point bytes (last 65 bytes)
|
|
310
|
-
if public_key_bytes.bytesize > 65
|
|
311
|
-
|
|
312
|
-
end
|
|
313
|
-
|
|
297
|
+
public_key_bytes = public_key_bytes[-65..-1] if public_key_bytes.bytesize > 65
|
|
298
|
+
|
|
314
299
|
# For OpenSSL 3.0+, we need to create the key differently
|
|
315
300
|
begin
|
|
316
301
|
# Try the OpenSSL 3.0+ way first
|
|
317
302
|
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
|
318
303
|
point = OpenSSL::PKey::EC::Point.new(group, public_key_bytes)
|
|
319
|
-
|
|
304
|
+
|
|
320
305
|
# Create key from point directly using ASN1
|
|
321
306
|
asn1 = OpenSSL::ASN1::Sequence([
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
307
|
+
OpenSSL::ASN1::Sequence([
|
|
308
|
+
OpenSSL::ASN1::ObjectId('id-ecPublicKey'),
|
|
309
|
+
OpenSSL::ASN1::ObjectId('prime256v1')
|
|
310
|
+
]),
|
|
311
|
+
OpenSSL::ASN1::BitString(public_key_bytes)
|
|
312
|
+
])
|
|
313
|
+
|
|
329
314
|
OpenSSL::PKey::EC.new(asn1.to_der)
|
|
330
|
-
rescue => e
|
|
315
|
+
rescue StandardError => e
|
|
331
316
|
# Fall back to old method for older OpenSSL
|
|
332
317
|
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
|
333
318
|
point = OpenSSL::PKey::EC::Point.new(group, public_key_bytes)
|
|
334
|
-
|
|
319
|
+
|
|
335
320
|
key = OpenSSL::PKey::EC.new(group)
|
|
336
321
|
key.public_key = point
|
|
337
322
|
key
|
|
@@ -345,4 +330,4 @@ module KeeperSecretsManager
|
|
|
345
330
|
end
|
|
346
331
|
end
|
|
347
332
|
end
|
|
348
|
-
end
|
|
333
|
+
end
|
|
@@ -20,7 +20,7 @@ module KeeperSecretsManager
|
|
|
20
20
|
instance_variables.each do |var|
|
|
21
21
|
key = var.to_s.delete('@')
|
|
22
22
|
value = instance_variable_get(var)
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
# Convert Ruby snake_case to camelCase for API
|
|
25
25
|
api_key = Utils.snake_to_camel(key)
|
|
26
26
|
hash[api_key] = value unless value.nil?
|
|
@@ -35,19 +35,20 @@ module KeeperSecretsManager
|
|
|
35
35
|
|
|
36
36
|
# Get secrets payload
|
|
37
37
|
class GetPayload < BasePayload
|
|
38
|
-
attr_accessor :public_key, :requested_records, :requested_folders, :file_uids
|
|
38
|
+
attr_accessor :public_key, :requested_records, :requested_folders, :file_uids, :request_links
|
|
39
39
|
|
|
40
40
|
def initialize
|
|
41
41
|
super()
|
|
42
42
|
@requested_records = nil
|
|
43
43
|
@requested_folders = nil
|
|
44
44
|
@file_uids = nil
|
|
45
|
+
@request_links = nil
|
|
45
46
|
end
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
# Create record payload
|
|
49
50
|
class CreatePayload < BasePayload
|
|
50
|
-
attr_accessor :record_uid, :record_key, :folder_uid, :folder_key,
|
|
51
|
+
attr_accessor :record_uid, :record_key, :folder_uid, :folder_key,
|
|
51
52
|
:data, :sub_folder_uid
|
|
52
53
|
|
|
53
54
|
def initialize
|
|
@@ -57,7 +58,7 @@ module KeeperSecretsManager
|
|
|
57
58
|
|
|
58
59
|
# Update record payload
|
|
59
60
|
class UpdatePayload < BasePayload
|
|
60
|
-
attr_accessor :record_uid, :data, :revision, :transaction_type
|
|
61
|
+
attr_accessor :record_uid, :data, :revision, :transaction_type, :links2_remove
|
|
61
62
|
|
|
62
63
|
def initialize
|
|
63
64
|
super()
|
|
@@ -87,7 +88,7 @@ module KeeperSecretsManager
|
|
|
87
88
|
# File upload payload
|
|
88
89
|
class FileUploadPayload < BasePayload
|
|
89
90
|
attr_accessor :file_record_uid, :file_record_key, :file_record_data,
|
|
90
|
-
:owner_record_uid, :owner_record_data, :link_key, :file_size
|
|
91
|
+
:owner_record_uid, :owner_record_data, :owner_record_revision, :link_key, :file_size
|
|
91
92
|
|
|
92
93
|
def initialize
|
|
93
94
|
super()
|
|
@@ -149,4 +150,4 @@ module KeeperSecretsManager
|
|
|
149
150
|
end
|
|
150
151
|
end
|
|
151
152
|
end
|
|
152
|
-
end
|
|
153
|
+
end
|