packages feed

webauthn-0.1.0.0: src/Crypto/WebAuthn/Model/WebIDL/Internal/Encoding.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}

-- | Stability: internal
-- This module handles the encoding of structures passed to 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.Encoding
  ( Encode (..),
  )
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.Encoding 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 qualified Data.Map as Map
import Data.Singletons (SingI)

-- | @'Encode' hs@ indicates that the Haskell-specific type @hs@ can be
-- encoded to the more generic JavaScript type @'IDL' hs@ with the 'encode' function.
class Convert a => Encode a where
  encode :: a -> IDL a
  default encode :: Coercible a (IDL a) => a -> IDL a
  encode = coerce

instance Encode hs => Encode (Maybe hs) where
  encode Nothing = Nothing
  encode (Just hs) = Just $ encode hs

instance Encode M.RpId

instance Encode M.RelyingPartyName

instance Encode M.UserHandle

instance Encode M.UserAccountDisplayName

instance Encode M.UserAccountName

instance Encode M.Challenge

instance Encode M.Timeout

instance Encode M.CredentialId

instance Encode M.AuthenticationExtensionsClientInputs where
  -- TODO: Extensions are not implemented by this library, see the TODO in the
  -- module documentation of `Crypto.WebAuthn.Model` for more information.
  encode M.AuthenticationExtensionsClientInputs {} = Map.empty

-- | <https://www.iana.org/assignments/cose/cose.xhtml#algorithms>
instance Encode Cose.CoseSignAlg where
  encode = Cose.fromCoseSignAlg

-- | <https://www.w3.org/TR/webauthn-2/#enum-credentialType>
instance Encode M.CredentialType where
  encode M.CredentialTypePublicKey = "public-key"

-- | <https://www.w3.org/TR/webauthn-2/#enumdef-authenticatortransport>
instance Encode [M.AuthenticatorTransport] where
  encode = map encodeTransport
    where
      encodeTransport M.AuthenticatorTransportUSB = "usb"
      encodeTransport M.AuthenticatorTransportNFC = "nfc"
      encodeTransport M.AuthenticatorTransportBLE = "ble"
      encodeTransport M.AuthenticatorTransportInternal = "internal"

-- | <https://www.w3.org/TR/webauthn-2/#enumdef-authenticatorattachment>
instance Encode M.AuthenticatorAttachment where
  encode M.AuthenticatorAttachmentPlatform = "platform"
  encode M.AuthenticatorAttachmentCrossPlatform = "cross-platform"

-- | <https://www.w3.org/TR/webauthn-2/#enum-residentKeyRequirement>
instance Encode M.ResidentKeyRequirement where
  encode M.ResidentKeyRequirementDiscouraged = Just "discouraged"
  encode M.ResidentKeyRequirementPreferred = Just "preferred"
  encode M.ResidentKeyRequirementRequired = Just "required"

-- | <https://www.w3.org/TR/webauthn-2/#enum-userVerificationRequirement>
instance Encode M.UserVerificationRequirement where
  encode M.UserVerificationRequirementRequired = Just "required"
  encode M.UserVerificationRequirementPreferred = Just "preferred"
  encode M.UserVerificationRequirementDiscouraged = Just "discouraged"

-- | <https://www.w3.org/TR/webauthn-2/#enum-attestation-convey>
instance Encode M.AttestationConveyancePreference where
  encode M.AttestationConveyancePreferenceNone = Just "none"
  encode M.AttestationConveyancePreferenceIndirect = Just "indirect"
  encode M.AttestationConveyancePreferenceDirect = Just "direct"
  encode M.AttestationConveyancePreferenceEnterprise = Just "enterprise"

instance Encode M.CredentialRpEntity where
  encode M.CredentialRpEntity {..} =
    IDL.PublicKeyCredentialRpEntity
      { id = encode creId,
        name = encode creName
      }

instance Encode M.CredentialUserEntity where
  encode M.CredentialUserEntity {..} =
    IDL.PublicKeyCredentialUserEntity
      { id = encode cueId,
        displayName = encode cueDisplayName,
        name = encode cueName
      }

instance Encode [M.CredentialParameters] where
  encode = map encodeParameters
    where
      encodeParameters M.CredentialParameters {..} =
        IDL.PublicKeyCredentialParameters
          { littype = encode cpTyp,
            alg = encode cpAlg
          }

