packages feed

webauthn-0.1.0.0: tests/Spec/Types.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-orphans #-}

module Spec.Types () where

import Crypto.Hash (hash)
import qualified Crypto.Random as Random
import qualified Crypto.WebAuthn.AttestationStatementFormat.None as None
import qualified Crypto.WebAuthn.Cose.Algorithm as Cose
import qualified Crypto.WebAuthn.Cose.Internal.Verify as Cose
import qualified Crypto.WebAuthn.Cose.Key as Cose
import qualified Crypto.WebAuthn.Model as M
import qualified Data.ByteString.Lazy as LBS
import Data.Maybe (fromJust)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Singletons (SingI, sing)
import Data.Text.Encoding (encodeUtf8)
import Data.UUID (UUID)
import qualified Data.UUID as UUID
import qualified Spec.Key as Key
import Spec.Util (runSeededMonadRandom)
import Test.QuickCheck
  ( Arbitrary (arbitrary),
    Gen,
    arbitraryBoundedEnum,
    elements,
    frequency,
    liftArbitrary,
    oneof,
    resize,
    shuffle,
    sublistOf,
  )
import Test.QuickCheck.Instances.Text ()

instance Arbitrary Key.KeyPair where
  arbitrary = do
    seed <- arbitrary
    runSeededMonadRandom seed . Key.newKeyPair <$> arbitrary

instance Arbitrary Cose.CoseSignAlg where
  arbitrary =
    oneof
      [ pure Cose.CoseSignAlgEdDSA,
        Cose.CoseSignAlgECDSA <$> arbitrary,
        Cose.CoseSignAlgRSA <$> arbitrary
      ]

instance Arbitrary Cose.PublicKey where
  arbitrary = Cose.fromCose <$> arbitrary

instance Arbitrary Cose.CosePublicKey where
  arbitrary = Key.pubKey <$> arbitrary

instance Arbitrary Cose.CoseCurveEdDSA where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary Cose.CoseCurveECDSA where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary Cose.CoseHashAlgECDSA where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary Cose.CoseHashAlgRSA where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary M.CredentialType where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary M.AuthenticatorTransport where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary M.AuthenticatorAttachment where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary M.ResidentKeyRequirement where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary M.UserVerificationRequirement where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary M.AttestationConveyancePreference where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary (M.AuthenticatorResponse 'M.Registration 'False) where
  arbitrary = M.AuthenticatorResponseRegistration <$> arbitrary <*> arbitrary

instance Arbitrary M.AssertionSignature where
  arbitrary = M.AssertionSignature <$> arbitrary

