| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -63,15 +63,24 @@ | |||
| 63 | 63 | except ImportError: # pragma: NO COVER | |
| 64 | 64 | es = None # type: ignore | |
| 65 | 65 | ||
| 66 | + try: | ||
| 67 | + from google.auth.crypt import pqc | ||
| 68 | + except ImportError: # pragma: NO COVER | ||
| 69 | + pqc = None # type: ignore | ||
| 70 | + | ||
| 66 | 71 | _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds | |
| 67 | 72 | _DEFAULT_MAX_CACHE_SIZE = 10 | |
| 68 | 73 | _ALGORITHM_TO_VERIFIER_CLASS = {"RS256": crypt.RSAVerifier} | |
| 69 | - _CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256", "ES384"]) | ||
| 74 | + _CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256", "ES384", "ML-DSA-65", "ML-DSA-87"]) | ||
| 70 | 75 | ||
| 71 | 76 | if es is not None: # pragma: NO COVER | |
| 72 | 77 | _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es.EsVerifier # type: ignore | |
| 73 | 78 | _ALGORITHM_TO_VERIFIER_CLASS["ES384"] = es.EsVerifier # type: ignore | |
| 74 | 79 | ||
| 80 | + if pqc is not None: # pragma: NO COVER | ||
| 81 | + _ALGORITHM_TO_VERIFIER_CLASS["ML-DSA-65"] = pqc.PqcVerifier # type: ignore | ||
| 82 | + _ALGORITHM_TO_VERIFIER_CLASS["ML-DSA-87"] = pqc.PqcVerifier # type: ignore | ||
| 83 | + | ||
| 75 | 84 | ||
| 76 | 85 | def encode(signer, payload, header=None, key_id=None): | |
| 77 | 86 | """Make a signed JWT. | |
@@ -98,6 +107,8 @@ def encode(signer, payload, header=None, key_id=None): | |||
| 98 | 107 | if "alg" not in header: | |
| 99 | 108 | if es is not None and isinstance(signer, es.EsSigner): | |
| 100 | 109 | header.update({"alg": signer.algorithm}) | |
| 110 | + elif pqc is not None and isinstance(signer, pqc.PqcSigner): | ||
| 111 | + header.update({"alg": signer.algorithm}) | ||
| 101 | 112 | else: | |
| 102 | 113 | header.update({"alg": "RS256"}) | |
| 103 | 114 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -111,6 +111,38 @@ def test_encode_basic_es384(es384_signer): | |||
| 111 | 111 | assert header == {"typ": "JWT", "alg": "ES384", "kid": es384_signer.key_id} | |
| 112 | 112 | ||
| 113 | 113 | ||
| 114 | + def test_encode_basic_mldsa(monkeypatch): | ||
| 115 | + class MockMLDSA65PrivateKey: | ||
| 116 | + def sign(self, message): | ||
| 117 | + return b"mldsa-sig" | ||
| 118 | + | ||
| 119 | + mock_mldsa = mock.Mock() | ||
| 120 | + mock_mldsa.MLDSA65PrivateKey = MockMLDSA65PrivateKey | ||
| 121 | + monkeypatch.setattr(crypt.pqc, "mldsa", mock_mldsa) | ||
| 122 | + monkeypatch.setattr( | ||
| 123 | + crypt.pqc.serialization, | ||
| 124 | + "load_pem_private_key", | ||
| 125 | + lambda key, password, backend: MockMLDSA65PrivateKey(), | ||
| 126 | + ) | ||
| 127 | + | ||
| 128 | + der_bytes = ( | ||
| 129 | + b"\x30\x20\x02\x01\x00\x30\x0b" | ||
| 130 | + b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x03\x12" | ||
| 131 | + b"\x04\x0a\x04\x08\x00\x00\x00\x00\x00\x00\x00\x00" | ||
| 132 | + ) | ||
| 133 | + import base64 | ||
| 134 | + | ||
| 135 | + b64_key = base64.b64encode(der_bytes).decode("ascii") | ||
| 136 | + pem = f"-----BEGIN PRIVATE KEY-----\n{b64_key}\n-----END PRIVATE KEY-----" | ||
| 137 | + mldsa_signer = crypt.PqcSigner.from_string(pem, "key-mldsa-65") | ||
| 138 | + | ||
| 139 | + test_payload = {"test": "value"} | ||
| 140 | + encoded = jwt.encode(mldsa_signer, test_payload) | ||
| 141 | + header, payload, _, _ = jwt._unverified_decode(encoded) | ||
| 142 | + assert payload == test_payload | ||
| 143 | + assert header == {"typ": "JWT", "alg": "ML-DSA-65", "kid": "key-mldsa-65"} | ||
| 144 | + | ||
| 145 | + | ||
| 114 | 146 | @pytest.fixture | |
| 115 | 147 | def token_factory(signer, es256_signer, es384_signer): | |
| 116 | 148 | def factory( | |
@@ -190,6 +222,49 @@ def test_decode_valid_es384(token_factory): | |||
| 190 | 222 | assert payload["metadata"]["meta"] == "data" | |
| 191 | 223 | ||
| 192 | 224 | ||
| 225 | + def test_decode_valid_mldsa(monkeypatch): | ||
| 226 | + class MockMLDSA65PrivateKey: | ||
| 227 | + def sign(self, message): | ||
| 228 | + return b"mldsa-sig" | ||
| 229 | + | ||
| 230 | + class MockMLDSA65PublicKey: | ||
| 231 | + def verify(self, signature, message): | ||
| 232 | + if signature != b"mldsa-sig": | ||
| 233 | + raise ValueError("Invalid signature") | ||
| 234 | + | ||
| 235 | + mock_mldsa = mock.Mock() | ||
| 236 | + mock_mldsa.MLDSA65PrivateKey = MockMLDSA65PrivateKey | ||
| 237 | + mock_mldsa.MLDSA65PublicKey = MockMLDSA65PublicKey | ||
| 238 | + monkeypatch.setattr(crypt.pqc, "mldsa", mock_mldsa) | ||
| 239 | + monkeypatch.setattr( | ||
| 240 | + crypt.pqc.serialization, | ||
| 241 | + "load_pem_private_key", | ||
| 242 | + lambda key, password, backend: MockMLDSA65PrivateKey(), | ||
| 243 | + ) | ||
| 244 | + monkeypatch.setattr( | ||
| 245 | + crypt.pqc.serialization, | ||
| 246 | + "load_pem_public_key", | ||
| 247 | + lambda pub, backend: MockMLDSA65PublicKey(), | ||
| 248 | + ) | ||
| 249 | + | ||
| 250 | + der_bytes = ( | ||
| 251 | + b"\x30\x20\x02\x01\x00\x30\x0b" | ||
| 252 | + b"\x06\x09\x60\x86\x48\x01\x65\x03\x04\x03\x12" | ||
| 253 | + b"\x04\x0a\x04\x08\x00\x00\x00\x00\x00\x00\x00\x00" | ||
| 254 | + ) | ||
| 255 | + import base64 | ||
| 256 | + | ||
| 257 | + b64_key = base64.b64encode(der_bytes).decode("ascii") | ||
| 258 | + pem = f"-----BEGIN PRIVATE KEY-----\n{b64_key}\n-----END PRIVATE KEY-----" | ||
| 259 | + mldsa_signer = crypt.PqcSigner.from_string(pem, "key-mldsa-65") | ||
| 260 | + | ||
| 261 | + now = _helpers.datetime_to_secs(_helpers.utcnow()) | ||
| 262 | + test_payload = {"test": "value", "iat": now, "exp": now + 300} | ||
| 263 | + encoded = jwt.encode(mldsa_signer, test_payload) | ||
| 264 | + payload = jwt.decode(encoded, certs="mock-pubkey") | ||
| 265 | + assert payload == test_payload | ||
| 266 | + | ||
| 267 | + | ||
| 193 | 268 | def test_decode_valid_with_audience(token_factory): | |
| 194 | 269 | payload = jwt.decode( | |
| 195 | 270 | token_factory(), certs=PUBLIC_CERT_BYTES, audience="audience@example.com" | |
| Back | FazBrowse Home | New Git URL |
0 commit comments