Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Sealing to a low-order X25519 peer key raises `SealedChannelError`, consistent
with other invalid channel keys, instead of leaking a backend `ValueError`.
These keys were already refused; no ciphertext format or acceptance rule changes.

### Removed

- The `opaque` attestation provider value. It had no collector and was never
Expand Down
5 changes: 4 additions & 1 deletion src/ca2a_runtime/channel/sealed.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ def seal(self, payload: bytes, *, aad: bytes = b"") -> bytes:
eph_priv = X25519PrivateKey.generate()
eph_pub = eph_priv.public_key().public_bytes_raw()
peer_pub_raw = self._peer_pub.public_bytes_raw()
shared = eph_priv.exchange(self._peer_pub)
try:
shared = eph_priv.exchange(self._peer_pub)
except ValueError as exc:
raise SealedChannelError("invalid peer public key", detail=str(exc)) from exc
key = _derive_key(shared, eph_pub, peer_pub_raw)
nonce = os.urandom(_NONCE_LEN)
ct = ChaCha20Poly1305(key).encrypt(nonce, payload, aad)
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/test_sealed_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,12 @@ def test_malformed_blob_rejected() -> None:
def test_invalid_peer_key_rejected() -> None:
with pytest.raises(SealedChannelError):
SealedChannel("not-hex-zz")


@pytest.mark.parametrize("peer_key", ["00" * 32, "01" + "00" * 31, "ec" + "ff" * 30 + "7f"])
def test_low_order_peer_key_has_channel_error(peer_key: str) -> None:
# These keys parse as X25519 keys but cannot produce a nonzero shared secret.
channel = SealedChannel(peer_key)
with pytest.raises(SealedChannelError, match="invalid peer public key") as caught:
channel.seal(b"secret", aad=b"session-A")
assert isinstance(caught.value.__cause__, ValueError)
Loading