99using SecureFolderFS . Core . Models ;
1010using SecureFolderFS . Core . VaultAccess ;
1111using SecureFolderFS . Shared . ComponentModel ;
12+ using SecureFolderFS . Shared . Extensions ;
1213using SecureFolderFS . Shared . Models ;
1314
1415namespace SecureFolderFS . Core . Routines . Operational
@@ -20,10 +21,8 @@ internal sealed class ModifyCredentialsRoutine : IModifyCredentialsRoutine
2021 private readonly VaultWriter _vaultWriter ;
2122 private KeyPair ? _keyPair ;
2223 private V4VaultKeystoreDataModel ? _existingV4KeystoreDataModel ;
23- private V3VaultKeystoreDataModel ? _keystoreDataModel ;
24- private V4VaultKeystoreDataModel ? _v4KeystoreDataModel ;
25- private VaultConfigurationDataModel ? _configDataModel ;
26- private V4VaultConfigurationDataModel ? _v4ConfigDataModel ;
24+ private V4VaultKeystoreDataModel ? _keystoreDataModel ;
25+ private V4VaultConfigurationDataModel ? _configDataModel ;
2726
2827 public ModifyCredentialsRoutine ( VaultReader vaultReader , VaultWriter vaultWriter )
2928 {
@@ -34,8 +33,7 @@ public ModifyCredentialsRoutine(VaultReader vaultReader, VaultWriter vaultWriter
3433 /// <inheritdoc/>
3534 public async Task InitAsync ( CancellationToken cancellationToken = default )
3635 {
37- await Task . CompletedTask ;
38- //_existingV4KeystoreDataModel = await _vaultReader.ReadKeystoreAsync<V4VaultKeystoreDataModel>(cancellationToken);
36+ _existingV4KeystoreDataModel = await _vaultReader . ReadKeystoreAsync < V4VaultKeystoreDataModel > ( cancellationToken ) ;
3937 }
4038
4139 /// <inheritdoc/>
@@ -50,28 +48,18 @@ public void SetUnlockContract(IDisposable unlockContract)
5048 /// <inheritdoc/>
5149 public void SetOptions ( VaultOptions vaultOptions )
5250 {
53- if ( vaultOptions . AppPlatform is null )
54- {
55- _configDataModel = VaultConfigurationDataModel . FromVaultOptions ( vaultOptions ) ;
56- _v4ConfigDataModel = null ;
57- }
58- else
59- {
60- _v4ConfigDataModel = V4VaultConfigurationDataModel . V4FromVaultOptions ( vaultOptions ) ;
61- _configDataModel = _v4ConfigDataModel . ToVaultConfigurationDataModel ( ) ;
62- }
51+ _configDataModel = V4VaultConfigurationDataModel . V4FromVaultOptions ( vaultOptions ) ;
6352 }
6453
6554 /// <inheritdoc/>
6655 public unsafe void SetCredentials ( IKeyUsage passkey )
6756 {
6857 ArgumentNullException . ThrowIfNull ( _keyPair ) ;
6958
70- // Generate new salt
59+ // Recovery/unlock-contract flow: rotate to a fresh entropy value under the new passkey.
7160 var salt = new byte [ Cryptography . Constants . KeyTraits . SALT_LENGTH ] ;
7261 RandomNumberGenerator . Fill ( salt ) ;
7362
74- // Encrypt a new keystore
7563 passkey . UseKey ( key =>
7664 {
7765 fixed ( byte * keyPtr = key )
@@ -80,26 +68,26 @@ public unsafe void SetCredentials(IKeyUsage passkey)
8068 _keyPair . UseKeys ( state , ( dekKey , macKey , s ) =>
8169 {
8270 var k = new ReadOnlySpan < byte > ( ( byte * ) s . keyPtr , s . keyLen ) ;
83- _keystoreDataModel = VaultParser . V3EncryptKeystore ( k , dekKey , macKey , salt ) ;
71+ _keystoreDataModel = VaultParser . V4EncryptKeystore ( k , dekKey , macKey , salt ) ;
8472 } ) ;
8573 }
8674 } ) ;
8775 }
8876
77+ /// <inheritdoc/>
8978 [ SkipLocalsInit ]
90- public unsafe void V4SetCredentials ( IKeyUsage oldPasskey , IKeyUsage newPasskey , CancellationToken cancellationToken = default )
79+ public unsafe void SetCredentials ( IKeyUsage oldPasskey , IKeyUsage newPasskey , CancellationToken cancellationToken = default )
9180 {
9281 ArgumentNullException . ThrowIfNull ( _keyPair ) ;
9382 ArgumentNullException . ThrowIfNull ( _existingV4KeystoreDataModel ) ;
9483
95- // Generate new salt for the re-encrypted keystore
9684 var salt = new byte [ Cryptography . Constants . KeyTraits . SALT_LENGTH ] ;
9785 RandomNumberGenerator . Fill ( salt ) ;
9886
99- // Decrypt existing SoftwareEntropy using the old passkey, then re-encrypt
100- // it under the new passkey alongside the ( unchanged) DEK and MAC keys.
101- // SoftwareEntropy must be preserved - regenerating it would change the KEK
102- // derivation and make the vault permanently unreadable .
87+ // Optional step-up flow: preserve existing entropy by decrypting it with the old passkey
88+ // and re-encrypting it under the new passkey next to unchanged DEK and MAC keys.
89+ // If old passkey material is unavailable (for example recovery-key driven rotation),
90+ // the single-passkey overload rotates to fresh entropy and still yields a valid keystore .
10391 Span < byte > softwareEntropy = stackalloc byte [ 32 ] ;
10492 try
10593 {
@@ -113,6 +101,9 @@ public unsafe void V4SetCredentials(IKeyUsage oldPasskey, IKeyUsage newPasskey,
113101 } ) ;
114102 }
115103
104+ if ( softwareEntropy . IsAllZeros ( ) )
105+ throw new CryptographicException ( "The old passkey material is unavailable." ) ;
106+
116107 fixed ( byte * softwareEntropyPtr = softwareEntropy )
117108 {
118109 var state = ( sePtr : ( nint ) softwareEntropyPtr , seLen : softwareEntropy . Length ) ;
@@ -126,7 +117,7 @@ public unsafe void V4SetCredentials(IKeyUsage oldPasskey, IKeyUsage newPasskey,
126117 var nk = new ReadOnlySpan < byte > ( ( byte * ) s2 . nkPtr , s2 . nkLen ) ;
127118 var se = new Span < byte > ( ( byte * ) s2 . outerState . sePtr , s2 . outerState . seLen ) ;
128119
129- _v4KeystoreDataModel = VaultParser . V4ReEncryptKeystore ( nk , dekKey , macKey , salt , se ) ;
120+ _keystoreDataModel = VaultParser . V4ReEncryptKeystore ( nk , dekKey , macKey , salt , se ) ;
130121 } ) ;
131122 }
132123 } ) ;
@@ -142,24 +133,18 @@ public unsafe void V4SetCredentials(IKeyUsage oldPasskey, IKeyUsage newPasskey,
142133 public async Task < IDisposable > FinalizeAsync ( CancellationToken cancellationToken )
143134 {
144135 ArgumentNullException . ThrowIfNull ( _keyPair ) ;
136+ ArgumentNullException . ThrowIfNull ( _keystoreDataModel ) ;
145137 ArgumentNullException . ThrowIfNull ( _configDataModel ) ;
146138
147139 // First, we need to fill in the PayloadMac of the content
148140 _keyPair . MacKey . UseKey ( macKey =>
149141 {
150- if ( _v4ConfigDataModel is not null )
151- VaultParser . V4CalculateConfigMac ( _v4ConfigDataModel , macKey , _v4ConfigDataModel . PayloadMac ) ;
152- else
153- VaultParser . CalculateConfigMac ( _configDataModel , macKey , _configDataModel . PayloadMac ) ;
142+ VaultParser . V4CalculateConfigMac ( _configDataModel , macKey , _configDataModel . PayloadMac ) ;
154143 } ) ;
155144
156145 // Write the whole configuration
157146 await _vaultWriter . WriteKeystoreAsync ( _keystoreDataModel , cancellationToken ) ;
158- //await _vaultWriter.WriteKeystoreAsync(_v4KeystoreDataModel, cancellationToken);
159- if ( _v4ConfigDataModel is not null )
160- await _vaultWriter . WriteV4ConfigurationAsync ( _v4ConfigDataModel , cancellationToken ) ;
161- else
162- await _vaultWriter . WriteConfigurationAsync ( _configDataModel , cancellationToken ) ;
147+ await _vaultWriter . WriteV4ConfigurationAsync ( _configDataModel , cancellationToken ) ;
163148
164149 // Key copies need to be created because the original ones are disposed of here
165150 using ( _keyPair )
0 commit comments