Public Access
88 lines
4.1 KiB
C#
88 lines
4.1 KiB
C#
using System.Runtime.Versioning;
|
|
using DodoSSH.Client.App.Platform;
|
|
using DodoSSH.Client.Session;
|
|
|
|
namespace DodoSSH.Client.App.Tests;
|
|
|
|
/// <summary>
|
|
/// The real TPM-backed store, as far as it can be exercised without a person. Which is not far.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <b>Two tests, and the reason there are only two is a measured finding.</b> A key created under
|
|
/// <c>CngUIProtectionLevels.ProtectKey</c> prompts at <em>creation</em>, not only at use: the policy means
|
|
/// "protect this key with a PIN", so Windows asks the user to set that up when the key is made. Sealing
|
|
/// therefore prompts as well as opening, even though sealing needs only the public half.
|
|
/// </para>
|
|
/// <para>
|
|
/// So anything that calls <c>SaveAsync</c>, <c>TryLoadAsync</c> with a blob present, or <c>ForgetAsync</c>
|
|
/// after a save will block a suite forever waiting for somebody to enter a PIN. That was found by writing
|
|
/// those tests and watching the run hang for ten minutes. They are gone; what is left is the two paths that
|
|
/// provably reach no dialog.
|
|
/// </para>
|
|
/// <para>
|
|
/// The rest of this store is verified by using the application, and that is not a gap this file can close —
|
|
/// a consent dialog needs hardware and a person by design. Disabling the UI policy to make it testable
|
|
/// would be testing a different class, and the one property worth having would be the property removed.
|
|
/// </para>
|
|
/// </remarks>
|
|
[SupportedOSPlatform("windows")]
|
|
public sealed class WindowsDeviceKeyStoreTests
|
|
{
|
|
private static CancellationToken Token => TestContext.Current.CancellationToken;
|
|
|
|
/// <summary>
|
|
/// The PIN prompt this platform never reaches, because Windows raises its own.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <c>ForThisMachine</c> takes one so that the Linux store has somewhere to ask from — see
|
|
/// <c>LinuxDeviceKeyStore</c>. On Windows nothing calls it, and answering null rather than throwing is
|
|
/// the honest stand-in: if the selection ever did reach for it here, the result would be a store that
|
|
/// declines rather than a test that fails somewhere unrelated.
|
|
/// </remarks>
|
|
private static ValueTask<string?> NoPin(DevicePinPurpose purpose, CancellationToken cancellationToken) =>
|
|
ValueTask.FromResult<string?>(null);
|
|
|
|
[Fact]
|
|
public async Task OnAMachineWithATpm_TheStoreOffersItself()
|
|
{
|
|
// IsSupported probes with a throwaway key carrying no UI policy, which is why this one is safe to
|
|
// run: no policy, no dialog. It is also the only honest availability test, because the platform
|
|
// provider reports itself present on machines where creating a key then fails.
|
|
SkipUnlessSupported();
|
|
|
|
var store = DesktopDeviceKeyStores.ForThisMachine(new ClientPaths(Path.GetTempPath()), NoPin);
|
|
|
|
(await store.ShouldBeOfType<ProbedDeviceKeyStore>().Chosen).ShouldBeOfType<WindowsDeviceKeyStore>();
|
|
(await store.IsAvailableAsync(Token)).ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task WithNothingSaved_LoadingReturnsNullWithoutPrompting()
|
|
{
|
|
// Reaches no dialog because it returns on the missing file, before touching the TPM at all. That is
|
|
// also what keeps a fresh machine's unlock screen quiet: it must not prompt for a key it has never
|
|
// been given. If this test ever hangs, that ordering has been lost.
|
|
SkipUnlessSupported();
|
|
|
|
var directory = Path.Combine(Path.GetTempPath(), $"dodossh-devicekey-{Guid.CreateVersion7():N}");
|
|
var store = new WindowsDeviceKeyStore(new ClientPaths(directory));
|
|
|
|
(await store.TryLoadAsync(Token)).ShouldBeNull();
|
|
|
|
// Nothing was created, so there is nothing to clean up — asserted, because a store that wrote a
|
|
// directory just to answer "no" would be leaving litter on every launch of an unregistered machine.
|
|
Directory.Exists(directory).ShouldBeFalse();
|
|
}
|
|
|
|
private static void SkipUnlessSupported()
|
|
{
|
|
var supported = OperatingSystem.IsWindows() && WindowsDeviceKeyStore.IsSupported();
|
|
|
|
if (!supported)
|
|
{
|
|
Assert.Skip("This machine has no TPM the platform crypto provider will hold a key in.");
|
|
}
|
|
}
|
|
}
|