@@ -15,8 +15,11 @@ import com.sensitiveinfo.internal.storage.SecureStorage
1515import com.sensitiveinfo.internal.util.AliasGenerator
1616import com.sensitiveinfo.internal.util.ReactContextHolder
1717import com.sensitiveinfo.internal.util.ServiceNameResolver
18+ import com.sensitiveinfo.internal.util.accessControlFromPersisted
19+ import com.sensitiveinfo.internal.util.securityLevelFromPersisted
1820import com.sensitiveinfo.internal.validation.AndroidStorageValidator
1921import com.sensitiveinfo.internal.validation.StorageValidator
22+ import com.sensitiveinfo.AndroidKeyRotationManager
2023import kotlinx.coroutines.CoroutineScope
2124import kotlinx.coroutines.Dispatchers
2225import kotlinx.coroutines.SupervisorJob
@@ -45,7 +48,8 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
4548 val securityAvailabilityResolver : SecurityAvailabilityResolver ,
4649 val serviceNameResolver : ServiceNameResolver ,
4750 val validator : StorageValidator ,
48- val responseBuilder : ResponseBuilder
51+ val responseBuilder : ResponseBuilder ,
52+ val keyRotationManager : AndroidKeyRotationManager
4953 )
5054
5155 @Volatile
@@ -72,7 +76,8 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
7276 securityAvailabilityResolver = securityAvailabilityResolver,
7377 serviceNameResolver = serviceNameResolver,
7478 validator = AndroidStorageValidator (),
75- responseBuilder = StandardResponseBuilder ()
79+ responseBuilder = StandardResponseBuilder (),
80+ keyRotationManager = AndroidKeyRotationManager (ctx)
7681 ).also { built ->
7782 dependencies = built
7883 }
@@ -123,7 +128,8 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
123128 securityLevel = resolved.securityLevel,
124129 backend = StorageBackend .ANDROIDKEYSTORE ,
125130 accessControl = resolved.accessControl,
126- timestamp = System .currentTimeMillis() / 1000.0
131+ timestamp = System .currentTimeMillis() / 1000.0 ,
132+ alias = alias
127133 )
128134
129135 // Step 7: Persist entry
@@ -211,8 +217,10 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
211217 securityLevel = SecurityLevel .SOFTWARE ,
212218 backend = StorageBackend .ANDROIDKEYSTORE ,
213219 accessControl = AccessControl .NONE ,
214- timestamp = System .currentTimeMillis() / 1000.0
215- )
220+ timestamp = System .currentTimeMillis() / 1000.0 ,
221+ alias = entry.alias
222+ ),
223+ service = service
216224 )
217225 }
218226 }
@@ -320,7 +328,8 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
320328 securityLevel = SecurityLevel .SOFTWARE ,
321329 backend = StorageBackend .ANDROIDKEYSTORE ,
322330 accessControl = AccessControl .NONE ,
323- timestamp = System .currentTimeMillis() / 1000.0
331+ timestamp = System .currentTimeMillis() / 1000.0 ,
332+ alias = entry.alias
324333 )
325334
326335 val value = if (includeValues && entry.ciphertext != null && entry.iv != null ) {
@@ -354,7 +363,8 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
354363 deps.responseBuilder.buildItem(
355364 key = key,
356365 value = value,
357- metadata = metadata
366+ metadata = metadata,
367+ service = service
358368 )
359369 } catch (e: Throwable ) {
360370 // Step 6: Skip items that fail to process
@@ -432,6 +442,103 @@ class HybridSensitiveInfo : HybridSensitiveInfoSpec() {
432442 }
433443 }
434444
445+ /* *
446+ * Re-encrypts all items with the current key.
447+ * Migrates items encrypted with old keys to the current key version.
448+ */
449+ override fun reEncryptAllItems (request : ReEncryptAllItemsRequest ): Promise <ReEncryptAllItemsResponse > {
450+ return Promise .async(coroutineScope) {
451+ val deps = ensureInitialized()
452+
453+ // Step 1: Resolve service
454+ val service = deps.serviceNameResolver.resolve(request.service ? : " " )
455+
456+ // Step 2: Get current key version
457+ var currentKeyVersion = deps.keyRotationManager.getCurrentKeyVersion()
458+ if (currentKeyVersion == null ) {
459+ // Generate a new key if none exists
460+ val newKeyId = System .currentTimeMillis().toString()
461+ val success = deps.keyRotationManager.generateNewKey(newKeyId, requiresBiometry = false )
462+ if (success) {
463+ deps.keyRotationManager.rotateToNewKey(newKeyId)
464+ currentKeyVersion = newKeyId
465+ } else {
466+ throw IllegalStateException (" Failed to generate initial key for re-encryption" )
467+ }
468+ }
469+
470+ // Step 3: Get all entries for the service
471+ val entries = deps.storage.readAll(service)
472+
473+ var reEncryptedCount = 0
474+ val errors = mutableListOf<ReEncryptError >()
475+
476+ // Step 4: Re-encrypt items that use old keys
477+ for ((key, entry) in entries) {
478+ try {
479+ if (entry.alias != currentKeyVersion && entry.ciphertext != null && entry.iv != null ) {
480+ // Get access control from persisted
481+ val accessControl = accessControlFromPersisted(entry.metadata.accessControl) ? : AccessControl .NONE
482+ val securityLevel = securityLevelFromPersisted(entry.metadata.securityLevel) ? : SecurityLevel .SOFTWARE
483+
484+ // Decrypt with old key
485+ val resolution = deps.cryptoManager.buildResolutionForPersisted(
486+ accessControl = accessControl,
487+ securityLevel = securityLevel,
488+ authenticators = entry.authenticators,
489+ requiresAuth = entry.requiresAuthentication,
490+ invalidateOnEnrollment = entry.invalidateOnEnrollment,
491+ useStrongBox = entry.useStrongBox
492+ )
493+
494+ val plaintext = deps.cryptoManager.decrypt(
495+ entry.alias,
496+ entry.ciphertext,
497+ entry.iv,
498+ resolution,
499+ null // No auth prompt for background operation
500+ )
501+
502+ // Encrypt with new key
503+ val newResolution = deps.cryptoManager.buildResolutionForPersisted(
504+ accessControl = accessControl,
505+ securityLevel = securityLevel,
506+ authenticators = entry.authenticators,
507+ requiresAuth = entry.requiresAuthentication,
508+ invalidateOnEnrollment = entry.invalidateOnEnrollment,
509+ useStrongBox = entry.useStrongBox
510+ )
511+
512+ val encryption = deps.cryptoManager.encrypt(
513+ currentKeyVersion,
514+ plaintext,
515+ newResolution,
516+ null
517+ )
518+
519+ // Update storage
520+ val updatedEntry = entry.copy(
521+ ciphertext = encryption.ciphertext,
522+ iv = encryption.iv,
523+ alias = currentKeyVersion
524+ )
525+ deps.storage.save(service, key, updatedEntry)
526+
527+ reEncryptedCount++
528+ }
529+ } catch (e: Exception ) {
530+ errors.add(ReEncryptError (key = key, error = e.message ? : " Unknown error" ))
531+ }
532+ }
533+
534+ // Step 5: Return results
535+ ReEncryptAllItemsResponse (
536+ itemsReEncrypted = reEncryptedCount.toDouble(),
537+ errors = errors.toTypedArray()
538+ )
539+ }
540+ }
541+
435542 private fun ensureInitialized (): Dependencies {
436543 dependencies?.let { return it }
437544
0 commit comments