Flip burn policy: remove _Burn, burn on any non-WireError failure
The burn decision now lives solely in the Session dispatch: chab3's WireError (unauthentic or truncated box) proves no key knowledge and burns nothing; any other ValueError/struct.error means a peer who opened the box misbehaved, so the credential burns. handshake.py drops its try/except wrappers and the _check helper; inner-box WireErrors are reclassified to plain ValueError since they occur inside an already valid outer box.
This commit is contained in:
1 parent
326af72a25
commit
2f8cafaa6d
5 files changed
+106
-123
No files matched your search
+11
-6
@@ -105,12 +105,17 @@ transports don't reimplement it.
|
|||||||
Everything attributable to the peer, the wire, or protocol-level misuse
|
Everything attributable to the peer, the wire, or protocol-level misuse
|
||||||
surfaces as `ProtocolError`; the application action is always the
|
surfaces as `ProtocolError`; the application action is always the
|
||||||
same -- log and disconnect. The conversion happens at one point, the
|
same -- log and disconnect. The conversion happens at one point, the
|
||||||
Session dispatch; the burn policy is already decided at the internal
|
Session dispatch, which also owns the burn policy: chab3's WireError
|
||||||
raise sites (handshake.py raises `_Burn` for invalid data inside a valid
|
(unauthentic or truncated box) proves no key knowledge and burns
|
||||||
box; chab3's ValueError means an unauthentic box) before anything
|
nothing; any other ValueError/struct.error means invalid data inside a
|
||||||
escapes. Genuine bugs (TypeError, AttributeError) propagate untouched;
|
validly opened box -- the peer knew the keys and misbehaved, so the
|
||||||
chab3 raises ValueError for bad wire data and TypeError only for program
|
credential burns. handshake.py therefore raises no special types; its
|
||||||
bugs (too-small output buffers). A corrupt LOCAL store entry raises
|
one exception is the inner boxes, whose WireError it reclassifies to a
|
||||||
|
plain ValueError because an inner box sits inside an already valid
|
||||||
|
outer one. Genuine bugs (TypeError, AttributeError) propagate
|
||||||
|
untouched; chab3 raises WireError (a ValueError) for bad wire data and
|
||||||
|
TypeError only for program bugs (too-small output buffers). A corrupt
|
||||||
|
LOCAL store entry raises
|
||||||
KeyError/struct.error from the constructor -- the caller's own data
|
KeyError/struct.error from the constructor -- the caller's own data
|
||||||
problem.
|
problem.
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,3 @@ applications can import it without the sans-io machinery."""
|
|||||||
|
|
||||||
class ProtocolError(Exception):
|
class ProtocolError(Exception):
|
||||||
"""Peer, wire, or protocol-misuse failure: log and disconnect."""
|
"""Peer, wire, or protocol-misuse failure: log and disconnect."""
|
||||||
|
|
||||||
|
|
||||||
class _Burn(Exception):
|
|
||||||
# internal handshake -> session signal: the credential must burn
|
|
||||||
pass
|
|
||||||
+83
-101
@@ -3,6 +3,12 @@ symmetric shared key, chab3-boxed throughout. Pure cryptography: a
|
|||||||
Registration or Resumption object holds one exchange's per-connection
|
Registration or Resumption object holds one exchange's per-connection
|
||||||
state, disposed by the caller at completion; sansio.py owns the state
|
state, disposed by the caller at completion; sansio.py owns the state
|
||||||
machine, key store and policies. See docs/protocol.md.
|
machine, key store and policies. See docs/protocol.md.
|
||||||
|
|
||||||
|
Error contract: chab3's WireError (unauthentic or truncated box)
|
||||||
|
escapes untouched -- it proves no key knowledge. Everything else
|
||||||
|
(struct.error, ValueError) means invalid data inside a validly opened
|
||||||
|
box: the peer knew the keys and misbehaved. sansio.py burns the
|
||||||
|
credential on anything that is not a WireError.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -19,8 +25,7 @@ from cryptography.hazmat.primitives.asymmetric.x25519 import (
|
|||||||
X25519PublicKey,
|
X25519PublicKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .chab3 import decrypt, encrypt, random
|
from .chab3 import WireError, decrypt, encrypt, random
|
||||||
from .exceptions import _Burn
|
|
||||||
from .kdf import kdf
|
from .kdf import kdf
|
||||||
|
|
||||||
# -- Fixed-size record layouts (struct handles packing + length checks) -----
|
# -- Fixed-size record layouts (struct handles packing + length checks) -----
|
||||||
@@ -73,17 +78,22 @@ def _traffic_keys(key: bytes, initiator: bool) -> tuple[bytes, bytes]:
|
|||||||
return (a2b, b2a) if initiator else (b2a, a2b)
|
return (a2b, b2a) if initiator else (b2a, a2b)
|
||||||
|
|
||||||
|
|
||||||
def _check(ok: bool, why: str) -> None:
|
|
||||||
if not ok:
|
|
||||||
raise _Burn(why)
|
|
||||||
|
|
||||||
|
|
||||||
def open_hello(msg1: bytes, *, shared: bytes, ad: bytes, flow: str) -> bytes:
|
def open_hello(msg1: bytes, *, shared: bytes, ad: bytes, flow: str) -> bytes:
|
||||||
# Open a msg1 box under a candidate credential's shared key (the
|
# Open a msg1 box under a candidate credential's shared key (the
|
||||||
# responder's caller scan); ValueError = not this credential
|
# responder's caller scan); WireError = not this credential
|
||||||
return decrypt(msg1, key=kdf(flow + "/msg1", shared), ad=ad)
|
return decrypt(msg1, key=kdf(flow + "/msg1", shared), ad=ad)
|
||||||
|
|
||||||
|
|
||||||
|
def _open_inner(ct: bytes, *, key: bytes) -> bytes:
|
||||||
|
# An inner box sits inside an already valid outer box: a tag failure
|
||||||
|
# there came from a peer who proved key knowledge, so it is not a
|
||||||
|
# wire error -- reclassify to keep the burn decision in sansio
|
||||||
|
try:
|
||||||
|
return decrypt(ct, key=key)
|
||||||
|
except WireError as e:
|
||||||
|
raise ValueError("inner box authentication failed") from e
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# AD-chained boxes: each box's associated data is the previous box's tag
|
# AD-chained boxes: each box's associated data is the previous box's tag
|
||||||
|
|
||||||
@@ -148,20 +158,17 @@ class Registration:
|
|||||||
# Open Bob's reply -- an outer AEAD failure burns nothing, it
|
# Open Bob's reply -- an outer AEAD failure burns nothing, it
|
||||||
# proves no key knowledge -- then challenge his static
|
# proves no key knowledge -- then challenge his static
|
||||||
plain = self._chain.decrypt(msg2, key=kdf("reg/msg2", self._shared))
|
plain = self._chain.decrypt(msg2, key=kdf("reg/msg2", self._shared))
|
||||||
try:
|
e_bob, ee_ct, inner = REG_MSG2.unpack(plain)
|
||||||
e_bob, ee_ct, inner = REG_MSG2.unpack(plain)
|
# ee is forward secrecy, made to our connection key only
|
||||||
# ee is forward secrecy, made to our connection key only
|
ee = self._eph_m.decapsulate(ee_ct)
|
||||||
ee = self._eph_m.decapsulate(ee_ct)
|
ee += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(e_bob))
|
||||||
ee += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(e_bob))
|
# Solving ee lets us read Bob's static from the inner box
|
||||||
# Solving ee lets us read Bob's static from the inner box
|
s_bob_m, s_bob_x = REG_MSG2_INNER.unpack(
|
||||||
s_bob_m, s_bob_x = REG_MSG2_INNER.unpack(
|
_open_inner(inner, key=kdf("reg/msg2/inner", ee))
|
||||||
decrypt(inner, key=kdf("reg/msg2/inner", ee))
|
)
|
||||||
)
|
# We challenge his static: only the real Bob can solve this
|
||||||
# We challenge his static: only the real Bob can solve this
|
es, es_ct = MLKEM768PublicKey.from_public_bytes(s_bob_m).encapsulate()
|
||||||
es, es_ct = MLKEM768PublicKey.from_public_bytes(s_bob_m).encapsulate()
|
es += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(s_bob_x))
|
||||||
es += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(s_bob_x))
|
|
||||||
except (struct.error, ValueError) as e:
|
|
||||||
raise _Burn(f"bad msg2 contents ({e})") from None
|
|
||||||
self._peer_pub = (s_bob_m, s_bob_x)
|
self._peer_pub = (s_bob_m, s_bob_x)
|
||||||
self._e_bob = e_bob
|
self._e_bob = e_bob
|
||||||
self._ee = ee
|
self._ee = ee
|
||||||
@@ -179,18 +186,13 @@ class Registration:
|
|||||||
# Bob's challenge to our static plus his root confirmation. The
|
# Bob's challenge to our static plus his root confirmation. The
|
||||||
# caller gates the answer (msg5) on the out-of-band comparison.
|
# caller gates the answer (msg5) on the out-of-band comparison.
|
||||||
plain = self._chain.decrypt(msg4, key=kdf("reg/msg4", self._shared, self._ee))
|
plain = self._chain.decrypt(msg4, key=kdf("reg/msg4", self._shared, self._ee))
|
||||||
try:
|
se_ct, tb = REG_MSG4.unpack(plain)
|
||||||
se_ct, tb = REG_MSG4.unpack(plain)
|
# Solving se proves to Bob that we hold Alice's static
|
||||||
# Solving se proves to Bob that we hold Alice's static
|
se = self._mlkem.decapsulate(se_ct)
|
||||||
se = self._mlkem.decapsulate(se_ct)
|
se += self._x25519.exchange(X25519PublicKey.from_public_bytes(self._e_bob))
|
||||||
se += self._x25519.exchange(X25519PublicKey.from_public_bytes(self._e_bob))
|
|
||||||
except (struct.error, ValueError) as e:
|
|
||||||
raise _Burn(f"bad msg4 contents ({e})") from None
|
|
||||||
self._se = se
|
self._se = se
|
||||||
_check(
|
if not hmac.compare_digest(tb, kdf("reg/confirm-b", self._root())):
|
||||||
hmac.compare_digest(tb, kdf("reg/confirm-b", self._root())),
|
raise ValueError("responder tag mismatch")
|
||||||
"responder tag mismatch",
|
|
||||||
)
|
|
||||||
sas = kdf("sas", self._shared, self._ee, self._reveal, length=4)
|
sas = kdf("sas", self._shared, self._ee, self._reveal, length=4)
|
||||||
msg5 = self._chain.encrypt(
|
msg5 = self._chain.encrypt(
|
||||||
kdf("reg/confirm-a", self._root()),
|
kdf("reg/confirm-a", self._root()),
|
||||||
@@ -204,15 +206,12 @@ class Registration:
|
|||||||
# msg2 from an opened msg1 (open_hello did the caller scan); our
|
# msg2 from an opened msg1 (open_hello did the caller scan); our
|
||||||
# connection key ships here once, Alice answers every leg with it
|
# connection key ships here once, Alice answers every leg with it
|
||||||
self._chain.advance(msg1)
|
self._chain.advance(msg1)
|
||||||
try:
|
e_alice_m, e_alice_x, commitment = REG_MSG1.unpack(opened)
|
||||||
e_alice_m, e_alice_x, commitment = REG_MSG1.unpack(opened)
|
self._ephemeral = X25519PrivateKey.generate()
|
||||||
self._ephemeral = X25519PrivateKey.generate()
|
e_bob = self._ephemeral.public_key().public_bytes_raw()
|
||||||
e_bob = self._ephemeral.public_key().public_bytes_raw()
|
# Forward secrecy, made to Alice's connection key only
|
||||||
# Forward secrecy, made to Alice's connection key only
|
ee, ee_ct = MLKEM768PublicKey.from_public_bytes(e_alice_m).encapsulate()
|
||||||
ee, ee_ct = MLKEM768PublicKey.from_public_bytes(e_alice_m).encapsulate()
|
ee += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(e_alice_x))
|
||||||
ee += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(e_alice_x))
|
|
||||||
except (struct.error, ValueError) as e:
|
|
||||||
raise _Burn(f"bad msg1 contents ({e})") from None
|
|
||||||
self._commitment = (e_alice_m, e_alice_x, commitment)
|
self._commitment = (e_alice_m, e_alice_x, commitment)
|
||||||
body = REG_MSG2.pack(
|
body = REG_MSG2.pack(
|
||||||
e_bob,
|
e_bob,
|
||||||
@@ -229,27 +228,22 @@ class Registration:
|
|||||||
# Alice's challenge to our static, her static and her reveal. The
|
# Alice's challenge to our static, her static and her reveal. The
|
||||||
# caller gates the answer (msg4) on the out-of-band comparison.
|
# caller gates the answer (msg4) on the out-of-band comparison.
|
||||||
plain = self._chain.decrypt(msg3, key=kdf("reg/msg3", self._shared, self._ee))
|
plain = self._chain.decrypt(msg3, key=kdf("reg/msg3", self._shared, self._ee))
|
||||||
try:
|
es_ct, inner = REG_MSG3.unpack(plain)
|
||||||
es_ct, inner = REG_MSG3.unpack(plain)
|
e_alice_m, e_alice_x, commitment = self._commitment
|
||||||
e_alice_m, e_alice_x, commitment = self._commitment
|
# Solving es proves to Alice that the static key they encapsulated to was ours
|
||||||
# Solving es proves to Alice that the static key they encapsulated to was ours
|
es = self._mlkem.decapsulate(es_ct)
|
||||||
es = self._mlkem.decapsulate(es_ct)
|
es += self._x25519.exchange(X25519PublicKey.from_public_bytes(e_alice_x))
|
||||||
es += self._x25519.exchange(X25519PublicKey.from_public_bytes(e_alice_x))
|
# This enables us to get Alice's static keys and reveal
|
||||||
# This enables us to get Alice's static keys and reveal
|
s_alice_m, s_alice_x, reveal = REG_MSG3_INNER.unpack(
|
||||||
s_alice_m, s_alice_x, reveal = REG_MSG3_INNER.unpack(
|
_open_inner(inner, key=kdf("reg/msg3/inner", es))
|
||||||
decrypt(inner, key=kdf("reg/msg3/inner", es))
|
|
||||||
)
|
|
||||||
# We send Alice the same test, verifying they have s_alice secrets
|
|
||||||
se, se_ct = MLKEM768PublicKey.from_public_bytes(s_alice_m).encapsulate()
|
|
||||||
se += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(s_alice_x))
|
|
||||||
except (struct.error, ValueError) as e:
|
|
||||||
raise _Burn(f"bad msg3 contents ({e})") from None
|
|
||||||
_check(
|
|
||||||
hmac.compare_digest(
|
|
||||||
commitment, kdf("commit", e_alice_m, e_alice_x, reveal)
|
|
||||||
),
|
|
||||||
"commitment reveal mismatch",
|
|
||||||
)
|
)
|
||||||
|
# We send Alice the same test, verifying they have s_alice secrets
|
||||||
|
se, se_ct = MLKEM768PublicKey.from_public_bytes(s_alice_m).encapsulate()
|
||||||
|
se += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(s_alice_x))
|
||||||
|
if not hmac.compare_digest(
|
||||||
|
commitment, kdf("commit", e_alice_m, e_alice_x, reveal)
|
||||||
|
):
|
||||||
|
raise ValueError("commitment reveal mismatch")
|
||||||
self._reveal = reveal
|
self._reveal = reveal
|
||||||
self._peer_pub = (s_alice_m, s_alice_x)
|
self._peer_pub = (s_alice_m, s_alice_x)
|
||||||
self._es = es
|
self._es = es
|
||||||
@@ -267,12 +261,10 @@ class Registration:
|
|||||||
msg5, key=kdf("reg/msg5", self._shared, self._ee, self._es)
|
msg5, key=kdf("reg/msg5", self._shared, self._ee, self._es)
|
||||||
)
|
)
|
||||||
if len(plain) != 32:
|
if len(plain) != 32:
|
||||||
raise _Burn("bad msg5 contents (expected a 32-byte tag)")
|
raise ValueError("bad msg5 contents (expected a 32-byte tag)")
|
||||||
ta = plain
|
ta = plain
|
||||||
_check(
|
if not hmac.compare_digest(ta, kdf("reg/confirm-a", self._root())):
|
||||||
hmac.compare_digest(ta, kdf("reg/confirm-a", self._root())),
|
raise ValueError("initiator tag mismatch")
|
||||||
"initiator tag mismatch",
|
|
||||||
)
|
|
||||||
|
|
||||||
# -- completion -------------------------------------------------------------
|
# -- completion -------------------------------------------------------------
|
||||||
|
|
||||||
@@ -313,20 +305,15 @@ class Resumption:
|
|||||||
def respondA(self, msg2: bytes) -> bytes:
|
def respondA(self, msg2: bytes) -> bytes:
|
||||||
# Open Bob's reply and solve his three challenges
|
# Open Bob's reply and solve his three challenges
|
||||||
plain = self._chain.decrypt(msg2, key=kdf("res/msg2", self._shared, self._sb))
|
plain = self._chain.decrypt(msg2, key=kdf("res/msg2", self._shared, self._sb))
|
||||||
try:
|
e_bob, sa_ct, ee_ct, tb = RES_MSG2.unpack(plain)
|
||||||
e_bob, sa_ct, ee_ct, tb = RES_MSG2.unpack(plain)
|
# Solving sa proves to Bob that we hold Alice's static
|
||||||
# Solving sa proves to Bob that we hold Alice's static
|
sa = self._mlkem.decapsulate(sa_ct)
|
||||||
sa = self._mlkem.decapsulate(sa_ct)
|
sa += self._x25519.exchange(X25519PublicKey.from_public_bytes(e_bob))
|
||||||
sa += self._x25519.exchange(X25519PublicKey.from_public_bytes(e_bob))
|
# ee is forward secrecy, made to our connection key
|
||||||
# ee is forward secrecy, made to our connection key
|
ee = self._eph_m.decapsulate(ee_ct)
|
||||||
ee = self._eph_m.decapsulate(ee_ct)
|
ee += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(e_bob))
|
||||||
ee += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(e_bob))
|
if not hmac.compare_digest(tb, kdf("res/tag-b", self._sb, sa, ee)):
|
||||||
except (struct.error, ValueError) as e:
|
raise ValueError("responder tag mismatch")
|
||||||
raise _Burn(f"bad msg2 contents ({e})") from None
|
|
||||||
_check(
|
|
||||||
hmac.compare_digest(tb, kdf("res/tag-b", self._sb, sa, ee)),
|
|
||||||
"responder tag mismatch",
|
|
||||||
)
|
|
||||||
self._sa = sa
|
self._sa = sa
|
||||||
self._ee = ee
|
self._ee = ee
|
||||||
return self._chain.encrypt(
|
return self._chain.encrypt(
|
||||||
@@ -339,22 +326,19 @@ class Resumption:
|
|||||||
def respondB(self, msg1: bytes, opened: bytes) -> bytes:
|
def respondB(self, msg1: bytes, opened: bytes) -> bytes:
|
||||||
# Prove our static, challenge Alice's, forward secrecy
|
# Prove our static, challenge Alice's, forward secrecy
|
||||||
self._chain.advance(msg1)
|
self._chain.advance(msg1)
|
||||||
try:
|
e_alice_m, e_alice_x, sb_ct = RES_MSG1.unpack(opened)
|
||||||
e_alice_m, e_alice_x, sb_ct = RES_MSG1.unpack(opened)
|
# Solving sb proves to Alice that we hold Bob's static
|
||||||
# Solving sb proves to Alice that we hold Bob's static
|
sb = self._mlkem.decapsulate(sb_ct)
|
||||||
sb = self._mlkem.decapsulate(sb_ct)
|
sb += self._x25519.exchange(X25519PublicKey.from_public_bytes(e_alice_x))
|
||||||
sb += self._x25519.exchange(X25519PublicKey.from_public_bytes(e_alice_x))
|
self._ephemeral = X25519PrivateKey.generate()
|
||||||
self._ephemeral = X25519PrivateKey.generate()
|
e_bob = self._ephemeral.public_key().public_bytes_raw()
|
||||||
e_bob = self._ephemeral.public_key().public_bytes_raw()
|
peer_m, peer_x = self._peer_pub
|
||||||
peer_m, peer_x = self._peer_pub
|
# We challenge Alice's static the same way
|
||||||
# We challenge Alice's static the same way
|
sa, sa_ct = MLKEM768PublicKey.from_public_bytes(peer_m).encapsulate()
|
||||||
sa, sa_ct = MLKEM768PublicKey.from_public_bytes(peer_m).encapsulate()
|
sa += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(peer_x))
|
||||||
sa += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(peer_x))
|
# And make forward secrecy to her connection key
|
||||||
# And make forward secrecy to her connection key
|
ee, ee_ct = MLKEM768PublicKey.from_public_bytes(e_alice_m).encapsulate()
|
||||||
ee, ee_ct = MLKEM768PublicKey.from_public_bytes(e_alice_m).encapsulate()
|
ee += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(e_alice_x))
|
||||||
ee += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(e_alice_x))
|
|
||||||
except (struct.error, ValueError) as e:
|
|
||||||
raise _Burn(f"bad msg1 contents ({e})") from None
|
|
||||||
self._sb = sb
|
self._sb = sb
|
||||||
self._sa = sa
|
self._sa = sa
|
||||||
self._ee = ee
|
self._ee = ee
|
||||||
@@ -366,10 +350,8 @@ class Resumption:
|
|||||||
ta = self._chain.decrypt(
|
ta = self._chain.decrypt(
|
||||||
msg3, key=kdf("res/msg3", self._shared, self._sb, self._sa, self._ee)
|
msg3, key=kdf("res/msg3", self._shared, self._sb, self._sa, self._ee)
|
||||||
)
|
)
|
||||||
_check(
|
if not hmac.compare_digest(ta, kdf("res/tag-a", self._sb, self._sa, self._ee)):
|
||||||
hmac.compare_digest(ta, kdf("res/tag-a", self._sb, self._sa, self._ee)),
|
raise ValueError("initiator tag mismatch")
|
||||||
"initiator tag mismatch",
|
|
||||||
)
|
|
||||||
|
|
||||||
# -- completion -------------------------------------------------------------
|
# -- completion -------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import struct
|
|||||||
from collections.abc import MutableMapping
|
from collections.abc import MutableMapping
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from .chab3 import Receiver, Sender, random
|
from .chab3 import Receiver, Sender, WireError, random
|
||||||
from .exceptions import ProtocolError, _Burn
|
from .exceptions import ProtocolError
|
||||||
from .handshake import Registration, Resumption, open_hello
|
from .handshake import Registration, Resumption, open_hello
|
||||||
from .kdf import kdf
|
from .kdf import kdf
|
||||||
|
|
||||||
@@ -156,15 +156,16 @@ class Session:
|
|||||||
out = self._st_data(msg)
|
out = self._st_data(msg)
|
||||||
case _:
|
case _:
|
||||||
raise ProtocolError(f"unexpected input in state {self.state}")
|
raise ProtocolError(f"unexpected input in state {self.state}")
|
||||||
except _Burn as b:
|
except WireError as e:
|
||||||
|
# unauthentic box: proves no key knowledge, nothing burns
|
||||||
|
_log.debug("fail state=%s: %r", self.state, e)
|
||||||
|
raise ProtocolError(str(e)) from None
|
||||||
|
except (ValueError, struct.error) as e:
|
||||||
# invalid data inside a validly opened box: the sender knew
|
# invalid data inside a validly opened box: the sender knew
|
||||||
# the keys and misbehaved -- retire the credential
|
# the keys and misbehaved -- retire the credential
|
||||||
self._delete_credential()
|
self._delete_credential()
|
||||||
_log.debug("fail state=%s: credential burned: %s", self.state, b)
|
_log.debug("fail state=%s: credential burned: %r", self.state, e)
|
||||||
raise ProtocolError(f"credential burned: {b}") from None
|
raise ProtocolError("credential burned") from None
|
||||||
except (ValueError, struct.error) as e:
|
|
||||||
_log.debug("fail state=%s: %r", self.state, e)
|
|
||||||
raise ProtocolError(str(e)) from None
|
|
||||||
_log.debug(
|
_log.debug(
|
||||||
"emit state=%s results=[%s]",
|
"emit state=%s results=[%s]",
|
||||||
self.state,
|
self.state,
|
||||||
|
|||||||
@@ -615,7 +615,7 @@ def test_loser_burn_spares_winner(psk):
|
|||||||
sx(m5x) # X completes first: wins the swap
|
sx(m5x) # X completes first: wins the swap
|
||||||
y._exchange._reveal = secrets.token_bytes(32) # Y lies about its reveal
|
y._exchange._reveal = secrets.token_bytes(32) # Y lies about its reveal
|
||||||
(m3y,) = y(m2y)
|
(m3y,) = y(m2y)
|
||||||
with pytest.raises(ProtocolError, match="commitment"):
|
with pytest.raises(ProtocolError, match="burned"):
|
||||||
sy(m3y)
|
sy(m3y)
|
||||||
assert len(srv["alice"]) == _CREDENTIAL.size # winner intact, no burn
|
assert len(srv["alice"]) == _CREDENTIAL.size # winner intact, no burn
|
||||||
|
|
||||||
@@ -650,7 +650,7 @@ def test_commitment_reveal_checked(psk):
|
|||||||
(m2,) = b(m1)
|
(m2,) = b(m1)
|
||||||
a._exchange._reveal = secrets.token_bytes(32) # alice lies about her reveal
|
a._exchange._reveal = secrets.token_bytes(32) # alice lies about her reveal
|
||||||
(m3,) = a(m2)
|
(m3,) = a(m2)
|
||||||
with pytest.raises(ProtocolError, match="commitment"):
|
with pytest.raises(ProtocolError, match="burned"):
|
||||||
b(m3)
|
b(m3)
|
||||||
assert b._store == {} # burned
|
assert b._store == {} # burned
|
||||||
|
|
||||||
@@ -741,7 +741,7 @@ def test_foreign_mlkem_static_rejected_at_msg4(psk):
|
|||||||
(m3,) = a(m2)
|
(m3,) = a(m2)
|
||||||
(vb,) = b(m3)
|
(vb,) = b(m3)
|
||||||
(m4,) = b(vb)
|
(m4,) = b(vb)
|
||||||
with pytest.raises(ProtocolError, match="responder tag"):
|
with pytest.raises(ProtocolError, match="burned"):
|
||||||
a(m4)
|
a(m4)
|
||||||
assert a._store == {} # burned
|
assert a._store == {} # burned
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user