instance Encode M.CredentialDescriptor where
  encode M.CredentialDescriptor {..} =
    IDL.PublicKeyCredentialDescriptor
      { littype = encode cdTyp,
        id = encode cdId,
        transports = encode cdTransports
      }

instance Encode M.AuthenticatorSelectionCriteria where
  encode M.AuthenticatorSelectionCriteria {..} =
    IDL.AuthenticatorSelectionCriteria
      { authenticatorAttachment = encode ascAuthenticatorAttachment,
        residentKey = encode ascResidentKey,
        -- [(spec)](https://www.w3.org/TR/webauthn-2/#dom-authenticatorselectioncriteria-requireresidentkey)
        -- Relying Parties SHOULD set it to true if, and only if, residentKey is set to required.
        requireResidentKey = Just (ascResidentKey == M.ResidentKeyRequirementRequired),
        userVerification = encode ascUserVerification
      }

instance Encode [M.CredentialDescriptor] where
  encode = Just . map encode

instance Encode (M.CredentialOptions 'K.Registration) where
  encode M.CredentialOptionsRegistration {..} =
    IDL.PublicKeyCredentialCreationOptions
      { rp = encode corRp,
        user = encode corUser,
        challenge = encode corChallenge,
        pubKeyCredParams = encode corPubKeyCredParams,
        timeout = encode corTimeout,
        excludeCredentials = encode corExcludeCredentials,
        authenticatorSelection = encode corAuthenticatorSelection,
        attestation = encode corAttestation,
        extensions = encode corExtensions
      }

instance Encode (M.CredentialOptions 'K.Authentication) where
  encode M.CredentialOptionsAuthentication {..} =
    IDL.PublicKeyCredentialRequestOptions
      { challenge = encode coaChallenge,
        timeout = encode coaTimeout,
        rpId = encode coaRpId,
        allowCredentials = encode coaAllowCredentials,
        userVerification = encode coaUserVerification,
        extensions = encode coaExtensions
      }

-- | [(spec)](https://www.w3.org/TR/webauthn-2/#iface-pkcredential)
-- Encodes the PublicKeyCredential for attestation, this instance is mostly used in the tests where we emulate the
-- of the client.
instance Encode (M.Credential 'K.Registration 'True) where
  encode M.Credential {..} =
    IDL.PublicKeyCredential
      { rawId = encode cIdentifier,
        response = encode cResponse,
        -- TODO: Extensions are not implemented by this library, see the TODO in the
        -- module documentation of `Crypto.WebAuthn.Model` for more information.
        clientExtensionResults = Map.empty
      }

-- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-authenticatorresponse-clientdatajson)
instance SingI c => Encode (M.CollectedClientData c 'True) where
  encode ccd = IDL.URLEncodedBase64 $ B.encodeCollectedClientData ccd

instance Encode (M.AuthenticatorResponse 'K.Authentication 'True) where
  encode M.AuthenticatorResponseAuthentication {..} =
    IDL.AuthenticatorAssertionResponse
      { clientDataJSON = encode araClientData,
        authenticatorData = IDL.URLEncodedBase64 $ M.unRaw $ M.adRawData araAuthenticatorData,
        signature = IDL.URLEncodedBase64 $ M.unAssertionSignature araSignature,
        userHandle = IDL.URLEncodedBase64 . M.unUserHandle <$> araUserHandle
      }

instance Encode (M.Credential 'K.Authentication 'True) where
  encode M.Credential {..} =
    IDL.PublicKeyCredential
      { rawId = encode cIdentifier,
        response = encode cResponse,
        -- TODO: Extensions are not implemented by this library, see the TODO in the
        -- module documentation of `Crypto.WebAuthn.Model` for more information.
        clientExtensionResults = Map.empty
      }

-- | [(spec)](https://www.w3.org/TR/webauthn-2/#iface-authenticatorresponse)
instance Encode (M.AuthenticatorResponse 'K.Registration 'True) where
  encode M.AuthenticatorResponseRegistration {..} =
    IDL.AuthenticatorAttestationResponse
      { clientDataJSON = encode arrClientData,
        attestationObject = encode arrAttestationObject
      }

-- | [(spec)](https://www.w3.org/TR/webauthn-2/#dom-authenticatorattestationresponse-attestationobject)
instance Encode (M.AttestationObject 'True) where
  encode ao = IDL.URLEncodedBase64 $ B.encodeAttestationObject ao