fix(keys): no copy of a key left on the way, and the README says what is wiped
Wrapping the account's keys and sealing a data key spread extractBytes() copies into the buffer libsodium encrypts: the buffer was wiped, the copies on the way weren't. The keys now go from guarded memory straight into it (runUnlockedSync), and the seed derived to check a signing key is disposed. The README said key bytes copied to the Dart heap were wiped afterwards; it now says exactly which are, and which Dart can't wipe (x as a BigInt, Strings, what isolates copy, the keyring's base64). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
67b26e0ea0
commit
3ca01915e2
2 changed files with 39 additions and 18 deletions
|
|
@ -50,9 +50,9 @@ final class AccountKeys._(
|
|||
signingSecretKey.length - sodium.crypto.sign.publicKeyBytes,
|
||||
),
|
||||
);
|
||||
final derived = sodium.crypto.sign.seedKeyPair(
|
||||
sodium.crypto.sign.skToSeed(signingSecret),
|
||||
);
|
||||
final seed = sodium.crypto.sign.skToSeed(signingSecret);
|
||||
final derived = sodium.crypto.sign.seedKeyPair(seed);
|
||||
seed.dispose();
|
||||
final matches = sodium.memcmp(derived.publicKey, signingPublic);
|
||||
derived.secretKey.dispose();
|
||||
if (!matches) {
|
||||
|
|
@ -120,11 +120,18 @@ WrappedAccountKeys wrapAccountKeys(
|
|||
final aead = sodium.crypto.aeadXChaCha20Poly1305IETF;
|
||||
final nonce = sodium.randombytes.buf(aead.nonceBytes);
|
||||
// libsodium encrypts a plain buffer: the private keys are copied out of
|
||||
// guarded memory for this moment, and the copy wiped right after.
|
||||
final plainText = Uint8List.fromList([
|
||||
...keys.encryption.secretKey.extractBytes(),
|
||||
...keys.signing.secretKey.extractBytes(),
|
||||
]);
|
||||
// guarded memory straight into it for this moment (no copy of each on
|
||||
// the way), and it is wiped right after.
|
||||
final encryptionBytes = sodium.crypto.box.secretKeyBytes;
|
||||
final plainText = Uint8List(
|
||||
encryptionBytes + sodium.crypto.sign.secretKeyBytes,
|
||||
);
|
||||
keys.encryption.secretKey.runUnlockedSync(
|
||||
(bytes) => plainText.setAll(0, bytes),
|
||||
);
|
||||
keys.signing.secretKey.runUnlockedSync(
|
||||
(bytes) => plainText.setAll(encryptionBytes, bytes),
|
||||
);
|
||||
try {
|
||||
return WrappedAccountKeys(
|
||||
nonce: nonce,
|
||||
|
|
|
|||
|
|
@ -49,12 +49,13 @@ Uint8List sealDataKey(
|
|||
}) {
|
||||
final id = utf8.encode(dataKey.keyId);
|
||||
// As account keys are wrapped, the key leaves guarded memory for this
|
||||
// moment only.
|
||||
final message = Uint8List.fromList([
|
||||
id.length,
|
||||
...id,
|
||||
...dataKey.key.extractBytes(),
|
||||
]);
|
||||
// moment only, straight into the message (no copy of it on the way).
|
||||
final message = Uint8List(1 + id.length + dataKey.key.length)
|
||||
..[0] = id.length
|
||||
..setAll(1, id);
|
||||
dataKey.key.runUnlockedSync(
|
||||
(bytes) => message.setAll(1 + id.length, bytes),
|
||||
);
|
||||
final Uint8List box;
|
||||
try {
|
||||
box = sodium.crypto.box.seal(message: message, publicKey: to);
|
||||
|
|
|
|||
Loading…
Reference in a new issue