diff --git a/quantumpipe/chab3.py b/quantumpipe/chab3.py index 920a042..462b031 100644 --- a/quantumpipe/chab3.py +++ b/quantumpipe/chab3.py @@ -18,6 +18,12 @@ NONCE_LEN = 12 _MAC_KEY_BLOCK = 64 +class WireError(ValueError): + """Failure an unauthenticated peer can cause with crafted packets: + truncated ciphertext, bad authentication tag. Distinct from TypeError, + which signals caller misuse (wrong buffer sizes, wrong key length).""" + + def nonce_increment(nonce: Buffer) -> None: """Increment the nonce in place, little-endian.""" n = memoryview(nonce).cast("B") @@ -33,11 +39,11 @@ def _session(key: Buffer, nonce: Buffer, ad: Buffer | None, maclen: int): key = memoryview(key).cast("B") nonce = memoryview(nonce).cast("B") if len(key) != KEY_LEN: - raise ValueError("key must be 32 bytes") + raise TypeError("key must be 32 bytes") if len(nonce) != NONCE_LEN: - raise ValueError("nonce must be 12 bytes") + raise TypeError("nonce must be 12 bytes") if not 0 <= maclen <= 32: - raise ValueError("maclen must be in 0..32") + raise TypeError("maclen must be in 0..32") # IETF layout: 32-bit little-endian block counter || 96-bit nonce stream = Cipher(ChaCha20(key, bytes(4) + nonce), mode=None).encryptor() if maclen == 0: @@ -98,10 +104,10 @@ def decrypt_into( ct = memoryview(ct).cast("B") if nonce is None: if len(ct) < NONCE_LEN + maclen: - raise ValueError("ciphertext buffer too short") + raise WireError("ciphertext buffer too short") nonce, ct = ct[:NONCE_LEN], ct[NONCE_LEN:] elif len(ct) < maclen: - raise ValueError("ciphertext buffer too short") + raise WireError("ciphertext buffer too short") used = len(ct) - maclen mv = memoryview(out).cast("B") if len(mv) < used: @@ -111,7 +117,7 @@ def decrypt_into( if mac is not None: mac.update(ct[:used]) if not hmac.compare_digest(bytes(ct[used:]), _tag(mac, used, maclen)): - raise ValueError("invalid authentication tag") + raise WireError("invalid authentication tag") stream.update_into(ct[:used], out) return out @@ -143,7 +149,7 @@ def decrypt( ct = memoryview(ct).cast("B") hdr = NONCE_LEN if nonce is None else 0 if len(ct) < hdr + maclen: - raise ValueError("ciphertext buffer too short") + raise WireError("ciphertext buffer too short") out = bytearray(len(ct) - hdr - maclen) decrypt_into(out, ct, key, nonce, ad=ad, maclen=maclen) return out @@ -153,7 +159,7 @@ class _Sequence: def __init__(self, key: Buffer, *, maclen: int = 16): key = memoryview(key).cast("B") if len(key) != KEY_LEN: - raise ValueError("key must be 32 bytes") + raise TypeError("key must be 32 bytes") self._key = key self._maclen = maclen self._nonce = bytearray(NONCE_LEN) @@ -191,7 +197,7 @@ class Receiver(_Sequence): def decrypt(self, ct: Buffer, *, ad: Buffer | None = None) -> bytearray: ct = memoryview(ct).cast("B") if len(ct) < self._maclen: - raise ValueError("ciphertext buffer too short") + raise WireError("ciphertext buffer too short") out = bytearray(len(ct) - self._maclen) self.decrypt_into(out, ct, ad=ad) return out