Created
September 1, 2026 19:44
-
-
Save oskar456/b9f611d9d94709cec7e9f118b7ab1039 to your computer and use it in GitHub Desktop.
Convert PEM ACME account key into base64url thumprint of the JWK representation used in http-01 challenge
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S uv run --script | |
| # /// script | |
| # dependencies = [ | |
| # "cryptography", | |
| # ] | |
| # /// | |
| import base64 | |
| import hashlib | |
| import json | |
| import sys | |
| from pathlib import Path | |
| from cryptography.hazmat.primitives.asymmetric import ec, rsa | |
| from cryptography.hazmat.primitives.serialization import load_pem_private_key | |
| def b64url(data: bytes) -> str: | |
| """Base64URL-encode without padding.""" | |
| return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii") | |
| def load_key() -> object: | |
| if len(sys.argv) > 1: | |
| with Path(sys.argv[1]).open("rb") as f: | |
| return load_pem_private_key(f.read(), password=None) | |
| return load_pem_private_key(sys.stdin.buffer.read(), password=None) | |
| def main() -> None: | |
| key = load_key() | |
| public = key.public_key() | |
| if isinstance(public, rsa.RSAPublicKey): | |
| numbers = public.public_numbers() | |
| jwk = { | |
| "e": b64url(numbers.e.to_bytes( | |
| (numbers.e.bit_length() + 7) // 8, "big" | |
| )), | |
| "kty": "RSA", | |
| "n": b64url(numbers.n.to_bytes( | |
| (numbers.n.bit_length() + 7) // 8, "big" | |
| )), | |
| } | |
| elif isinstance(public, ec.EllipticCurvePublicKey): | |
| numbers = public.public_numbers() | |
| curves = { | |
| "secp256r1": "P-256", | |
| "secp384r1": "P-384", | |
| "secp521r1": "P-521", | |
| } | |
| try: | |
| curve = curves[public.curve.name] | |
| except KeyError: | |
| raise ValueError( | |
| f"Unsupported EC curve: {public.curve.name}" | |
| ) from None | |
| coordinate_size = (public.curve.key_size + 7) // 8 | |
| jwk = { | |
| "crv": curve, | |
| "kty": "EC", | |
| "x": b64url(numbers.x.to_bytes(coordinate_size, "big")), | |
| "y": b64url(numbers.y.to_bytes(coordinate_size, "big")), | |
| } | |
| else: | |
| raise ValueError(f"Unsupported key type: {type(public).__name__}") | |
| # RFC 7638 canonical JWK representation: | |
| # lexicographically sorted member names, no whitespace. | |
| canonical_jwk = json.dumps( | |
| jwk, | |
| sort_keys=True, | |
| separators=(",", ":"), | |
| ) | |
| thumbprint = b64url( | |
| hashlib.sha256(canonical_jwk.encode("utf-8")).digest() | |
| ) | |
| print(thumbprint) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment