unprotect method
Verifies and decrypts a previously parsed SecureMessage
.
INPUT:
msg
: a format-validated message (seeSecureMessage.fromWire
).
OUTPUT:
- plaintext bytes if authentication and decryption succeed.
Throws:
- InvalidMessageException for version mismatch or window out-of-range.
- AuthenticationFailedException on HMAC mismatch.
- DecryptionFailedException if AES decryption/padding fails.
HINT:
- Always call this on the server for incoming requests and on the client for responses returned by the server.
Implementation
Uint8List unprotect(SecureMessage msg) {
// 1) Protocol version check (extensible: allow only exact match for v1).
if (msg.version != _cfg.protocolVersion) {
throw InvalidMessageException(
cause: ArgumentError.value(
msg.version,
'version',
'Unsupported protocol version; expected ${_cfg.protocolVersion}.',
),
);
}
// 2) Window skew check (reject too old/future messages quickly).
_enforceWindowSkew(msg.window);
// 3) Verify tag in constant time (Encrypt-then-MAC).
final computedTag = TagDeriver.derive(
macKey: _keys.macKey,
window: msg.window,
nonce: msg.nonce,
ciphertext: msg.ciphertext,
);
final ok = Bytes.constantTimeEquals(computedTag, msg.tag);
// Best-effort zeroization (mutable copy).
Bytes.secureZero(computedTag);
if (!ok) {
throw AuthenticationFailedException();
}
// 4) Derive IV and decrypt.
final iv = IvDeriver.derive(
macKey: _keys.macKey,
window: msg.window,
nonce: msg.nonce,
);
// If AES fails (e.g., bad padding), OtpCipher throws DecryptionFailedException.
final plaintext = OtpCipher.decrypt(
encKey: _keys.encKey,
iv: iv,
ciphertext: msg.ciphertext,
);
return plaintext;
}