webauthn-0.1.0.0: src/Crypto/WebAuthn/Model/WebIDL/Internal/Decoding.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RecordWildCards #-}
-- | Stability: internal
-- This module handles the decoding of structures returned by the
-- [create()](https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-create)
-- and [get()](https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-get)
-- methods while [Registering a New Credential](https://www.w3.org/TR/webauthn-2/#sctn-registering-a-new-credential)
-- and [Verifying an Authentication Assertion](https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion) respectively.
module Crypto.WebAuthn.Model.WebIDL.Internal.Decoding
( Decode (..),
DecodeCreated (..),
)
where
import qualified Crypto.WebAuthn.Cose.Algorithm as Cose
import qualified Crypto.WebAuthn.Model.Kinds as K
import qualified Crypto.WebAuthn.Model.Types as M
import qualified Crypto.WebAuthn.Model.WebIDL.Internal.Binary.Decoding as B
import Crypto.WebAuthn.Model.WebIDL.Internal.Convert (Convert (IDL))
import qualified Crypto.WebAuthn.Model.WebIDL.Types as IDL
import qualified Crypto.WebAuthn.WebIDL as IDL
import Data.Coerce (Coercible, coerce)
import Data.Maybe (catMaybes, mapMaybe)
import Data.Singletons (SingI)
import Data.Text (Text)
-- | @'Decode' a@ indicates that the Haskell-specific type @a@ can be
-- decoded from the more generic JavaScript type @'IDL' a@ with the 'decode' function.
class Convert a => Decode a where
decode :: IDL a -> Either Text a
default decode :: Coercible (IDL a) a => IDL a -> Either Text a
decode = pure . coerce
-- | Like 'Decode', but with a 'decodeCreated' function that also takes a
-- 'M.SupportedAttestationStatementFormats' in order to allow decoding to depend
-- on the supported attestation formats.
class Convert a => DecodeCreated a where
decodeCreated :: M.SupportedAttestationStatementFormats -> IDL a -> Either Text a
instance Decode a => Decode (Maybe a) where
decode Nothing = pure Nothing
decode (Just a) = Just <$> decode a
instance Decode M.CredentialId
instance Decode M.AssertionSignature
instance Decode M.UserHandle
instance Decode M.AuthenticationExtensionsClientOutputs where
-- TODO: Extensions are not implemented by this library, see the TODO in the
-- module documentation of `Crypto.WebAuthn.Model` for more information.
decode _ = pure M.AuthenticationExtensionsClientOutputs {}
instance SingI c => Decode (M.CollectedClientData c 'True) where
decode (IDL.URLEncodedBase64 bytes) = B.decodeCollectedClientData bytes
instance Decode (M.AuthenticatorData 'K.Authentication 'True) where
decode (IDL.URLEncodedBase64 bytes) = B.decodeAuthenticatorData bytes
instance Decode (M.AuthenticatorResponse 'K.Authentication 'True) where
decode IDL.AuthenticatorAssertionResponse {..} = do
araClientData <- decode clientDataJSON
araAuthenticatorData <- decode authenticatorData
araSignature <- decode signature
araUserHandle <- decode userHandle
pure $ M.AuthenticatorResponseAuthentication {..}
instance Decode (M.Credential 'K.Authentication 'True) where
decode IDL.PublicKeyCredential {..} = do
cIdentifier <- decode rawId
cResponse <- decode response
cClientExtensionResults <- decode clientExtensionResults
pure $ M.Credential {..}
instance Decode M.RpId
instance Decode M.RelyingPartyName
instance Decode M.CredentialRpEntity where
decode IDL.PublicKeyCredentialRpEntity {..} = do
creId <- decode id
creName <- decode name
pure $ M.CredentialRpEntity {..}
instance Decode M.UserAccountDisplayName
instance Decode M.UserAccountName
instance Decode M.CredentialUserEntity where
decode IDL.PublicKeyCredentialUserEntity {..} = do
cueId <- decode id
cueDisplayName <- decode displayName
cueName <- decode name
pure $ M.CredentialUserEntity {..}
instance Decode M.Challenge
instance Decode Cose.CoseSignAlg where
-- The specification does not inspect the algorithm until
-- assertion/attestation. We implement the check here to go to a Haskell
-- type. Erring on the side of caution by failing to parse if an unsupported
-- alg was encountered.
decode = Cose.toCoseSignAlg
instance Decode M.Timeout
-- | [(spec)](https://www.w3.org/TR/webauthn-2/#enum-transport)
instance Decode [M.AuthenticatorTransport] where
decode = pure . mapMaybe decodeTransport
where
decodeTransport "usb" = Just M.AuthenticatorTransportUSB
decodeTransport "nfc" = Just M.AuthenticatorTransportNFC
decodeTransport "ble" = Just M.AuthenticatorTransportBLE
decodeTransport "internal" = Just M.AuthenticatorTransportInternal
decodeTransport _ = Nothing
-- | [(spec)](https://www.w3.org/TR/webauthn-2/#dictionary-credential-descriptor)
-- [The type] member contains the type of the public key credential the caller
-- is referring to. The value SHOULD be a member of
-- PublicKeyCredentialType but client platforms MUST ignore any
-- PublicKeyCredentialDescriptor with an unknown type.
instance Decode [M.CredentialDescriptor] where
decode Nothing = pure []
decode (Just xs) = catMaybes <$> traverse decodeDescriptor xs
where
decodeDescriptor :: IDL.PublicKeyCredentialDescriptor -> Either Text (Maybe M.CredentialDescriptor)
decodeDescriptor IDL.PublicKeyCredentialDescriptor {littype = "public-key", ..} = do
let cdTyp = M.CredentialTypePublicKey
cdId <- decode id
cdTransports <- decode transports
pure . Just $ M.CredentialDescriptor {..}
decodeDescriptor _ = pure Nothing
-- | [(spec)](https://www.w3.org/TR/webauthn-2/#enum-userVerificationRequirement)
-- The value SHOULD be a member of UserVerificationRequirement but client
-- platforms MUST ignore unknown values, treating an unknown value as if the
-- member does not exist. The default is "preferred".
instance Decode M.UserVerificationRequirement where
decode (Just "discouraged") = Right M.UserVerificationRequirementDiscouraged
decode (Just "preferred") = Right M.UserVerificationRequirementPreferred
decode (Just "required") = Right M.UserVerificationRequirementRequired
decode _ = Right M.UserVerificationRequirementPreferred
-- | [(spec)](https://www.w3.org/TR/webauthn-2/#dictionary-authenticatorSelection)
instance Decode M.AuthenticatorSelectionCriteria where
decode IDL.AuthenticatorSelectionCriteria {..} = do
let ascAuthenticatorAttachment = decodeAttachment =<< authenticatorAttachment
ascResidentKey = decodeResidentKey residentKey
ascUserVerification <- decode userVerification
pure $ M.AuthenticatorSelectionCriteria {..}
where
-- Any unknown values must be ignored, treating them as if the member does not exist
decodeAttachment "platform" = Just M.AuthenticatorAttachmentPlatform
decodeAttachment "cross-platform" = Just M.AuthenticatorAttachmentCrossPlatform
decodeAttachment _ = Nothing
-- [(spec)](https://www.w3.org/TR/webauthn-2/#dom-authenticatorselectioncriteria-residentkey)
-- The value SHOULD be a member of ResidentKeyRequirement but client platforms
-- MUST ignore unknown values, treating an unknown value as if the member does not
-- exist. If no value is given then the effective value is required if
-- requireResidentKey is true or discouraged if it is false or absent.
decodeResidentKey :: Maybe IDL.DOMString -> M.ResidentKeyRequirement
decodeResidentKey (Just "discouraged") = M.ResidentKeyRequirementDiscouraged
decodeResidentKey (Just "preferred") = M.ResidentKeyRequirementPreferred
decodeResidentKey (Just "required") = M.ResidentKeyRequirementRequired
decodeResidentKey _ = case requireResidentKey of
Just True -> M.ResidentKeyRequirementRequired
_ -> M.ResidentKeyRequirementDiscouraged
-- | [(spec)](https://www.w3.org/TR/webauthn-2/#enumdef-attestationconveyancepreference)
-- Its values SHOULD be members of AttestationConveyancePreference. Client
-- platforms MUST ignore unknown values, treating an unknown value as if the
-- member does not exist. Its default value is "none".
instance Decode M.AttestationConveyancePreference where
decode (Just "none") = Right M.AttestationConveyancePreferenceNone
decode (Just "indirect") = Right M.AttestationConveyancePreferenceIndirect
decode (Just "direct") = Right M.AttestationConveyancePreferenceDirect
decode (Just "enterprise") = Right M.AttestationConveyancePreferenceEnterprise
decode _ = Right M.AttestationConveyancePreferenceNone
-- [(spec)](https://www.w3.org/TR/webauthn-2/#dictdef-publickeycredentialparameters)
-- [The type] member specifies the type of credential to be created. The value SHOULD
-- be a member of PublicKeyCredentialType but client platforms MUST ignore
-- unknown values, ignoring any PublicKeyCredentialParameters with an unknown
-- type.
instance Decode [M.CredentialParameters] where
decode xs = catMaybes <$> traverse decodeParam xs
where
decodeParam :: IDL.PublicKeyCredentialParameters -> Either Text (Maybe M.CredentialParameters)
decodeParam IDL.PublicKeyCredentialParameters {littype = "public-key", ..} = do
let cpTyp = M.CredentialTypePublicKey
cpAlg <- decode alg
pure . Just $ M.CredentialParameters {..}
decodeParam _ = pure Nothing
-- | [(spec)](https://www.w3.org/TR/webauthn-2/#dictionary-makecredentialoptions)
instance Decode (M.CredentialOptions 'K.Registration) where
decode IDL.PublicKeyCredentialCreationOptions {..} = do
corRp <- decode rp
corUser <- decode user
corChallenge <- decode challenge
corPubKeyCredParams <- decode pubKeyCredParams
corTimeout <- decode timeout
corExcludeCredentials <- decode excludeCredentials
corAuthenticatorSelection <- decode authenticatorSelection
corAttestation <- decode attestation
let corExtensions = M.AuthenticationExtensionsClientInputs {} <$ extensions
pure $ M.CredentialOptionsRegistration {..}
-- | [(spec)](https://www.w3.org/TR/webauthn-2/#dictionary-assertion-options)
instance Decode (M.CredentialOptions 'K.Authentication) where
decode IDL.PublicKeyCredentialRequestOptions {..} = do
coaChallenge <- decode challenge
coaTimeout <- decode timeout
coaRpId <- decode rpId
coaAllowCredentials <- decode allowCredentials
coaUserVerification <- decode userVerification
let coaExtensions = M.AuthenticationExtensionsClientInputs {} <$ extensions
pure $ M.CredentialOptionsAuthentication {..}
-- | [(spec)](https://www.w3.org/TR/webauthn-2/#sctn-generating-an-attestation-object)
instance DecodeCreated (M.AttestationObject 'True) where
decodeCreated supportedFormats (IDL.URLEncodedBase64 bytes) =
B.decodeAttestationObject supportedFormats bytes
instance DecodeCreated (M.AuthenticatorResponse 'K.Registration 'True) where
decodeCreated supportedFormats IDL.AuthenticatorAttestationResponse {..} = do
arrClientData <- decode clientDataJSON
arrAttestationObject <- decodeCreated supportedFormats attestationObject
pure $ M.AuthenticatorResponseRegistration {..}
instance DecodeCreated (M.Credential 'K.Registration 'True) where
decodeCreated supportedFormats IDL.PublicKeyCredential {..} = do
cIdentifier <- decode rawId
cResponse <- decodeCreated supportedFormats response
cClientExtensionResults <- decode clientExtensionResults
pure $ M.Credential {..}