packages feed

webauthn 0.1.1.0 → 0.2.0.0

raw patch · 22 files changed

+549/−246 lines, 22 filesdep −deriving-aesondep ~aesondep ~hashabledep ~josePVP ok

version bump matches the API change (PVP)

Dependencies removed: deriving-aeson

Dependency ranges changed: aeson, hashable, jose, unordered-containers

API changes (from Hackage documentation)

- Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: VerificationErrorAndroidKeyAllApplicationsFieldFound :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: VerificationErrorAndroidKeyOriginFieldInvalid :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: VerificationErrorAndroidKeyPurposeFieldInvalid :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: VerificationErrorClientDataHashMismatch :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: VerificationErrorCredentialKeyMismatch :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: VerificationErrorVerificationFailure :: Text -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: VerificationErrorFailedIntegrityCheck :: Integrity -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: VerificationErrorInvalidNonce :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: VerificationErrorResponseInFuture :: DateTime -> DateTime -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: VerificationErrorResponseTooOld :: DateTime -> DateTime -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.Apple: PublickeyMismatch :: PublicKey -> PublicKey -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: InvalidCertificatePublicKey :: PubKey -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: InvalidSignature :: SignatureFailure -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: NonECDSACredentialPublicKey :: PublicKey -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: WrongCoordinateSize :: Int -> Int -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: instance GHC.Exception.Type.Exception Crypto.WebAuthn.AttestationStatementFormat.FidoU2F.DecodingError
- Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: instance GHC.Show.Show Crypto.WebAuthn.AttestationStatementFormat.FidoU2F.DecodingError
- Crypto.WebAuthn.AttestationStatementFormat.Packed: VerificationErrorASN1Error :: ASN1Error -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.Packed: VerificationErrorAlgorithmMismatch :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.Packed: VerificationErrorCertificateAAGUIDMismatch :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.Packed: VerificationErrorCertificateRequirementsUnmet :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.Packed: VerificationErrorCredentialAAGUIDMissing :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.Packed: VerificationErrorInvalidSignature :: Text -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.Packed: VerificationErrorVerificationFailure :: SignatureFailure -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorASN1Error :: ASN1Error -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorBasicConstraintsTrue :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorCertificateAAGUIDMismatch :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorCertificateVersion :: Int -> Int -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorCredentialAAGUIDMissing :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorCredentialKeyMismatch :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorExtKeyOIDMissing :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorHashMismatch :: ByteString -> ByteString -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorInvalidMagicNumber :: Word32 -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorInvalidName :: ByteString -> ByteString -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorInvalidNameAlgorithm :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorInvalidPublicKey :: Text -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorInvalidType :: Word16 -> VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorNonEmptySubjectField :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorUnknownHashFunction :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorUnknownVendor :: VerificationError
- Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationErrorVerificationFailure :: Text -> VerificationError
- Crypto.WebAuthn.Internal.Utils: CustomJSON :: a -> CustomJSON (t :: k) a
- Crypto.WebAuthn.Internal.Utils: [unCustomJSON] :: CustomJSON (t :: k) a -> a
- Crypto.WebAuthn.Internal.Utils: data Lowercase
- Crypto.WebAuthn.Internal.Utils: instance Deriving.Aeson.StringModifier Crypto.WebAuthn.Internal.Utils.Lowercase
- Crypto.WebAuthn.Internal.Utils: newtype CustomJSON (t :: k) a
- Crypto.WebAuthn.Internal.Utils: type EnumJSONEncoding (prefix :: Symbol) = CustomJSON '[ConstructorTagModifier '[StripPrefix prefix, Lowercase]]
- Crypto.WebAuthn.Internal.Utils: type JSONEncoding = CustomJSON '[OmitNothingFields, FieldLabelModifier (StripPrefix "lit")]
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: AndroidKeyAllApplicationsFieldFound :: VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: AndroidKeyOriginFieldInvalid :: Maybe Integer -> Maybe Integer -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: AndroidKeyPurposeFieldInvalid :: Maybe (Set Integer) -> Maybe (Set Integer) -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: HashMismatch :: Digest SHA256 -> Digest SHA256 -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: PublicKeyMismatch :: PublicKey -> PublicKey -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: VerificationFailure :: Text -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: [certificateChallenge] :: VerificationError -> Digest SHA256
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: [certificatePublicKey] :: VerificationError -> PublicKey
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: [clientDataHash] :: VerificationError -> Digest SHA256
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: [credentialDataPublicKey] :: VerificationError -> PublicKey
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: [softwareEnforcedOrigin] :: VerificationError -> Maybe Integer
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: [softwareEnforcedPurpose] :: VerificationError -> Maybe (Set Integer)
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: [teeEnforcedOrigin] :: VerificationError -> Maybe Integer
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidKey: [teeEnforcedPurpose] :: VerificationError -> Maybe (Set Integer)
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: IntegrityCheckFailed :: Integrity -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: NonceMismatch :: Text -> Text -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: ResponseTimeInvalid :: DateTime -> DateTime -> DateTime -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: [calculatedNonce] :: VerificationError -> Text
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: [generatedtime] :: VerificationError -> DateTime
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: [lowerBound] :: VerificationError -> DateTime
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: [responseNonce] :: VerificationError -> Text
+ Crypto.WebAuthn.AttestationStatementFormat.AndroidSafetyNet: [upperBound] :: VerificationError -> DateTime
+ Crypto.WebAuthn.AttestationStatementFormat.Apple: PublicKeyMismatch :: PublicKey -> PublicKey -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.Apple: [calculatedNonce] :: VerificationError -> Digest SHA256
+ Crypto.WebAuthn.AttestationStatementFormat.Apple: [certificatePublicKey] :: VerificationError -> PublicKey
+ Crypto.WebAuthn.AttestationStatementFormat.Apple: [credentialDataPublicKey] :: VerificationError -> PublicKey
+ Crypto.WebAuthn.AttestationStatementFormat.Apple: [receivedNonce] :: VerificationError -> Digest SHA256
+ Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: CertificatePublicKeyInvalid :: PubKey -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: CoordinateSizeInvalid :: Int -> Int -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: CredentialPublicKeyNotECDSA :: PublicKey -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: SignatureInvalid :: SignatureFailure -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: [xLength] :: VerificationError -> Int
+ Crypto.WebAuthn.AttestationStatementFormat.FidoU2F: [yLength] :: VerificationError -> Int
+ Crypto.WebAuthn.AttestationStatementFormat.Packed: AlgorithmMismatch :: CoseSignAlg -> CoseSignAlg -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.Packed: CertificateAAGUIDMismatch :: AAGUID -> AAGUID -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.Packed: CertificateRequirementsUnmet :: VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.Packed: InvalidSignature :: Text -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.Packed: VerificationFailure :: SignatureFailure -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.Packed: [attestedCredentialDataAAGUID] :: VerificationError -> AAGUID
+ Crypto.WebAuthn.AttestationStatementFormat.Packed: [certificateExtensionAAGUID] :: VerificationError -> AAGUID
+ Crypto.WebAuthn.AttestationStatementFormat.Packed: [credentialAlg] :: VerificationError -> CoseSignAlg
+ Crypto.WebAuthn.AttestationStatementFormat.Packed: [statementAlg] :: VerificationError -> CoseSignAlg
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: ASN1Error :: ASN1Error -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: BasicConstraintsTrue :: VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: CertificateAAGUIDMismatch :: AAGUID -> AAGUID -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: CertificateVersionInvalid :: Int -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: CredentialAAGUIDMissing :: VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: ExtKeyOIDMissing :: VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: HashFunctionUnknown :: VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: HashMismatch :: ByteString -> ByteString -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: MagicNumberInvalid :: Word32 -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: NameAlgorithmInvalid :: TPMAlgId -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: NameMismatch :: ByteString -> ByteString -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: PublicKeyInvalid :: Text -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: PublicKeyMismatch :: PublicKey -> PublicKey -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: SubjectFieldNotEmpty :: [(OID, ASN1CharacterString)] -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: TPMAlgECC :: TPMAlgId
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: TPMAlgRSA :: TPMAlgId
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: TPMAlgSHA1 :: TPMAlgId
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: TPMAlgSHA256 :: TPMAlgId
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: TypeInvalid :: Word16 -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: VendorUnknown :: Text -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: VerificationFailure :: Text -> VerificationError
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: [attestedCredentialDataAAGUID] :: VerificationError -> AAGUID
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: [calculatedHash] :: VerificationError -> ByteString
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: [certificateExtensionAAGUID] :: VerificationError -> AAGUID
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: [certificatePublicKey] :: VerificationError -> PublicKey
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: [certifyInfoName] :: VerificationError -> ByteString
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: [credentialDataPublicKey] :: VerificationError -> PublicKey
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: [extraData] :: VerificationError -> ByteString
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: [pubAreaName] :: VerificationError -> ByteString
+ Crypto.WebAuthn.AttestationStatementFormat.TPM: data TPMAlgId
+ Crypto.WebAuthn.Internal.Utils: enumJSONEncodingOptions :: String -> Options
+ Crypto.WebAuthn.Internal.Utils: jsonEncodingOptions :: Options
- Crypto.WebAuthn.Model.Types: [CredentialOptionsAuthentication] :: {coaChallenge :: Challenge " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-challenge) This member represents a challenge that the selected [authenticator](https://www.w3.org/TR/webauthn-2/#authenticator) signs, along with other data, when producing an [authentication assertion](https://www.w3.org/TR/webauthn-2/#authentication-assertion). See the [§ 13.4.3 Cryptographic Challenges](https://www.w3.org/TR/webauthn-2/#sctn-cryptographic-challenges) security consideration.", coaTimeout :: Maybe Timeout " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-timeout) This OPTIONAL member specifies a time, in milliseconds, that the caller is willing to wait for the call to complete. The value is treated as a hint, and MAY be overridden by the [client](https://www.w3.org/TR/webauthn-2/#client).", coaRpId :: Maybe RpId " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-rpid) This OPTIONAL member specifies the [relying party identifier](https://www.w3.org/TR/webauthn-2/#relying-party-identifier) claimed by the caller. If omitted, its value will be the `[CredentialsContainer](https://w3c.github.io/webappsec-credential-management/#credentialscontainer)` object’s [relevant settings object](https://html.spec.whatwg.org/multipage/webappapis.html#relevant-settings-object)'s [origin](https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-origin)'s [effective domain](https://html.spec.whatwg.org/multipage/origin.html#concept-origin-effective-domain).", coaAllowCredentials :: [CredentialDescriptor] " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-allowcredentials) This OPTIONAL member contains a list of 'CredentialDescriptor' objects representing [public key credentials](https://www.w3.org/TR/webauthn-2/#public-key-credential) acceptable to the caller, in descending order of the caller’s preference (the first item in the list is the most preferred credential, and so on down the list).", coaUserVerification :: UserVerificationRequirement " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-userverification) This OPTIONAL member describes the [Relying Party](https://www.w3.org/TR/webauthn-2/#relying-party)'s requirements regarding [user verification](https://www.w3.org/TR/webauthn-2/#user-verification) for the `[get()](https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-get)` operation.", coaExtensions :: Maybe AuthenticationExtensionsClientInputs " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-extensions) This OPTIONAL member contains additional parameters requesting additional processing by the client and authenticator. For example, if transaction confirmation is sought from the user, then the prompt string might be included as an extension."} -> CredentialOptions 'Authentication
+ Crypto.WebAuthn.Model.Types: [CredentialOptionsAuthentication] :: {coaChallenge :: Challenge " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-challenge) This member represents a challenge that the selected [authenticator](https://www.w3.org/TR/webauthn-2/#authenticator) signs, along with other data, when producing an [authentication assertion](https://www.w3.org/TR/webauthn-2/#authentication-assertion). See the [§ 13.4.3 Cryptographic Challenges](https://www.w3.org/TR/webauthn-2/#sctn-cryptographic-challenges) security consideration.", coaTimeout :: Maybe Timeout " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-timeout) This OPTIONAL member specifies a time, in milliseconds, that the caller is willing to wait for the call to complete. The value is treated as a hint, and MAY be overridden by the [client](https://www.w3.org/TR/webauthn-2/#client).", coaRpId :: Maybe RpId " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-rpid) This OPTIONAL member specifies the [relying party identifier](https://www.w3.org/TR/webauthn-2/#relying-party-identifier) claimed by the caller. If omitted, its value will be the `[CredentialsContainer](https://w3c.github.io/webappsec-credential-management/#credentialscontainer)` object’s [relevant settings object](https://html.spec.whatwg.org/multipage/webappapis.html#relevant-settings-object)'s [origin](https://html.spec.whatwg.org/multipage/webappapis.html#concept-settings-object-origin)'s [effective domain](https://html.spec.whatwg.org/multipage/origin.html#concept-origin-effective-domain).", coaAllowCredentials :: [CredentialDescriptor] " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-allowcredentials) This OPTIONAL member contains a list of 'CredentialDescriptor' objects representing [public key credentials](https://www.w3.org/TR/webauthn-2/#public-key-credential) acceptable to the caller, in descending order of the caller’s preference (the first item in the list is the most preferred credential, and so on down the list).", coaUserVerification :: UserVerificationRequirement " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-userverification) This OPTIONAL member describes the [Relying Party](https://www.w3.org/TR/webauthn-2/#relying-party)'s requirements regarding [user verification](https://www.w3.org/TR/webauthn-2/#user-verification) for the `[get()](https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-get)` operation.", coaExtensions :: Maybe AuthenticationExtensionsClientInputs " [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-extensions) This OPTIONAL member contains additional parameters requesting additional processing by the client and authenticator. For example, if transaction confirmation is sought from the user, then the prompt string might be included as an extension. TODO: Extensions are not implemented by this library, see "Crypto.WebAuthn.Model#extensions"."} -> CredentialOptions 'Authentication
- Crypto.WebAuthn.Model.WebIDL.Internal.Binary.Decoding: decodeCollectedClientData :: forall c. SingI c => ByteString -> Either Text (CollectedClientData c 'True)
+ Crypto.WebAuthn.Model.WebIDL.Internal.Binary.Decoding: decodeCollectedClientData :: forall (c :: CeremonyKind). SingI c => ByteString -> Either Text (CollectedClientData c 'True)
- Crypto.WebAuthn.Model.WebIDL.Internal.Binary.Encoding: encodeCollectedClientData :: forall c. SingI c => CollectedClientData c 'True -> ByteString
+ Crypto.WebAuthn.Model.WebIDL.Internal.Binary.Encoding: encodeCollectedClientData :: forall (c :: CeremonyKind). SingI c => CollectedClientData c 'True -> ByteString
- Crypto.WebAuthn.Model.WebIDL.Internal.Binary.Encoding: encodeRawCredential :: forall c raw. SingI c => Credential c raw -> Credential c 'True
+ Crypto.WebAuthn.Model.WebIDL.Internal.Binary.Encoding: encodeRawCredential :: forall (c :: CeremonyKind) raw. SingI c => Credential c raw -> Credential c 'True

Files

README.md view
@@ -8,7 +8,7 @@ authentication to the web, logging in using a FIDO U2F Security Key, finger print scanner and some other authenticator. -This project was originally developed in 2020 as a Zurihac project by+This project was originally developed in 2020 as a [Zurihac project](https://wire.engineering/haskell/events/2020/06/18/wire-goes-zurihac.html) by [@arianvp](https://github.com/arianvp/) and [@duijf](https://github.com/duijf). Starting September 2021 a team at [Tweag](https://www.tweag.io/) was sponsored by [Mercury](https://mercury.com/) to create a production-ready open-source@@ -34,6 +34,12 @@ multiple hours.  All further instructions in this README assume that you are in a Nix shell.++## Getting Started+To get started with the library it is best to read the documentation of the+`Crypto.WebAuthn` module. An example of how to use this library to implement a+relying party can be found in the `server` directory, particularly the+`server/src/Main.hs` module.  ## Developing the Library The [Haskell Language Server (hls)][hls] and [Ormolu][ormolu] are highly
changelog.md view
@@ -1,3 +1,12 @@+### 0.2.0.0++* [#115](https://github.com/tweag/webauthn/pull/115) Increase the upper bound+  of the supported Aeson versions, allowing the library to be built with Aeson+  2.0. Drop the deriving-aeson dependency.+* [#117](https://github.com/tweag/webauthn/pull/117) Rename and expand+  documentation for attestation statement format errors. Some unused errors+  were removed.+ ### 0.1.1.0  * [#111](https://github.com/tweag/webauthn/pull/111) Support the
src/Crypto/WebAuthn/AttestationStatementFormat/AndroidKey.hs view
@@ -175,20 +175,48 @@ data VerificationError   = -- | The public key in the certificate is different from the on in the     -- attested credential data-    VerificationErrorCredentialKeyMismatch+    PublicKeyMismatch+      { -- | The public key part of the credential data+        credentialDataPublicKey :: Cose.PublicKey,+        -- | The public key extracted from the signed certificate+        certificatePublicKey :: Cose.PublicKey+      }   | -- | The challenge field of the certificate extension does not match the     -- clientDataHash-    VerificationErrorClientDataHashMismatch+    -- (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 "attestation" extension is scoped to all applications instead of just the RpId-    VerificationErrorAndroidKeyAllApplicationsFieldFound-  | -- | The origin field(s) were not equal to KM_ORIGIN_GENERATED-    VerificationErrorAndroidKeyOriginFieldInvalid+    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 purpose field(s) were not equal to the singleton set containing-    -- KM_PURPOSE_SIGN-    VerificationErrorAndroidKeyPurposeFieldInvalid+    -- 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 Public key cannot verify the signature over the authenticatorData     -- and the clientDataHash.-    VerificationErrorVerificationFailure Text+    VerificationFailure Text   deriving (Show, Exception)  -- | [(spec)](https://android.googlesource.com/platform/hardware/libhardware/+/master/include/hardware/keymaster_defs.h)@@ -204,7 +232,7 @@    asfIdentifier _ = "android-key" -  asfDecode _ xs = do+  asfDecode _ xs =     case (xs !? "alg", xs !? "sig", xs !? "x5c") of       (Just (CBOR.TInt algId), Just (CBOR.TBytes sig), Just (CBOR.TList (NE.nonEmpty -> Just x5cRaw))) -> do         alg <- Cose.toCoseSignAlg algId@@ -247,16 +275,19 @@     let signedData = rawData <> convert (M.unClientDataHash clientDataHash)     case Cose.verify alg pubKey signedData sig of       Right () -> pure ()-      Left err -> failure $ VerificationErrorVerificationFailure err+      Left err -> failure $ VerificationFailure err      -- 3. Verify that the public key in the first certificate in x5c matches the credentialPublicKey in the     -- attestedCredentialData in authenticatorData.-    unless (Cose.fromCose (M.acdCredentialPublicKey adAttestedCredentialData) == pubKey) $ failure VerificationErrorCredentialKeyMismatch+    let credentialPublicKey = Cose.fromCose (M.acdCredentialPublicKey adAttestedCredentialData)+    unless (credentialPublicKey == pubKey) . failure $ PublicKeyMismatch credentialPublicKey pubKey      -- 4. Verify that the attestationChallenge field in the attestation certificate extension data is identical to     -- clientDataHash.     -- See https://source.android.com/security/keystore/attestation for the ASN1 description-    unless (attestationChallenge attExt == M.unClientDataHash clientDataHash) $ failure VerificationErrorClientDataHashMismatch+    let attChallenge = attestationChallenge attExt+    let clientDataHashDigest = M.unClientDataHash clientDataHash+    unless (attChallenge == clientDataHashDigest) . failure $ HashMismatch attChallenge clientDataHashDigest      -- 5. Verify the following using the appropriate authorization list from the attestation certificate extension data: @@ -264,8 +295,8 @@     -- authorization list (softwareEnforced nor teeEnforced), since     -- PublicKeyCredential MUST be scoped to the RP ID.     let software = softwareEnforced attExt-        tee = teeEnforced attExt-    when (isJust (allApplications software) || isJust (allApplications tee)) $ failure VerificationErrorAndroidKeyAllApplicationsFieldFound+    let tee = teeEnforced attExt+    when (isJust (allApplications software) || isJust (allApplications tee)) $ failure AndroidKeyAllApplicationsFieldFound      -- 5.b For the following, use only the teeEnforced authorization list if the     -- RP wants to accept only keys from a trusted execution environment,@@ -273,15 +304,15 @@     -- 5.b.1 The value in the AuthorizationList.origin field is equal to KM_ORIGIN_GENERATED.     -- 5.b.2 The value in the AuthorizationList.purpose field is equal to KM_PURPOSE_SIGN.     -- NOTE: This statement is ambiguous as the purpose field is a set. Existing libraries take the same approach, checking if KM_PURPOSE_SIGN is the only member.-    let targetSet = Just $ Set.singleton kmPurposeSign+    let targetSet = Set.singleton kmPurposeSign     case requiredTrustLevel of       SoftwareEnforced -> do-        unless (origin software == Just kmOriginGenerated || origin tee == Just kmOriginGenerated) $ failure VerificationErrorAndroidKeyOriginFieldInvalid-        unless (targetSet == purpose software || targetSet == purpose tee) $ failure VerificationErrorAndroidKeyPurposeFieldInvalid+        unless (origin software == Just kmOriginGenerated || origin tee == Just kmOriginGenerated) . failure $ AndroidKeyOriginFieldInvalid (origin tee) (origin software)+        unless (Just targetSet == purpose software || Just targetSet == purpose tee) . failure $ AndroidKeyPurposeFieldInvalid (purpose tee) (purpose software)         pure ()       TeeEnforced -> do-        unless (origin tee == Just kmOriginGenerated) $ failure VerificationErrorAndroidKeyOriginFieldInvalid-        unless (targetSet == purpose tee) $ failure VerificationErrorAndroidKeyPurposeFieldInvalid+        unless (origin tee == Just kmOriginGenerated) . failure $ AndroidKeyOriginFieldInvalid (origin tee) Nothing+        unless (Just targetSet == purpose tee) . failure $ AndroidKeyPurposeFieldInvalid (purpose tee) Nothing         pure ()      -- 6. If successful, return implementation-specific values representing attestation type Basic and attestation trust
src/Crypto/WebAuthn/AttestationStatementFormat/AndroidSafetyNet.hs view
@@ -121,16 +121,25 @@ data VerificationError   = -- | The receiced nonce was not set to the concatenation of the     -- authenticator data and client data hash-    VerificationErrorInvalidNonce-  | -- | The response was created to far in the past-    -- (first: now, second: generated time)-    VerificationErrorResponseTooOld HG.DateTime HG.DateTime-  | -- | The response was created to far in the future-    -- (first: now, second: generated time)-    VerificationErrorResponseInFuture HG.DateTime HG.DateTime+    NonceMismatch+      { -- | Nonce from the AndroidSafetyNet response+        responseNonce :: Text,+        -- | Base64 encoding of the SHA-256 hash of the concatenation of+        -- authenticatorData and clientDataHash+        calculatedNonce :: 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 integrity check failed based on the required integrity from the     -- format-    VerificationErrorFailedIntegrityCheck Integrity+    IntegrityCheckFailed Integrity   deriving (Show, Exception)  androidHostName :: VerificationHostName@@ -215,7 +224,8 @@     let signedData = rawData <> BA.convert (M.unClientDataHash clientDataHash)     let hashedData = Hash.hashWith Hash.SHA256 signedData     let encodedData = decodeUtf8 . Base64.encode $ BA.convert hashedData-    unless (nonce response == encodedData) . failure $ VerificationErrorInvalidNonce+    let responseNonce = nonce response+    unless (responseNonce == encodedData) . failure $ NonceMismatch responseNonce encodedData      -- 4. Verify that the SafetyNet response actually came from the SafetyNet service by following the steps in the     -- SafetyNet online documentation.@@ -236,15 +246,17 @@     -- NOTE: For WebAuthn, we need not care about the package name or the app's signing certificate. The Nonce as     -- has already been dealt with.     let generatedTime = HG.timeConvert $ timestampMs response-    when ((generatedTime `HG.timeAdd` driftBackwardsTolerance) < now) $ failure $ VerificationErrorResponseTooOld now generatedTime-    when (generatedTime > (now `HG.timeAdd` driftForwardsTolerance)) $ failure $ VerificationErrorResponseInFuture now generatedTime+    let lowerBound = now `HG.timeAdd` negate (HG.toSeconds driftBackwardsTolerance)+    let upperBound = now `HG.timeAdd` driftForwardsTolerance+    when (generatedTime < lowerBound) $ failure $ ResponseTimeInvalid lowerBound upperBound generatedTime+    when (generatedTime > upperBound) $ failure $ ResponseTimeInvalid lowerBound upperBound generatedTime      let integrity = case (basicIntegrity response, ctsProfileMatch response) of           (_, True) -> CTSProfileIntegrity           (True, False) -> BasicIntegrity           (False, False) -> NoIntegrity     unless (integrity >= requiredIntegrity) $-      failure $ VerificationErrorFailedIntegrityCheck integrity+      failure $ IntegrityCheckFailed integrity      -- 5. If successful, return implementation-specific values representing attestation type Basic and attestation trust     -- path x5c.
src/Crypto/WebAuthn/AttestationStatementFormat/Apple.hs view
@@ -48,12 +48,22 @@ data VerificationError   = -- | The nonce found in the certificate extension does not match the     -- expected nonce-    -- (first: expected, second: received)-    NonceMismatch (Digest SHA256) (Digest SHA256)+    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 public Key found in the certificate does not match the     -- credential's public key.-    -- (first: credential, second: certificate)-    PublickeyMismatch Cose.PublicKey Cose.PublicKey+    PublicKeyMismatch+      { -- | The public key part of the credential data+        credentialDataPublicKey :: Cose.PublicKey,+        -- | The public key extracted from the signed certificate+        certificatePublicKey :: Cose.PublicKey+      }   deriving (Show, Exception)  -- | [(spec)](https://www.w3.org/TR/webauthn-2/#sctn-apple-anonymous-attestation)@@ -152,7 +162,7 @@       -- 5. Verify that the credential public key equals the Subject Public Key       -- of credCert.       let credentialPublicKey = Cose.fromCose $ M.acdCredentialPublicKey credData-      unless (credentialPublicKey == pubKey) . failure $ PublickeyMismatch credentialPublicKey pubKey+      unless (credentialPublicKey == pubKey) . failure $ PublicKeyMismatch credentialPublicKey pubKey        -- 6. If successful, return implementation-specific values representing       -- attestation type Anonymization CA and attestation trust path x5c.
src/Crypto/WebAuthn/AttestationStatementFormat/FidoU2F.hs view
@@ -36,28 +36,21 @@ instance Show Format where   show = Text.unpack . M.asfIdentifier --- | Decoding errors specific to Fido U2F attestation-data DecodingError-  = -- | No Signature field was present-    NoSig-  | -- | No x5c certificate was present-    NoX5C-  | -- | Multiple x5c certificates were found where only one was expected-    MultipleX5C-  | -- | There was an error decoding the x5c certificate, string is the error resulted by the `Data.X509.decodeSignedCertificate` function-    DecodingErrorX5C String-  deriving (Show, Exception)- -- | Verification errors specific to Fido U2F attestation data VerificationError   = -- | The public key in the certificate was not an EC Key or the curve was not the p256 curve-    InvalidCertificatePublicKey X509.PubKey+    CertificatePublicKeyInvalid X509.PubKey   | -- | The credential public key is not an ECDSA key-    NonECDSACredentialPublicKey Cose.PublicKey+    CredentialPublicKeyNotECDSA Cose.PublicKey   | -- | The x and/or y coordinates of the credential public key don't have a length of 32 bytes-    WrongCoordinateSize Int Int+    CoordinateSizeInvalid+      { -- | Actual length in bytes of the x coordinate+        xLength :: Int,+        -- | Actual length in bytes of the y coordinate+        yLength :: Int+      }   | -- | The provided public key cannot validate the signature over the verification data-    InvalidSignature X509.SignatureFailure+    SignatureInvalid X509.SignatureFailure   deriving (Show, Exception)  -- | [(spec)](https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation)@@ -116,8 +109,8 @@         X509.PubKeyEC pk ->           case X509.ecPubKeyCurveName pk of             Just SEC_p256r1 -> pure ()-            _ -> failure $ InvalidCertificatePublicKey certPubKey-        _ -> failure $ InvalidCertificatePublicKey certPubKey+            _ -> failure $ CertificatePublicKeyInvalid certPubKey+        _ -> failure $ CertificatePublicKeyInvalid certPubKey        -- 3. Extract the claimed rpIdHash from authenticatorData, and the claimed       -- credentialId and credentialPublicKey from authenticatorData.attestedCredentialData.@@ -140,7 +133,7 @@         Cose.PublicKeyECDSA {ecdsaX = xb, ecdsaY = yb} -> do           let xlen = BS.length xb               ylen = BS.length yb-          unless (xlen == 32 && ylen == 32) $ failure $ WrongCoordinateSize xlen ylen+          unless (xlen == 32 && ylen == 32) $ failure $ CoordinateSizeInvalid xlen ylen            -- 4.c Let publicKeyU2F be the concatenation 0x04 || x || y.           let publicKeyU2F = BS.singleton 0x04 <> xb <> yb@@ -149,15 +142,20 @@           -- clientDataHash || credentialId || publicKeyU2F) (see Section 4.3 of           -- [FIDO-U2F-Message-Formats]).           let credId = M.unCredentialId acdCredentialId-              verificationData = BS.singleton 0x00 <> BA.convert (M.unRpIdHash adRpIdHash) <> BA.convert (M.unClientDataHash clientDataHash) <> credId <> publicKeyU2F+              verificationData =+                BS.singleton 0x00+                  <> BA.convert (M.unRpIdHash adRpIdHash)+                  <> BA.convert (M.unClientDataHash clientDataHash)+                  <> credId+                  <> publicKeyU2F            -- 6. Verify the sig using verificationData and the certificate public key per           -- section 4.1.4 of [SEC1] with SHA-256 as the hash function used in step two.           case X509.verifySignature (X509.SignatureALG X509.HashSHA256 X509.PubKeyALG_EC) certPubKey verificationData sig of             X509.SignaturePass -> pure ()-            X509.SignatureFailed e -> failure $ InvalidSignature e+            X509.SignatureFailed e -> failure $ SignatureInvalid e           pure ()-        key -> failure $ NonECDSACredentialPublicKey key+        key -> failure $ CredentialPublicKeyNotECDSA key        -- 7. Optionally, inspect x5c and consult externally provided knowledge to       -- determine whether attStmt conveys a Basic or AttCA attestation.
src/Crypto/WebAuthn/AttestationStatementFormat/Packed.hs view
@@ -19,8 +19,8 @@ import qualified Crypto.WebAuthn.Cose.Internal.Verify as Cose import qualified Crypto.WebAuthn.Cose.Key as Cose import Crypto.WebAuthn.Internal.Utils (IdFidoGenCeAAGUID (IdFidoGenCeAAGUID), failure)+import Crypto.WebAuthn.Model (AAGUID) import qualified Crypto.WebAuthn.Model.Types as M-import Data.ASN1.Error (ASN1Error) import qualified Data.ASN1.OID as OID import Data.Aeson (ToJSON, object, toJSON, (.=)) import Data.Bifunctor (first)@@ -63,25 +63,31 @@ data VerificationError   = -- | The Algorithm from the attestation format does not match the algorithm     -- of the key in the credential data-    VerificationErrorAlgorithmMismatch+    AlgorithmMismatch+      { -- | The algorithm received in the attestation statement+        statementAlg :: Cose.CoseSignAlg,+        -- | The algorithm of the credentialPublicKey in authenticatorData+        credentialAlg :: Cose.CoseSignAlg+      }   | -- | The statement key cannot verify the signature over the attested     -- credential data and client data for self attestation-    VerificationErrorInvalidSignature Text+    InvalidSignature Text   | -- | The statement certificate cannot verify the signature over the attested     -- credential data and client data for nonself attestation-    VerificationErrorVerificationFailure X509.SignatureFailure+    VerificationFailure X509.SignatureFailure   | -- | The certificate does not meet the requirements layed out in the     -- webauthn specification     -- https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation-cert-requirements-    VerificationErrorCertificateRequirementsUnmet-  | -- | The (supposedly) ASN1 encoded certificate extension could not be-    -- decoded-    VerificationErrorASN1Error ASN1Error-  | -- | The certificate extension does not contain a AAGUID-    VerificationErrorCredentialAAGUIDMissing+    CertificateRequirementsUnmet   | -- | The AAGUID in the certificate extension does not match the AAGUID in     -- the authenticator data-    VerificationErrorCertificateAAGUIDMismatch+    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+      }   deriving (Show, Exception)  instance M.AttestationStatementFormat Format where@@ -136,25 +142,25 @@           -- Validate that alg matches the algorithm of the credentialPublicKey in authenticatorData.           let key = M.acdCredentialPublicKey credData               signAlg = Cose.keySignAlg key-          when (stmtAlg /= signAlg) $ failure VerificationErrorAlgorithmMismatch+          when (stmtAlg /= signAlg) . failure $ AlgorithmMismatch stmtAlg signAlg            -- Verify that sig is a valid signature over the concatenation of           -- authenticatorData and clientDataHash using the credential public key with alg.           case Cose.verify signAlg (Cose.fromCose key) signedData stmtSig of             Right () -> pure ()-            Left err -> failure $ VerificationErrorInvalidSignature err+            Left err -> failure $ InvalidSignature err            pure $ M.SomeAttestationType M.AttestationTypeSelf          -- Basic, AttCA-        Just (x5c@(certCred :| _), IdFidoGenCeAAGUID credAAGUID) -> do+        Just (x5c@(certCred :| _), IdFidoGenCeAAGUID certAAGUID) -> do           let cert = X509.getCertificate certCred               pubKey = X509.certPubKey cert           -- Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash using           -- the attestation public key in attestnCert with the algorithm specified in alg.           case X509.verifySignature (X509.SignatureALG X509.HashSHA256 X509.PubKeyALG_EC) pubKey signedData stmtSig of             X509.SignaturePass -> pure ()-            X509.SignatureFailed err -> failure $ VerificationErrorVerificationFailure err+            X509.SignatureFailed err -> failure $ VerificationFailure err            -- Verify that attestnCert meets the requirements in § 8.2.1 Packed Attestation Statement Certificate           -- Requirements.@@ -165,12 +171,12 @@                 && hasDnElement X509.DnCommonName dnElements                 && findDnElement X509.DnOrganizationUnit dnElements == Just "Authenticator Attestation"             )-            $ failure VerificationErrorCertificateRequirementsUnmet+            $ failure CertificateRequirementsUnmet            -- If attestnCert contains an extension with OID 1.3.6.1.4.1.45724.1.1.4 (id-fido-gen-ce-aaguid) verify that           -- the value of this extension matches the aaguid in authenticatorData.           let aaguid = M.acdAaguid credData-          unless (aaguid == credAAGUID) $ failure VerificationErrorCertificateAAGUIDMismatch+          unless (certAAGUID == aaguid) . failure $ CertificateAAGUIDMismatch certAAGUID aaguid            pure $             M.SomeAttestationType $
src/Crypto/WebAuthn/AttestationStatementFormat/TPM.hs view
@@ -11,6 +11,8 @@   ( format,     Format (..),     VerificationError (..),+    -- Exported because it's part of an error constructor+    TPMAlgId (..),   ) where @@ -24,6 +26,7 @@ import qualified Crypto.WebAuthn.Cose.Internal.Verify as Cose import qualified Crypto.WebAuthn.Cose.Key as Cose import Crypto.WebAuthn.Internal.Utils (IdFidoGenCeAAGUID (IdFidoGenCeAAGUID), failure)+import Crypto.WebAuthn.Model.Identifier (AAGUID) import qualified Crypto.WebAuthn.Model.Types as M import Data.ASN1.Error (ASN1Error) import Data.ASN1.OID (OID)@@ -233,48 +236,69 @@ data VerificationError   = -- | The public key in the certificate is different from the on in the     -- attested credential data-    VerificationErrorCredentialKeyMismatch-  | -- | The magic number in certInfo was not set to TPM_GENERATED_VALUE-    VerificationErrorInvalidMagicNumber Word32-  | -- | The type in certInfo was not set to TPM_ST_ATTEST_CERTIFY-    VerificationErrorInvalidType Word16+    PublicKeyMismatch+      { -- | The public key extracted from the certificate+        certificatePublicKey :: Cose.PublicKey,+        -- | The public key part of the credential data+        credentialDataPublicKey :: 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)+    TypeInvalid Word16   | -- | The algorithm specified in the nameAlg field is unsupported or is not     -- a valid name algorithm-    VerificationErrorInvalidNameAlgorithm+    NameAlgorithmInvalid TPMAlgId   | -- | The calulated name does not match the provided name.-    -- (first: expected, second: received)-    VerificationErrorInvalidName BS.ByteString BS.ByteString+    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 public key in the certificate was invalid, either because the it     -- had an unexpected algorithm, or because it was otherwise malformed-    VerificationErrorInvalidPublicKey Text-  | -- | The certificate didn't have the expected version-value-    -- (first: expected, second: received)-    VerificationErrorCertificateVersion Int Int+    PublicKeyInvalid Text+  | -- | The certificate didn't have the expected version-value (2)+    CertificateVersionInvalid Int   | -- | The Public key cannot verify the signature over the authenticatorData     -- and the clientDataHash.-    VerificationErrorVerificationFailure Text+    VerificationFailure Text   | -- | The subject field was not empty-    VerificationErrorNonEmptySubjectField+    SubjectFieldNotEmpty [(OID, X509.ASN1CharacterString)]   | -- | The vendor was unknown-    VerificationErrorUnknownVendor+    VendorUnknown Text   | -- | The Extended Key Usage did not contain the 2.23.133.8.3 OID-    VerificationErrorExtKeyOIDMissing+    ExtKeyOIDMissing   | -- | The CA component of the basic constraints extension was set to True-    VerificationErrorBasicConstraintsTrue-  | -- | The AAGUID in the certificate extension does not match the AAGUID in-    -- the authenticator data-    VerificationErrorCertificateAAGUIDMismatch+    BasicConstraintsTrue+  | -- | 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+      }   | -- | The (supposedly) ASN1 encoded certificate extension could not be     -- decoded-    VerificationErrorASN1Error ASN1Error+    ASN1Error ASN1Error   | -- | The certificate extension does not contain a AAGUID-    VerificationErrorCredentialAAGUIDMissing+    CredentialAAGUIDMissing   | -- | The desired algorithm does not have a known associated hash function-    VerificationErrorUnknownHashFunction+    HashFunctionUnknown   | -- | The calculated hash over the attToBeSigned does not match the received     -- hash-    -- (first: calculated, second: received)-    VerificationErrorHashMismatch BS.ByteString BS.ByteString+    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+      }   deriving (Show, Exception)  -- [(spec)](https://www.trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf)@@ -499,7 +523,7 @@       -- fields of pubArea is identical to the credentialPublicKey in the       -- attestedCredentialData in authenticatorData.       let pubKey = Cose.fromCose $ M.acdCredentialPublicKey adAttestedCredentialData-      unless (pubKey == pubAreaKey) $ failure VerificationErrorCredentialKeyMismatch+      unless (pubAreaKey == pubKey) . failure $ PublicKeyMismatch pubAreaKey pubKey        -- 3. Concatenate authenticatorData and clientDataHash to form attToBeSigned.       let attToBeSigned = adRawData <> BA.convert (M.unClientDataHash clientDataHash)@@ -507,20 +531,20 @@       -- 4. Validate that certInfo is valid:       -- 4.1 Verify that magic is set to TPM_GENERATED_VALUE.       let magic = tpmsaMagic certInfo-      unless (magic == tpmGeneratedValue) . failure $ VerificationErrorInvalidMagicNumber magic+      unless (magic == tpmGeneratedValue) . failure $ MagicNumberInvalid magic        -- 4.2 Verify that type is set to TPM_ST_ATTEST_CERTIFY.       let typ = tpmsaType certInfo-      unless (typ == tpmStAttestCertify) . failure $ VerificationErrorInvalidType typ+      unless (typ == tpmStAttestCertify) . failure $ TypeInvalid typ        -- 4.3 Verify that extraData is set to the hash of attToBeSigned using       -- the hash algorithm employed in "alg".       case hashWithCorrectAlgorithm alg attToBeSigned of         Just attHash -> do           let extraData = tpmsaExtraData certInfo-          unless (attHash == extraData) . failure $ VerificationErrorHashMismatch attHash extraData+          unless (attHash == extraData) . failure $ HashMismatch attHash extraData           pure ()-        Nothing -> failure VerificationErrorUnknownHashFunction+        Nothing -> failure HashFunctionUnknown        -- 4.5 Verify that attested contains a TPMS_CERTIFY_INFO structure as       -- specified in [TPMv2-Part2] section 10.12.3, whose name field contains@@ -528,22 +552,22 @@       -- nameAlg field of pubArea using the procedure specified in       -- [TPMv2-Part1] section 16.       let mPubAreaHash = case tpmtpNameAlg pubArea of-            TPMAlgSHA1 -> Just $ BA.convert $ hashWith SHA1 pubAreaRaw-            TPMAlgSHA256 -> Just $ BA.convert $ hashWith SHA256 pubAreaRaw-            TPMAlgECC -> Nothing-            TPMAlgRSA -> Nothing+            TPMAlgSHA1 -> Right $ BA.convert $ hashWith SHA1 pubAreaRaw+            TPMAlgSHA256 -> Right $ BA.convert $ hashWith SHA256 pubAreaRaw+            TPMAlgECC -> Left TPMAlgECC+            TPMAlgRSA -> Left TPMAlgRSA        case mPubAreaHash of-        Just pubAreaHash -> do+        Right pubAreaHash -> do           let pubName = LBS.toStrict $                 Put.runPut $ do                   Put.putWord16be (tpmtpNameAlgRaw pubArea)                   Put.putByteString pubAreaHash            let name = tpmsciName (tpmsaAttested certInfo)-          unless (name == pubName) . failure $ VerificationErrorInvalidName pubName name+          unless (name == pubName) . failure $ NameMismatch pubName name           pure ()-        Nothing -> failure VerificationErrorInvalidNameAlgorithm+        Left alg -> failure $ NameAlgorithmInvalid alg        -- 4.6 Verify that x5c is present       -- NOTE: Done in decoding@@ -560,8 +584,8 @@       case Cose.fromX509 $ X509.certPubKey unsignedAikCert of         Right certPubKey -> case Cose.verify alg certPubKey certInfoRaw sig of           Right () -> pure ()-          Left err -> failure $ VerificationErrorVerificationFailure err-        Left err -> failure $ VerificationErrorInvalidPublicKey err+          Left err -> failure $ VerificationFailure err+        Left err -> failure $ PublicKeyInvalid err        -- 4.9 Verify that aikCert meets the requirements in § 8.3.1 TPM Attestation       -- Statement Certificate Requirements.@@ -569,23 +593,25 @@       -- 4.9.1 Version MUST be set to 3.       -- Version ::= INTEGER { v1(0), v2(1), v3(2) }, see https://datatracker.ietf.org/doc/html/rfc5280.html#section-4.1       let version = X509.certVersion unsignedAikCert-      unless (version == 2) . failure $ VerificationErrorCertificateVersion 2 version+      unless (version == 2) . failure $ CertificateVersionInvalid version       -- 4.9.2. Subject field MUST be set to empty.-      unless (null . X509.getDistinguishedElements $ X509.certSubjectDN unsignedAikCert) $ failure VerificationErrorNonEmptySubjectField+      let subject = X509.getDistinguishedElements $ X509.certSubjectDN unsignedAikCert+      unless (null subject) . failure $ SubjectFieldNotEmpty subject       -- 4.9.3 The Subject Alternative Name extension MUST be set as defined in       -- [TPMv2-EK-Profile] section 3.2.9.       -- 4.9.3.1 The TPM manufacturer identifies the manufacturer of the TPM. This value MUST be the       -- vendor ID defined in the TCG Vendor ID Registry[3]-      unless (Set.member (tpmManufacturer subjectAlternativeName) tpmManufacturers) $ failure VerificationErrorUnknownVendor+      let vendor = tpmManufacturer subjectAlternativeName+      unless (Set.member vendor tpmManufacturers) . failure $ VendorUnknown vendor        -- 4.9.4 The Extended Key Usage extension MUST contain the OID       -- 2.23.133.8.3 ("joint-iso-itu-t(2) internationalorganizations(23) 133       -- tcg-kp(8) tcg-kp-AIKCertificate(3)").-      unless (X509.KeyUsagePurpose_Unknown [2, 23, 133, 8, 3] `elem` extendedKeyUsage) $ failure VerificationErrorExtKeyOIDMissing+      unless (X509.KeyUsagePurpose_Unknown [2, 23, 133, 8, 3] `elem` extendedKeyUsage) $ failure ExtKeyOIDMissing        -- 4.9.5 The Basic Constraints extension MUST have the CA component set       -- to false.-      when basicConstraintsCA $ failure VerificationErrorBasicConstraintsTrue+      when basicConstraintsCA $ failure BasicConstraintsTrue        -- 4.9.6 An Authority Information Access (AIA) extension with entry       -- id-ad-ocsp and a CRL Distribution Point extension [RFC5280] are both@@ -598,8 +624,10 @@       -- If aikCert contains an extension with OID 1.3.6.1.4.1.45724.1.1.4       -- (id-fido-gen-ce-aaguid) verify that the value of this extension       -- matches the aaguid in authenticatorData.+      let credentialAAGUID = M.acdAaguid adAttestedCredentialData       case aaguidExt of-        Just (IdFidoGenCeAAGUID aaguid) -> unless (M.acdAaguid adAttestedCredentialData == aaguid) $ failure VerificationErrorCertificateAAGUIDMismatch+        Just (IdFidoGenCeAAGUID aaguid) -> do+          unless (aaguid == credentialAAGUID) . failure $ CertificateAAGUIDMismatch aaguid credentialAAGUID         Nothing -> pure ()        pure $
src/Crypto/WebAuthn/Internal/Utils.hs view
@@ -5,10 +5,8 @@ -- -- Internal utilities module Crypto.WebAuthn.Internal.Utils-  ( JSONEncoding,-    EnumJSONEncoding,-    Aeson.CustomJSON (..),-    Lowercase,+  ( jsonEncodingOptions,+    enumJSONEncodingOptions,     failure,     certificateSubjectKeyIdentifier,     IdFidoGenCeAAGUID (..),@@ -23,32 +21,37 @@ import qualified Data.ASN1.Parse as ASN1 import Data.ASN1.Prim (ASN1 (OctetString)) import qualified Data.ASN1.Types as ASN1+import qualified Data.Aeson as Aeson import Data.Bifunctor (first) import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS import Data.Char (toLower)+import Data.List (stripPrefix) import Data.List.NonEmpty (NonEmpty)+import Data.Maybe (fromMaybe) import qualified Data.UUID as UUID import Data.Validation (Validation (Failure)) import Data.X509 (Extension) import qualified Data.X509 as X509-import qualified Deriving.Aeson as Aeson-import GHC.TypeLits (Symbol) --- | Custom JSONEncoding for use in the library. We add a "lit" prefix to every+-- | Custom Aeson Options for use in the library. We add a "lit" prefix to every -- field that would otherwise be a Haskell keyword.-type JSONEncoding = Aeson.CustomJSON '[Aeson.OmitNothingFields, Aeson.FieldLabelModifier (Aeson.StripPrefix "lit")]---- | Type for 'Aeson.StringModifier' that makes all characters lowercase-data Lowercase---- | Deriving.Aeson instance turning a string into lowercase.-instance Aeson.StringModifier Lowercase where-  getStringModifier = map toLower+jsonEncodingOptions :: Aeson.Options+jsonEncodingOptions =+  Aeson.defaultOptions+    { Aeson.omitNothingFields = True,+      Aeson.fieldLabelModifier = \l -> fromMaybe l $ stripPrefix "lit" l+    }  -- | Custom JSON Encoding for enumerations, strips the given prefix and maps -- all constructors to lowercase.-type EnumJSONEncoding (prefix :: Symbol) = Aeson.CustomJSON '[Aeson.ConstructorTagModifier '[Aeson.StripPrefix prefix, Lowercase]]+enumJSONEncodingOptions :: String -> Aeson.Options+enumJSONEncodingOptions prefix =+  Aeson.defaultOptions+    { Aeson.omitNothingFields = True,+      Aeson.fieldLabelModifier = \l -> fromMaybe l $ stripPrefix prefix l,+      Aeson.constructorTagModifier = \l -> map toLower . fromMaybe l $ stripPrefix prefix l+    }  -- | A convenience function for creating a 'Validation' failure of a single -- 'NonEmpty' value
src/Crypto/WebAuthn/Metadata/FidoRegistry.hs view
@@ -15,9 +15,8 @@   ) where -import Crypto.WebAuthn.Internal.Utils (EnumJSONEncoding)+import Crypto.WebAuthn.Internal.Utils (enumJSONEncodingOptions) import qualified Data.Aeson as Aeson-import Deriving.Aeson (CustomJSON (CustomJSON)) import GHC.Generics (Generic)  -- | [(spec)](https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#user-verification-methods)@@ -36,8 +35,13 @@   | USER_VERIFY_NONE   | USER_VERIFY_ALL   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via EnumJSONEncoding "USER_VERIFY_" UserVerificationMethod +instance Aeson.FromJSON UserVerificationMethod where+  parseJSON = Aeson.genericParseJSON $ enumJSONEncodingOptions "USER_VERIFY_"++instance Aeson.ToJSON UserVerificationMethod where+  toJSON = Aeson.genericToJSON $ enumJSONEncodingOptions "USER_VERIFY_"+ -- | [(spec)](https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#key-protection-types) data KeyProtectionType   = KEY_PROTECTION_SOFTWARE@@ -46,16 +50,26 @@   | KEY_PROTECTION_SECURE_ELEMENT   | KEY_PROTECTION_REMOTE_HANDLE   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via EnumJSONEncoding "KEY_PROTECTION_" KeyProtectionType +instance Aeson.FromJSON KeyProtectionType where+  parseJSON = Aeson.genericParseJSON $ enumJSONEncodingOptions "KEY_PROTECTION_"++instance Aeson.ToJSON KeyProtectionType where+  toJSON = Aeson.genericToJSON $ enumJSONEncodingOptions "KEY_PROTECTION_"+ -- | [(spec)](https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#matcher-protection-types) data MatcherProtectionType   = MATCHER_PROTECTION_SOFTWARE   | MATCHER_PROTECTION_TEE   | MATCHER_PROTECTION_ON_CHIP   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via EnumJSONEncoding "MATCHER_PROTECTION_" MatcherProtectionType +instance Aeson.FromJSON MatcherProtectionType where+  parseJSON = Aeson.genericParseJSON $ enumJSONEncodingOptions "MATCHER_PROTECTION_"++instance Aeson.ToJSON MatcherProtectionType where+  toJSON = Aeson.genericToJSON $ enumJSONEncodingOptions "MATCHER_PROTECTION_"+ -- | [(spec)](https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#authenticator-attachment-hints) data AuthenticatorAttachmentHint   = ATTACHMENT_HINT_INTERNAL@@ -68,8 +82,13 @@   | ATTACHMENT_HINT_READY   | ATTACHMENT_HINT_WIFI_DIRECT   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via EnumJSONEncoding "ATTACHMENT_HINT_" AuthenticatorAttachmentHint +instance Aeson.FromJSON AuthenticatorAttachmentHint where+  parseJSON = Aeson.genericParseJSON $ enumJSONEncodingOptions "ATTACHMENT_HINT_"++instance Aeson.ToJSON AuthenticatorAttachmentHint where+  toJSON = Aeson.genericToJSON $ enumJSONEncodingOptions "ATTACHMENT_HINT_"+ -- | [(spec)](https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#transaction-confirmation-display-types) data TransactionConfirmationDisplayType   = TRANSACTION_CONFIRMATION_DISPLAY_ANY@@ -78,8 +97,13 @@   | TRANSACTION_CONFIRMATION_DISPLAY_HARDWARE   | TRANSACTION_CONFIRMATION_DISPLAY_REMOTE   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via EnumJSONEncoding "TRANSACTION_CONFIRMATION_DISPLAY_" TransactionConfirmationDisplayType +instance Aeson.FromJSON TransactionConfirmationDisplayType where+  parseJSON = Aeson.genericParseJSON $ enumJSONEncodingOptions "TRANSACTION_CONFIRMATION_DISPLAY_"++instance Aeson.ToJSON TransactionConfirmationDisplayType where+  toJSON = Aeson.genericToJSON $ enumJSONEncodingOptions "TRANSACTION_CONFIRMATION_DISPLAY_"+ -- | [(spec)](https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#authentication-algorithms) data AuthenticationAlgorithm   = ALG_SIGN_SECP256R1_ECDSA_SHA256_RAW@@ -101,8 +125,13 @@   | ALG_SIGN_SECP512R1_ECDSA_SHA512_RAW   | ALG_SIGN_ED25519_EDDSA_SHA512_RAW   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via EnumJSONEncoding "ALG_SIGN_" AuthenticationAlgorithm +instance Aeson.FromJSON AuthenticationAlgorithm where+  parseJSON = Aeson.genericParseJSON $ enumJSONEncodingOptions "ALG_SIGN_"++instance Aeson.ToJSON AuthenticationAlgorithm where+  toJSON = Aeson.genericToJSON $ enumJSONEncodingOptions "ALG_SIGN_"+ -- | [(spec)](https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#public-key-representation-formats) data PublicKeyRepresentationFormat   = ALG_KEY_ECC_X962_RAW@@ -111,8 +140,13 @@   | ALG_KEY_RSA_2048_DER   | ALG_KEY_COSE   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via EnumJSONEncoding "ALG_KEY_" PublicKeyRepresentationFormat +instance Aeson.FromJSON PublicKeyRepresentationFormat where+  parseJSON = Aeson.genericParseJSON $ enumJSONEncodingOptions "ALG_KEY_"++instance Aeson.ToJSON PublicKeyRepresentationFormat where+  toJSON = Aeson.genericToJSON $ enumJSONEncodingOptions "ALG_KEY_"+ -- | [(spec)](https://fidoalliance.org/specs/common-specs/fido-registry-v2.1-ps-20191217.html#authenticator-attestation-types) data AuthenticatorAttestationType   = ATTESTATION_BASIC_FULL@@ -120,4 +154,9 @@   | ATTESTATION_ECDAA   | ATTESTATION_ATTCA   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via EnumJSONEncoding "ATTESTATION_" AuthenticatorAttestationType++instance Aeson.FromJSON AuthenticatorAttestationType where+  parseJSON = Aeson.genericParseJSON $ enumJSONEncodingOptions "ATTESTATION_"++instance Aeson.ToJSON AuthenticatorAttestationType where+  toJSON = Aeson.genericToJSON $ enumJSONEncodingOptions "ATTESTATION_"
src/Crypto/WebAuthn/Metadata/Service/Processing.hs view
@@ -45,6 +45,7 @@ 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,@@ -56,22 +57,23 @@       ),     SubjectKeyIdentifier,   )-import Data.Aeson (Value (Object))-import qualified Data.Aeson as Aeson+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 Data.HashMap.Strict (HashMap, (!?)) import qualified Data.HashMap.Strict as HashMap import Data.Hourglass (DateTime) import qualified Data.List.NonEmpty as NE-import Data.Maybe (mapMaybe)-import Data.Singletons (SingI, sing)+import Data.Singletons (singByProxy) import Data.Text (Text) import qualified Data.Text as Text 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@@ -179,7 +181,7 @@ jwtToJson blob rootCert now = runExcept $ do   jwt <- decodeCompact $ LBS.fromStrict blob   claims <- runReaderT (verifyClaims (defaultJWTValidationSettings (const True)) rootCert jwt) now-  return $ claims ^. unregisteredClaims+  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@@ -188,12 +190,23 @@ -- and `Crypto.WebAuthn.Metadata.Service.Types.mpEntries` fields are most -- important. jsonToPayload :: HashMap Text Value -> Either Text Service.MetadataPayload-jsonToPayload value = case Aeson.fromJSON $ Object value of-  Aeson.Error err -> Left $ Text.pack err-  Aeson.Success payload -> case decodeMetadataPayload payload of+jsonToPayload value = case Aeson.parseEither metadataPayloadParser value of+  Left err -> Left $ Text.pack err+  Right payload -> case decodeMetadataPayload payload of     Left err -> Left err     Right result -> pure result +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"+ -- | Creates a 'Service.MetadataServiceRegistry' from a list of -- 'Service.SomeMetadataEntry', which can either be obtained from a -- 'Service.MetadataPayload's 'Service.mpEntries' field, or be constructed@@ -204,33 +217,14 @@ createMetadataRegistry :: [Service.SomeMetadataEntry] -> Service.MetadataServiceRegistry createMetadataRegistry entries = Service.MetadataServiceRegistry {..}   where-    fido2Entries = HashMap.fromList $ mapMaybe getFido2Pairs entries-    fidoU2FEntries = HashMap.fromList $ mapMaybe getFidoU2FPairs entries--    getFido2Pairs (Service.SomeMetadataEntry ident entry) = getFido2Pairs' ident entry-    getFidoU2FPairs (Service.SomeMetadataEntry ident entry) = getFidoU2FPairs' ident entry--    getFido2Pairs' ::-      forall p.-      SingI p =>-      AuthenticatorIdentifier p ->-      Service.MetadataEntry p ->-      Maybe (AAGUID, Service.MetadataEntry 'M.Fido2)-    getFido2Pairs' ident entry = case sing @p of-      M.SFido2 ->-        Just (idAaguid ident, entry)-      _ -> Nothing+    fido2Entries = HashMap.fromList fido2Pairs+    fidoU2FEntries = HashMap.fromList fidoU2FPairs+    (fido2Pairs, fidoU2FPairs) = partitionEithers $ map fromSomeMetadataEntry entries -    getFidoU2FPairs' ::-      forall p.-      SingI p =>-      AuthenticatorIdentifier p ->-      Service.MetadataEntry p ->-      Maybe (SubjectKeyIdentifier, Service.MetadataEntry 'M.FidoU2F)-    getFidoU2FPairs' ident entry = case sing @p of-      M.SFidoU2F ->-        Just (idSubjectKeyIdentifier ident, entry)-      _ -> Nothing+    fromSomeMetadataEntry :: Service.SomeMetadataEntry -> Either (AAGUID, Service.MetadataEntry 'M.Fido2) (SubjectKeyIdentifier, Service.MetadataEntry 'M.FidoU2F)+    fromSomeMetadataEntry (Service.SomeMetadataEntry ident entry) = case singByProxy ident of+      M.SFido2 -> Left (idAaguid ident, entry)+      M.SFidoU2F -> Right (idSubjectKeyIdentifier ident, entry)  -- | Query a 'Service.MetadataEntry' for an 'M.AuthenticatorIdentifier' queryMetadata ::
src/Crypto/WebAuthn/Metadata/Service/WebIDL.hs view
@@ -17,7 +17,7 @@ -- Unless otherwise specified, if a WebIDL dictionary member is DOMString, it MUST NOT be empty. -- Unless otherwise specified, if a WebIDL dictionary member is a List, it MUST NOT be an empty list. -import Crypto.WebAuthn.Internal.Utils (CustomJSON (CustomJSON), JSONEncoding)+import Crypto.WebAuthn.Internal.Utils (jsonEncodingOptions) import Crypto.WebAuthn.Metadata.Statement.WebIDL (AAGUID, MetadataStatement) import qualified Crypto.WebAuthn.Metadata.UAF as UAF import qualified Crypto.WebAuthn.WebIDL as IDL@@ -46,8 +46,13 @@     -- entryRogueListHash :: IDL.DOMString   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding MetadataBLOBPayloadEntry +instance Aeson.FromJSON MetadataBLOBPayloadEntry where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON MetadataBLOBPayloadEntry where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#biometricstatusreport-dictionary) data BiometricStatusReport = BiometricStatusReport   { -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#dom-biometricstatusreport-certlevel)@@ -66,8 +71,13 @@     certificationRequirementsVersion :: Maybe IDL.DOMString   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding BiometricStatusReport +instance Aeson.FromJSON BiometricStatusReport where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON BiometricStatusReport where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#statusreport-dictionary) data StatusReport = StatusReport   { -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#dom-statusreport-status)@@ -90,8 +100,13 @@     certificationRequirementsVersion :: Maybe IDL.DOMString   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding StatusReport +instance Aeson.FromJSON StatusReport where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON StatusReport where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#authenticatorstatus-enum) data AuthenticatorStatus   = NOT_FIDO_CERTIFIED@@ -110,8 +125,13 @@   | FIDO_CERTIFIED_L3   | FIDO_CERTIFIED_L3plus   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding AuthenticatorStatus +instance Aeson.FromJSON AuthenticatorStatus where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON AuthenticatorStatus where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#metadata-blob-payload-dictionary) data MetadataBLOBPayload = MetadataBLOBPayload   { -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#dom-metadatablobpayload-legalheader)@@ -124,4 +144,9 @@     entries :: [MetadataBLOBPayloadEntry]   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding MetadataBLOBPayload++instance Aeson.FromJSON MetadataBLOBPayload where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON MetadataBLOBPayload where+  toJSON = Aeson.genericToJSON jsonEncodingOptions
src/Crypto/WebAuthn/Metadata/Statement/WebIDL.hs view
@@ -26,7 +26,7 @@   ) where -import Crypto.WebAuthn.Internal.Utils (CustomJSON (CustomJSON), EnumJSONEncoding, JSONEncoding)+import Crypto.WebAuthn.Internal.Utils (enumJSONEncodingOptions, jsonEncodingOptions) import qualified Crypto.WebAuthn.Metadata.FidoRegistry as Registry import qualified Crypto.WebAuthn.Metadata.UAF as UAF import qualified Crypto.WebAuthn.WebIDL as IDL@@ -53,8 +53,13 @@     blockSlowdown :: Maybe IDL.UnsignedShort   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding CodeAccuracyDescriptor +instance Aeson.FromJSON CodeAccuracyDescriptor where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON CodeAccuracyDescriptor where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#biometricaccuracydescriptor-dictionary) data BiometricAccuracyDescriptor = BiometricAccuracyDescriptor   { -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#dom-biometricaccuracydescriptor-selfattestedfrr)@@ -69,8 +74,13 @@     blockSlowdown :: Maybe IDL.UnsignedShort   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding BiometricAccuracyDescriptor +instance Aeson.FromJSON BiometricAccuracyDescriptor where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON BiometricAccuracyDescriptor where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#patternaccuracydescriptor-dictionary) data PatternAccuracyDescriptor = PatternAccuracyDescriptor   { -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#dom-patternaccuracydescriptor-mincomplexity)@@ -84,8 +94,13 @@     blockSlowdown :: Maybe IDL.UnsignedShort   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding PatternAccuracyDescriptor +instance Aeson.FromJSON PatternAccuracyDescriptor where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON PatternAccuracyDescriptor where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#verificationmethoddescriptor-dictionary) data VerificationMethodDescriptor = VerificationMethodDescriptor   { -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#dom-verificationmethoddescriptor-userverificationmethod)@@ -98,8 +113,13 @@     paDesc :: Maybe PatternAccuracyDescriptor   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding VerificationMethodDescriptor +instance Aeson.FromJSON VerificationMethodDescriptor where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON VerificationMethodDescriptor where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#verificationmethodandcombinations-typedef) newtype VerificationMethodANDCombinations = VerificationMethodANDCombinations (NonEmpty VerificationMethodDescriptor)   deriving (Show, Eq)@@ -115,8 +135,13 @@     b :: IDL.UnsignedShort   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding RgbPaletteEntry +instance Aeson.FromJSON RgbPaletteEntry where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON RgbPaletteEntry where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#displaypngcharacteristicsdescriptor-dictionary) data DisplayPNGCharacteristicsDescriptor = DisplayPNGCharacteristicsDescriptor   { -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#dom-displaypngcharacteristicsdescriptor-width)@@ -137,8 +162,13 @@     plte :: Maybe (NonEmpty RgbPaletteEntry)   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding DisplayPNGCharacteristicsDescriptor +instance Aeson.FromJSON DisplayPNGCharacteristicsDescriptor where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON DisplayPNGCharacteristicsDescriptor where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#ecdaatrustanchor-dictionary) data EcdaaTrustAnchor = EcdaaTrustAnchor   { -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#dom-ecdaatrustanchor-x)@@ -155,8 +185,13 @@     litG1Curve :: IDL.DOMString   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding EcdaaTrustAnchor +instance Aeson.FromJSON EcdaaTrustAnchor where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON EcdaaTrustAnchor where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#extensiondescriptor-dictionary) data ExtensionDescriptor = ExtensionDescriptor   { -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#dom-extensiondescriptor-id)@@ -169,8 +204,13 @@     fail_if_unknown :: IDL.Boolean   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding ExtensionDescriptor +instance Aeson.FromJSON ExtensionDescriptor where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON ExtensionDescriptor where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://fidoalliance.org/specs/mds/fido-metadata-statement-v3.0-ps-20210518.html#alternativedescriptions-dictionary) -- TODO: Replace Text with -- <https://hackage.haskell.org/package/aeson-2.0.2.0/docs/Data-Aeson-Key.html#t:Key>@@ -253,12 +293,22 @@     authenticatorGetInfo :: Maybe AuthenticatorGetInfo   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding MetadataStatement +instance Aeson.FromJSON MetadataStatement where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON MetadataStatement where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | Possible FIDO protocol families for 'protocolFamily' data ProtocolFamily   = ProtocolFamilyUAF   | ProtocolFamilyU2F   | ProtocolFamilyFIDO2   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via EnumJSONEncoding "ProtocolFamily" ProtocolFamily++instance Aeson.FromJSON ProtocolFamily where+  parseJSON = Aeson.genericParseJSON $ enumJSONEncodingOptions "ProtocolFamily"++instance Aeson.ToJSON ProtocolFamily where+  toJSON = Aeson.genericToJSON $ enumJSONEncodingOptions "ProtocolFamily"
src/Crypto/WebAuthn/Metadata/UAF.hs view
@@ -8,11 +8,10 @@   ) where -import Crypto.WebAuthn.Internal.Utils (JSONEncoding)+import Crypto.WebAuthn.Internal.Utils (jsonEncodingOptions) import qualified Crypto.WebAuthn.WebIDL as IDL import qualified Data.Aeson as Aeson import Data.Text (Text)-import qualified Deriving.Aeson as Aeson import GHC.Generics (Generic)  -- | [(spec)](https://fidoalliance.org/specs/fido-uaf-v1.2-ps-20201020/fido-uaf-protocol-v1.2-ps-20201020.html#authenticator-attestation-id-aaid-typedef)@@ -35,4 +34,9 @@     minor :: IDL.UnsignedShort   }   deriving (Show, Eq, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding Version++instance Aeson.FromJSON Version where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON Version where+  toJSON = Aeson.genericToJSON jsonEncodingOptions
src/Crypto/WebAuthn/Model/Types.hs view
@@ -760,6 +760,33 @@ -- and -- [requesting](https://www.w3.org/TR/webauthn-2/#dictionary-assertion-options). -- The CeremonyKind araument specifies which.+--+-- Values of this type are send to the client to+-- [create](https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-create)+-- and+-- [get](https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-get)+-- a credential. After they have been sent, they have to be stored awaiting the+-- response from the client for further validation. At least the following+-- fields have to be stored, the others are not currently used.+--+--  For `Crypto.WebAuthn.Operation.Registration.verifyRegistrationResponse`:+--+-- - `corChallenge`+-- - `ascUserVerification` of `corAuthenticatorSelection`+-- - `corPubKeyCredParams`+-- - `corUser` (of which `cueId` is used in the+--   `Crypto.WebAuthn.Operation.Registration.verifyRegistrationResponse`, and it+--   and the other fields are need to be stored permanently by the relying party+--   as the user entity).+--+--  For `Crypto.WebAuthn.Operation.Authentication.verifyAuthenticationResponse`:+--+-- - `coaChallenge`+-- - `coaUserVerification`+-- - `coaAllowCredentials`+--+-- Depending on implementation choices by the RP, some of these fields might+-- additionally be constants, and could thus also be omitted when storing. data CredentialOptions (c :: CeremonyKind) where   -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dictionary-makecredentialoptions)   CredentialOptionsRegistration ::@@ -846,6 +873,7 @@       -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-extensions)       -- This OPTIONAL member contains additional parameters requesting additional processing by the client and authenticator.       -- For example, if transaction confirmation is sought from the user, then the prompt string might be included as an extension.+      -- TODO: Extensions are not implemented by this library, see "Crypto.WebAuthn.Model#extensions".       coaExtensions :: Maybe AuthenticationExtensionsClientInputs     } ->     CredentialOptions 'Authentication@@ -910,7 +938,7 @@   }   deriving (Eq, Show) -instance SingI c => ToJSON (CollectedClientData c raw) where+instance SingI c => ToJSON (CollectedClientData (c :: CeremonyKind) raw) where   toJSON CollectedClientData {..} =     object       [ "webauthnKind" .= sing @c,@@ -1114,7 +1142,14 @@  -- | Attempt to find the desired attestation statement format in a map of -- supported formats. Can then be used to perform attestation.-lookupAttestationStatementFormat :: Text -> SupportedAttestationStatementFormats -> Maybe SomeAttestationStatementFormat+lookupAttestationStatementFormat ::+  -- | The desired format, e.g. "android-safetynet" or "none"+  Text ->+  -- | The [attestation statement formats](https://www.w3.org/TR/webauthn-2/#sctn-attestation-formats)+  -- that should be supported. The value of 'Crypto.WebAuthn.allSupportedFormats'+  -- can be passed here, but additional or custom formats may also be used if needed.+  SupportedAttestationStatementFormats ->+  Maybe SomeAttestationStatementFormat lookupAttestationStatementFormat id (SupportedAttestationStatementFormats sasf) = sasf !? id  -- | [(spec)](https://www.w3.org/TR/webauthn-2/#attestation-object)
src/Crypto/WebAuthn/Model/WebIDL/Internal/Binary/Decoding.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -23,7 +24,7 @@ import Control.Monad.Except (MonadError (throwError)) import Control.Monad.State (MonadState (get, put), StateT (runStateT)) import qualified Crypto.Hash as Hash-import Crypto.WebAuthn.Internal.Utils (CustomJSON (CustomJSON), JSONEncoding)+import Crypto.WebAuthn.Internal.Utils (jsonEncodingOptions) import Crypto.WebAuthn.Model.Identifier (AAGUID (AAGUID)) import qualified Crypto.WebAuthn.Model.Kinds as K import qualified Crypto.WebAuthn.Model.Types as M@@ -216,14 +217,16 @@     -- tokenBinding :: Maybe TokenBinding   }   deriving (Generic)-  -- Note: Encoding should NOT be derived via aeson. See the Encoding module instead-  deriving (Aeson.FromJSON) via JSONEncoding ClientDataJSON +-- Note: Encoding should NOT be derived via aeson. See the Encoding module instead+instance Aeson.FromJSON ClientDataJSON where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions+ -- | Decodes a 'M.CollectedClientData' from a 'BS.ByteString'. This is needed -- to parse the [clientDataJSON](https://www.w3.org/TR/webauthn-2/#dom-authenticatorresponse-clientdatajson) -- field in the [AuthenticatorResponse](https://www.w3.org/TR/webauthn-2/#iface-authenticatorresponse) -- structure, which is used for both attestation and assertion-decodeCollectedClientData :: forall c. SingI c => BS.ByteString -> Either Text (M.CollectedClientData c 'True)+decodeCollectedClientData :: forall (c :: K.CeremonyKind). SingI c => BS.ByteString -> Either Text (M.CollectedClientData c 'True) decodeCollectedClientData bytes = do   -- https://www.w3.org/TR/webauthn-2/#collectedclientdata-json-compatible-serialization-of-client-data   ClientDataJSON {..} <-
src/Crypto/WebAuthn/Model/WebIDL/Internal/Binary/Encoding.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -42,7 +43,7 @@ -- | Encodes all raw fields of a 'M.Credential'. This function is -- mainly useful for testing that the encoding/decoding functions are correct. -- The counterpart to this function is 'Crypto.WebAuthn.Model.Binary.Decoding.stripRawCredential'-encodeRawCredential :: forall c raw. SingI c => M.Credential c raw -> M.Credential c 'True+encodeRawCredential :: forall (c :: K.CeremonyKind) raw. SingI c => M.Credential c raw -> M.Credential c 'True encodeRawCredential M.Credential {..} =   M.Credential     { cResponse = case sing @c of@@ -132,7 +133,7 @@         credentialLength :: Word16         credentialLength = fromIntegral $ BS.length $ M.unCredentialId acdCredentialId -    encodeRawAttestedCredentialData :: forall c raw. SingI c => M.AttestedCredentialData c raw -> M.AttestedCredentialData c 'True+    encodeRawAttestedCredentialData :: forall (c :: K.CeremonyKind) raw. SingI c => M.AttestedCredentialData c raw -> M.AttestedCredentialData c 'True     encodeRawAttestedCredentialData = case sing @c of       K.SRegistration -> \M.AttestedCredentialData {..} ->         M.AttestedCredentialData@@ -195,5 +196,5 @@  -- | Encodes an 'M.CollectedClientData' as a 'BS.ByteString'. This is needed by -- the client side to generate a valid JSON response-encodeCollectedClientData :: forall c. SingI c => M.CollectedClientData c 'True -> BS.ByteString+encodeCollectedClientData :: forall (c :: K.CeremonyKind). SingI c => M.CollectedClientData c 'True -> BS.ByteString encodeCollectedClientData M.CollectedClientData {..} = M.unRaw ccdRawData
src/Crypto/WebAuthn/Model/WebIDL/Internal/Decoding.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE RecordWildCards #-}  -- | Stability: internal@@ -56,7 +57,7 @@   -- module documentation of `Crypto.WebAuthn.Model` for more information.   decode _ = pure M.AuthenticationExtensionsClientOutputs {} -instance SingI c => Decode (M.CollectedClientData c 'True) where+instance SingI c => Decode (M.CollectedClientData (c :: K.CeremonyKind) 'True) where   decode (IDL.URLEncodedBase64 bytes) = B.decodeCollectedClientData bytes  instance Decode (M.AuthenticatorData 'K.Authentication 'True) where
src/Crypto/WebAuthn/Model/WebIDL/Internal/Encoding.hs view
@@ -185,7 +185,7 @@       }  -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-authenticatorresponse-clientdatajson)-instance SingI c => Encode (M.CollectedClientData c 'True) where+instance SingI c => Encode (M.CollectedClientData (c :: K.CeremonyKind) 'True) where   encode ccd = IDL.URLEncodedBase64 $ B.encodeCollectedClientData ccd  instance Encode (M.AuthenticatorResponse 'K.Authentication 'True) where
src/Crypto/WebAuthn/Model/WebIDL/Types.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE StandaloneDeriving #-}  -- | Stability: experimental -- This module models direct representations of JavaScript objects interacting with the@@ -42,7 +41,7 @@   ) where -import Crypto.WebAuthn.Internal.Utils (CustomJSON (CustomJSON), JSONEncoding)+import Crypto.WebAuthn.Internal.Utils (jsonEncodingOptions) import qualified Crypto.WebAuthn.WebIDL as IDL import qualified Data.Aeson as Aeson import Data.Map (Map)@@ -71,8 +70,13 @@     extensions :: Maybe (Map Text Aeson.Value)   }   deriving (Eq, Show, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding PublicKeyCredentialCreationOptions +instance Aeson.FromJSON PublicKeyCredentialCreationOptions where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON PublicKeyCredentialCreationOptions where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dictionary-rp-credential-params) data PublicKeyCredentialRpEntity = PublicKeyCredentialRpEntity   { -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrpentity-id)@@ -81,8 +85,13 @@     name :: IDL.DOMString   }   deriving (Eq, Show, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding PublicKeyCredentialRpEntity +instance Aeson.FromJSON PublicKeyCredentialRpEntity where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON PublicKeyCredentialRpEntity where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dictionary-user-credential-params) data PublicKeyCredentialUserEntity = PublicKeyCredentialUserEntity   { -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialuserentity-id)@@ -93,8 +102,13 @@     name :: IDL.DOMString   }   deriving (Eq, Show, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding PublicKeyCredentialUserEntity +instance Aeson.FromJSON PublicKeyCredentialUserEntity where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON PublicKeyCredentialUserEntity where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dictionary-credential-params) data PublicKeyCredentialParameters = PublicKeyCredentialParameters   { -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialparameters-type)@@ -103,8 +117,13 @@     alg :: COSEAlgorithmIdentifier   }   deriving (Eq, Show, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding PublicKeyCredentialParameters +instance Aeson.FromJSON PublicKeyCredentialParameters where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON PublicKeyCredentialParameters where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://www.w3.org/TR/webauthn-2/#sctn-alg-identifier) type COSEAlgorithmIdentifier = IDL.Long @@ -118,8 +137,13 @@     transports :: Maybe [IDL.DOMString]   }   deriving (Eq, Show, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding PublicKeyCredentialDescriptor +instance Aeson.FromJSON PublicKeyCredentialDescriptor where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON PublicKeyCredentialDescriptor where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dictdef-authenticatorselectioncriteria) data AuthenticatorSelectionCriteria = AuthenticatorSelectionCriteria   { -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-authenticatorselectioncriteria-authenticatorattachment)@@ -132,8 +156,13 @@     userVerification :: Maybe IDL.DOMString   }   deriving (Eq, Show, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding AuthenticatorSelectionCriteria +instance Aeson.FromJSON AuthenticatorSelectionCriteria where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON AuthenticatorSelectionCriteria where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dictionary-assertion-options) data PublicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions   { -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialrequestoptions-challenge)@@ -150,8 +179,13 @@     extensions :: Maybe (Map Text Aeson.Value)   }   deriving (Eq, Show, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding PublicKeyCredentialRequestOptions +instance Aeson.FromJSON PublicKeyCredentialRequestOptions where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON PublicKeyCredentialRequestOptions where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://www.w3.org/TR/webauthn-2/#iface-pkcredential) data PublicKeyCredential response = PublicKeyCredential   { -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-publickeycredential-identifier-slot)@@ -163,17 +197,11 @@   }   deriving (Eq, Show, Generic) -deriving via-  JSONEncoding (PublicKeyCredential response)-  instance-    Aeson.FromJSON response =>-    Aeson.FromJSON (PublicKeyCredential response)+instance Aeson.FromJSON response => Aeson.FromJSON (PublicKeyCredential response) where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions -deriving via-  JSONEncoding (PublicKeyCredential response)-  instance-    Aeson.ToJSON response =>-    Aeson.ToJSON (PublicKeyCredential response)+instance Aeson.ToJSON response => Aeson.ToJSON (PublicKeyCredential response) where+  toJSON = Aeson.genericToJSON jsonEncodingOptions  -- | [(spec)](https://www.w3.org/TR/webauthn-2/#iface-authenticatorattestationresponse) data AuthenticatorAttestationResponse = AuthenticatorAttestationResponse@@ -187,8 +215,13 @@     transports :: Maybe [IDL.DOMString]   }   deriving (Eq, Show, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding AuthenticatorAttestationResponse +instance Aeson.FromJSON AuthenticatorAttestationResponse where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON AuthenticatorAttestationResponse where+  toJSON = Aeson.genericToJSON jsonEncodingOptions+ -- | [(spec)](https://www.w3.org/TR/webauthn-2/#iface-authenticatorassertionresponse) data AuthenticatorAssertionResponse = AuthenticatorAssertionResponse   { -- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-authenticatorresponse-clientdatajson)@@ -201,4 +234,9 @@     userHandle :: Maybe IDL.ArrayBuffer   }   deriving (Eq, Show, Generic)-  deriving (Aeson.FromJSON, Aeson.ToJSON) via JSONEncoding AuthenticatorAssertionResponse++instance Aeson.FromJSON AuthenticatorAssertionResponse where+  parseJSON = Aeson.genericParseJSON jsonEncodingOptions++instance Aeson.ToJSON AuthenticatorAssertionResponse where+  toJSON = Aeson.genericToJSON jsonEncodingOptions
tests/MetadataSpec.hs view
@@ -4,18 +4,19 @@ module MetadataSpec (spec) where  import Crypto.WebAuthn.Metadata.Service.Processing (RootCertificate (RootCertificate), fidoAllianceRootCertificate, jsonToPayload, jwtToJson)-import Crypto.WebAuthn.Metadata.Service.WebIDL (MetadataBLOBPayload)-import Data.Aeson (Result (Success), ToJSON (toJSON), Value (Object), decodeFileStrict, fromJSON)+import Crypto.WebAuthn.Metadata.Service.WebIDL (MetadataBLOBPayload, entries, legalHeader, nextUpdate, no)+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 qualified Data.X509 as X509 import qualified Data.X509.CertificateStore as X509 import System.Hourglass (dateCurrent)-import Test.Hspec (SpecWith, describe, it, shouldSatisfy)+import Test.Hspec (SpecWith, describe, it, shouldBe, shouldSatisfy) import Test.Hspec.Expectations.Json (shouldBeUnorderedJson)  golden :: FilePath -> SpecWith ()@@ -34,7 +35,10 @@      Just expectedPayload <- decodeFileStrict $ "tests/golden-metadata/" <> subdir <> "/payload.json" -    Object result `shouldBeUnorderedJson` expectedPayload+    (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)    it "can decode and reencode the payload to the partially parsed JSON" $ do     Just payload <- decodeFileStrict $ "tests/golden-metadata/" <> subdir <> "/payload.json"
webauthn.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: webauthn-version: 0.1.1.0+version: 0.2.0.0 license: Apache-2.0 license-file: LICENSE copyright:@@ -17,11 +17,19 @@ bug-reports: https://github.com/tweag/webauthn/issues synopsis: Relying party (server) implementation of the WebAuthn 2 specification description:-  Implements the [Relying Party conformance class](https://www.w3.org/TR/webauthn-2/#sctn-conforming-relying-parties)+  == About+  This library implements the [Relying Party conformance class](https://www.w3.org/TR/webauthn-2/#sctn-conforming-relying-parties)   of the [Web Authentication Level 2](https://www.w3.org/TR/webauthn-2/) specification.   This allows web applications to create strong, attested, scoped, public key-based   credentials for the purpose of strongly authenticating users.   .+  == Getting started+  The "Crypto.WebAuthn" module and its documentation is the best place to get+  started with the library.+  The example server: [Main.hs](https://github.com/tweag/webauthn/blob/master/server/src/Main.hs)+  shows how this module may be used to implement a relying party.+  .+  == Stability   While the general design of the library won't change, it's still in an alpha   state, so smaller breaking changes should be expected for now. We will   however follow the [PVP](https://pvp.haskell.org/) and properly label changes@@ -66,9 +74,7 @@   hs-source-dirs: src   build-depends:     base                  >= 4.14.3 && < 4.15,-    -- deriving-aeson has compilation performance problems with aeson >= 2.0,-    -- see https://github.com/fumieval/deriving-aeson/issues/16-    aeson                 >= 1.4.7.1 && < 2.0,+    aeson                 >= 1.4.7.1 && < 2.1,     asn1-encoding         >= 0.9.6 && < 0.10,     asn1-parse            >= 0.9.5 && < 0.10,     asn1-types            >= 0.3.4 && < 0.4,@@ -79,11 +85,10 @@     cborg                 >= 0.2.6 && < 0.3,     containers            >= 0.6.5 && < 0.7,     cryptonite            >= 0.29 && < 0.30,-    deriving-aeson        >= 0.2.8 && < 0.3,     file-embed            >= 0.0.15 && < 0.1,-    hashable              >= 1.3.0 && < 1.4,+    hashable              >= 1.3.0 && < 1.5,     hourglass             >= 0.2.12 && < 0.3,-    jose                  >= 0.8.5 && < 0.9,+    jose                  >= 0.8.5 && < 0.10,     lens                  >= 4.19.2 && < 4.20,     memory                >= 0.15.0 && < 0.16,     monad-time            >= 0.3.1 && < 0.4,@@ -181,6 +186,7 @@     serialise,     singletons,     text,+    unordered-containers,     uuid,     validation,     webauthn,