generated from coulomb/repo-seed
refactor(local-identity): post-Stage4 cleanups and micro-fixes
- audit: chmod only on file creation, not every append (TOCTOU fix) - jwt_utils: add extract_unverified_payload() helper - cli: use extract_unverified_payload + JWTError instead of inline decode - keys: extract _public_key_bytes() helper, import _b64url from jwt_utils - security: FileNotFoundError try/except instead of path.exists() (TOCTOU fix) - serve: cache JWK response at server init instead of per-request recompute 138 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,6 @@ The corresponding public key is never stored separately — it is always
|
||||
derived from the private key on load.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -17,6 +16,7 @@ from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
|
||||
|
||||
from .jwt_utils import _b64url_encode as _b64url
|
||||
from .store import _store_dir
|
||||
|
||||
|
||||
@@ -48,27 +48,28 @@ def ensure_signing_key() -> RSAPrivateKey:
|
||||
return private_key
|
||||
|
||||
|
||||
def _b64url(data: bytes) -> str:
|
||||
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
|
||||
def _public_key_bytes(private_key: RSAPrivateKey) -> tuple[bytes, bytes]:
|
||||
"""Return (n_bytes, e_bytes) for the public key — shared by key_id and jwk_public."""
|
||||
pub = private_key.public_key().public_numbers()
|
||||
n_bytes = pub.n.to_bytes((pub.n.bit_length() + 7) // 8, byteorder="big")
|
||||
e_bytes = pub.e.to_bytes((pub.e.bit_length() + 7) // 8, byteorder="big")
|
||||
return n_bytes, e_bytes
|
||||
|
||||
|
||||
def key_id(private_key: RSAPrivateKey) -> str:
|
||||
"""Return a stable 16-hex-char key ID derived from the public key modulus."""
|
||||
n = private_key.public_key().public_numbers().n
|
||||
n_bytes = n.to_bytes((n.bit_length() + 7) // 8, byteorder="big")
|
||||
n_bytes, _ = _public_key_bytes(private_key)
|
||||
return hashlib.sha256(n_bytes).hexdigest()[:16]
|
||||
|
||||
|
||||
def jwk_public(private_key: RSAPrivateKey) -> dict:
|
||||
"""Return the RSA public key as a JWK dict (RS256, sig use)."""
|
||||
pub = private_key.public_key().public_numbers()
|
||||
n_bytes = pub.n.to_bytes((pub.n.bit_length() + 7) // 8, byteorder="big")
|
||||
e_bytes = pub.e.to_bytes((pub.e.bit_length() + 7) // 8, byteorder="big")
|
||||
n_bytes, e_bytes = _public_key_bytes(private_key)
|
||||
return {
|
||||
"kty": "RSA",
|
||||
"use": "sig",
|
||||
"alg": "RS256",
|
||||
"kid": key_id(private_key),
|
||||
"kid": hashlib.sha256(n_bytes).hexdigest()[:16],
|
||||
"n": _b64url(n_bytes),
|
||||
"e": _b64url(e_bytes),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user