Public Access
63 lines
2.8 KiB
C#
63 lines
2.8 KiB
C#
using DodoSSH.Client.Session;
|
|
|
|
namespace DodoSSH.Client.App.Platform;
|
|
|
|
/// <summary>
|
|
/// The device key store this machine supports, chosen once and off the UI thread.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Choosing a store means probing for it, and on Windows the probe is creating an RSA-2048 key inside the TPM
|
|
/// and deleting it again — see <see cref="WindowsDeviceKeyStore.IsSupported"/> — which is a slow device doing
|
|
/// real work. It used to run synchronously on the UI thread three times over: once to choose the store at
|
|
/// startup, again when the unlock card asked whether to offer the gesture, and again after every unlock when
|
|
/// the security page asked whether to offer registering. A machine does not gain or lose a TPM while this
|
|
/// process runs, so the answer is worked out once, on the thread pool, and every question after it is a
|
|
/// lookup.
|
|
/// </para>
|
|
/// <para>
|
|
/// Available means the choice landed on a real store, which is what the probe that chose it established.
|
|
/// Everything else waits for the choice and hands over to it — in practice it has long since finished,
|
|
/// since nothing reaches for the device key until somebody is at the unlock card.
|
|
/// </para>
|
|
/// </remarks>
|
|
internal sealed class ProbedDeviceKeyStore : IDeviceKeyStore
|
|
{
|
|
private readonly Task<IDeviceKeyStore> chosen;
|
|
|
|
/// <summary>Starts choosing at once, on the thread pool.</summary>
|
|
internal ProbedDeviceKeyStore(Func<IDeviceKeyStore> choose)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(choose);
|
|
chosen = Task.Run(choose);
|
|
}
|
|
|
|
/// <summary>The store the probe settled on, for a test that needs to know which.</summary>
|
|
internal Task<IDeviceKeyStore> Chosen => chosen;
|
|
|
|
/// <inheritdoc />
|
|
public async ValueTask<bool> IsAvailableAsync(CancellationToken cancellationToken) =>
|
|
await chosen.WaitAsync(cancellationToken).ConfigureAwait(false) is not UnavailableDeviceKeyStore;
|
|
|
|
/// <inheritdoc />
|
|
public async ValueTask SaveAsync(ReadOnlyMemory<byte> devicePrivateKey, CancellationToken cancellationToken)
|
|
{
|
|
var store = await chosen.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
await store.SaveAsync(devicePrivateKey, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async ValueTask<byte[]?> TryLoadAsync(CancellationToken cancellationToken)
|
|
{
|
|
var store = await chosen.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
return await store.TryLoadAsync(cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async ValueTask ForgetAsync(CancellationToken cancellationToken)
|
|
{
|
|
var store = await chosen.WaitAsync(cancellationToken).ConfigureAwait(false);
|
|
await store.ForgetAsync(cancellationToken).ConfigureAwait(false);
|
|
}
|
|
}
|