packages feed

webauthn 0.10.0.0 → 0.11.0.0

raw patch · 34 files changed

+500/−404 lines, 34 filesdep ~monad-timenew-uploaderbinary-added

Dependency ranges changed: monad-time

Files

changelog.md view
@@ -1,3 +1,12 @@+### 0.11.0.0++* [#195](https://github.com/tweag/webauthn/pull/195) Allow for conditional mediation during registration.+  Unlike the rest of this library, which strictly follows the L2 version of this spec, this feature is defined+  in the [L3 draft](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin). However conditional create+  greatly reduces to barrier to entry for users to use passkeys we decided to include this feature in this+  library early. (Thanks to [nwalsh1995](https://github.com/nwalsh1995))+* [#187](https://github.com/tweag/webauthn/pull/187) Implement monotonicTime in MonadTime to resolve build-time warning+ ### 0.10.0.0  * [#184](https://github.com/tweag/webauthn/pull/184) Pass a list of allowed origins instead of a single origin.
+ root-certs/tpm/AMD/AMD-Pluton-Global-Factory-ICA.crt view

binary file changed (absent → 1101 bytes)

+ root-certs/tpm/AMD/AMD-Root-CA.crt view

binary file changed (absent → 1535 bytes)

+ root-certs/tpm/Intel/ODCA_CA2_CSME_Intermediate.crt view

binary file changed (absent → 807 bytes)

+ root-certs/tpm/Intel/ODCA_CA2_OSSE_Intermediate.crt view

binary file changed (absent → 808 bytes)

+ root-certs/tpm/NationZ/NSEccRootCA001.crt view

binary file changed (absent → 667 bytes)

+ root-certs/tpm/NationZ/NSRsaRootCA001.crt view

binary file changed (absent → 1440 bytes)

+ root-certs/tpm/NationZ/NSTPMEccRootCA001.crt view

binary file changed (absent → 657 bytes)

+ root-certs/tpm/NationZ/NSTPMRsaRootCA001.crt view

binary file changed (absent → 1430 bytes)

+ root-certs/tpm/QC/qwes_prod_ek_provisioning_root.crt view

binary file changed (absent → 722 bytes)

