|
| 1 | +from cryptography.exceptions import InvalidSignature |
| 2 | +from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat |
| 3 | + |
| 4 | +from .cose import COSEAlgorithmIdentifier |
| 5 | +from .exceptions import MLDSANotSupported |
| 6 | +from .decode_credential_public_key import DecodedMLDSAPublicKey |
| 7 | + |
| 8 | + |
| 9 | +class MLDSAPublicKey(DecodedMLDSAPublicKey): |
| 10 | + """ |
| 11 | + Something vaguely shaped like other PublicKey classes in cryptography. Going with something |
| 12 | + like this till the cryptography library itself supports PQC directly. |
| 13 | + """ |
| 14 | + |
| 15 | + def __init__(self, decoded_public_key: DecodedMLDSAPublicKey) -> None: |
| 16 | + assert_ml_dsa_dependencies() |
| 17 | + |
| 18 | + super().__init__( |
| 19 | + kty=decoded_public_key.kty, |
| 20 | + alg=decoded_public_key.alg, |
| 21 | + pub=decoded_public_key.pub, |
| 22 | + ) |
| 23 | + |
| 24 | + def verify(self, signature: bytes, data: bytes) -> None: |
| 25 | + """ |
| 26 | + Verify the ML-DSA signature. Raises `cryptography.exceptions.InvalidSignature` to blend in. |
| 27 | + """ |
| 28 | + from dilithium_py.ml_dsa import ML_DSA_44, ML_DSA_65, ML_DSA_87 |
| 29 | + |
| 30 | + if self.alg == COSEAlgorithmIdentifier.ML_DSA_44: |
| 31 | + verified = ML_DSA_44.verify(self.pub, data, signature) |
| 32 | + elif self.alg == COSEAlgorithmIdentifier.ML_DSA_65: |
| 33 | + verified = ML_DSA_65.verify(self.pub, data, signature) |
| 34 | + elif self.alg == COSEAlgorithmIdentifier.ML_DSA_87: |
| 35 | + verified = ML_DSA_87.verify(self.pub, data, signature) |
| 36 | + |
| 37 | + if not verified: |
| 38 | + raise InvalidSignature() |
| 39 | + |
| 40 | + def public_bytes(self, encoding: Encoding, format: PublicFormat) -> bytes: |
| 41 | + """ |
| 42 | + From https://datatracker.ietf.org/doc/draft-ietf-cose-dilithium/09/: |
| 43 | +
|
| 44 | + "The "pub" parameter is the ML-DSA public key, as described in |
| 45 | + Section 5.3 of FIPS-204." |
| 46 | +
|
| 47 | + This method simply returns the bytes, with no support for other encodings or formats. |
| 48 | + Nothing that A) provides attestation, and B) uses PQC for public keys will use this |
| 49 | + method right now. |
| 50 | + """ |
| 51 | + return self.pub |
| 52 | + |
| 53 | + |
| 54 | +def assert_ml_dsa_dependencies() -> None: |
| 55 | + """ |
| 56 | + Check that necessary dependencies are present for handling responses containing ML-DSA public |
| 57 | + keys. |
| 58 | +
|
| 59 | + Raises: |
| 60 | + `webauthn.helpers.exceptions.MLDSANotSupported` if those dependencies are missing |
| 61 | + """ |
| 62 | + try: |
| 63 | + import dilithium_py |
| 64 | + except Exception: |
| 65 | + raise MLDSANotSupported( |
| 66 | + "Please install https://pypi.org/project/dilithium-py to verify ML-DSA responses with py_webauthn" |
| 67 | + ) |
0 commit comments