fix(app): no sheet is left busy by an error nothing expected
An instance's salt of another length made the device's cryptography throw an ArgumentError, a wrapping's nonce a RangeError, and libsodium or the keyring may throw their own; none was a refusal, so the step's error escaped and the sheet stayed busy, which can't be closed. halkyon_keys now checks the lengths of what comes from elsewhere before libsodium sees it (KeyRefusal.malformed: a salt, a wrapping, a sealed key's box, a sealed payload), the device's cryptography turns anything else into the general failure, and so does every password cubit, the error logged by its type alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
4914df348e
commit
cf69e62ad4
3 changed files with 318 additions and 32 deletions
|
|
@ -137,10 +137,6 @@ Future<void> main() async {
|
|||
for (final tampered in [
|
||||
WrappedAccountKeys(nonce: wrapped.nonce, cipherText: cipherText),
|
||||
WrappedAccountKeys(nonce: nonce, cipherText: wrapped.cipherText),
|
||||
WrappedAccountKeys(
|
||||
nonce: wrapped.nonce,
|
||||
cipherText: wrapped.cipherText.sublist(1),
|
||||
),
|
||||
]) {
|
||||
expect(
|
||||
() => unwrap(tampered, password.keyEncryptionKey),
|
||||
|
|
@ -177,6 +173,33 @@ Future<void> main() async {
|
|||
refusedFor(KeyRefusal.notTheAccountsKeys),
|
||||
);
|
||||
});
|
||||
|
||||
test('wrappings of other lengths, as an instance might send them, are '
|
||||
'refused rather than thrown on', () {
|
||||
for (final (nonce, cipherText) in [
|
||||
(Uint8List(23), wrapped.cipherText),
|
||||
(Uint8List(25), wrapped.cipherText),
|
||||
(Uint8List(0), Uint8List(0)),
|
||||
(wrapped.nonce, Uint8List(15)),
|
||||
(wrapped.nonce, wrapped.cipherText.sublist(1)),
|
||||
(wrapped.nonce, Uint8List(wrapped.cipherText.length + 1)),
|
||||
]) {
|
||||
expect(
|
||||
() => unwrap(
|
||||
WrappedAccountKeys(nonce: nonce, cipherText: cipherText),
|
||||
password.keyEncryptionKey,
|
||||
),
|
||||
throwsA(
|
||||
isA<KeyRefused>().having(
|
||||
(refused) => refused.reason,
|
||||
'reason',
|
||||
KeyRefusal.malformed,
|
||||
),
|
||||
),
|
||||
reason: '${nonce.length} / ${cipherText.length}',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('wrapped again under the recovery key, the phrase opens them', () {
|
||||
|
|
|
|||
|
|
@ -109,6 +109,24 @@ Future<void> main() async {
|
|||
throwsArgumentError,
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'a sealed key too short to hold a box is refused, never thrown on',
|
||||
() {
|
||||
// Signed by the sealer, so only the box's own length can fail it.
|
||||
final signature = sodium.crypto.sign.detached(
|
||||
message: Uint8List(0),
|
||||
secretKey: account.signing.secretKey,
|
||||
);
|
||||
for (final box in [Uint8List(1), Uint8List(47)]) {
|
||||
expect(
|
||||
() => open(Uint8List.fromList([...signature, ...box])),
|
||||
throwsA(isA<KeyRefused>()),
|
||||
reason: '${box.length}',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('content, sealed and signed', () {
|
||||
|
|
|
|||
|
|
@ -54,17 +54,29 @@ Future<void> main() async {
|
|||
expect(keys.srpPrivateValue, greaterThan(BigInt.zero));
|
||||
});
|
||||
|
||||
test('a salt of another length is refused', () {
|
||||
expect(
|
||||
() => derivePasswordKeys(
|
||||
sodium,
|
||||
password: 'a password',
|
||||
salt: Uint8List(15),
|
||||
kdf: cheap,
|
||||
),
|
||||
throwsArgumentError,
|
||||
);
|
||||
});
|
||||
test(
|
||||
'a salt of another length, as an instance might send one, is refused',
|
||||
() {
|
||||
for (final length in [0, 15, 17]) {
|
||||
expect(
|
||||
() => derivePasswordKeys(
|
||||
sodium,
|
||||
password: 'a password',
|
||||
salt: Uint8List(length),
|
||||
kdf: cheap,
|
||||
),
|
||||
throwsA(
|
||||
isA<KeyRefused>().having(
|
||||
(refused) => refused.reason,
|
||||
'reason',
|
||||
KeyRefusal.malformed,
|
||||
),
|
||||
),
|
||||
reason: '$length',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test('new salts are 16 random bytes', () {
|
||||
final first = newPasswordSalt(sodium);
|
||||
|
|
|
|||
Loading…
Reference in a new issue