using System.Runtime.Versioning; namespace DodoSSH.Client.Session.Tests; /// /// The real TPM-backed store, as far as it can be exercised without a person. Which is not far. /// /// /// /// Two tests, and the reason there are only two is a measured finding. A key created under /// CngUIProtectionLevels.ProtectKey prompts at creation, 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. /// /// /// So anything that calls SaveAsync, TryLoadAsync with a blob present, or ForgetAsync /// 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. /// /// /// 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. /// /// [SupportedOSPlatform("windows")] public sealed class WindowsDeviceKeyStoreTests { private static CancellationToken Token => TestContext.Current.CancellationToken; [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 = DeviceKeyStores.ForThisMachine(new ClientPaths(Path.GetTempPath())); store.ShouldBeOfType(); (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() && DeviceKeyStores.ForThisMachine(new ClientPaths(Path.GetTempPath())) is WindowsDeviceKeyStore; if (!supported) { Assert.Skip("This machine has no TPM the platform crypto provider will hold a key in."); } } }