Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions SecRandom.Core.Tests/PluginDrawServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,13 @@ public async Task<bool> AuthorizeAsync(IReadOnlyCollection<SecurityOperation> op
return true;
}

public Task<bool> AuthorizePasswordAsync(TopLevel xamlRoot, Func<Task> action, CancellationToken cancellationToken = default)
public Task<bool> AuthorizePasswordAsync(Func<Task> action, CancellationToken cancellationToken = default)
=> AuthorizeAsync(SecurityOperation.OpenSettings, action, cancellationToken);

public Task<SecurityAuthorizationResult> AuthorizeSettingsAsync(Func<Task> action, Func<Task> previewAction, CancellationToken cancellationToken = default)
=> Task.FromResult(new SecurityAuthorizationResult(allow, false));

public Task<bool> UpdateSecuritySettingsAsync(TopLevel xamlRoot, Action update, CancellationToken cancellationToken = default)
public Task<bool> UpdateSecuritySettingsAsync(Action update, CancellationToken cancellationToken = default)
{
if (allow)
update();
Expand All @@ -201,9 +201,6 @@ public Task<bool> RemovePasswordAsync(string currentPassword, CancellationToken
public Task<string?> BeginTotpSetupAsync(CancellationToken cancellationToken = default)
=> Task.FromResult<string?>(null);

public Task<string?> BeginTotpSetupAsync(TopLevel xamlRoot, CancellationToken cancellationToken = default)
=> Task.FromResult<string?>(null);

public Task CancelTotpSetupAsync(string secret, CancellationToken cancellationToken = default)
=> Task.CompletedTask;

Expand All @@ -219,15 +216,9 @@ public Task<IReadOnlyList<UsbDeviceInfo>> GetUsbDevicesAsync(CancellationToken c
public Task<bool> BindUsbAsync(string deviceId, CancellationToken cancellationToken = default)
=> Task.FromResult(allow);

public Task<bool> BindUsbAsync(TopLevel xamlRoot, string deviceId, CancellationToken cancellationToken = default)
=> Task.FromResult(allow);

public Task<bool> UnbindUsbAsync(string bindingId, CancellationToken cancellationToken = default)
=> Task.FromResult(allow);

public Task<bool> UnbindUsbAsync(TopLevel xamlRoot, string bindingId, CancellationToken cancellationToken = default)
=> Task.FromResult(allow);

public bool TryUpdateSettings(Action update)
{
if (allow)
Expand Down
50 changes: 24 additions & 26 deletions SecRandom.Core.Tests/SecurityServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ public async Task UpdateSecuritySettingsAsync_WhenPasswordIsAccepted_EnablesProt
fixture.ConfigService.ResetSaveCount();

var updated = await fixture.Service.UpdateSecuritySettingsAsync(
null!,
() => fixture.ConfigHandler.Data.SecuritySettings.SecurityEnabled = true,
TestContext.Current.CancellationToken);

Expand All @@ -128,7 +127,6 @@ public async Task UpdateSecuritySettingsAsync_WhenPasswordIsRejected_DoesNotChan
fixture.ConfigService.ResetSaveCount();

var updated = await fixture.Service.UpdateSecuritySettingsAsync(
null!,
() => fixture.ConfigHandler.Data.SecuritySettings.SecurityEnabled = true,
TestContext.Current.CancellationToken);

Expand Down Expand Up @@ -180,7 +178,7 @@ public async Task BeginTotpSetupAsync_WhenPasswordIsRejected_DoesNotRevealANewSe
var fixture = CreateFixture(Password("wrong"));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);

var secret = await fixture.Service.BeginTotpSetupAsync(null!, TestContext.Current.CancellationToken);
var secret = await fixture.Service.BeginTotpSetupAsync(TestContext.Current.CancellationToken);

Assert.Null(secret);
Assert.Equal([SecurityFactor.Password], Assert.Single(fixture.Prompt.Requests).RequiredFactors);
Expand Down Expand Up @@ -210,7 +208,7 @@ public async Task RemovePasswordAsync_WhenUsbIsBound_DeletesTheBindingKey()
Password("secret1"),
new UsbDriveInfo("H:", "Remove password USB", "volume:remove-password", usbRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
Assert.True(await fixture.Service.BindUsbAsync(null!, "volume:remove-password", TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.BindUsbAsync("volume:remove-password", TestContext.Current.CancellationToken));
Assert.True(File.Exists(Path.Combine(usbRoot, ".SecRandom.safety.key")));

var removed = await fixture.Service.RemovePasswordAsync("secret1", TestContext.Current.CancellationToken);
Expand All @@ -224,7 +222,7 @@ public async Task CancelTotpSetupAsync_WhenConfirmationIsCancelled_CannotActivat
{
var fixture = CreateFixture(Password("secret1"));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
var secret = await fixture.Service.BeginTotpSetupAsync(null!, TestContext.Current.CancellationToken);
var secret = await fixture.Service.BeginTotpSetupAsync(TestContext.Current.CancellationToken);
Assert.NotNull(secret);

await fixture.Service.CancelTotpSetupAsync(secret, TestContext.Current.CancellationToken);
Expand All @@ -240,7 +238,7 @@ public async Task ConfirmTotpAsync_WhenCredentialSaveFails_ReturnsFalseAndKeepsT
var writeFault = new ThrowOnNthCredentialWrite();
var fixture = CreateFixture(Password("secret1"), writeFault);
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
var secret = await fixture.Service.BeginTotpSetupAsync(null!, TestContext.Current.CancellationToken);
var secret = await fixture.Service.BeginTotpSetupAsync(TestContext.Current.CancellationToken);
Assert.NotNull(secret);
writeFault.ThrowOnWrite = writeFault.WriteCalls + 1;

Expand Down Expand Up @@ -273,7 +271,7 @@ public async Task VerifyAsync_WhenAnySelectedFactorModeHasBoundUsb_AuthorizesWit
Password("secret1"),
new UsbDriveInfo("P:", "Authorization USB", "volume:usb-any", usbRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
Assert.True(await fixture.Service.BindUsbAsync(null!, "volume:usb-any", TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.BindUsbAsync("volume:usb-any", TestContext.Current.CancellationToken));
fixture.ConfigHandler.Data.SecuritySettings.SecurityEnabled = true;
fixture.ConfigHandler.Data.SecuritySettings.UsbBindingEnabled = true;
fixture.ConfigHandler.Data.SecuritySettings.RequireAllSelectedFactors = false;
Expand All @@ -293,7 +291,7 @@ public async Task VerifyAsync_WhenAllSelectedFactorModeHasOnlyUsb_RejectsAuthori
Password("secret1"),
new UsbDriveInfo("Q:", "Authorization USB", "volume:usb-all", usbRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
Assert.True(await fixture.Service.BindUsbAsync(null!, "volume:usb-all", TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.BindUsbAsync("volume:usb-all", TestContext.Current.CancellationToken));
fixture.ConfigHandler.Data.SecuritySettings.SecurityEnabled = true;
fixture.ConfigHandler.Data.SecuritySettings.UsbBindingEnabled = true;
fixture.ConfigHandler.Data.SecuritySettings.RequireAllSelectedFactors = true;
Expand Down Expand Up @@ -336,7 +334,7 @@ public async Task GetUsbDevicesAsync_ProjectsBoundAndUnboundRemovableDevices()
new UsbDriveInfo("F:", "Backup USB", "volume:F", secondRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);

var bound = await fixture.Service.BindUsbAsync(null!, "volume:E", TestContext.Current.CancellationToken);
var bound = await fixture.Service.BindUsbAsync("volume:E", TestContext.Current.CancellationToken);
var devices = await fixture.Service.GetUsbDevicesAsync(TestContext.Current.CancellationToken);

Assert.True(bound);
Expand All @@ -356,7 +354,7 @@ public async Task GetUsbDevicesAsync_WhenBoundVolumeMoves_RecognizesItByDeviceId
Password("secret1"),
new UsbDriveInfo("E:", "Portable USB", "volume:4A2B", originalRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
Assert.True(await fixture.Service.BindUsbAsync(null!, "volume:4A2B", TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.BindUsbAsync("volume:4A2B", TestContext.Current.CancellationToken));

Directory.Move(originalRoot, movedRoot);
fixture.UsbCatalog.SetDevices(new UsbDriveInfo("F:", "Portable USB", "volume:4A2B", movedRoot));
Expand All @@ -376,15 +374,15 @@ public async Task GetUsbDevicesAsync_WhenMarkerIsMissing_DoesNotProjectTheVolume
Password("secret1"),
new UsbDriveInfo("E:", "Marker USB", "volume:missing-marker", usbRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
Assert.True(await fixture.Service.BindUsbAsync(null!, "volume:missing-marker", TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.BindUsbAsync("volume:missing-marker", TestContext.Current.CancellationToken));

File.Delete(Path.Combine(usbRoot, ".SecRandom.safety.key"));

var device = Assert.Single(await fixture.Service.GetUsbDevicesAsync(TestContext.Current.CancellationToken));
Assert.False(device.IsBound);
Assert.True(device.IsPresent);

Assert.True(await fixture.Service.BindUsbAsync(null!, "volume:missing-marker", TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.BindUsbAsync("volume:missing-marker", TestContext.Current.CancellationToken));
Assert.Single(await fixture.Service.GetUsbBindingsAsync(TestContext.Current.CancellationToken));
}

Expand All @@ -397,7 +395,7 @@ public async Task BindUsbAsync_WhenPasswordIsRejected_DoesNotWriteABindingKey()
new UsbDriveInfo("G:", "Rejected USB", "volume:G", usbRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);

var bound = await fixture.Service.BindUsbAsync(null!, "volume:G", TestContext.Current.CancellationToken);
var bound = await fixture.Service.BindUsbAsync("volume:G", TestContext.Current.CancellationToken);

Assert.False(bound);
Assert.False(File.Exists(Path.Combine(usbRoot, ".SecRandom.safety.key")));
Expand All @@ -413,7 +411,7 @@ public async Task BindUsbAsync_WhenCallerSuppliesAPathInsteadOfADeviceId_Rejects
new UsbDriveInfo("I:", "Path input USB", "volume:path-input", usbRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);

var bound = await fixture.Service.BindUsbAsync(null!, usbRoot, TestContext.Current.CancellationToken);
var bound = await fixture.Service.BindUsbAsync(usbRoot, TestContext.Current.CancellationToken);

Assert.False(bound);
Assert.False(File.Exists(Path.Combine(usbRoot, ".SecRandom.safety.key")));
Expand All @@ -431,7 +429,7 @@ public async Task BindUsbAsync_WhenCredentialSaveFails_RemovesTheWrittenUsbKey()
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
writeFault.ThrowOnWrite = writeFault.WriteCalls + 2;

var bound = await fixture.Service.BindUsbAsync(null!, "volume:save-failure", TestContext.Current.CancellationToken);
var bound = await fixture.Service.BindUsbAsync("volume:save-failure", TestContext.Current.CancellationToken);

Assert.False(bound);
Assert.False(File.Exists(Path.Combine(usbRoot, ".SecRandom.safety.key")));
Expand All @@ -449,7 +447,7 @@ public async Task BindUsbAsync_WhenUsbAlreadyContainsASafetyKey_RejectsAndPreser
new UsbDriveInfo("M:", "Existing key USB", "volume:existing-key", usbRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);

var bound = await fixture.Service.BindUsbAsync(null!, "volume:existing-key", TestContext.Current.CancellationToken);
var bound = await fixture.Service.BindUsbAsync("volume:existing-key", TestContext.Current.CancellationToken);

Assert.False(bound);
Assert.Equal("existing-token", File.ReadAllText(existingKeyPath, System.Text.Encoding.ASCII));
Expand All @@ -466,11 +464,11 @@ public async Task UnbindUsbAsync_WhenCredentialSaveFails_KeepsTheExistingUsbKey(
writeFault,
new UsbDriveInfo("K:", "Unbind persistence USB", "volume:unbind-save-failure", usbRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
Assert.True(await fixture.Service.BindUsbAsync(null!, "volume:unbind-save-failure", TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.BindUsbAsync("volume:unbind-save-failure", TestContext.Current.CancellationToken));
var binding = Assert.Single(await fixture.Service.GetUsbBindingsAsync(TestContext.Current.CancellationToken));
writeFault.ThrowOnWrite = writeFault.WriteCalls + 2;

var unbound = await fixture.Service.UnbindUsbAsync(null!, binding.Id, TestContext.Current.CancellationToken);
var unbound = await fixture.Service.UnbindUsbAsync(binding.Id, TestContext.Current.CancellationToken);

Assert.False(unbound);
Assert.True(File.Exists(Path.Combine(usbRoot, ".SecRandom.safety.key")));
Expand All @@ -484,17 +482,17 @@ public async Task BindUsbAsync_AfterUnbindWhileVolumeIsMissing_ReplacesItsPendin
var drive = new UsbDriveInfo("N:", "Pending marker USB", "volume:pending-marker", usbRoot);
var fixture = CreateFixture(Password("secret1"), drive);
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
Assert.True(await fixture.Service.BindUsbAsync(null!, drive.DeviceId, TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.BindUsbAsync(drive.DeviceId, TestContext.Current.CancellationToken));

var markerPath = Path.Combine(usbRoot, ".SecRandom.safety.key");
var originalToken = File.ReadAllText(markerPath, System.Text.Encoding.ASCII);
var binding = Assert.Single(await fixture.Service.GetUsbBindingsAsync(TestContext.Current.CancellationToken));
fixture.UsbCatalog.SetDevices();

Assert.True(await fixture.Service.UnbindUsbAsync(null!, binding.Id, TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.UnbindUsbAsync(binding.Id, TestContext.Current.CancellationToken));
fixture.UsbCatalog.SetDevices(drive);

Assert.True(await fixture.Service.BindUsbAsync(null!, drive.DeviceId, TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.BindUsbAsync(drive.DeviceId, TestContext.Current.CancellationToken));
Assert.NotEqual(originalToken, File.ReadAllText(markerPath, System.Text.Encoding.ASCII));
Assert.Single(await fixture.Service.GetUsbBindingsAsync(TestContext.Current.CancellationToken));
}
Expand All @@ -509,7 +507,7 @@ public async Task RemovePasswordAsync_WhenCredentialSaveFails_KeepsTheBoundUsbKe
writeFault,
new UsbDriveInfo("L:", "Password removal USB", "volume:remove-password-save-failure", usbRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
Assert.True(await fixture.Service.BindUsbAsync(null!, "volume:remove-password-save-failure", TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.BindUsbAsync("volume:remove-password-save-failure", TestContext.Current.CancellationToken));
writeFault.ThrowOnWrite = writeFault.WriteCalls + 1;

var removed = await fixture.Service.RemovePasswordAsync("secret1", TestContext.Current.CancellationToken);
Expand All @@ -529,7 +527,7 @@ public async Task RemovePasswordAsync_WhenTheBoundKeyChanged_StillDeletesTheExte
Password("secret1"),
new UsbDriveInfo("O:", "Replaced key USB", "volume:remove-password-replaced-key", usbRoot));
await fixture.Service.SetPasswordAsync("secret1", cancellationToken: TestContext.Current.CancellationToken);
Assert.True(await fixture.Service.BindUsbAsync(null!, "volume:remove-password-replaced-key", TestContext.Current.CancellationToken));
Assert.True(await fixture.Service.BindUsbAsync("volume:remove-password-replaced-key", TestContext.Current.CancellationToken));
File.WriteAllText(keyPath, "replacement-token", System.Text.Encoding.ASCII);

var removed = await fixture.Service.RemovePasswordAsync("secret1", TestContext.Current.CancellationToken);
Expand Down Expand Up @@ -599,13 +597,13 @@ private sealed class ScriptedPrompt(SecurityVerificationResponse response) : ISe
{
public List<SecurityVerificationRequest> Requests { get; } = [];

public Task<SecurityVerificationResponse> RequestAsync(
TopLevel xamlRoot,
public Task<SecurityVerificationResult> RequestAsync(
SecurityVerificationRequest request,
Func<SecurityVerificationResponse, CancellationToken, Task<SecurityVerificationResult>> verify,
CancellationToken cancellationToken = default)
{
Requests.Add(request);
return Task.FromResult(response);
return verify(response, cancellationToken);
}
}

Expand Down
2 changes: 1 addition & 1 deletion SecRandom.Core.Tests/SettingsMarkupTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ public void SecuritySettingsUseVerifiedEnablementAndSeparatePasswordCommands()
Assert.Contains("Click=\"ChangePassword_OnClick\"", markup, StringComparison.Ordinal);
Assert.Contains("Click=\"RemovePassword_OnClick\"", markup, StringComparison.Ordinal);
Assert.Contains("UpdateSecuritySettingsAsync", source, StringComparison.Ordinal);
Assert.Contains("BeginTotpSetupAsync(xamlRoot", source, StringComparison.Ordinal);
Assert.Contains("BeginTotpSetupAsync(", source, StringComparison.Ordinal);
Assert.Contains("GetUsbDevicesAsync", source, StringComparison.Ordinal);
}

Expand Down
Loading
Loading