instance Arbitrary (M.AuthenticatorResponse 'M.Authentication 'False) where
  arbitrary =
    M.AuthenticatorResponseAuthentication
      <$> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary (M.RawField 'False) where
  arbitrary = pure M.NoRaw

instance Arbitrary (M.CollectedClientData c 'False) where
  arbitrary = M.CollectedClientData <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary

instance Arbitrary (M.AttestationObject 'False) where
  arbitrary = do
    aoAuthData <- arbitrary
    ArbitraryAttestationStatementFormat aoFmt <- arbitrary
    aoAttStmt <- arbitrary
    pure M.AttestationObject {..}

-- Like SomeAttestationStatementFormat, but with an Arbitrary constraint on the AttStmt
data ArbitraryAttestationStatementFormat
  = forall a.
    (Arbitrary (M.AttStmt a), M.AttestationStatementFormat a) =>
    ArbitraryAttestationStatementFormat a

instance Arbitrary ArbitraryAttestationStatementFormat where
  arbitrary =
    elements
      [ ArbitraryAttestationStatementFormat None.Format
      --ArbitraryAttestationStatementFormat Packed.Format,
      --ArbitraryAttestationStatementFormat FidoU2F.Format,
      --ArbitraryAttestationStatementFormat AndroidKey.Format
      ]

instance Arbitrary M.SignatureCounter where
  arbitrary = M.SignatureCounter <$> arbitrary

instance SingI c => Arbitrary (M.AuthenticatorData c 'False) where
  arbitrary = M.AuthenticatorData <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary

instance Arbitrary M.Challenge where
  arbitrary = M.Challenge <$> arbitrary

instance Arbitrary M.Origin where
  arbitrary = M.Origin <$> arbitrary

instance Arbitrary M.RpIdHash where
  arbitrary = do
    rpId <- encodeUtf8 <$> arbitrary
    pure $ M.RpIdHash $ hash rpId

instance Arbitrary M.AuthenticatorDataFlags where
  arbitrary = M.AuthenticatorDataFlags <$> arbitrary <*> arbitrary

instance SingI c => Arbitrary (M.AttestedCredentialData c 'False) where
  arbitrary = case sing @c of
    M.SRegistration -> M.AttestedCredentialData <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
    M.SAuthentication -> pure M.NoAttestedCredentialData

instance Arbitrary M.AAGUID where
  arbitrary =
    M.AAGUID
      <$> frequency
        [ (1, pure UUID.nil),
          (10, randomUUID <$> arbitrary)
        ]
    where
      randomUUID :: Integer -> UUID
      randomUUID seed = fromJust $ UUID.fromByteString $ LBS.fromStrict bytes
        where
          rng = Random.drgNewSeed $ Random.seedFromInteger seed
          (bytes, _) = Random.withDRG rng $ Random.getRandomBytes 16

instance Arbitrary M.CredentialId where
  arbitrary = M.CredentialId <$> arbitrary

instance Arbitrary M.AuthenticatorExtensionOutputs where
  arbitrary = pure M.AuthenticatorExtensionOutputs {}

instance Arbitrary M.RpId where
  arbitrary = M.RpId <$> arbitrary

instance Arbitrary M.RelyingPartyName where
  arbitrary = M.RelyingPartyName <$> arbitrary

instance Arbitrary M.CredentialRpEntity where
  arbitrary =
    M.CredentialRpEntity
      <$> arbitrary
      <*> arbitrary

instance Arbitrary M.UserHandle where
  arbitrary = M.UserHandle <$> arbitrary

instance Arbitrary M.UserAccountDisplayName where
  arbitrary = M.UserAccountDisplayName <$> arbitrary

instance Arbitrary M.UserAccountName where
  arbitrary = M.UserAccountName <$> arbitrary

instance Arbitrary M.CredentialUserEntity where
  arbitrary =
    M.CredentialUserEntity
      <$> arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary M.Timeout where
  arbitrary = M.Timeout <$> arbitrary

instance Arbitrary M.CredentialDescriptor where
  arbitrary =
    M.CredentialDescriptor M.CredentialTypePublicKey
      <$> arbitrary
      <*> liftArbitrary shuffledSubset

instance Arbitrary M.AuthenticatorSelectionCriteria where
  arbitrary =
    M.AuthenticatorSelectionCriteria
      <$> arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary M.AuthenticationExtensionsClientInputs where
  arbitrary = pure M.AuthenticationExtensionsClientInputs

instance Arbitrary (M.CredentialOptions 'M.Registration) where
  arbitrary =
    M.CredentialOptionsRegistration
      <$> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> parameters
      <*> arbitrary
      <*> resize 4 arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary (M.CredentialOptions 'M.Authentication) where
  arbitrary =
    M.CredentialOptionsAuthentication
      <$> arbitrary
      <*> arbitrary
      <*> arbitrary
      <*> resize 4 arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary M.AuthenticationExtensionsClientOutputs where
  arbitrary = pure M.AuthenticationExtensionsClientOutputs

instance Arbitrary (M.Credential 'M.Registration 'False) where
  arbitrary =
    M.Credential
      <$> arbitrary
      <*> arbitrary
      <*> arbitrary

instance Arbitrary (M.Credential 'M.Authentication 'False) where
  arbitrary =
    M.Credential
      <$> arbitrary
      <*> arbitrary
      <*> arbitrary

shuffledSubset :: (Ord a, Bounded a, Enum a) => Gen [a]
shuffledSubset = subset >>= shuffle . Set.toList

shuffledSubsetWith :: Ord a => Set a -> Gen [a]
shuffledSubsetWith set = subsetWith set >>= shuffle . Set.toList

subset :: (Ord a, Bounded a, Enum a) => Gen (Set a)
subset = Set.fromList <$> sublistOf (Set.toList completeSet)

subsetWith :: Ord a => Set a -> Gen (Set a)
subsetWith set = Set.fromList <$> sublistOf (Set.toList set)

parameters :: Gen [M.CredentialParameters]
parameters = do
  algs <- shuffledSubsetWith $ Set.fromList allCoseAlgs
  pure $ M.CredentialParameters M.CredentialTypePublicKey <$> algs

completeSet :: (Ord a, Bounded a, Enum a) => Set a
completeSet = Set.fromList [minBound .. maxBound]

allCoseAlgs :: [Cose.CoseSignAlg]
allCoseAlgs =
  [ Cose.CoseSignAlgEdDSA,
    Cose.CoseSignAlgECDSA Cose.CoseHashAlgECDSASHA256,
    Cose.CoseSignAlgECDSA Cose.CoseHashAlgECDSASHA384,
    Cose.CoseSignAlgECDSA Cose.CoseHashAlgECDSASHA512,
    Cose.CoseSignAlgRSA Cose.CoseHashAlgRSASHA1,
    Cose.CoseSignAlgRSA Cose.CoseHashAlgRSASHA256,
    Cose.CoseSignAlgRSA Cose.CoseHashAlgRSASHA384,
    Cose.CoseSignAlgRSA Cose.CoseHashAlgRSASHA512
  ]