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
5 changed files with 318 additions and 32 deletions
|
|
@ -157,6 +157,16 @@ AccountKeys unwrapAccountKeys(
|
|||
signingPublicKey.length != sodium.crypto.sign.publicKeyBytes) {
|
||||
throw const KeyRefused(KeyRefusal.notTheAccountsKeys);
|
||||
}
|
||||
// Checked before libsodium sees them: of another length it throws a
|
||||
// RangeError, which nothing above expects.
|
||||
final aead = sodium.crypto.aeadXChaCha20Poly1305IETF;
|
||||
if (wrapped.nonce.length != aead.nonceBytes ||
|
||||
wrapped.cipherText.length !=
|
||||
sodium.crypto.box.secretKeyBytes +
|
||||
sodium.crypto.sign.secretKeyBytes +
|
||||
aead.aBytes) {
|
||||
throw const KeyRefused(KeyRefusal.malformed);
|
||||
}
|
||||
final Uint8List plainText;
|
||||
try {
|
||||
plainText = sodium.crypto.aeadXChaCha20Poly1305IETF.decrypt(
|
||||
|
|
|
|||
|
|
@ -88,6 +88,11 @@ DataKey openDataKey(
|
|||
throw const KeyRefused(KeyRefusal.forgedSignature);
|
||||
}
|
||||
final box = Uint8List.sublistView(sealed, signatureBytes);
|
||||
// Of a box's least length or longer, as libsodium throws a RangeError for
|
||||
// shorter ones, which nothing above expects.
|
||||
if (box.length < sodium.crypto.box.sealBytes) {
|
||||
throw const KeyRefused(KeyRefusal.malformed);
|
||||
}
|
||||
if (!sodium.crypto.sign.verifyDetached(
|
||||
message: _signed(keys.encryptionPublicKey, box),
|
||||
signature: Uint8List.sublistView(sealed, 0, signatureBytes),
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ enum KeyRefusal() {
|
|||
/// isn't the author's.
|
||||
forgedSignature,
|
||||
|
||||
/// Bytes from elsewhere (an instance's salt, a wrapping, a sealed key)
|
||||
/// aren't of the length they must have: refused before anything reads
|
||||
/// them, as a device must never trust what an instance sends.
|
||||
malformed,
|
||||
|
||||
/// Argon2id's costs named for an account are below what any account may
|
||||
/// have, or of another algorithm.
|
||||
weakPasswordKdf,
|
||||
|
|
|
|||
|
|
@ -129,11 +129,7 @@ PasswordKeys derivePasswordKeys(
|
|||
PasswordKdf kdf = PasswordKdf.current,
|
||||
}) {
|
||||
if (salt.length != sodium.crypto.pwhash.saltBytes) {
|
||||
throw ArgumentError.value(
|
||||
salt.length,
|
||||
'salt',
|
||||
'must be ${sodium.crypto.pwhash.saltBytes} bytes',
|
||||
);
|
||||
throw const KeyRefused(KeyRefusal.malformed);
|
||||
}
|
||||
final masterKey = sodium.crypto.pwhash.callStr(
|
||||
outLen: _keyBytes,
|
||||
|
|
|
|||
|
|
@ -86,7 +86,8 @@ Uint8List openContent(
|
|||
throw const KeyRefused(KeyRefusal.forgedSignature);
|
||||
}
|
||||
final aead = sodium.crypto.aeadXChaCha20Poly1305IETF;
|
||||
if (sealed.nonce.length != aead.nonceBytes) {
|
||||
if (sealed.nonce.length != aead.nonceBytes ||
|
||||
sealed.payload.length < aead.aBytes) {
|
||||
throw const KeyRefused(KeyRefusal.wrongKeyOrTampered);
|
||||
}
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue