-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathUnlockRoutine.cs
More file actions
143 lines (126 loc) · 5.71 KB
/
Copy pathUnlockRoutine.cs
File metadata and controls
143 lines (126 loc) · 5.71 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
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using SecureFolderFS.Core.Cryptography;
using SecureFolderFS.Core.DataModels;
using SecureFolderFS.Core.Models;
using SecureFolderFS.Core.Validators;
using SecureFolderFS.Core.VaultAccess;
using SecureFolderFS.Shared.ComponentModel;
using SecureFolderFS.Shared.Models;
using SecureFolderFS.Shared.SecureStore;
namespace SecureFolderFS.Core.Routines.Operational
{
/// <inheritdoc cref="ICredentialsRoutine"/>
internal sealed class UnlockRoutine : ICredentialsRoutine
{
private readonly VaultReader _vaultReader;
private V4VaultKeystoreDataModel? _keystoreDataModel;
private V4VaultConfigurationDataModel? _configDataModel;
private VaultSharesDataModel? _sharesDataModel;
private SecureKey? _dekKey;
private SecureKey? _macKey;
public UnlockRoutine(VaultReader vaultReader)
{
_vaultReader = vaultReader;
}
/// <inheritdoc/>
public async Task InitAsync(CancellationToken cancellationToken)
{
_configDataModel = await _vaultReader.ReadV4ConfigurationAsync(cancellationToken);
_keystoreDataModel = await _vaultReader.ReadKeystoreAsync<V4VaultKeystoreDataModel>(cancellationToken);
_sharesDataModel = await _vaultReader.ReadComplementationAsync(cancellationToken);
}
/// <inheritdoc/>
public void SetCredentials(IKeyUsage passkey)
{
ArgumentNullException.ThrowIfNull(_configDataModel);
ArgumentNullException.ThrowIfNull(_keystoreDataModel);
var authenticationMethod = AuthenticationMethod.FromString(_configDataModel.AuthenticationMethod);
var derived = string.IsNullOrWhiteSpace(authenticationMethod.Complementation)
? passkey.UseKey(key => VaultParser.V4DeriveKeystore(key, _keystoreDataModel))
: DeriveComplementedKeystore(passkey, authenticationMethod);
_dekKey = SecureKey.TakeOwnership(derived.dekKey);
_macKey = SecureKey.TakeOwnership(derived.macKey);
}
private (byte[] dekKey, byte[] macKey) DeriveComplementedKeystore(IKeyUsage passkey, AuthenticationMethod authenticationMethod)
{
ArgumentNullException.ThrowIfNull(_configDataModel);
ArgumentNullException.ThrowIfNull(_keystoreDataModel);
Exception? lastException = null;
var primaryMethodId = authenticationMethod.Methods.FirstOrDefault() ?? throw new InvalidOperationException("Primary authentication is missing.");
try
{
return passkey.UseKey(key =>
{
Span<byte> complementSecret = stackalloc byte[32];
try
{
VaultParser.V4DeriveComplementKey(key, _configDataModel.Uid, primaryMethodId, complementSecret);
return VaultParser.V4DeriveKeystore(complementSecret, _keystoreDataModel);
}
finally
{
CryptographicOperations.ZeroMemory(complementSecret);
}
});
}
catch (CryptographicException ex)
{
lastException = ex;
}
foreach (var share in _sharesDataModel?.Shares ?? [])
{
if (share.WrappedComplementSecret is null
|| share.Tag is null
|| share.Nonce is null
|| share.AuthenticationMethodId is null)
continue;
if (!string.Equals(share.AuthenticationMethodId, authenticationMethod.Complementation, StringComparison.Ordinal))
continue;
byte[]? complementSecret = null;
try
{
complementSecret = passkey.UseKey(key => VaultParser.V4UnwrapComplementSecret(key, _configDataModel.Uid, share));
return VaultParser.V4DeriveKeystore(complementSecret, _keystoreDataModel);
}
catch (CryptographicException ex)
{
lastException = ex;
}
finally
{
if (complementSecret is not null)
CryptographicOperations.ZeroMemory(complementSecret);
}
}
throw lastException ?? new CryptographicException("The complemented credentials could not unlock this vault.");
}
/// <inheritdoc/>
public async Task<IDisposable> FinalizeAsync(CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(_dekKey);
ArgumentNullException.ThrowIfNull(_macKey);
ArgumentNullException.ThrowIfNull(_configDataModel);
ArgumentNullException.ThrowIfNull(_keystoreDataModel);
using (_dekKey)
using (_macKey)
{
// Check if the payload has not been tampered with
var validator = new ConfigurationValidator(_macKey);
await validator.ValidateAsync(_configDataModel, cancellationToken);
// In this case, we rely on the consumer to take ownership of the keys, and thus manage their lifetimes
// Key copies need to be created because the original ones are disposed of here
return new SecurityWrapper(KeyPair.ImportKeys(_dekKey, _macKey), _configDataModel);
}
}
/// <inheritdoc/>
public void Dispose()
{
_dekKey?.Dispose();
_macKey?.Dispose();
}
}
}