summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-04 03:03:47 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-04 03:03:47 +0200
commitdad2157ada303a45655d2919f62905369525636f (patch)
tree083d08cd15e1258e4fbcca3c3c619debb2857022 /packages
parent41282b997e016da0773276a935434dd03631e1f3 (diff)
downloadmeshbay-dad2157ada303a45655d2919f62905369525636f.tar.gz
fix: tolerate clock skew when verifying JWTs (leeway 60s)
A client whose clock is a little fast could not connect at all: the MNP handshake verified the hub-issued token with no leeway, so a token whose `iat` was a few seconds ahead of the node's clock failed with "the token is not yet valid (iat)". Seen against a freshly-resumed VM guest. `meshbay_common.handshake.JWT_LEEWAY_SECONDS = 60` is the shared value; applied to the handshake, the node's own hub-token decode, revocation-token verification, and the hub's access-token decode. 60s absorbs NTP-level skew without meaningfully widening the window on a stolen token (they already carry a jti and an exp). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-common/src/meshbay_common/handshake.py10
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/auth.py4
-rw-r--r--packages/meshbay-node/src/meshbay_node/hub_client.py4
-rw-r--r--packages/meshbay-node/src/meshbay_node/revocation.py2
4 files changed, 17 insertions, 3 deletions
diff --git a/packages/meshbay-common/src/meshbay_common/handshake.py b/packages/meshbay-common/src/meshbay_common/handshake.py
index 2f3d641..f7d4911 100644
--- a/packages/meshbay-common/src/meshbay_common/handshake.py
+++ b/packages/meshbay-common/src/meshbay_common/handshake.py
@@ -77,6 +77,13 @@ ROLE_NODE = "node"
NONCE_LEN = 32
+# Clock-skew tolerance for JWT `iat`/`exp`/`nbf`. The token is issued by the
+# hub and verified by a node, on two machines whose clocks are only as close
+# as their NTP — and a VM guest that has just resumed can be tens of seconds
+# out. Without this a slightly-fast client cannot connect at all
+# ("token is not yet valid (iat)").
+JWT_LEEWAY_SECONDS = 60
+
class HandshakeError(Exception):
"""
@@ -230,7 +237,8 @@ def authorize_token(
check entirely and fell back to the node's first group (M1).
"""
try:
- decoded = jwt.decode(token, hub_pk_pem, algorithms=["EdDSA"])
+ decoded = jwt.decode(token, hub_pk_pem, algorithms=["EdDSA"],
+ leeway=JWT_LEEWAY_SECONDS)
except Exception as exc:
raise HandshakeError(f"Invalid JWT: {exc}") from exc
diff --git a/packages/meshbay-hub/src/meshbay_hub/auth.py b/packages/meshbay-hub/src/meshbay_hub/auth.py
index 2bf59db..34baf45 100644
--- a/packages/meshbay-hub/src/meshbay_hub/auth.py
+++ b/packages/meshbay-hub/src/meshbay_hub/auth.py
@@ -166,7 +166,9 @@ def decode_access_token(token: str) -> dict:
"""Verify and decode an access token. Raises on failure."""
if _hub_pk_pem is None:
raise RuntimeError("Hub keypair not loaded")
- return jwt.decode(token, _hub_pk_pem, algorithms=["EdDSA"])
+ # Clock-skew tolerance (meshbay_common.handshake.JWT_LEEWAY_SECONDS): a
+ # client whose clock is a little fast must still be able to call the API.
+ return jwt.decode(token, _hub_pk_pem, algorithms=["EdDSA"], leeway=60)
# ── Email encryption at rest ──────────────────────────────────────────────────
diff --git a/packages/meshbay-node/src/meshbay_node/hub_client.py b/packages/meshbay-node/src/meshbay_node/hub_client.py
index 1e975fa..8873958 100644
--- a/packages/meshbay-node/src/meshbay_node/hub_client.py
+++ b/packages/meshbay-node/src/meshbay_node/hub_client.py
@@ -129,7 +129,9 @@ class HubClient:
access_token = data["access_token"]
- decoded = jwt.decode(access_token, hub_pk_pem, algorithms=["EdDSA"])
+ from meshbay_common.handshake import JWT_LEEWAY_SECONDS
+ decoded = jwt.decode(access_token, hub_pk_pem, algorithms=["EdDSA"],
+ leeway=JWT_LEEWAY_SECONDS)
# No pk_user claim to check any more: tokens carry no key. What binds this
# token to this node is the Ed25519 challenge it was issued against.
assert "jti" in decoded, "Hub token missing jti — hub is outdated"
diff --git a/packages/meshbay-node/src/meshbay_node/revocation.py b/packages/meshbay-node/src/meshbay_node/revocation.py
index abbff4d..d3ee18f 100644
--- a/packages/meshbay-node/src/meshbay_node/revocation.py
+++ b/packages/meshbay-node/src/meshbay_node/revocation.py
@@ -66,7 +66,9 @@ class RevocationSubscriber:
def verify_and_apply(self, token: str) -> bool:
"""Verify a revocation token and apply it. Returns True if valid."""
try:
+ from meshbay_common.handshake import JWT_LEEWAY_SECONDS
payload = jwt.decode(token, self._hub_pk_pem, algorithms=["EdDSA"],
+ leeway=JWT_LEEWAY_SECONDS,
options={"verify_exp": False})
if payload.get("type") != "revocation":
return False