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:
Tizian 「ティツィアーン」 2026-10-21 14:41:28 +02:00
commit cf69e62ad4
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -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);