using DodoSSH.Client.Session; namespace DodoSSH.Client.App.Platform; /// /// The device key store this machine supports, chosen once and off the UI thread. /// /// /// /// 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 — 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. /// /// /// 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. /// /// internal sealed class ProbedDeviceKeyStore : IDeviceKeyStore { private readonly Task chosen; /// Starts choosing at once, on the thread pool. internal ProbedDeviceKeyStore(Func choose) { ArgumentNullException.ThrowIfNull(choose); chosen = Task.Run(choose); } /// The store the probe settled on, for a test that needs to know which. internal Task Chosen => chosen; /// public async ValueTask IsAvailableAsync(CancellationToken cancellationToken) => await chosen.WaitAsync(cancellationToken).ConfigureAwait(false) is not UnavailableDeviceKeyStore; /// public async ValueTask SaveAsync(ReadOnlyMemory devicePrivateKey, CancellationToken cancellationToken) { var store = await chosen.WaitAsync(cancellationToken).ConfigureAwait(false); await store.SaveAsync(devicePrivateKey, cancellationToken).ConfigureAwait(false); } /// public async ValueTask TryLoadAsync(CancellationToken cancellationToken) { var store = await chosen.WaitAsync(cancellationToken).ConfigureAwait(false); return await store.TryLoadAsync(cancellationToken).ConfigureAwait(false); } /// public async ValueTask ForgetAsync(CancellationToken cancellationToken) { var store = await chosen.WaitAsync(cancellationToken).ConfigureAwait(false); await store.ForgetAsync(cancellationToken).ConfigureAwait(false); } }