{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
module Crypto.WebAuthn.AttestationStatementFormat.Apple
( format,
Format (..),
VerificationError (..),
)
where
import qualified Codec.CBOR.Term as CBOR
import Control.Exception (Exception)
import Control.Monad (forM, unless)
import Crypto.Hash (Digest, SHA256, digestFromByteString, hash)
import qualified Crypto.WebAuthn.Cose.Internal.Verify as Cose
import qualified Crypto.WebAuthn.Cose.PublicKey as Cose
import qualified Crypto.WebAuthn.Cose.PublicKeyWithSignAlg as Cose
import Crypto.WebAuthn.Internal.Utils (failure)
import qualified Crypto.WebAuthn.Model.Types as M
import qualified Data.ASN1.Parse as ASN1
import qualified Data.ASN1.Types as ASN1
import Data.Aeson (ToJSON, object, toJSON, (.=))
import Data.Bifunctor (first)
import qualified Data.ByteArray as BA
import Data.FileEmbed (embedFile)
import Data.HashMap.Strict ((!?))
import Data.List.NonEmpty (NonEmpty ((:|)), toList)
import qualified Data.List.NonEmpty as NE
import qualified Data.Text as Text
import qualified Data.X509 as X509
import qualified Data.X509.CertificateStore as X509
data Format = Format
instance Show Format where
show :: Format -> [Char]
show = Text -> [Char]
Text.unpack (Text -> [Char]) -> (Format -> Text) -> Format -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Format -> Text
forall a. AttestationStatementFormat a => a -> Text
M.asfIdentifier
data VerificationError
=
NonceMismatch
{
VerificationError -> Digest SHA256
calculatedNonce :: Digest SHA256,
VerificationError -> Digest SHA256
receivedNonce :: Digest SHA256
}
|
PublicKeyMismatch
{
VerificationError -> PublicKey
credentialDataPublicKey :: Cose.PublicKey,
VerificationError -> PublicKey
certificatePublicKey :: Cose.PublicKey
}
deriving (Int -> VerificationError -> ShowS
[VerificationError] -> ShowS
VerificationError -> [Char]
(Int -> VerificationError -> ShowS)
-> (VerificationError -> [Char])
-> ([VerificationError] -> ShowS)
-> Show VerificationError
forall a.
(Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> VerificationError -> ShowS
showsPrec :: Int -> VerificationError -> ShowS
$cshow :: VerificationError -> [Char]
show :: VerificationError -> [Char]
$cshowList :: [VerificationError] -> ShowS
showList :: [VerificationError] -> ShowS
Show, Show VerificationError
Typeable VerificationError
Typeable VerificationError
-> Show VerificationError
-> (VerificationError -> SomeException)
-> (SomeException -> Maybe VerificationError)
-> (VerificationError -> [Char])
-> Exception VerificationError
SomeException -> Maybe VerificationError
VerificationError -> [Char]
VerificationError -> SomeException
forall e.
Typeable e
-> Show e
-> (e -> SomeException)
-> (SomeException -> Maybe e)
-> (e -> [Char])
-> Exception e
$ctoException :: VerificationError -> SomeException
toException :: VerificationError -> SomeException
$cfromException :: SomeException -> Maybe VerificationError
fromException :: SomeException -> Maybe VerificationError
$cdisplayException :: VerificationError -> [Char]
displayException :: VerificationError -> [Char]
Exception)
data Statement = Statement
{ Statement -> NonEmpty SignedCertificate
x5c :: NE.NonEmpty X509.SignedCertificate,
Statement -> Digest SHA256
sNonce :: Digest SHA256,
Statement -> PublicKey
pubKey :: Cose.PublicKey
}
deriving (Statement -> Statement -> Bool
(Statement -> Statement -> Bool)
-> (Statement -> Statement -> Bool) -> Eq Statement
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Statement -> Statement -> Bool
== :: Statement -> Statement -> Bool
$c/= :: Statement -> Statement -> Bool
/= :: Statement -> Statement -> Bool
Eq, Int -> Statement -> ShowS
[Statement] -> ShowS
Statement -> [Char]
(Int -> Statement -> ShowS)
-> (Statement -> [Char])
-> ([Statement] -> ShowS)
-> Show Statement
forall a.
(Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Statement -> ShowS
showsPrec :: Int -> Statement -> ShowS
$cshow :: Statement -> [Char]
show :: Statement -> [Char]
$cshowList :: [Statement] -> ShowS
showList :: [Statement] -> ShowS
Show)
instance ToJSON Statement where
toJSON :: Statement -> Value
toJSON Statement {NonEmpty SignedCertificate
Digest SHA256
PublicKey
x5c :: Statement -> NonEmpty SignedCertificate
sNonce :: Statement -> Digest SHA256
pubKey :: Statement -> PublicKey
x5c :: NonEmpty SignedCertificate
sNonce :: Digest SHA256
pubKey :: PublicKey
..} =
[Pair] -> Value
object
[ Key
"x5c" Key -> NonEmpty SignedCertificate -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
forall v. ToJSON v => Key -> v -> Pair
.= NonEmpty SignedCertificate
x5c
]
newtype AppleNonceExtension = AppleNonceExtension
{ AppleNonceExtension -> Digest SHA256
nonce :: Digest SHA256
}
deriving (AppleNonceExtension -> AppleNonceExtension -> Bool
(AppleNonceExtension -> AppleNonceExtension -> Bool)
-> (AppleNonceExtension -> AppleNonceExtension -> Bool)
-> Eq AppleNonceExtension
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AppleNonceExtension -> AppleNonceExtension -> Bool
== :: AppleNonceExtension -> AppleNonceExtension -> Bool
$c/= :: AppleNonceExtension -> AppleNonceExtension -> Bool
/= :: AppleNonceExtension -> AppleNonceExtension -> Bool
Eq, Int -> AppleNonceExtension -> ShowS
[AppleNonceExtension] -> ShowS
AppleNonceExtension -> [Char]
(Int -> AppleNonceExtension -> ShowS)
-> (AppleNonceExtension -> [Char])
-> ([AppleNonceExtension] -> ShowS)
-> Show AppleNonceExtension
forall a.
(Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AppleNonceExtension -> ShowS
showsPrec :: Int -> AppleNonceExtension -> ShowS
$cshow :: AppleNonceExtension -> [Char]
show :: AppleNonceExtension -> [Char]
$cshowList :: [AppleNonceExtension] -> ShowS
showList :: [AppleNonceExtension] -> ShowS
Show)
instance X509.Extension AppleNonceExtension where
extOID :: AppleNonceExtension -> OID
extOID = OID -> AppleNonceExtension -> OID
forall a b. a -> b -> a
const [Integer
1, Integer
2, Integer
840, Integer
113635, Integer
100, Integer
8, Integer
2]
extHasNestedASN1 :: Proxy AppleNonceExtension -> Bool
extHasNestedASN1 = Bool -> Proxy AppleNonceExtension -> Bool
forall a b. a -> b -> a
const Bool
False
extEncode :: AppleNonceExtension -> [ASN1]
extEncode = [Char] -> AppleNonceExtension -> [ASN1]
forall a. HasCallStack => [Char] -> a
error [Char]
"extEncode for AppleNonceExtension is unimplemented"
extDecode :: [ASN1] -> Either [Char] AppleNonceExtension
extDecode = ParseASN1 AppleNonceExtension
-> [ASN1] -> Either [Char] AppleNonceExtension
forall a. ParseASN1 a -> [ASN1] -> Either [Char] a
ASN1.runParseASN1 ParseASN1 AppleNonceExtension
decode
where
decode :: ASN1.ParseASN1 AppleNonceExtension
decode :: ParseASN1 AppleNonceExtension
decode = do
ASN1.OctetString ByteString
nonce <-
ASN1ConstructionType -> ParseASN1 ASN1 -> ParseASN1 ASN1
forall a. ASN1ConstructionType -> ParseASN1 a -> ParseASN1 a
ASN1.onNextContainer ASN1ConstructionType
ASN1.Sequence (ParseASN1 ASN1 -> ParseASN1 ASN1)
-> ParseASN1 ASN1 -> ParseASN1 ASN1
forall a b. (a -> b) -> a -> b
$
ASN1ConstructionType -> ParseASN1 ASN1 -> ParseASN1 ASN1
forall a. ASN1ConstructionType -> ParseASN1 a -> ParseASN1 a
ASN1.onNextContainer (ASN1Class -> Int -> ASN1ConstructionType
ASN1.Container ASN1Class
ASN1.Context Int
1) ParseASN1 ASN1
ASN1.getNext
ParseASN1 AppleNonceExtension
-> (Digest SHA256 -> ParseASN1 AppleNonceExtension)
-> Maybe (Digest SHA256)
-> ParseASN1 AppleNonceExtension
forall b a. b -> (a -> b) -> Maybe a -> b
maybe
([Char] -> ParseASN1 AppleNonceExtension
forall a. [Char] -> ParseASN1 a
forall (m :: * -> *) a. MonadFail m => [Char] -> m a
fail [Char]
"The nonce in the Extention was not a valid SHA256 hash")
(AppleNonceExtension -> ParseASN1 AppleNonceExtension
forall a. a -> ParseASN1 a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (AppleNonceExtension -> ParseASN1 AppleNonceExtension)
-> (Digest SHA256 -> AppleNonceExtension)
-> Digest SHA256
-> ParseASN1 AppleNonceExtension
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Digest SHA256 -> AppleNonceExtension
AppleNonceExtension)
(ByteString -> Maybe (Digest SHA256)
forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
ba -> Maybe (Digest a)
digestFromByteString ByteString
nonce)
instance M.AttestationStatementFormat Format where
type AttStmt Format = Statement
asfIdentifier :: Format -> Text
asfIdentifier Format
_ = Text
"apple"
asfDecode :: Format -> HashMap Text Term -> Either Text (AttStmt Format)
asfDecode Format
_ HashMap Text Term
xs = case HashMap Text Term
xs HashMap Text Term -> Text -> Maybe Term
forall k v. (Eq k, Hashable k) => HashMap k v -> k -> Maybe v
!? Text
"x5c" of
Just (CBOR.TList ([Term] -> Maybe (NonEmpty Term)
forall a. [a] -> Maybe (NonEmpty a)
NE.nonEmpty -> Just NonEmpty Term
x5cRaw)) -> do
x5c :: NonEmpty SignedCertificate
x5c@(SignedCertificate
credCert :| [SignedCertificate]
_) <- NonEmpty Term
-> (Term -> Either Text SignedCertificate)
-> Either Text (NonEmpty SignedCertificate)
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM NonEmpty Term
x5cRaw ((Term -> Either Text SignedCertificate)
-> Either Text (NonEmpty SignedCertificate))
-> (Term -> Either Text SignedCertificate)
-> Either Text (NonEmpty SignedCertificate)
forall a b. (a -> b) -> a -> b
$ \case
CBOR.TBytes ByteString
certBytes ->
([Char] -> Text)
-> Either [Char] SignedCertificate -> Either Text SignedCertificate
forall a b c. (a -> b) -> Either a c -> Either b c
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first ((Text
"Failed to decode signed certificate: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>) (Text -> Text) -> ([Char] -> Text) -> [Char] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> Text
Text.pack) (ByteString -> Either [Char] SignedCertificate
X509.decodeSignedCertificate ByteString
certBytes)
Term
cert ->
Text -> Either Text SignedCertificate
forall a b. a -> Either a b
Left (Text -> Either Text SignedCertificate)
-> Text -> Either Text SignedCertificate
forall a b. (a -> b) -> a -> b
$ Text
"Certificate CBOR value is not bytes: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> [Char] -> Text
Text.pack (Term -> [Char]
forall a. Show a => a -> [Char]
show Term
cert)
let cert :: Certificate
cert = SignedCertificate -> Certificate
X509.getCertificate SignedCertificate
credCert
PublicKey
pubKey <- PubKey -> Either Text PublicKey
Cose.fromX509 (PubKey -> Either Text PublicKey)
-> PubKey -> Either Text PublicKey
forall a b. (a -> b) -> a -> b
$ Certificate -> PubKey
X509.certPubKey Certificate
cert
AppleNonceExtension {Digest SHA256
nonce :: AppleNonceExtension -> Digest SHA256
nonce :: Digest SHA256
..} <- case Extensions -> Maybe (Either [Char] AppleNonceExtension)
forall a. Extension a => Extensions -> Maybe (Either [Char] a)
X509.extensionGetE (Extensions -> Maybe (Either [Char] AppleNonceExtension))
-> Extensions -> Maybe (Either [Char] AppleNonceExtension)
forall a b. (a -> b) -> a -> b
$ Certificate -> Extensions
X509.certExtensions Certificate
cert of
Just (Right AppleNonceExtension
ext) -> AppleNonceExtension -> Either Text AppleNonceExtension
forall a. a -> Either Text a
forall (f :: * -> *) a. Applicative f => a -> f a
pure AppleNonceExtension
ext
Just (Left [Char]
err) -> Text -> Either Text AppleNonceExtension
forall a b. a -> Either a b
Left (Text -> Either Text AppleNonceExtension)
-> Text -> Either Text AppleNonceExtension
forall a b. (a -> b) -> a -> b
$ Text
"Failed to decode certificate apple nonce extension: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> [Char] -> Text
Text.pack [Char]
err
Maybe (Either [Char] AppleNonceExtension)
Nothing -> Text -> Either Text AppleNonceExtension
forall a b. a -> Either a b
Left Text
"Certificate apple nonce extension is missing"
Statement -> Either Text Statement
forall a. a -> Either Text a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Statement -> Either Text Statement)
-> Statement -> Either Text Statement
forall a b. (a -> b) -> a -> b
$ NonEmpty SignedCertificate
-> Digest SHA256 -> PublicKey -> Statement
Statement NonEmpty SignedCertificate
x5c Digest SHA256
nonce PublicKey
pubKey
Maybe Term
_ -> Text -> Either Text (AttStmt Format)
forall a b. a -> Either a b
Left (Text -> Either Text (AttStmt Format))
-> Text -> Either Text (AttStmt Format)
forall a b. (a -> b) -> a -> b
$ Text
"CBOR map didn't have expected value types (x5c: nonempty list): " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> [Char] -> Text
Text.pack (HashMap Text Term -> [Char]
forall a. Show a => a -> [Char]
show HashMap Text Term
xs)
asfEncode :: Format -> AttStmt Format -> Term
asfEncode Format
_ Statement {NonEmpty SignedCertificate
Digest SHA256
PublicKey
x5c :: Statement -> NonEmpty SignedCertificate
sNonce :: Statement -> Digest SHA256
pubKey :: Statement -> PublicKey
x5c :: NonEmpty SignedCertificate
sNonce :: Digest SHA256
pubKey :: PublicKey
..} =
let encodedx5c :: [Term]
encodedx5c = (SignedCertificate -> Term) -> [SignedCertificate] -> [Term]
forall a b. (a -> b) -> [a] -> [b]
map (ByteString -> Term
CBOR.TBytes (ByteString -> Term)
-> (SignedCertificate -> ByteString) -> SignedCertificate -> Term
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SignedCertificate -> ByteString
forall a.
(Show a, Eq a, ASN1Object a) =>
SignedExact a -> ByteString
X509.encodeSignedObject) ([SignedCertificate] -> [Term]) -> [SignedCertificate] -> [Term]
forall a b. (a -> b) -> a -> b
$ NonEmpty SignedCertificate -> [SignedCertificate]
forall a. NonEmpty a -> [a]
toList NonEmpty SignedCertificate
x5c
in [(Term, Term)] -> Term
CBOR.TMap
[ (Text -> Term
CBOR.TString Text
"x5c", [Term] -> Term
CBOR.TList [Term]
encodedx5c)
]
type AttStmtVerificationError Format = VerificationError
asfVerify :: Format
-> DateTime
-> AttStmt Format
-> AuthenticatorData 'Registration 'True
-> ClientDataHash
-> Validation
(NonEmpty (AttStmtVerificationError Format)) SomeAttestationType
asfVerify
Format
_
DateTime
_
Statement {NonEmpty SignedCertificate
Digest SHA256
PublicKey
x5c :: Statement -> NonEmpty SignedCertificate
sNonce :: Statement -> Digest SHA256
pubKey :: Statement -> PublicKey
x5c :: NonEmpty SignedCertificate
sNonce :: Digest SHA256
pubKey :: PublicKey
..}
M.AuthenticatorData {adAttestedCredentialData :: forall (c :: CeremonyKind) (raw :: Bool).
AuthenticatorData c raw -> AttestedCredentialData c raw
adAttestedCredentialData = AttestedCredentialData 'Registration 'True
credData, Maybe AuthenticatorExtensionOutputs
AuthenticatorDataFlags
SignatureCounter
RpIdHash
RawField 'True
adRpIdHash :: RpIdHash
adFlags :: AuthenticatorDataFlags
adSignCount :: SignatureCounter
adExtensions :: Maybe AuthenticatorExtensionOutputs
adRawData :: RawField 'True
adRpIdHash :: forall (c :: CeremonyKind) (raw :: Bool).
AuthenticatorData c raw -> RpIdHash
adFlags :: forall (c :: CeremonyKind) (raw :: Bool).
AuthenticatorData c raw -> AuthenticatorDataFlags
adSignCount :: forall (c :: CeremonyKind) (raw :: Bool).
AuthenticatorData c raw -> SignatureCounter
adExtensions :: forall (c :: CeremonyKind) (raw :: Bool).
AuthenticatorData c raw -> Maybe AuthenticatorExtensionOutputs
adRawData :: forall (c :: CeremonyKind) (raw :: Bool).
AuthenticatorData c raw -> RawField raw
..}
ClientDataHash
clientDataHash = do
let nonceToHash :: ByteString
nonceToHash = RawField 'True -> ByteString
M.unRaw RawField 'True
adRawData ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Digest SHA256 -> ByteString
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
bin -> bout
BA.convert (ClientDataHash -> Digest SHA256
M.unClientDataHash ClientDataHash
clientDataHash)
let nonce :: Digest SHA256
nonce = ByteString -> Digest SHA256
forall ba a.
(ByteArrayAccess ba, HashAlgorithm a) =>
ba -> Digest a
hash ByteString
nonceToHash
Bool
-> Validation (NonEmpty VerificationError) ()
-> Validation (NonEmpty VerificationError) ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Digest SHA256
nonce Digest SHA256 -> Digest SHA256 -> Bool
forall a. Eq a => a -> a -> Bool
== Digest SHA256
sNonce) (Validation (NonEmpty VerificationError) ()
-> Validation (NonEmpty VerificationError) ())
-> (VerificationError
-> Validation (NonEmpty VerificationError) ())
-> VerificationError
-> Validation (NonEmpty VerificationError) ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. VerificationError -> Validation (NonEmpty VerificationError) ()
forall e a. e -> Validation (NonEmpty e) a
failure (VerificationError -> Validation (NonEmpty VerificationError) ())
-> VerificationError -> Validation (NonEmpty VerificationError) ()
forall a b. (a -> b) -> a -> b
$ Digest SHA256 -> Digest SHA256 -> VerificationError
NonceMismatch Digest SHA256
nonce Digest SHA256
sNonce
let credentialPublicKey :: PublicKey
credentialPublicKey = PublicKeyWithSignAlg -> PublicKey
Cose.publicKey (PublicKeyWithSignAlg -> PublicKey)
-> PublicKeyWithSignAlg -> PublicKey
forall a b. (a -> b) -> a -> b
$ AttestedCredentialData 'Registration 'True -> PublicKeyWithSignAlg
forall (raw :: Bool).
AttestedCredentialData 'Registration raw -> PublicKeyWithSignAlg
M.acdCredentialPublicKey AttestedCredentialData 'Registration 'True
credData
Bool
-> Validation (NonEmpty VerificationError) ()
-> Validation (NonEmpty VerificationError) ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (PublicKey
credentialPublicKey PublicKey -> PublicKey -> Bool
forall a. Eq a => a -> a -> Bool
== PublicKey
pubKey) (Validation (NonEmpty VerificationError) ()
-> Validation (NonEmpty VerificationError) ())
-> (VerificationError
-> Validation (NonEmpty VerificationError) ())
-> VerificationError
-> Validation (NonEmpty VerificationError) ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. VerificationError -> Validation (NonEmpty VerificationError) ()
forall e a. e -> Validation (NonEmpty e) a
failure (VerificationError -> Validation (NonEmpty VerificationError) ())
-> VerificationError -> Validation (NonEmpty VerificationError) ()
forall a b. (a -> b) -> a -> b
$ PublicKey -> PublicKey -> VerificationError
PublicKeyMismatch PublicKey
credentialPublicKey PublicKey
pubKey
pure $
AttestationType ('Verifiable 'Fido2) -> SomeAttestationType
forall (k :: AttestationKind).
AttestationType k -> SomeAttestationType
M.SomeAttestationType (AttestationType ('Verifiable 'Fido2) -> SomeAttestationType)
-> AttestationType ('Verifiable 'Fido2) -> SomeAttestationType
forall a b. (a -> b) -> a -> b
$
VerifiableAttestationType
-> AttestationChain 'Fido2 -> AttestationType ('Verifiable 'Fido2)
forall (p :: ProtocolKind).
VerifiableAttestationType
-> AttestationChain p -> AttestationType ('Verifiable p)
M.AttestationTypeVerifiable VerifiableAttestationType
M.VerifiableAttestationTypeAnonCA (NonEmpty SignedCertificate -> AttestationChain 'Fido2
M.Fido2Chain NonEmpty SignedCertificate
x5c)
asfTrustAnchors :: Format -> VerifiableAttestationType -> CertificateStore
asfTrustAnchors Format
_ VerifiableAttestationType
_ = CertificateStore
rootCertificateStore
rootCertificateStore :: X509.CertificateStore
rootCertificateStore :: CertificateStore
rootCertificateStore = [SignedCertificate] -> CertificateStore
X509.makeCertificateStore [SignedCertificate
rootCertificate]
rootCertificate :: X509.SignedCertificate
rootCertificate :: SignedCertificate
rootCertificate = case ByteString -> Either [Char] SignedCertificate
X509.decodeSignedCertificate $(embedFile "root-certs/apple/Apple_WebAuthn_Root_CA.crt") of
Left [Char]
err -> [Char] -> SignedCertificate
forall a. HasCallStack => [Char] -> a
error ([Char] -> SignedCertificate) -> [Char] -> SignedCertificate
forall a b. (a -> b) -> a -> b
$ [Char]
"Error while decoding Apple root certificate: " [Char] -> ShowS
forall a. Semigroup a => a -> a -> a
<> [Char]
err
Right SignedCertificate
cert -> SignedCertificate
cert
format :: M.SomeAttestationStatementFormat
format :: SomeAttestationStatementFormat
format = Format -> SomeAttestationStatementFormat
forall a.
AttestationStatementFormat a =>
a -> SomeAttestationStatementFormat
M.SomeAttestationStatementFormat Format
Format