fix(app): derive an address private key only when revealed
Address rows and the address detail sheet no longer carry the WIF of every listed address for the whole session. The key is derived after the password gate and dropped when hidden, when the sheet closes or when the wallet closes. See ADR 0037.
This commit is contained in:
1 parent
8bcee27c23
commit
06d1f59c2b
8 files changed
+119
-23
No files matched your search
@@ -70,11 +70,20 @@ public sealed class MainWindowViewModelSecurityTests : VmTestBase
|
||||
|
||||
var row = vm.Addresses.First(a => !a.IsChange && a.Indice == 0);
|
||||
Assert.True(row.HasPrivKey);
|
||||
var wif = account.GetPrivateKey(false, 0)!.GetWif(PalladiumNetworks.For(NetKind.Mainnet)).ToString();
|
||||
|
||||
// No row holds a private key before the reveal (reflection over every string property).
|
||||
Assert.All(vm.Addresses, r => Assert.DoesNotContain(wif, StringValues(r)));
|
||||
Assert.DoesNotContain(typeof(AddressRow).GetProperties(), p => p.Name.Contains("PrivKey") && p.PropertyType == typeof(string));
|
||||
|
||||
vm.ShowAddressInfo(row);
|
||||
Assert.DoesNotContain(wif, StringValues(vm.AddressInfo!));
|
||||
Assert.Equal("", vm.RevealedPrivKey);
|
||||
|
||||
vm.RequestPrivKeyRevealCommand.Execute(null);
|
||||
Assert.True(vm.IsPrivKeyPromptOpen);
|
||||
Assert.False(vm.IsAddressPrivKeyRevealed);
|
||||
Assert.Equal("", vm.RevealedPrivKey);
|
||||
|
||||
vm.PrivKeyPromptPassword = "wrong";
|
||||
vm.ConfirmPrivKeyPasswordCommand.Execute(null);
|
||||
@@ -85,12 +94,48 @@ public sealed class MainWindowViewModelSecurityTests : VmTestBase
|
||||
vm.ConfirmPrivKeyPasswordCommand.Execute(null);
|
||||
Assert.True(vm.IsAddressPrivKeyRevealed);
|
||||
Assert.False(vm.IsPrivKeyPromptOpen);
|
||||
Assert.Equal(wif, vm.RevealedPrivKey);
|
||||
Assert.DoesNotContain(wif, StringValues(vm.AddressInfo!)); // still not stored in the info
|
||||
Assert.All(vm.Addresses, r => Assert.DoesNotContain(wif, StringValues(r)));
|
||||
|
||||
vm.HideAddressPrivKeyCommand.Execute(null); // hiding drops the key
|
||||
Assert.False(vm.IsAddressPrivKeyRevealed);
|
||||
Assert.Equal("", vm.RevealedPrivKey);
|
||||
|
||||
vm.RequestPrivKeyRevealCommand.Execute(null); // reveal again, then close the sheet
|
||||
vm.PrivKeyPromptPassword = "pw";
|
||||
vm.ConfirmPrivKeyPasswordCommand.Execute(null);
|
||||
Assert.Equal(wif, vm.RevealedPrivKey);
|
||||
vm.CloseAddressInfoCommand.Execute(null); // closing the sheet re-hides everything
|
||||
Assert.Equal("", vm.RevealedPrivKey);
|
||||
vm.ShowAddressInfo(row);
|
||||
Assert.False(vm.IsAddressPrivKeyRevealed);
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public Task ClosingTheWallet_DropsARevealedPrivateKey() => UiSession.Run(async () =>
|
||||
{
|
||||
var account = HdAccount.FromMnemonic(ParseMnemonic(), null, ScriptKind.NativeSegwit, ChainProfiles.Mainnet);
|
||||
WireFundedChain(account.GetReceiveAddress(0), 1_000_000);
|
||||
var vm = new MainWindowViewModel();
|
||||
RestoreFlow(vm, Mnemonic, null, "pk2");
|
||||
await Eventually(() => !vm.IsSyncing && vm.Addresses.Count > 0);
|
||||
|
||||
vm.ShowAddressInfo(vm.Addresses.First(a => !a.IsChange && a.Indice == 0));
|
||||
vm.RequestPrivKeyRevealCommand.Execute(null); // no password: revealed directly
|
||||
Assert.NotEqual("", vm.RevealedPrivKey);
|
||||
|
||||
vm.CloseWalletCommand.Execute(null);
|
||||
Assert.Equal("", vm.RevealedPrivKey);
|
||||
Assert.False(vm.IsAddressPrivKeyRevealed);
|
||||
Assert.Null(vm.AddressInfo);
|
||||
});
|
||||
|
||||
private static IEnumerable<string> StringValues(object o) =>
|
||||
o.GetType().GetProperties()
|
||||
.Where(p => p.PropertyType == typeof(string) && p.GetIndexParameters().Length == 0)
|
||||
.Select(p => (string?)p.GetValue(o) ?? "");
|
||||
|
||||
[Fact]
|
||||
public Task WatchOnlyAddressRows_NeverCarryAPrivateKey() => UiSession.Run(async () =>
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user