src/Crypto/WebAuthn.hs view
@@ -139,8 +139,10 @@     -- This is the main functionality implemented by the library. This module     -- exports these two main symbols:     --+    -- * 'verifyRegistrationResponseL3': Verifies a 'Credential' response for+    --   registration using the L3 spec, supporting conditional create.     -- * 'verifyRegistrationResponse': Verifies a 'Credential' response for-    --   registration.+    --   registration using the L2 spec.     -- * 'verifyAuthenticationResponse': Verifies a 'Credential' response for     --   authentication.     module Crypto.WebAuthn.Operation,
src/Crypto/WebAuthn/AttestationStatementFormat/AndroidKey.hs view
@@ -178,44 +178,40 @@   = -- | The public key in the certificate is different from the on in the     -- attested credential data     PublicKeyMismatch-      { -- | The public key part of the credential data-        credentialDataPublicKey :: Cose.PublicKey,-        -- | The public key extracted from the signed certificate-        certificatePublicKey :: Cose.PublicKey-      }+      -- | The public key part of the credential data+      Cose.PublicKey+      -- | The public key extracted from the signed certificate+      Cose.PublicKey   | -- | The challenge field of the certificate extension does not match the     -- clientDataHash     -- (first: challenge from certificate extension, second: clientDataHash)     HashMismatch-      { -- | The challenge part of the-        -- [@attestation-extension@](https://source.android.com/security/keystore/attestation#attestation-extension)-        certificateChallenge :: Digest SHA256,-        -- | The client data hash-        clientDataHash :: Digest SHA256-      }+      -- | The challenge part of the+      -- [@attestation-extension@](https://source.android.com/security/keystore/attestation#attestation-extension)+      (Digest SHA256)+      -- | The client data hash+      (Digest SHA256)   | -- | The "attestation" extension is scoped to all applications instead of just the RpId     AndroidKeyAllApplicationsFieldFound   | -- | The origin field(s) were not equal to KM_ORIGIN_GENERATED (0)     -- (first: tee-enforced origin, second: software-enforced origin (if allowed by the specified Format))     AndroidKeyOriginFieldInvalid-      { -- | The origin enforced by the trusted execution environment-        teeEnforcedOrigin :: Maybe Integer,-        -- | The origin enforced by software. NOTE: This field is explicitly-        -- set to `Nothing` if the `Format` specified `TeeEnforced` as the-        -- `requiredTrustLevel`.-        softwareEnforcedOrigin :: Maybe Integer-      }+      -- | The origin enforced by the trusted execution environment+      (Maybe Integer)+      -- | The origin enforced by software. NOTE: This field is explicitly+      -- set to `Nothing` if the `Format` specified `TeeEnforced` as the+      -- `requiredTrustLevel`.+      (Maybe Integer)   | -- | The purpose field(s) were not equal to the singleton set containing     -- KM_PURPOSE_SIGN (2)     -- (first: tee-enforced purpose, second: software-enforced purpose (if allowed by the specified Format))     AndroidKeyPurposeFieldInvalid-      { -- | The purpose enforced by the trusted execution environment-        teeEnforcedPurpose :: Maybe (Set Integer),-        -- | The purpose enforced by software. NOTE: This field is explicitly-        -- set to `Nothing` if the `Format` specified `TeeEnforced` as the-        -- `requiredTrustLevel`.-        softwareEnforcedPurpose :: Maybe (Set Integer)-      }+      -- | The purpose enforced by the trusted execution environment+      (Maybe (Set Integer))+      -- | The purpose enforced by software. NOTE: This field is explicitly+      -- set to `Nothing` if the `Format` specified `TeeEnforced` as the+      -- `requiredTrustLevel`.+      (Maybe (Set Integer))   | -- | The Public key cannot verify the signature over the authenticatorData     -- and the clientDataHash.     VerificationFailure Text
src/Crypto/WebAuthn/AttestationStatementFormat/AndroidSafetyNet.hs view
@@ -122,21 +122,19 @@   = -- | The receiced nonce was not set to the concatenation of the     -- authenticator data and client data hash     NonceMismatch-      { -- | Nonce from the AndroidSafetyNet response-        responseNonce :: Text,-        -- | Base64 encoding of the SHA-256 hash of the concatenation of-        -- authenticatorData and clientDataHash-        calculatedNonce :: Text-      }+      -- | Nonce from the AndroidSafetyNet response+      Text+      -- | Base64 encoding of the SHA-256 hash of the concatenation of+      -- authenticatorData and clientDataHash+      Text   | -- | The response was created to far in the past or future     ResponseTimeInvalid-      { -- | The UTC time minus the allowed drift specified in the `Format`.-        lowerBound :: HG.DateTime,-        -- | The UTC time plus the allowed drift specified in the `Format`.-        upperBound :: HG.DateTime,-        -- | The UTC time when the Android SafetyNet response was generated-        generatedtime :: HG.DateTime-      }+      -- | The UTC time minus the allowed drift specified in the `Format`.+      HG.DateTime+      -- | The UTC time plus the allowed drift specified in the `Format`.+      HG.DateTime+      -- | The UTC time when the Android SafetyNet response was generated+      HG.DateTime   | -- | The integrity check failed based on the required integrity from the     -- format     IntegrityCheckFailed Integrity
src/Crypto/WebAuthn/AttestationStatementFormat/Apple.hs view
@@ -50,21 +50,19 @@   = -- | The nonce found in the certificate extension does not match the     -- expected nonce     NonceMismatch-      { -- | The SHA256 hash of the concatenation of the @authenticatorData@-        -- and @clientDataHash@-        calculatedNonce :: Digest SHA256,-        -- | The nonce from the Apple nonce certificate extension-        -- (1.2.840.113635.100.8.2)-        receivedNonce :: Digest SHA256-      }+      -- | The SHA256 hash of the concatenation of the @authenticatorData@+      -- and @clientDataHash@+      (Digest SHA256)+      -- | The nonce from the Apple nonce certificate extension+      -- (1.2.840.113635.100.8.2)+      (Digest SHA256)   | -- | The public Key found in the certificate does not match the     -- credential's public key.     PublicKeyMismatch-      { -- | The public key part of the credential data-        credentialDataPublicKey :: Cose.PublicKey,-        -- | The public key extracted from the signed certificate-        certificatePublicKey :: Cose.PublicKey-      }+      -- | The public key part of the credential data+      Cose.PublicKey+      -- | The public key extracted from the signed certificate+      Cose.PublicKey   deriving (Show, Exception)  -- | [(spec)](https://www.w3.org/TR/webauthn-2/#sctn-apple-anonymous-attestation)
src/Crypto/WebAuthn/AttestationStatementFormat/FidoU2F.hs view
@@ -47,11 +47,10 @@     CredentialPublicKeyNotCoseEC2 Cose.CosePublicKey   | -- | The x and/or y coordinates of the credential public key are longer than 32 bytes     CoordinateSizeInvalid-      { -- | Actual length in bytes of the x coordinate-        xLength :: Int,-        -- | Actual length in bytes of the y coordinate-        yLength :: Int-      }+      -- | Actual length in bytes of the x coordinate+      Int+      -- | Actual length in bytes of the y coordinate+      Int   | -- | The provided public key cannot validate the signature over the verification data     SignatureInvalid X509.SignatureFailure   deriving (Show, Exception)
src/Crypto/WebAuthn/AttestationStatementFormat/Packed.hs view
@@ -65,11 +65,10 @@   = -- | The Algorithm from the attestation format does not match the algorithm     -- of the key in the credential data     AlgorithmMismatch-      { -- | The algorithm received in the attestation statement-        statementAlg :: Cose.CoseSignAlg,-        -- | The algorithm of the credentialPublicKey in authenticatorData-        credentialAlg :: Cose.CoseSignAlg-      }+      -- | The algorithm received in the attestation statement+      Cose.CoseSignAlg+      -- | The algorithm of the credentialPublicKey in authenticatorData+      Cose.CoseSignAlg   | -- | The statement key cannot verify the signature over the attested     -- credential data and client data for self attestation     InvalidSignature Text@@ -83,12 +82,11 @@   | -- | The AAGUID in the certificate extension does not match the AAGUID in     -- the authenticator data     CertificateAAGUIDMismatch-      { -- | AAGUID from the id-fido-gen-ce-aaguid certificate extension-        certificateExtensionAAGUID :: AAGUID,-        -- | A AGUID from the attested credential data in the authenticator-        -- data-        attestedCredentialDataAAGUID :: AAGUID-      }+      -- | AAGUID from the id-fido-gen-ce-aaguid certificate extension+      AAGUID+      -- | A AGUID from the attested credential data in the authenticator+      -- data+      AAGUID   deriving (Show, Exception)  instance M.AttestationStatementFormat Format where
src/Crypto/WebAuthn/AttestationStatementFormat/TPM.hs view
@@ -149,33 +149,53 @@ -- section 8.3 type TPMAObject = Word32 +-- | The TPMS_RSA_PARMS structure as specified in+-- [TPMv2-Part2](https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf)+-- section 12.2.3.5.+data TPMSRSAParms = TPMSRSAParms+  { tpmsrpSymmetric :: Word16,+    tpmsrpScheme :: Word16,+    tpmsrpKeyBits :: Word16,+    tpmsrpExponent :: Word32+  }+  deriving (Eq, Show, Generic, ToJSON)++-- | The TPMS_ECC_PARMS structure as specified in+-- [TPMv2-Part2](https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf)+-- section 12.2.3.6.+data TPMSECCParms = TPMSECCParms+  { tpmsepSymmetric :: Word16,+    tpmsepScheme :: Word16,+    tpmsepCurveId :: Cose.CoseCurveECDSA,+    tpmsepkdf :: Word16+  }+  deriving (Eq, Show, Generic, ToJSON)+ -- | The TPMU_PUBLIC_PARMS structure as specified in -- [TPMv2-Part2](https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf) -- section 12.2.3.7. data TPMUPublicParms-  = TPMSRSAParms-      { tpmsrpSymmetric :: Word16,-        tpmsrpScheme :: Word16,-        tpmsrpKeyBits :: Word16,-        tpmsrpExponent :: Word32-      }-  | TPMSECCParms-      { tpmsepSymmetric :: Word16,-        tpmsepScheme :: Word16,-        tpmsepCurveId :: Cose.CoseCurveECDSA,-        tpmsepkdf :: Word16-      }+  = TPMUPublicParmsRSA TPMSRSAParms+  | TPMUPublicParmsECC TPMSECCParms   deriving (Eq, Show, Generic, ToJSON) +-- | The TPMS_ECC_POINT structure as specified in+-- [TPMv2-Part2](https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf)+-- section 11.2.5.2.+data TPMSECCPoint = TPMSECCPoint+  { -- | X coordinate.+    tpmseX :: PrettyHexByteString,+    -- | Y coordinate.+    tpmseY :: PrettyHexByteString+  }+  deriving (Eq, Show, Generic, ToJSON)+ -- | The TPMU_PUBLIC_ID structure as specified in -- [TPMv2-Part2](https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf) -- section 12.2.3.2. data TPMUPublicId   = TPM2BPublicKeyRSA PrettyHexByteString-  | TPMSECCPoint-      { tpmseX :: PrettyHexByteString,-        tpmseY :: PrettyHexByteString-      }+  | TPMUPublicIdECCPoint TPMSECCPoint   deriving (Eq, Show, Generic, ToJSON)  -- | The TPMT_PUBLIC structure (see [TPMv2-Part2](https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf) section 12.2.4) used by the TPM to represent the credential public key.@@ -246,11 +266,10 @@   = -- | The public key in the certificate is different from the on in the     -- attested credential data     PublicKeyMismatch-      { -- | The public key extracted from the certificate-        certificatePublicKey :: Cose.PublicKey,-        -- | The public key part of the credential data-        credentialDataPublicKey :: Cose.PublicKey-      }+      -- | The public key extracted from the certificate+      Cose.PublicKey+      -- | The public key part of the credential data+      Cose.PublicKey   | -- | The magic number in certInfo was not set to TPM_GENERATED_VALUE (0xff544347)     MagicNumberInvalid Word32   | -- | The type in certInfo was not set to TPM_ST_ATTEST_CERTIFY (0x8017)@@ -260,13 +279,12 @@     NameAlgorithmInvalid TPMAlgId   | -- | The calulated name does not match the provided name.     NameMismatch-      { -- | The name calculated from the TPMT_PUBLIC structure with the name-        -- algorithm.-        pubAreaName :: BS.ByteString,-        -- | The expected name from TPMS_CERTIFY_INFO of the TPMS_ATTEST-        -- structure-        certifyInfoName :: BS.ByteString-      }+      -- | The name calculated from the TPMT_PUBLIC structure with the name+      -- algorithm.+      BS.ByteString+      -- | The expected name from TPMS_CERTIFY_INFO of the TPMS_ATTEST+      -- structure+      BS.ByteString   | -- | The public key in the certificate was invalid, either because the it     -- had an unexpected algorithm, or because it was otherwise malformed     PublicKeyInvalid Text@@ -286,11 +304,10 @@   | -- | The AAGUID in the attested credential data does not match the AAGUID     -- in the fido certificate extension     CertificateAAGUIDMismatch-      { -- | AAGUID from the id-fido-gen-ce-aaguid certificate extension-        certificateExtensionAAGUID :: AAGUID,-        -- | AAGUID from the attested credential data-        attestedCredentialDataAAGUID :: AAGUID-      }+      -- | AAGUID from the id-fido-gen-ce-aaguid certificate extension+      AAGUID+      -- | AAGUID from the attested credential data+      AAGUID   | -- | The (supposedly) ASN1 encoded certificate extension could not be     -- decoded     ASN1Error ASN1Error@@ -301,13 +318,12 @@   | -- | The calculated hash over the attToBeSigned does not match the received     -- hash     HashMismatch-      { -- | The hash of the concatenation of the @authenticatorData@ and-        -- @clientDataHash@ (@attToBeSigned@) calculated by the @alg@ specified in-        -- the @Statement@.-        calculatedHash :: BS.ByteString,-        -- | The extra data from the TPMS_ATTEST structure.-        extraData :: BS.ByteString-      }+      -- | The hash of the concatenation of the @authenticatorData@ and+      -- @clientDataHash@ (@attToBeSigned@) calculated by the @alg@ specified in+      -- the @Statement@.+      BS.ByteString+      -- | The extra data from the TPMS_ATTEST structure.+      BS.ByteString   deriving (Show, Exception)  -- [(spec)](https://www.trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf)@@ -476,7 +492,7 @@         tpmsrpKeyBits <- Get.getWord16be         -- An exponent of zero indicates that the exponent is the default of 2^16 + 1         tpmsrpExponent <- (\e -> if e == 0 then 65537 else e) <$> Get.getWord32be-        pure TPMSRSAParms {..}+        pure (TPMUPublicParmsRSA TPMSRSAParms {..})       getTPMUPublicParms TPMAlgSHA1 = fail "SHA1 does not have public key parameters"       getTPMUPublicParms TPMAlgSHA256 = fail "SHA256 does not have public key parameters"       getTPMUPublicParms TPMAlgECC = do@@ -484,7 +500,7 @@         tpmsepScheme <- Get.getWord16be         tpmsepCurveId <- toCurveId =<< Get.getWord16be         tpmsepkdf <- Get.getWord16be-        pure TPMSECCParms {..}+        pure (TPMUPublicParmsECC TPMSECCParms {..})        getTPMUPublicId :: TPMAlgId -> Get.Get TPMUPublicId       getTPMUPublicId TPMAlgRSA = TPM2BPublicKeyRSA <$> getTPMByteString@@ -493,32 +509,34 @@       getTPMUPublicId TPMAlgECC = do         tpmseX <- getTPMByteString         tpmseY <- getTPMByteString-        pure TPMSECCPoint {..}+        pure (TPMUPublicIdECCPoint TPMSECCPoint {..})        extractPublicKey :: TPMTPublic -> Either Text Cose.PublicKey       extractPublicKey         TPMTPublic           { tpmtpType = TPMAlgRSA,-            tpmtpParameters = TPMSRSAParms {..},+            tpmtpParameters = TPMUPublicParmsRSA TPMSRSAParms {..},             tpmtpUnique = TPM2BPublicKeyRSA (PrettyHexByteString nb)           } =-          Cose.checkPublicKey+          Cose.checkPublicKey $             Cose.PublicKeyRSA-              { rsaN = os2ip nb,-                rsaE = toInteger tpmsrpExponent-              }+              Cose.RSAPublicKey+                { rsaN = os2ip nb,+                  rsaE = toInteger tpmsrpExponent+                }       extractPublicKey         TPMTPublic           { tpmtpType = TPMAlgECC,-            tpmtpParameters = TPMSECCParms {..},-            tpmtpUnique = TPMSECCPoint {tpmseX = PrettyHexByteString tpmseX, tpmseY = PrettyHexByteString tpmseY}+            tpmtpParameters = TPMUPublicParmsECC TPMSECCParms {..},+            tpmtpUnique = TPMUPublicIdECCPoint TPMSECCPoint {tpmseX = PrettyHexByteString tpmseX, tpmseY = PrettyHexByteString tpmseY}           } =-          Cose.checkPublicKey+          Cose.checkPublicKey $             Cose.PublicKeyECDSA-              { ecdsaCurve = tpmsepCurveId,-                ecdsaX = os2ip tpmseX,-                ecdsaY = os2ip tpmseY-              }+              Cose.ECDSAPublicKey+                { ecdsaCurve = tpmsepCurveId,+                  ecdsaX = os2ip tpmseX,+                  ecdsaY = os2ip tpmseY+                }       extractPublicKey key = Left $ "Unsupported TPM public key: " <> Text.pack (show key)    asfEncode _ Statement {..} =
src/Crypto/WebAuthn/Cose/Internal/Verify.hs view
@@ -61,11 +61,12 @@ -- | Turns a X.509 certificates 'X509.PubKey' into a 'Cose.PublicKey' fromX509 :: X509.PubKey -> Either Text Cose.PublicKey fromX509 (X509.PubKeyEd25519 key) =-  Cose.checkPublicKey+  Cose.checkPublicKey $     Cose.PublicKeyEdDSA-      { eddsaCurve = Cose.CoseCurveEd25519,-        eddsaX = Cose.EdDSAKeyBytes $ convert key-      }+      Cose.EdDSAPublicKey+        { eddsaCurve = Cose.CoseCurveEd25519,+          eddsaX = Cose.EdDSAKeyBytes $ convert key+        } fromX509 (X509.PubKeyEC X509.PubKeyEC_Named {..}) = do   let curve = ECC.getCurveByName pubkeyEC_name   ecdsaCurve <- Cose.fromCryptCurveECDSA pubkeyEC_name@@ -73,15 +74,16 @@     Nothing -> Left "Failed to unserialize ECDSA point in X509 certificate"     Just res -> pure res   unchecked <- case point of-    ECC.Point ecdsaX ecdsaY -> Right $ Cose.PublicKeyECDSA {..}+    ECC.Point ecdsaX ecdsaY -> Right $ Cose.PublicKeyECDSA Cose.ECDSAPublicKey {..}     ECC.PointO -> Left "The infinity point is not supported"   Cose.checkPublicKey unchecked fromX509 (X509.PubKeyRSA RSA.PublicKey {..}) =-  Cose.checkPublicKey+  Cose.checkPublicKey $     Cose.PublicKeyRSA-      { rsaN = public_n,-        rsaE = public_e-      }+      Cose.RSAPublicKey+        { rsaN = public_n,+          rsaE = public_e+        } fromX509 key = Left $ "X509 public key algorithm is not supported: " <> Text.pack (show (X509.pubkeyToAlg key))  -- | Verifies an asymmetric signature for a message using a@@ -92,7 +94,7 @@ verify :: Cose.PublicKeyWithSignAlg -> Cose.Message -> Cose.Signature -> Either Text () verify   Cose.PublicKeyWithSignAlg-    { publicKey = Cose.PublicKey Cose.PublicKeyEdDSA {eddsaCurve = Cose.CoseCurveEd25519, ..},+    { publicKey = Cose.PublicKey (Cose.PublicKeyEdDSA Cose.EdDSAPublicKey {eddsaCurve = Cose.CoseCurveEd25519, ..}),       signAlg = Cose.CoseSignAlgEdDSA     }   msg@@ -108,7 +110,7 @@       else Left "EdDSA Signature invalid" verify   Cose.PublicKeyWithSignAlg-    { publicKey = Cose.PublicKey Cose.PublicKeyECDSA {..},+    { publicKey = Cose.PublicKey (Cose.PublicKeyECDSA Cose.ECDSAPublicKey {..}),       signAlg = Cose.CoseSignAlgECDSA (toCryptHashECDSA -> SomeHashAlgorithm hash)     }   msg@@ -138,7 +140,7 @@       else Left "ECDSA Signature invalid" verify   Cose.PublicKeyWithSignAlg-    { publicKey = Cose.PublicKey Cose.PublicKeyRSA {..},+    { publicKey = Cose.PublicKey (Cose.PublicKeyRSA Cose.RSAPublicKey {..}),       signAlg = Cose.CoseSignAlgRSA (toCryptHashRSA -> SomeHashAlgorithmASN1 hash)     }   msg
src/Crypto/WebAuthn/Cose/PublicKey.hs view
@@ -12,6 +12,9 @@     checkPublicKey,     PublicKey (PublicKey),     EdDSAKeyBytes (..),+    EdDSAPublicKey (..),+    ECDSAPublicKey (..),+    RSAPublicKey (..),      -- * COSE Elliptic Curves     CoseCurveEdDSA (..),@@ -39,6 +42,83 @@   deriving newtype (Eq)   deriving (Show, ToJSON) via PrettyHexByteString +-- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-2.2)+-- EdDSA Signature Algorithm+--+-- [RFC8032](https://datatracker.ietf.org/doc/html/rfc8032) describes the+-- elliptic curve signature scheme Edwards-curve+-- Digital Signature Algorithm (EdDSA). In that document, the signature+-- algorithm is instantiated using parameters for edwards25519 and+-- edwards448 curves. The document additionally describes two variants+-- of the EdDSA algorithm: Pure EdDSA, where no hash function is applied+-- to the content before signing, and HashEdDSA, where a hash function+-- is applied to the content before signing and the result of that hash+-- function is signed. For EdDSA, the content to be signed (either the+-- message or the pre-hash value) is processed twice inside of the+-- signature algorithm. For use with COSE, only the pure EdDSA version+-- is used.+--+-- Security considerations are [here](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-2.2.1)+data EdDSAPublicKey = EdDSAPublicKey+  { -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-7.2)+    -- The elliptic curve to use+    eddsaCurve :: CoseCurveEdDSA,+    -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-7.2)+    -- This contains the public key bytes.+    eddsaX :: EdDSAKeyBytes+  }+  deriving (Eq, Show, Generic, ToJSON)++-- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-2.1)+-- ECDSA Signature Algorithm+--+-- This document defines ECDSA to work only with the curves P-256,+-- P-384, and P-521. Future documents may define it to work with other+-- curves and points in the future.+--+-- In order to promote interoperability, it is suggested that SHA-256 be+-- used only with curve P-256, SHA-384 be used only with curve P-384,+-- and SHA-512 be used with curve P-521. This is aligned with the recommendation in+-- [Section 4 of RFC5480](https://datatracker.ietf.org/doc/html/rfc5480#section-4).+--+-- Security considerations are [here](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-2.1.1)+data ECDSAPublicKey = ECDSAPublicKey+  { -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-7.1.1)+    -- The elliptic curve to use+    ecdsaCurve :: CoseCurveECDSA,+    -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-7.1.1)+    -- This contains the x-coordinate for the EC point. The integer is+    -- converted to a byte string as defined in [SEC1]. Leading zero+    -- octets MUST be preserved.+    ecdsaX :: Integer,+    -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-7.1.1)+    -- This contains the value of the+    -- y-coordinate for the EC point. When encoding the value y, the+    -- integer is converted to an byte string (as defined in+    -- [SEC1](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#ref-SEC1))+    -- and encoded as a CBOR bstr. Leading zero octets MUST be+    -- preserved.+    ecdsaY :: Integer+  }+  deriving (Eq, Show, Generic, ToJSON)++-- | [(spec)](https://www.rfc-editor.org/rfc/rfc8812.html#section-2)+-- [RSASSA-PKCS1-v1_5](https://www.rfc-editor.org/rfc/rfc8017#section-8.2) Signature Algorithm+--+-- A key of size 2048 bits or larger MUST be used with these algorithms.+-- Security considerations are [here](https://www.rfc-editor.org/rfc/rfc8812.html#section-5)+data RSAPublicKey = RSAPublicKey+  { -- | [(spec)](https://www.rfc-editor.org/rfc/rfc8230.html#section-4)+    -- The RSA modulus n is a product of u distinct odd primes+    -- r_i, i = 1, 2, ..., u, where u >= 2+    rsaN :: Integer,+    -- | [(spec)](https://www.rfc-editor.org/rfc/rfc8230.html#section-4)+    -- The RSA public exponent e is an integer between 3 and n - 1 satisfying+    -- GCD(e,\\lambda(n)) = 1, where \\lambda(n) = LCM(r_1 - 1, ..., r_u - 1)+    rsaE :: Integer+  }+  deriving (Eq, Show, Generic, ToJSON)+ -- | [(spec)](https://www.w3.org/TR/webauthn-2/#credentialpublickey) -- A structured representation of a [COSE_Key](https://datatracker.ietf.org/doc/html/rfc8152#section-7) -- limited to what is know to be necessary for Webauthn public keys for the@@ -51,74 +131,18 @@   = -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-2.2)     -- EdDSA Signature Algorithm     ---    -- [RFC8032](https://datatracker.ietf.org/doc/html/rfc8032) describes the-    -- elliptic curve signature scheme Edwards-curve-    -- Digital Signature Algorithm (EdDSA). In that document, the signature-    -- algorithm is instantiated using parameters for edwards25519 and-    -- edwards448 curves. The document additionally describes two variants-    -- of the EdDSA algorithm: Pure EdDSA, where no hash function is applied-    -- to the content before signing, and HashEdDSA, where a hash function-    -- is applied to the content before signing and the result of that hash-    -- function is signed. For EdDSA, the content to be signed (either the-    -- message or the pre-hash value) is processed twice inside of the-    -- signature algorithm. For use with COSE, only the pure EdDSA version-    -- is used.-    --     -- Security considerations are [here](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-2.2.1)-    PublicKeyEdDSA-      { -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-7.2)-        -- The elliptic curve to use-        eddsaCurve :: CoseCurveEdDSA,-        -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-7.2)-        -- This contains the public key bytes.-        eddsaX :: EdDSAKeyBytes-      }+    PublicKeyEdDSA EdDSAPublicKey   | -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-2.1)     -- ECDSA Signature Algorithm     ---    -- This document defines ECDSA to work only with the curves P-256,-    -- P-384, and P-521. Future documents may define it to work with other-    -- curves and points in the future.-    ---    -- In order to promote interoperability, it is suggested that SHA-256 be-    -- used only with curve P-256, SHA-384 be used only with curve P-384,-    -- and SHA-512 be used with curve P-521. This is aligned with the recommendation in-    -- [Section 4 of RFC5480](https://datatracker.ietf.org/doc/html/rfc5480#section-4).-    --     -- Security considerations are [here](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-2.1.1)-    PublicKeyECDSA-      { -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-7.1.1)-        -- The elliptic curve to use-        ecdsaCurve :: CoseCurveECDSA,-        -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-7.1.1)-        -- This contains the x-coordinate for the EC point. The integer is-        -- converted to a byte string as defined in [SEC1]. Leading zero-        -- octets MUST be preserved.-        ecdsaX :: Integer,-        -- | [(spec)](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#section-7.1.1)-        -- This contains the value of the-        -- y-coordinate for the EC point. When encoding the value y, the-        -- integer is converted to an byte string (as defined in-        -- [SEC1](https://datatracker.ietf.org/doc/html/draft-ietf-cose-rfc8152bis-algs-12#ref-SEC1))-        -- and encoded as a CBOR bstr. Leading zero octets MUST be-        -- preserved.-        ecdsaY :: Integer-      }+    PublicKeyECDSA ECDSAPublicKey   | -- | [(spec)](https://www.rfc-editor.org/rfc/rfc8812.html#section-2)     -- [RSASSA-PKCS1-v1_5](https://www.rfc-editor.org/rfc/rfc8017#section-8.2) Signature Algorithm     ---    -- A key of size 2048 bits or larger MUST be used with these algorithms.     -- Security considerations are [here](https://www.rfc-editor.org/rfc/rfc8812.html#section-5)-    PublicKeyRSA-      { -- | [(spec)](https://www.rfc-editor.org/rfc/rfc8230.html#section-4)-        -- The RSA modulus n is a product of u distinct odd primes-        -- r_i, i = 1, 2, ..., u, where u >= 2-        rsaN :: Integer,-        -- | [(spec)](https://www.rfc-editor.org/rfc/rfc8230.html#section-4)-        -- The RSA public exponent e is an integer between 3 and n - 1 satisfying-        -- GCD(e,\\lambda(n)) = 1, where \\lambda(n) = LCM(r_1 - 1, ..., r_u - 1)-        rsaE :: Integer-      }+    PublicKeyRSA RSAPublicKey   deriving (Eq, Show, Generic)  -- | An arbitrary and potentially unstable JSON encoding, only intended for@@ -144,7 +168,7 @@  -- | Checks whether an 'UncheckedPublicKey' is valid. This is the only way to construct a t'PublicKey' checkPublicKey :: UncheckedPublicKey -> Either Text PublicKey-checkPublicKey key@PublicKeyEdDSA {..}+checkPublicKey key@(PublicKeyEdDSA EdDSAPublicKey {..})   | actualSize == expectedSize = Right $ CheckedPublicKey key   | otherwise =       Left $@@ -159,7 +183,7 @@   where     actualSize = BS.length $ unEdDSAKeyBytes eddsaX     expectedSize = coordinateSizeEdDSA eddsaCurve-checkPublicKey key@PublicKeyECDSA {..}+checkPublicKey key@(PublicKeyECDSA ECDSAPublicKey {..})   | ECC.isPointValid curve point = Right $ CheckedPublicKey key   | otherwise =       Left $
src/Crypto/WebAuthn/Cose/PublicKeyWithSignAlg.hs view
@@ -107,13 +107,13 @@ -- using the [CTAP2 canonical CBOR encoding form](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#ctap2-canonical-cbor-encoding-form) instance Serialise CosePublicKey where   encode PublicKeyWithSignAlg {..} = case publicKey of-    P.PublicKey P.PublicKeyEdDSA {..} ->+    P.PublicKey (P.PublicKeyEdDSA P.EdDSAPublicKey {..}) ->       common R.CoseKeyTypeOKP         <> encode R.CoseKeyTypeParameterOKPCrv         <> encode (fromCurveEdDSA eddsaCurve)         <> encode R.CoseKeyTypeParameterOKPX         <> encodeBytes (P.unEdDSAKeyBytes eddsaX)-    P.PublicKey P.PublicKeyECDSA {..} ->+    P.PublicKey (P.PublicKeyECDSA P.ECDSAPublicKey {..}) ->       common R.CoseKeyTypeEC2         <> encode R.CoseKeyTypeParameterEC2Crv         <> encode (fromCurveECDSA ecdsaCurve)@@ -127,7 +127,7 @@         <> encodeBytes (i2ospOf_ size ecdsaY)       where         size = P.coordinateSizeECDSA ecdsaCurve-    P.PublicKey P.PublicKeyRSA {..} ->+    P.PublicKey (P.PublicKeyRSA P.RSAPublicKey {..}) ->       common R.CoseKeyTypeRSA         -- https://www.rfc-editor.org/rfc/rfc8230.html#section-4         -- > The octet sequence MUST utilize the minimum@@ -218,7 +218,7 @@             eddsaCurve <- toCurveEdDSA <$> decode             decodeExpected R.CoseKeyTypeParameterOKPX             eddsaX <- P.EdDSAKeyBytes <$> decodeBytesCanonical-            pure P.PublicKeyEdDSA {..}+            pure (P.PublicKeyEdDSA P.EdDSAPublicKey {..})            decodeECDSAKey :: Decoder s P.UncheckedPublicKey           decodeECDSAKey = do@@ -239,7 +239,7 @@                 TypeBool -> fail "Compressed EC2 y coordinate not yet supported"                 typ -> fail $ "Unexpected type in EC2 y parameter: " <> show typ -            pure P.PublicKeyECDSA {..}+            pure (P.PublicKeyECDSA P.ECDSAPublicKey {..})            decodeRSAKey :: Decoder s P.UncheckedPublicKey           decodeRSAKey = do@@ -252,7 +252,7 @@             rsaN <- os2ipNoLeading =<< decodeBytesCanonical             decodeExpected R.CoseKeyTypeParameterRSAE             rsaE <- os2ipNoLeading =<< decodeBytesCanonical-            pure P.PublicKeyRSA {..}+            pure (P.PublicKeyRSA P.RSAPublicKey {..})  -- | Same as 'os2ip', but throws an error if there are not exactly as many bytes as expected. Thus any successful result of this function will give the same 'BS.ByteString' back if encoded with @'i2ospOf_' size@. os2ipWithSize :: (MonadFail m) => Int -> BS.ByteString -> m Integer
src/Crypto/WebAuthn/Internal/DateOrphans.hs view
@@ -21,7 +21,7 @@ module Crypto.WebAuthn.Internal.DateOrphans () where  import Control.Monad.Reader (ReaderT, asks)-import Control.Monad.Time (MonadTime, currentTime)+import Control.Monad.Time (MonadTime, currentTime, monotonicTime) import Data.Fixed (Fixed (MkFixed), HasResolution, Nano) import Data.Hourglass (Elapsed (Elapsed), ElapsedP (ElapsedP), NanoSeconds (NanoSeconds), Seconds (Seconds), Time, Timeable, timeConvert, timeFromElapsedP, timeGetElapsedP) import Data.Time (UTCTime, nominalDiffTimeToSeconds, secondsToNominalDiffTime)@@ -45,3 +45,4 @@  instance (Timeable t, Monad m) => MonadTime (ReaderT t m) where   currentTime = asks timeConvert+  monotonicTime = asks (realToFrac . timeGetElapsedP)
src/Crypto/WebAuthn/Metadata.hs view
@@ -9,6 +9,7 @@   ) where +import qualified Crypto.WebAuthn.Metadata.Service.Decode as Service import qualified Crypto.WebAuthn.Metadata.Service.Processing as Service import qualified Crypto.WebAuthn.Metadata.Service.Types as Service import Data.Bifunctor (Bifunctor (second), first)@@ -30,6 +31,6 @@   -- | Either a certifcate error or a list of errors, a registry of metadata entries or both where the MDS has bad entries   Either Text (These (NE.NonEmpty Text) Service.MetadataServiceRegistry) metadataBlobToRegistry bytes now = do-  json <- first (Text.pack . show) (Service.jwtToJson bytes Service.fidoAllianceRootCertificate now)-  let payload = Service.jsonToPayload json+  metadataPayload <- first (Text.pack . show) (Service.jwtToAdditionalData bytes Service.fidoAllianceRootCertificate now)+  let payload = Service.decodeMetadataPayload metadataPayload   pure $ second (Service.createMetadataRegistry . Service.mpEntries) payload
src/Crypto/WebAuthn/Metadata/FidoRegistry.hs view
@@ -122,7 +122,7 @@   | ALG_SIGN_RSASSA_PKCSV15_SHA512_RAW   | ALG_SIGN_RSASSA_PKCSV15_SHA1_RAW   | ALG_SIGN_SECP384R1_ECDSA_SHA384_RAW-  | ALG_SIGN_SECP512R1_ECDSA_SHA512_RAW+  | ALG_SIGN_SECP521R1_ECDSA_SHA512_RAW   | ALG_SIGN_ED25519_EDDSA_SHA512_RAW   deriving (Show, Eq, Generic) 
src/Crypto/WebAuthn/Metadata/Service/Processing.hs view
@@ -15,13 +15,12 @@     ProcessingError (..),     createMetadataRegistry,     queryMetadata,-    jwtToJson,-    jsonToPayload,+    jwtToAdditionalData,     fidoAllianceRootCertificate,   ) where -import Control.Lens ((^.), (^?), _Just)+import Control.Lens ((^?), _Just) import Control.Lens.Combinators (makeClassyPrisms) import Control.Monad.Except (MonadError, runExcept, throwError) import Control.Monad.Reader (MonadReader, ask, runReaderT)@@ -38,37 +37,27 @@     decodeCompact,     defaultJWTValidationSettings,     param,-    unregisteredClaims,-    verifyClaims,+    verifyJWT,   ) import Crypto.WebAuthn.Internal.DateOrphans ()-import Crypto.WebAuthn.Metadata.Service.Decode (decodeMetadataPayload) import qualified Crypto.WebAuthn.Metadata.Service.Types as Service-import qualified Crypto.WebAuthn.Metadata.Service.WebIDL as ServiceIDL import qualified Crypto.WebAuthn.Model as M import Crypto.WebAuthn.Model.Identifier   ( AAGUID,     AuthenticatorIdentifier (AuthenticatorIdentifierFido2, AuthenticatorIdentifierFidoU2F),     SubjectKeyIdentifier,   )-import Data.Aeson (Value) import qualified Data.Aeson.Types as Aeson import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS import Data.Either (partitionEithers) import Data.FileEmbed (embedFile)-import Data.HashMap.Strict (HashMap, (!?)) import qualified Data.HashMap.Strict as HashMap import Data.Hourglass (DateTime)-import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NE-import Data.Text (Text)-import qualified Data.Text as Text-import Data.These (These (This)) import qualified Data.X509 as X509 import qualified Data.X509.CertificateStore as X509 import qualified Data.X509.Validation as X509-import GHC.Exts (fromList, toList)  -- | A root certificate along with the host it should be verified against data RootCertificate = RootCertificate@@ -164,41 +153,20 @@       Just errors ->         throwError $ ProcessingValidationErrors errors --- | Extracts a FIDO Metadata payload JSON value from a JWT bytestring according to https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html-jwtToJson ::+-- | Extracts additional data from a JWT bytestring+jwtToAdditionalData ::+  (Aeson.FromJSON addData) =>   -- | The bytes of the JWT blob   BS.ByteString ->   -- | The root certificate the blob is signed with   RootCertificate ->   -- | The current time for which to validate the JWT blob   DateTime ->-  Either ProcessingError (HashMap Text Value)-jwtToJson blob rootCert now = runExcept $ do+  Either ProcessingError addData+jwtToAdditionalData blob rootCert now = runExcept $ do   jwt <- decodeCompact $ LBS.fromStrict blob-  claims <- runReaderT (verifyClaims (defaultJWTValidationSettings (const True)) rootCert jwt) now-  return . fromList . toList $ claims ^. unregisteredClaims---- | Decodes a FIDO Metadata payload JSON value to a 'Service.MetadataPayload',--- returning an error when the JSON is invalid, and ignoring any entries not--- relevant for webauthn. For the purposes of implementing the--- relying party the `Crypto.WebAuthn.Metadata.Service.Types.mpNextUpdate`--- and `Crypto.WebAuthn.Metadata.Service.Types.mpEntries` fields are most--- important.-jsonToPayload :: HashMap Text Value -> These (NonEmpty Text) Service.MetadataPayload-jsonToPayload value = case Aeson.parseEither metadataPayloadParser value of-  Left err -> This (Text.pack err NE.:| [])-  Right payload -> decodeMetadataPayload payload--metadataPayloadParser :: HashMap Text Aeson.Value -> Aeson.Parser ServiceIDL.MetadataBLOBPayload-metadataPayloadParser hm = case (hm !? "legalHeader", hm !? "no", hm !? "nextUpdate", hm !? "entries") of-  (Just legalHeader, Just no, Just nextUpdate, Just entries) -> do-    legalHeader <- Aeson.parseJSON legalHeader-    no <- Aeson.parseJSON no-    nextUpdate <- Aeson.parseJSON nextUpdate-    entries <- Aeson.parseJSON entries-    pure $-      ServiceIDL.MetadataBLOBPayload {..}-  _ -> fail "Could not decode MetadataBLOB: missing fields"+  payload <- runReaderT (verifyJWT (defaultJWTValidationSettings (const True)) rootCert jwt) now+  return $ Service.additionalData payload  -- | Creates a 'Service.MetadataServiceRegistry' from a list of -- 'Service.SomeMetadataEntry', which can either be obtained from a
src/Crypto/WebAuthn/Metadata/Service/Types.hs view
@@ -13,14 +13,16 @@     MetadataEntry (..),     SomeMetadataEntry (..),     StatusReport (..),+    ClaimSetSubtype (..),   ) where +import qualified Crypto.JWT as JWT import qualified Crypto.WebAuthn.Metadata.Service.WebIDL as ServiceIDL import Crypto.WebAuthn.Metadata.Statement.Types (MetadataStatement) import qualified Crypto.WebAuthn.Model as M import Crypto.WebAuthn.Model.Identifier (AAGUID, AuthenticatorIdentifier, SubjectKeyIdentifier)-import Data.Aeson (ToJSON)+import qualified Data.Aeson as Aeson import Data.HashMap.Strict (HashMap) import Data.Hourglass (Date) import Data.List.NonEmpty (NonEmpty)@@ -93,7 +95,7 @@ -- | An arbitrary and potentially unstable JSON encoding, only intended for -- logging purposes. To actually encode and decode structures, use the -- "Crypto.WebAuthn.Encoding" modules-deriving instance ToJSON (MetadataEntry p)+deriving instance Aeson.ToJSON (MetadataEntry p)  -- | Same as 'MetadataEntry', but with its type parameter erased data SomeMetadataEntry = forall p. (SingI p) => SomeMetadataEntry (MetadataEntry p)@@ -125,4 +127,18 @@ -- | An arbitrary and potentially unstable JSON encoding, only intended for -- logging purposes. To actually encode and decode structures, use the -- "Crypto.WebAuthn.Encoding" modules-deriving instance ToJSON StatusReport+deriving instance Aeson.ToJSON StatusReport++data ClaimSetSubtype addData = ClaimSetSubtype+  { additionalData :: addData,+    claimSet :: JWT.ClaimsSet+  }++instance (Aeson.FromJSON addData) => Aeson.FromJSON (ClaimSetSubtype addData) where+  parseJSON = Aeson.withObject "ClaimSetSubtype" $ \o ->+    ClaimSetSubtype+      <$> Aeson.parseJSON (Aeson.Object o)+      <*> Aeson.parseJSON (Aeson.Object o)++instance JWT.HasClaimsSet (ClaimSetSubtype a) where+  claimsSet f s = fmap (\cs -> s {claimSet = cs}) (f (claimSet s))
src/Crypto/WebAuthn/Model/Types.hs view
@@ -65,6 +65,7 @@     AttestationKind (..),     AttestationType (..),     VerifiableAttestationType (..),+    CredentialMediationRequirement (..),      -- * Newtypes     RpId (..),@@ -1023,7 +1024,7 @@       -- that wish to limit the creation of multiple credentials for the same account on a single authenticator.       -- The [client](https://www.w3.org/TR/webauthn-2/#client) is requested to return an error if the new credential       -- would be created on an authenticator that also contains one of the credentials enumerated in this parameter.-      -- The default value of this field is 'Crypto.WebAuthn.Model.Defaults.corExcludeCredentials'.+      -- The default value of this field is 'Crypto.WebAuthn.Model.Defaults.corExcludeCredentialsDefault'.       corExcludeCredentials :: [CredentialDescriptor],       -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialcreationoptions-authenticatorselection)       -- This member is intended for use by [Relying Parties](https://www.w3.org/TR/webauthn-2/#relying-party)@@ -1667,3 +1668,14 @@ -- logging purposes. To actually encode and decode structures, use the -- "Crypto.WebAuthn.Encoding" modules deriving instance ToJSON (Credential c raw)++-- | [(spec)](https://www.w3.org/TR/credential-management-1/#enumdef-credentialmediationrequirement)+-- The 'CredentialMediationRequirement' enum defines the requirements for+-- [user mediation](https://www.w3.org/TR/credential-management-1/#user-mediation).+-- Currently only `CredentialMediationRequirementConditional` is supported during credential creation.+data CredentialMediationRequirement = +  CredentialMediationRequirementSilent |+  CredentialMediationRequirementOptional |+  CredentialMediationRequirementConditional |+  CredentialMediationRequirementRequired+  deriving (Eq, Show)
src/Crypto/WebAuthn/Operation/Authentication.hs view
@@ -34,65 +34,59 @@ import Data.ByteArray (convert) import qualified Data.ByteString.Lazy as LBS import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as NE import Data.Text (Text) import Data.Validation (Validation)-import qualified Data.List.NonEmpty as NE  -- | Errors that may occur during [assertion](https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion) data AuthenticationError   = -- | The provided Credential was not one explicitly allowed by the server     AuthenticationCredentialDisallowed-      { -- | The credentials allowed by the server-        aeAllowedCredentials :: [M.CredentialDescriptor],-        -- | The credential returned by the client-        aeReceivedCredential :: M.Credential 'M.Authentication 'True-      }+      -- | The credentials allowed by the server+      [M.CredentialDescriptor]+      -- | The credential returned by the client+      (M.Credential 'M.Authentication 'True)   | -- | The received credential does not match the currently identified user     AuthenticationIdentifiedUserHandleMismatch-      { -- | The `M.UserHandle` of the user who is attempting authentication-        aeIdentifiedUser :: M.UserHandle,-        -- | The owner of the credential passed to the-        -- `verifyAuthenticationResponse` function (retrieved from the-        -- database)-        aeRegisteredUser :: M.UserHandle-      }+      -- | The `M.UserHandle` of the user who is attempting authentication+      M.UserHandle+      -- | The owner of the credential passed to the+      -- `verifyAuthenticationResponse` function (retrieved from the+      -- database)+      M.UserHandle   | -- | The stored credential does not match the user specified in the     -- response     AuthenticationCredentialUserHandleMismatch-      { -- | The `M.UserHandle` of the user who is attempting authentication-        aeIdentifiedUser :: M.UserHandle,-        -- | The `M.UserHandle` reported by the authenticator in the response-        aeAuthenticatorUser :: M.UserHandle-      }+      -- | The `M.UserHandle` of the user who is attempting authentication+      M.UserHandle+      -- | The `M.UserHandle` reported by the authenticator in the response+      M.UserHandle   | -- | No user was identified and the response did not specify a user     AuthenticationCannotVerifyUserHandle   | -- | The received challenge does not match the originally created     -- challenge     AuthenticationChallengeMismatch-      { -- | The challenge created by the relying party and part of the-        -- `M.CredentialOptions`-        aeCreatedChallenge :: M.Challenge,-        -- | The challenge received from the client, part of the response-        aeReceivedChallenge :: M.Challenge-      }+      -- | The challenge created by the relying party and part of the+      -- `M.CredentialOptions`+      M.Challenge+      -- | The challenge received from the client, part of the response+      M.Challenge   | -- | The origin derived by the client does match any of the assumed origins     AuthenticationOriginMismatch-      { -- | The origins explicitly passed to the `verifyAuthenticationResponse`-        -- response, set by the RP-        aeExpectedOrigins :: NonEmpty M.Origin,-        -- | The origin received from the client as part of the client data-        aeReceivedOrigin :: M.Origin-      }+      -- | The origins explicitly passed to the `verifyAuthenticationResponse`+      -- response, set by the RP+      (NonEmpty M.Origin)+      -- | The origin received from the client as part of the client data+      M.Origin   | -- | The rpIdHash in the authData is not a valid hash over the RpId     -- expected by the Relying party     AuthenticationRpIdHashMismatch-      { -- | The RP ID hash explicitly passed to the-        -- `verifyAuthenticationResponse` response, set by the RP-        aeExpectedRpIdHash :: M.RpIdHash,-        -- | The RP ID hash received from the client as part of the authenticator-        -- data-        aeReceivedRpIdHash :: M.RpIdHash-      }+      -- | The RP ID hash explicitly passed to the+      -- `verifyAuthenticationResponse` response, set by the RP+      M.RpIdHash+      -- | The RP ID hash received from the client as part of the authenticator+      -- data+      M.RpIdHash   | -- | The UserPresent bit was not set in the authData     AuthenticationUserNotPresent   | -- | The UserVerified bit was not set in the authData while user@@ -164,7 +158,7 @@ -- -- Though this library implements the WebAuthn L2 spec, for origin validation we -- follow the L3 draft. This is because allowing multiple origins is often--- needed in the wild. See [Validating the origin of a credential](https://www.w3.org/tr/webauthn-3/#sctn-validating-origin) +-- needed in the wild. See [Validating the origin of a credential](https://www.w3.org/tr/webauthn-3/#sctn-validating-origin) -- more details. -- In the simplest case, just a single origin is allowed and this is the 'M.RpId' with @https://@ prepended: --
src/Crypto/WebAuthn/Operation/Registration.hs view
@@ -19,6 +19,7 @@ -- which is a high level overview of the registration procedure. module Crypto.WebAuthn.Operation.Registration   ( verifyRegistrationResponse,+    verifyRegistrationResponseL3,     RegistrationError (..),     RegistrationResult (..),     AuthenticatorModel (..),@@ -62,30 +63,27 @@   = -- | The received challenge does not match the originally created     -- challenge     RegistrationChallengeMismatch-      { -- | The challenge created by the relying party and part of the-        -- `M.CredentialOptions`-        reCreatedChallenge :: M.Challenge,-        -- | The challenge received from the client, part of the response-        reReceivedChallenge :: M.Challenge-      }+      -- | The challenge created by the relying party and part of the+      -- `M.CredentialOptions`+      M.Challenge+      -- | The challenge received from the client, part of the response+      M.Challenge   | -- | The returned origin does not match any of the the relying party's origins     RegistrationOriginMismatch-      { -- | The origins explicitly passed to the `verifyRegistrationResponse`-        -- response, set by the RP-        reExpectedOrigins :: NonEmpty M.Origin,-        -- | The origin received from the client as part of the client data-        reReceivedOrigin :: M.Origin-      }+      -- | The origins explicitly passed to the `verifyRegistrationResponse`+      -- response, set by the RP+      (NonEmpty M.Origin)+      -- | The origin received from the client as part of the client data+      M.Origin   | -- | The rpIdHash in the authData is not a valid hash over the RpId     -- expected by the Relying party     RegistrationRpIdHashMismatch-      { -- | The RP ID hash explicitly passed to the-        -- `verifyRegistrationResponse` response, set by the RP-        reExpectedRpIdHash :: M.RpIdHash,-        -- | The RP ID hash received from the client as part of the authenticator-        -- data-        reReceivedRpIdHash :: M.RpIdHash-      }+      -- | The RP ID hash explicitly passed to the+      -- `verifyRegistrationResponse` response, set by the RP+      M.RpIdHash+      -- | The RP ID hash received from the client as part of the authenticator+      -- data+      M.RpIdHash   | -- | The userpresent bit in the authdata was not set     RegistrationUserNotPresent   | -- | The userverified bit in the authdata was not set@@ -93,11 +91,10 @@   | -- | The algorithm received from the client was not one of the algorithms     -- we (the relying party) requested from the client.     RegistrationPublicKeyAlgorithmDisallowed-      { -- | The signing algorithms requested by the RP-        reAllowedSigningAlgorithms :: [Cose.CoseSignAlg],-        -- | The signing algorithm received from the client-        reReceivedSigningAlgorithm :: Cose.CoseSignAlg-      }+      -- | The signing algorithms requested by the RP+      [Cose.CoseSignAlg]+      -- | The signing algorithm received from the client+      Cose.CoseSignAlg   | -- | There was some exception in the statement format specific section     forall a. (M.AttestationStatementFormat a) => RegistrationAttestationFormatError a (NonEmpty (M.AttStmtVerificationError a)) @@ -264,7 +261,7 @@ deriving instance ToJSON RegistrationResult  -- | [(spec)](https://www.w3.org/TR/webauthn-2/#sctn-registering-a-new-credential)--- Verifies a 'M.Credential' response for a [registration ceremony](https://www.w3.org/TR/webauthn-2/#registration-ceremony). +-- Verifies a 'M.Credential' response for a [registration ceremony](https://www.w3.org/TR/webauthn-2/#registration-ceremony). -- -- The resulting 'rrEntry' of this call should be stored in a database by the -- Relying Party. The 'rrAttestationStatement' contains the result of the@@ -273,7 +270,7 @@ -- -- Though this library implements the WebAuthn L2 spec, for origin validation we -- follow the L3 draft. This is because allowing multiple origins is often--- needed in the wild. See [Validating the origin of a credential](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin) +-- needed in the wild. See [Validating the origin of a credential](https://www.w3.org/TR/webauthn-3/#sctn-validating-origin) -- more details. -- In the simplest case, just a single origin is allowed and this is the 'M.RpId' with @https://@ prepended: --@@ -313,7 +310,45 @@   rpIdHash   registry   currentTime+  options+  =+      verifyRegistrationResponseL3+        origins+        rpIdHash+        registry+        currentTime+        options+        M.CredentialMediationRequirementOptional++-- | Like 'verifyRegistrationResponse', but allows passing the credential mediation requirement+-- If you don't need conditional create functionality, use 'verifyRegistrationResponse' instead.+verifyRegistrationResponseL3 ::+  -- | The list of allowed origins for the ceremony+  NonEmpty M.Origin ->+  -- | The relying party id+  M.RpIdHash ->+  -- | The metadata registry, used for verifying the validity of the+  -- attestation by looking up root certificates+  Meta.MetadataServiceRegistry ->+  -- | The current time, used for verifying the validity of the attestation+  -- statement certificate chain+  DateTime ->+  -- | The options passed to the create() method+  M.CredentialOptions 'M.Registration ->+  -- | The credential mediation requirement expected for the ceremony+  M.CredentialMediationRequirement ->+  -- | The response from the authenticator+  M.Credential 'M.Registration 'True ->+  -- | Either a nonempty list of validation errors in case the attestation FailedReason+  -- Or () in case of a result.+  Validation (NonEmpty RegistrationError) RegistrationResult+verifyRegistrationResponseL3+  origins+  rpIdHash+  registry+  currentTime   options@M.CredentialOptionsRegistration {..}+  credentialMediationRequirement   credential@M.Credential     { M.cResponse =         M.AuthenticatorResponseRegistration@@ -403,8 +438,8 @@         failure $           RegistrationRpIdHashMismatch rpIdHash (M.adRpIdHash authData) -      -- 14. Verify that the User Present bit of the flags in authData is set.-      unless (M.adfUserPresent (M.adFlags authData)) $+      -- 14. If credentialMediationRequirement is not Conditional, verify that the User Present bit of the flags in authData is set.+      unless (credentialMediationRequirement == M.CredentialMediationRequirementConditional || M.adfUserPresent (M.adFlags authData)) $         failure RegistrationUserNotPresent        -- 15. If user verification is required for this registration, verify that
tests/Emulation.hs view
@@ -121,61 +121,64 @@ spec :: SpecWith () spec =   describe "None" $ do-    it "rejects unknown origin during registration" $  do-      property $ \seed authenticator allowedOrigins' origin' -> not (null allowedOrigins') && origin' `notElem` allowedOrigins' ==> do-        let origin = M.Origin origin'-        let allowedOrigins = M.Origin <$> NE.fromList allowedOrigins'-        let annotatedOrigin = AnnotatedOrigin { aoRpId = M.RpId "localhost", aoOrigin = origin }-        let registry = mempty-        let userAgentConformance = mempty-        let Right (registerResult, _, _) = runApp seed (register annotatedOrigin allowedOrigins userAgentConformance authenticator registry predeterminedDateTime)-        registerResult `shouldSatisfy` \case-          Left errors -> any (\case O.RegistrationOriginMismatch _ _ -> True; _ -> False) errors-          Right _ -> False+    it "rejects unknown origin during registration" $ do+      property $ \seed authenticator allowedOrigins' origin' ->+        not (null allowedOrigins') && origin' `notElem` allowedOrigins' ==> do+          let origin = M.Origin origin'+          let allowedOrigins = M.Origin <$> NE.fromList allowedOrigins'+          let annotatedOrigin = AnnotatedOrigin {aoRpId = M.RpId "localhost", aoOrigin = origin}+          let registry = mempty+          let userAgentConformance = mempty+          let Right (registerResult, _, _) = runApp seed (register annotatedOrigin allowedOrigins userAgentConformance authenticator registry predeterminedDateTime)+          registerResult `shouldSatisfy` \case+            Left errors -> any (\case O.RegistrationOriginMismatch _ _ -> True; _ -> False) errors+            Right _ -> False     it "rejects unknown origin during login" $ do-      property $ \seed authenticator allowedOrigins' origin' -> not (null allowedOrigins') && origin' `notElem` allowedOrigins'  ==> do-        let allowedOrigins = M.Origin <$> NE.fromList allowedOrigins'-        let origin = NE.head allowedOrigins-        let wrongOrigin = M.Origin origin'-        let annotatedOrigin = AnnotatedOrigin { aoRpId = M.RpId "localhost", aoOrigin = origin }-        let wrongAnnotatedOrigin = AnnotatedOrigin { aoRpId = M.RpId "localhost", aoOrigin = wrongOrigin }-        let registry = mempty-        let userAgentConformance = mempty-        let Right (registerResult, authenticator', options) = runApp seed (register annotatedOrigin allowedOrigins userAgentConformance authenticator registry predeterminedDateTime)-        let registerResult' = second O.rrEntry registerResult-        registerResult' `shouldSatisfy` validAttestationResult authenticator userAgentConformance options-        case registerResult' of-          Right credentialEntry -> do-            let Right loginResult = runApp (seed + 1) (login wrongAnnotatedOrigin allowedOrigins userAgentConformance authenticator' credentialEntry)-            loginResult `shouldSatisfy` \case-              Left errors -> any (\case O.AuthenticationOriginMismatch _ _ -> True; _ -> False) errors-              Right _ -> False-          _ -> pure ()+      property $ \seed authenticator allowedOrigins' origin' ->+        not (null allowedOrigins') && origin' `notElem` allowedOrigins' ==> do+          let allowedOrigins = M.Origin <$> NE.fromList allowedOrigins'+          let origin = NE.head allowedOrigins+          let wrongOrigin = M.Origin origin'+          let annotatedOrigin = AnnotatedOrigin {aoRpId = M.RpId "localhost", aoOrigin = origin}+          let wrongAnnotatedOrigin = AnnotatedOrigin {aoRpId = M.RpId "localhost", aoOrigin = wrongOrigin}+          let registry = mempty+          let userAgentConformance = mempty+          let Right (registerResult, authenticator', options) = runApp seed (register annotatedOrigin allowedOrigins userAgentConformance authenticator registry predeterminedDateTime)+          let registerResult' = second O.rrEntry registerResult+          registerResult' `shouldSatisfy` validAttestationResult authenticator userAgentConformance options+          case registerResult' of+            Right credentialEntry -> do+              let Right loginResult = runApp (seed + 1) (login wrongAnnotatedOrigin allowedOrigins userAgentConformance authenticator' credentialEntry)+              loginResult `shouldSatisfy` \case+                Left errors -> any (\case O.AuthenticationOriginMismatch _ _ -> True; _ -> False) errors+                Right _ -> False+            _ -> pure ()      it "succeeds" $-      property $ \seed authenticator userAgentConformance  allowedOrigins' -> length allowedOrigins' > 1 ==> do-        let allowedOrigins = M.Origin <$> NE.fromList allowedOrigins'-        let annotatedOrigin =-              AnnotatedOrigin-                { aoRpId = M.RpId "localhost",-                  aoOrigin = NE.head allowedOrigins-                }-        -        -- Since our emulator only supports None attestation the registry can be left empty.-        let registry = mempty-        -- We are not currently interested in client or authenticator fails, we-        -- only wish to test our relying party implementation and are thus only-        -- interested in its errors.-        let Right (registerResult, authenticator', options) = runApp seed (register annotatedOrigin allowedOrigins userAgentConformance authenticator registry predeterminedDateTime)-        -- Since we only do None attestation, we only care about the resulting entry-        let registerResult' = second O.rrEntry registerResult-        registerResult' `shouldSatisfy` validAttestationResult authenticator userAgentConformance options-        -- Only if attestation succeeded can we continue with assertion-        case registerResult' of-          Right credentialEntry -> do-            let Right loginResult = runApp (seed + 1) (login annotatedOrigin allowedOrigins userAgentConformance authenticator' credentialEntry)-            loginResult `shouldSatisfy` validAssertionResult authenticator userAgentConformance-          _ -> pure ()+      property $ \seed authenticator userAgentConformance allowedOrigins' ->+        length allowedOrigins' > 1 ==> do+          let allowedOrigins = M.Origin <$> NE.fromList allowedOrigins'+          let annotatedOrigin =+                AnnotatedOrigin+                  { aoRpId = M.RpId "localhost",+                    aoOrigin = NE.head allowedOrigins+                  }++          -- Since our emulator only supports None attestation the registry can be left empty.+          let registry = mempty+          -- We are not currently interested in client or authenticator fails, we+          -- only wish to test our relying party implementation and are thus only+          -- interested in its errors.+          let Right (registerResult, authenticator', options) = runApp seed (register annotatedOrigin allowedOrigins userAgentConformance authenticator registry predeterminedDateTime)+          -- Since we only do None attestation, we only care about the resulting entry+          let registerResult' = second O.rrEntry registerResult+          registerResult' `shouldSatisfy` validAttestationResult authenticator userAgentConformance options+          -- Only if attestation succeeded can we continue with assertion+          case registerResult' of+            Right credentialEntry -> do+              let Right loginResult = runApp (seed + 1) (login annotatedOrigin allowedOrigins userAgentConformance authenticator' credentialEntry)+              loginResult `shouldSatisfy` validAssertionResult authenticator userAgentConformance+            _ -> pure ()  -- | Validates the result of attestation. Ensures that the proper errors are -- resulted in if the authenticator exhibits nonconforming behaviour, and
tests/Main.hs view
@@ -73,18 +73,28 @@ -- This is because some of our tests cannot be verfied (for different reasons). registerTestFromFile :: FilePath -> M.Origin -> M.RpId -> Bool -> Service.MetadataServiceRegistry -> HG.DateTime -> IO () registerTestFromFile fp origin rpId verifiable service now = do+  registerTestFromFile' fp origin rpId verifiable service now M.CredentialMediationRequirementOptional++-- | Same as 'registerTestFromFile', but allows to specifies the mediation requirement as conditional.+registerTestFromFileConditional :: FilePath -> M.Origin -> M.RpId -> Bool -> Service.MetadataServiceRegistry -> HG.DateTime -> IO ()+registerTestFromFileConditional fp origin rpId verifiable service now = do+  registerTestFromFile' fp origin rpId verifiable service now M.CredentialMediationRequirementConditional++registerTestFromFile' :: FilePath -> M.Origin -> M.RpId -> Bool -> Service.MetadataServiceRegistry -> HG.DateTime -> M.CredentialMediationRequirement -> IO ()+registerTestFromFile' fp origin rpId verifiable service now mediation = do   pkCredential <-     either (error . show) id . WJ.wjDecodeCredentialRegistration       <$> decodeFile fp   let options = defaultPublicKeyCredentialCreationOptions pkCredential   let registerResult =         toEither $-          O.verifyRegistrationResponse+          O.verifyRegistrationResponseL3             (NE.singleton origin)             (M.RpIdHash . hash . encodeUtf8 . M.unRpId $ rpId)             service             now             options+            mediation             pkCredential   registerResult `shouldSatisfy` isExpectedAttestationResponse pkCredential options verifiable @@ -353,6 +363,15 @@       registerTestFromFile         "tests/responses/attestation/tpm-es256-01.json"         "https://localhost:44329"+        "localhost"+        False -- Uses a fake certificate in the chain+        registry+        predeterminedDateTime+  describe "Conditional create register" $ do+    it "tests whether conditional create registration bypasses UP check" $+      registerTestFromFileConditional+        "tests/responses/attestation/conditional-create.json"+        "http://localhost:8080"         "localhost"         False -- Uses a fake certificate in the chain         registry
tests/MetadataSpec.hs view
@@ -4,21 +4,20 @@ module MetadataSpec (spec) where  import Crypto.WebAuthn.Metadata (metadataBlobToRegistry)-import Crypto.WebAuthn.Metadata.Service.Processing (RootCertificate (RootCertificate), fidoAllianceRootCertificate, jsonToPayload, jwtToJson)-import Crypto.WebAuthn.Metadata.Service.WebIDL (MetadataBLOBPayload, entries, legalHeader, nextUpdate, no)+import Crypto.WebAuthn.Metadata.Service.Processing (ProcessingError, RootCertificate (RootCertificate), fidoAllianceRootCertificate, jwtToAdditionalData)+import Crypto.WebAuthn.Metadata.Service.WebIDL (MetadataBLOBPayload) import Data.Aeson (Result (Success), ToJSON (toJSON), decodeFileStrict, fromJSON) import Data.Aeson.Types (Result (Error)) import qualified Data.ByteString as BS import Data.Either (isRight)-import Data.HashMap.Strict ((!), (!?)) import qualified Data.PEM as PEM import qualified Data.Text as Text import Data.Text.Encoding (decodeUtf8)-import Data.These (These (That, These, This))+import Data.These (These (These)) import qualified Data.X509 as X509 import qualified Data.X509.CertificateStore as X509 import Spec.Util (predeterminedDateTime)-import Test.Hspec (SpecWith, describe, it, shouldBe, shouldSatisfy)+import Test.Hspec (SpecWith, describe, it, shouldSatisfy) import Test.Hspec.Expectations.Json (shouldBeUnorderedJson)  golden :: FilePath -> SpecWith ()@@ -32,14 +31,11 @@         store = X509.makeCertificateStore [cert]      blobBytes <- BS.readFile $ "tests/golden-metadata/" <> subdir <> "/blob.jwt"-    let Right result = jwtToJson blobBytes (RootCertificate store origin) predeterminedDateTime+    let Right result = jwtToAdditionalData blobBytes (RootCertificate store origin) predeterminedDateTime      Just expectedPayload <- decodeFileStrict $ "tests/golden-metadata/" <> subdir <> "/payload.json" -    (result !? "legalHeader") `shouldBe` toJSON <$> legalHeader expectedPayload-    (result !? "no") `shouldBe` Just (toJSON (no expectedPayload))-    (result !? "nextUpdate") `shouldBe` Just (toJSON (nextUpdate expectedPayload))-    (result ! "entries") `shouldBeUnorderedJson` toJSON (entries expectedPayload)+    toJSON (result :: MetadataBLOBPayload) `shouldBeUnorderedJson` expectedPayload    it "can decode and reencode the payload to the partially parsed JSON" $ do     Just payload <- decodeFileStrict $ "tests/golden-metadata/" <> subdir <> "/payload.json"@@ -48,13 +44,6 @@       Success (value :: MetadataBLOBPayload) ->         toJSON value `shouldBeUnorderedJson` payload -  it "can decode and reencode the payload to the partially parsed JSON" $ do-    Just value <- decodeFileStrict $ "tests/golden-metadata/" <> subdir <> "/payload.json"-    case jsonToPayload value of-      This err -> fail $ show err-      These err _result -> fail $ show err-      That _result -> pure ()- spec :: SpecWith () spec = do   describe "Golden" $ do@@ -63,7 +52,8 @@   describe "fidoAllianceRootCertificate" $ do     it "can validate the payload" $ do       blobBytes <- BS.readFile "tests/golden-metadata/big/blob.jwt"-      jwtToJson blobBytes fidoAllianceRootCertificate predeterminedDateTime `shouldSatisfy` isRight+      let metadata = jwtToAdditionalData blobBytes fidoAllianceRootCertificate predeterminedDateTime :: Either ProcessingError MetadataBLOBPayload+      metadata `shouldSatisfy` isRight   describe "MDS with errors" $ do     it "can process an MDS file with errors" $ do       blobBytes <- BS.readFile "tests/golden-metadata/big/blob-with-errors.jwt"
tests/Spec/Key.hs view
@@ -65,9 +65,10 @@       pubKey' = Ed25519.toPublic privKey'       unchecked =         Cose.PublicKeyEdDSA-          { eddsaCurve = Cose.CoseCurveEd25519,-            eddsaX = Cose.EdDSAKeyBytes $ convert pubKey'-          }+          Cose.EdDSAPublicKey+            { eddsaCurve = Cose.CoseCurveEd25519,+              eddsaX = Cose.EdDSAKeyBytes $ convert pubKey'+            }       pubKey = fromRight (error "unreachable") $ Cose.checkPublicKey unchecked       cosePubKey = fromRight (error "unreachable") $ Cose.makePublicKeyWithSignAlg pubKey Cose.CoseSignAlgEdDSA   pure KeyPair {..}@@ -85,10 +86,11 @@        unchecked =         Cose.PublicKeyECDSA-          { ecdsaCurve = coseCurve,-            ecdsaX = x,-            ecdsaY = y-          }+          Cose.ECDSAPublicKey+            { ecdsaCurve = coseCurve,+              ecdsaX = x,+              ecdsaY = y+            }       privKey =         PrivateKeyECDSA           { ecdsaCurve = coseCurve,@@ -104,10 +106,11 @@   (RSA.PublicKey {..}, RSA.PrivateKey {..}) <- RSA.generate publicSizeBytes 65537   let unchecked =         Cose.PublicKeyRSA-          { -- rsaHash = hash,-            rsaN = public_n,-            rsaE = public_e-          }+          Cose.RSAPublicKey+            { -- rsaHash = hash,+              rsaN = public_n,+              rsaE = public_e+            }       privKey =         PrivateKeyRSA           { rsaN = public_n,@@ -156,18 +159,18 @@ sign signAlg privKey _ = error $ "sign: Combination of signature algorithm " <> show signAlg <> " and private key " <> show privKey <> " is not valid or supported"  toX509 :: Cose.UncheckedPublicKey -> X509.PubKey-toX509 Cose.PublicKeyEdDSA {eddsaCurve = Cose.CoseCurveEd25519, ..} =+toX509 (Cose.PublicKeyEdDSA Cose.EdDSAPublicKey {eddsaCurve = Cose.CoseCurveEd25519, ..}) =   let key = case Ed25519.publicKey $ Cose.unEdDSAKeyBytes eddsaX of         CryptoFailed err -> error $ "Failed to create a crypton Ed25519 public key of a bytestring with size " <> show (BS.length $ Cose.unEdDSAKeyBytes eddsaX) <> ": " <> show err         CryptoPassed res -> res    in X509.PubKeyEd25519 key-toX509 Cose.PublicKeyECDSA {..} =+toX509 (Cose.PublicKeyECDSA Cose.ECDSAPublicKey {..}) =   let curveName = Cose.toCryptCurveECDSA ecdsaCurve       size = Cose.coordinateSizeECDSA ecdsaCurve       serialisedPoint = X509.SerializedPoint $ BS.singleton 0x04 <> i2ospOf_ size ecdsaX <> i2ospOf_ size ecdsaY       key = X509.PubKeyEC_Named curveName serialisedPoint    in X509.PubKeyEC key-toX509 Cose.PublicKeyRSA {..} =+toX509 (Cose.PublicKeyRSA Cose.RSAPublicKey {..}) =   let key =         RSA.PublicKey           { public_size = BS.length (i2osp rsaN),
+ tests/responses/attestation/conditional-create.json view
@@ -0,0 +1,10 @@+{+  "type": "public-key",+  "id": "KbeXjEj6HgsT_opADhRF46CXsWRR7Y8UndF03-f2sF8XRaimojYz950Mi2fN2S3DSDGyI5I3S_IZyGJbT85E6w",+  "rawId": "KbeXjEj6HgsT_opADhRF46CXsWRR7Y8UndF03-f2sF8XRaimojYz950Mi2fN2S3DSDGyI5I3S_IZyGJbT85E6w",+  "response": {+    "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiaUxXVlU3bTJjWV9HQjR3SVN1Q3JKUSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCIsImNyb3NzT3JpZ2luIjpmYWxzZX0",+    "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NYAAAABAAAAAAAAAAAAAAAAAAAAAAAQCm3l4xI-h4LE_6KQA4UReOgl7FkUe2PFJ3RdN_n9rBfF0WopqI2M_edDItnzdktw0gxsiOSN0vyGchiW0_OROulAQIDJiABIVggd6q1rL84dPlHqncwa69sOrqfs300BAp1lYv9pRlqIOgiWCA8sAhHyFxsiqy4HpSRVwIG5ZMcUU3iT8evBl7DHFh-9g"+  },+  "clientExtensionResults": {}+}
webauthn.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: webauthn-version: 0.10.0.0+version: 0.11.0.0 license: Apache-2.0 license-file: LICENSE copyright:@@ -90,7 +90,7 @@     jose                    >= 0.11 && < 0.12,     lens                    >= 4.18.1 && < 5.3,     memory                  >= 0.15.0 && < 0.19,-    monad-time              >= 0.3.1 && < 0.5,+    monad-time              >= 0.4.0 && < 0.5,     mtl                     >= 2.2.2 && < 2.4,     serialise               >= 0.2.3 && < 0.3,     singletons              >= 2.6 && < 3.2,