-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathModifyCredentialsRoutine.cs
More file actions
160 lines (140 loc) · 6.56 KB
/
ModifyCredentialsRoutine.cs
File metadata and controls
160 lines (140 loc) · 6.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using System;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using SecureFolderFS.Core.Cryptography;
using SecureFolderFS.Core.Cryptography.SecureStore;
using SecureFolderFS.Core.DataModels;
using SecureFolderFS.Core.Models;
using SecureFolderFS.Core.VaultAccess;
using SecureFolderFS.Shared.ComponentModel;
using SecureFolderFS.Shared.Extensions;
using SecureFolderFS.Shared.Models;
namespace SecureFolderFS.Core.Routines.Operational
{
/// <inheritdoc cref="IModifyCredentialsRoutine"/>
internal sealed class ModifyCredentialsRoutine : IModifyCredentialsRoutine
{
private readonly VaultReader _vaultReader;
private readonly VaultWriter _vaultWriter;
private KeyPair? _keyPair;
private V4VaultKeystoreDataModel? _existingV4KeystoreDataModel;
private V4VaultKeystoreDataModel? _keystoreDataModel;
private V4VaultConfigurationDataModel? _configDataModel;
public ModifyCredentialsRoutine(VaultReader vaultReader, VaultWriter vaultWriter)
{
_vaultReader = vaultReader;
_vaultWriter = vaultWriter;
}
/// <inheritdoc/>
public async Task InitAsync(CancellationToken cancellationToken = default)
{
_existingV4KeystoreDataModel = await _vaultReader.ReadKeystoreAsync<V4VaultKeystoreDataModel>(cancellationToken);
}
/// <inheritdoc/>
public void SetUnlockContract(IDisposable unlockContract)
{
if (unlockContract is not IWrapper<Security> securityWrapper)
throw new ArgumentException($"The {nameof(unlockContract)} is invalid.");
_keyPair = securityWrapper.Inner.KeyPair;
}
/// <inheritdoc/>
public void SetOptions(VaultOptions vaultOptions)
{
_configDataModel = V4VaultConfigurationDataModel.V4FromVaultOptions(vaultOptions);
}
/// <inheritdoc/>
public unsafe void SetCredentials(IKeyUsage passkey)
{
ArgumentNullException.ThrowIfNull(_keyPair);
// Recovery/unlock-contract flow: rotate to a fresh entropy value under the new passkey.
var salt = new byte[Cryptography.Constants.KeyTraits.SALT_LENGTH];
RandomNumberGenerator.Fill(salt);
passkey.UseKey(key =>
{
fixed (byte* keyPtr = key)
{
var state = (keyPtr: (nint)keyPtr, keyLen: key.Length);
_keyPair.UseKeys(state, (dekKey, macKey, s) =>
{
var k = new ReadOnlySpan<byte>((byte*)s.keyPtr, s.keyLen);
_keystoreDataModel = VaultParser.V4EncryptKeystore(k, dekKey, macKey, salt);
});
}
});
}
/// <inheritdoc/>
[SkipLocalsInit]
public unsafe void SetCredentials(IKeyUsage oldPasskey, IKeyUsage newPasskey, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(_keyPair);
ArgumentNullException.ThrowIfNull(_existingV4KeystoreDataModel);
var salt = new byte[Cryptography.Constants.KeyTraits.SALT_LENGTH];
RandomNumberGenerator.Fill(salt);
// Optional step-up flow: preserve existing entropy by decrypting it with the old passkey
// and re-encrypting it under the new passkey next to unchanged DEK and MAC keys.
// If old passkey material is unavailable (for example recovery-key driven rotation),
// the single-passkey overload rotates to fresh entropy and still yields a valid keystore.
Span<byte> softwareEntropy = stackalloc byte[32];
try
{
fixed (byte* softwareEntropyPtr = softwareEntropy)
{
var state = (sePtr: (nint)softwareEntropyPtr, seLen: softwareEntropy.Length);
oldPasskey.UseKey(state, (oldKey, s) =>
{
var se = new Span<byte>((byte*)s.sePtr, s.seLen);
VaultParser.V4DecryptSoftwareEntropy(oldKey, _existingV4KeystoreDataModel, se);
});
}
if (softwareEntropy.IsAllZeros())
throw new CryptographicException("The old passkey material is unavailable.");
fixed (byte* softwareEntropyPtr = softwareEntropy)
{
var state = (sePtr: (nint)softwareEntropyPtr, seLen: softwareEntropy.Length);
newPasskey.UseKey(state, (newKey, s) =>
{
fixed (byte* newKeyPtr = newKey)
{
var state2 = (nkPtr: (nint)newKeyPtr, nkLen: newKey.Length, outerState: state);
_keyPair.UseKeys(state2, (dekKey, macKey, s2) =>
{
var nk = new ReadOnlySpan<byte>((byte*)s2.nkPtr, s2.nkLen);
var se = new Span<byte>((byte*)s2.outerState.sePtr, s2.outerState.seLen);
_keystoreDataModel = VaultParser.V4ReEncryptKeystore(nk, dekKey, macKey, salt, se);
});
}
});
}
}
finally
{
CryptographicOperations.ZeroMemory(softwareEntropy);
}
}
/// <inheritdoc/>
public async Task<IDisposable> FinalizeAsync(CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(_keyPair);
ArgumentNullException.ThrowIfNull(_keystoreDataModel);
ArgumentNullException.ThrowIfNull(_configDataModel);
// First, we need to fill in the PayloadMac of the content
_keyPair.MacKey.UseKey(macKey =>
{
VaultParser.V4CalculateConfigMac(_configDataModel, macKey, _configDataModel.PayloadMac);
});
// Write the whole configuration
await _vaultWriter.WriteKeystoreAsync(_keystoreDataModel, cancellationToken);
await _vaultWriter.WriteV4ConfigurationAsync(_configDataModel, cancellationToken);
// Key copies need to be created because the original ones are disposed of here
using (_keyPair)
return new SecurityWrapper(_keyPair.CreateCopy(), _configDataModel);
}
/// <inheritdoc/>
public void Dispose()
{
_keyPair?.Dispose();
}
}
}