@@ -7,12 +7,16 @@ import com.sensitiveinfo.internal.auth.BiometricAuthenticator
77import com.sensitiveinfo.internal.crypto.AccessControlResolver
88import com.sensitiveinfo.internal.crypto.CryptoManager
99import com.sensitiveinfo.internal.crypto.SecurityAvailabilityResolver
10+ import com.sensitiveinfo.internal.response.ResponseBuilder
11+ import com.sensitiveinfo.internal.response.StandardResponseBuilder
1012import com.sensitiveinfo.internal.storage.PersistedEntry
1113import com.sensitiveinfo.internal.storage.PersistedMetadata
1214import com.sensitiveinfo.internal.storage.SecureStorage
1315import com.sensitiveinfo.internal.util.AliasGenerator
1416import com.sensitiveinfo.internal.util.ReactContextHolder
1517import com.sensitiveinfo.internal.util.ServiceNameResolver
18+ import com.sensitiveinfo.internal.validation.AndroidStorageValidator
19+ import com.sensitiveinfo.internal.validation.StorageValidator
1620import kotlinx.coroutines.CoroutineScope
1721import kotlinx.coroutines.Dispatchers
1822import kotlinx.coroutines.SupervisorJob
@@ -21,8 +25,16 @@ import kotlin.jvm.Volatile
2125/* *
2226 * Android Keystore implementation of the SensitiveInfo Nitro module.
2327 *
24- * This class provides secure storage for sensitive data on Android using the Android Keystore
28+ * Provides secure storage for sensitive data on Android using the Android Keystore
2529 * for key management and SharedPreferences for encrypted data persistence.
30+ *
31+ * The implementation follows a consistent pattern across all methods:
32+ * 1. Validate inputs using [StorageValidator]
33+ * 2. Resolve access control and security parameters
34+ * 3. Perform cryptographic or storage operations
35+ * 4. Build responses using [ResponseBuilder] for type conversion
36+ *
37+ * @since 6.0.0
2638 */
2739class HybridSensitiveInfo : HybridSensitiveInfoSpec () {
2840 private data class Dependencies (
@@ -31,7 +43,9 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
3143 val cryptoManager : CryptoManager ,
3244 val accessControlResolver : AccessControlResolver ,
3345 val securityAvailabilityResolver : SecurityAvailabilityResolver ,
34- val serviceNameResolver : ServiceNameResolver
46+ val serviceNameResolver : ServiceNameResolver ,
47+ val validator : StorageValidator ,
48+ val responseBuilder : ResponseBuilder
3549 )
3650
3751 @Volatile
@@ -56,31 +70,63 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
5670 cryptoManager = cryptoManager,
5771 accessControlResolver = accessControlResolver,
5872 securityAvailabilityResolver = securityAvailabilityResolver,
59- serviceNameResolver = serviceNameResolver
73+ serviceNameResolver = serviceNameResolver,
74+ validator = AndroidStorageValidator (),
75+ responseBuilder = StandardResponseBuilder ()
6076 ).also { built ->
6177 dependencies = built
6278 }
6379 }
6480 }
6581 }
6682
83+ /* *
84+ * Sets an item in secure storage.
85+ *
86+ * Process:
87+ * 1. Validates the key, value, and options
88+ * 2. Resolves service name and access control
89+ * 3. Generates Keystore alias from service and key
90+ * 4. Encrypts plaintext using CryptoManager
91+ * 5. Creates metadata for tracking security properties
92+ * 6. Persists encrypted entry and metadata
93+ * 7. Returns mutation result with metadata
94+ *
95+ * @param request The set request containing key, value, and options
96+ * @return Promise resolving to MutationResult with metadata
97+ * @throws IllegalArgumentException if key or value is invalid
98+ * @throws java.security.KeyStoreException if Keystore operation fails
99+ */
67100 override fun setItem (request : SensitiveInfoSetRequest ): Promise <MutationResult > {
68101 return Promise .async(coroutineScope) {
69102 val deps = ensureInitialized()
103+
104+ // Step 1: Validate inputs
105+ deps.validator.validateKey(request.key)
106+ deps.validator.validateValue(request.value)
107+
108+ // Step 2: Resolve service name
70109 val service = deps.serviceNameResolver.resolve(request.service)
110+
111+ // Step 3: Resolve access control
71112 val resolved = deps.accessControlResolver.resolve(request.accessControl)
113+
114+ // Step 4: Generate alias for Keystore entry
72115 val alias = AliasGenerator .aliasFor(service, request.key)
73116
117+ // Step 5: Encrypt plaintext
74118 val plaintext = request.value.toByteArray(Charsets .UTF_8 )
75119 val encryption = deps.cryptoManager.encrypt(alias, plaintext, resolved, request.authenticationPrompt)
76120
121+ // Step 6: Create metadata
77122 val metadata = StorageMetadata (
78123 securityLevel = resolved.securityLevel,
79124 backend = StorageBackend .ANDROIDKEYSTORE ,
80125 accessControl = resolved.accessControl,
81126 timestamp = System .currentTimeMillis() / 1000.0
82127 )
83128
129+ // Step 7: Persist entry
84130 val entry = PersistedEntry (
85131 alias = alias,
86132 ciphertext = encryption.ciphertext,
@@ -94,21 +140,47 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
94140
95141 deps.storage.save(service, request.key, entry)
96142
97- MutationResult (metadata = metadata)
143+ // Step 8: Build response using response builder
144+ deps.responseBuilder.buildMutationResult(metadata)
98145 }
99146 }
100147
148+ /* *
149+ * Retrieves an item from secure storage.
150+ *
151+ * Process:
152+ * 1. Resolves service name
153+ * 2. Reads encrypted entry and metadata from storage
154+ * 3. If include_value is true, decrypts the ciphertext
155+ * 4. Rebuilds access control from persisted metadata
156+ * 5. Reconstructs item from decrypted value and metadata
157+ * 6. Builds response using response builder
158+ *
159+ * @param request The get request with key and authentication prompt
160+ * @return Promise resolving to SensitiveInfoItem or null if not found
161+ * @throws IllegalArgumentException if key is invalid
162+ * @throws java.security.KeyStoreException if decryption fails
163+ */
101164 override fun getItem (request : SensitiveInfoGetRequest ): Promise <SensitiveInfoItem ?> {
102165 return Promise .async(coroutineScope) {
103166 val deps = ensureInitialized()
167+
168+ // Step 1: Validate key
169+ deps.validator.validateKey(request.key)
170+
171+ // Step 2: Resolve service name
104172 val service = deps.serviceNameResolver.resolve(request.service)
105173
174+ // Step 3: Read entry from storage
106175 val entry = deps.storage.read(service, request.key)
107176 if (entry == null ) {
108177 return @async null
109178 }
110179
180+ // Step 4: Decode metadata
111181 val metadata = entry.metadata.toStorageMetadata()
182+
183+ // Step 5: Decrypt value if requested
112184 val value = if (request.includeValue == true && entry.ciphertext != null && entry.iv != null ) {
113185 val resolution = deps.cryptoManager.buildResolutionForPersisted(
114186 accessControl = metadata?.accessControl ? : AccessControl .NONE ,
@@ -131,9 +203,9 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
131203 null
132204 }
133205
134- SensitiveInfoItem (
206+ // Step 6: Build response using response builder
207+ deps.responseBuilder.buildItem(
135208 key = request.key,
136- service = service,
137209 value = value,
138210 metadata = metadata ? : StorageMetadata (
139211 securityLevel = SecurityLevel .SOFTWARE ,
@@ -145,36 +217,103 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
145217 }
146218 }
147219
220+ /* *
221+ * Deletes an item from secure storage.
222+ *
223+ * Process:
224+ * 1. Validates the key
225+ * 2. Resolves service name
226+ * 3. Reads the stored entry to get Keystore alias
227+ * 4. Deletes the Keystore key using the alias
228+ * 5. Deletes the encrypted data from storage
229+ * 6. Returns success boolean
230+ *
231+ * @param request The delete request containing key
232+ * @return Promise resolving to boolean (success)
233+ * @throws IllegalArgumentException if key is invalid
234+ * @throws java.security.KeyStoreException if key deletion fails
235+ */
148236 override fun deleteItem (request : SensitiveInfoDeleteRequest ): Promise <Boolean > {
149237 return Promise .async(coroutineScope) {
150238 val deps = ensureInitialized()
239+
240+ // Step 1: Validate key
241+ deps.validator.validateKey(request.key)
242+
243+ // Step 2: Resolve service name
151244 val service = deps.serviceNameResolver.resolve(request.service)
152245
246+ // Step 3: Read entry to get alias
153247 val entry = deps.storage.read(service, request.key)
154248 if (entry != null ) {
249+ // Step 4: Delete Keystore key
155250 deps.cryptoManager.deleteKey(entry.alias)
156251 }
157252
253+ // Step 5: Delete storage entry
158254 deps.storage.delete(service, request.key)
255+
256+ // Step 6: Return success
257+ true
159258 }
160259 }
161260
261+ /* *
262+ * Checks if an item exists in secure storage.
263+ *
264+ * Process:
265+ * 1. Validates the key
266+ * 2. Resolves service name
267+ * 3. Checks if entry exists in storage
268+ * 4. Returns boolean existence
269+ *
270+ * @param request The has request containing key
271+ * @return Promise resolving to boolean (exists)
272+ * @throws IllegalArgumentException if key is invalid
273+ */
162274 override fun hasItem (request : SensitiveInfoHasRequest ): Promise <Boolean > {
163275 return Promise .async(coroutineScope) {
164276 val deps = ensureInitialized()
277+
278+ // Step 1: Validate key
279+ deps.validator.validateKey(request.key)
280+
281+ // Step 2: Resolve service name
165282 val service = deps.serviceNameResolver.resolve(request.service)
283+
284+ // Step 3: Check storage
166285 deps.storage.contains(service, request.key)
167286 }
168287 }
169288
289+ /* *
290+ * Retrieves all items for a service.
291+ *
292+ * Process:
293+ * 1. Resolves service name
294+ * 2. Reads all entries from storage for the service
295+ * 3. For each entry, decrypts if include_values is true
296+ * 4. Handles decryption failures gracefully (skips value if fails)
297+ * 5. Builds item using response builder
298+ * 6. Filters out items that fail to process
299+ * 7. Returns array of items
300+ *
301+ * @param request The enumerate request with optional include_values flag
302+ * @return Promise resolving to array of SensitiveInfoItem
303+ * @throws IllegalArgumentException if service is invalid
304+ */
170305 override fun getAllItems (request : SensitiveInfoEnumerateRequest ? ): Promise <Array <SensitiveInfoItem >> {
171306 return Promise .async(coroutineScope) {
172307 val deps = ensureInitialized()
308+
309+ // Step 1: Resolve service name
173310 val service = deps.serviceNameResolver.resolve(request?.service)
174311
312+ // Step 2: Read all entries
175313 val entries = deps.storage.readAll(service)
176314 val includeValues = request?.includeValues ? : false
177315
316+ // Step 3: Map entries to items
178317 entries.mapNotNull { (key, entry) ->
179318 try {
180319 val metadata = entry.metadata.toStorageMetadata() ? : StorageMetadata (
@@ -204,50 +343,86 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
204343 )
205344 String (plaintext, Charsets .UTF_8 )
206345 } catch (e: Throwable ) {
207- // If decryption fails, skip including the value
346+ // Gracefully handle decryption failures
208347 null
209348 }
210349 } else {
211350 null
212351 }
213352
214- SensitiveInfoItem (
353+ // Step 5: Build item using response builder
354+ deps.responseBuilder.buildItem(
215355 key = key,
216- service = service,
217356 value = value,
218357 metadata = metadata
219358 )
220359 } catch (e: Throwable ) {
221- // Skip items that fail to process
360+ // Step 6: Skip items that fail to process
222361 null
223362 }
224363 }.toTypedArray()
225364 }
226365 }
227366
367+ /* *
368+ * Clears all items for a service.
369+ *
370+ * Process:
371+ * 1. Validates options
372+ * 2. Resolves service name
373+ * 3. Reads all entries to get all Keystore aliases
374+ * 4. Deletes all Keystore keys
375+ * 5. Clears all SharedPreferences entries for the service
376+ * 6. Returns success
377+ *
378+ * @param request Optional SensitiveInfoOptions containing service name
379+ * @return Promise resolving to Unit (void)
380+ * @throws IllegalArgumentException if service is invalid
381+ * @throws java.security.KeyStoreException if any key deletion fails
382+ */
228383 override fun clearService (request : SensitiveInfoOptions ? ): Promise <Unit > {
229384 return Promise .async(coroutineScope) {
230385 val deps = ensureInitialized()
386+
387+ // Step 1: Validate options
388+ deps.validator.validateOptions(request)
389+
390+ // Step 2: Resolve service name
231391 val service = deps.serviceNameResolver.resolve(request?.service)
232392
233- // Get all entries for the service and delete their keys
393+ // Step 3: Read all entries for this service
234394 val entries = deps.storage.readAll(service)
395+
396+ // Step 4: Delete all Keystore keys
235397 for ((_, entry) in entries) {
236398 deps.cryptoManager.deleteKey(entry.alias)
237399 }
238400
239- // Clear SharedPreferences
401+ // Step 5: Clear SharedPreferences
240402 deps.storage.clear(service)
241403
242404 Unit
243405 }
244406 }
245407
408+ /* *
409+ * Gets supported security levels for the platform.
410+ *
411+ * Process:
412+ * 1. Queries security availability resolver for capabilities
413+ * 2. Converts capabilities to SecurityAvailability object
414+ * 3. Returns structured response
415+ *
416+ * @return Promise resolving to SecurityAvailability with platform capabilities
417+ */
246418 override fun getSupportedSecurityLevels (): Promise <SecurityAvailability > {
247419 return Promise .async(coroutineScope) {
248420 val deps = ensureInitialized()
421+
422+ // Step 1: Query capabilities
249423 val capabilities = deps.securityAvailabilityResolver.resolve()
250424
425+ // Step 2: Build and return response
251426 SecurityAvailability (
252427 secureEnclave = capabilities.secureEnclave,
253428 strongBox = capabilities.strongBox,
0 commit comments