Skip to content

Commit

Permalink
support packed
Browse files Browse the repository at this point in the history
  • Loading branch information
fumieval committed Apr 23, 2019
1 parent b0db694 commit e3610dd
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ dist-newstyle
.stack-work/
*~
.ghc.environment*
.DS_Store
55 changes: 43 additions & 12 deletions src/Web/WebAuthn.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module Web.WebAuthn (
, CredentialData(..)
-- * verfication
, VerificationFailure(..)
, registerCredential
, verify
) where

import Prelude hiding (fail)
Expand Down Expand Up @@ -127,10 +127,12 @@ data AuthenticatorAttestationResponse = AuthenticatorAttestationResponse

data Attestation = Attestation
{ attestationAuthData :: AuthenticatorData
, attestationAuthDataRaw :: ByteString
, attestationStatement :: AttestationStatement
}

data StmtFIDOU2F = StmtFIDOU2F (X509.SignedExact X509.Certificate) ByteString
deriving Show

decodeFIDOU2F :: CBOR.Decoder s StmtFIDOU2F
decodeFIDOU2F = do
Expand All @@ -143,6 +145,29 @@ decodeFIDOU2F = do
cert <- either fail pure $ X509.decodeSignedCertificate certBS
return (StmtFIDOU2F cert sig)

data StmtPacked = StmtPacked Int ByteString (X509.SignedExact X509.Certificate)
deriving Show

decodePacked :: CBOR.Decoder s StmtPacked
decodePacked = do
_ <- CBOR.decodeMapLen
assertKey "alg"
alg <- CBOR.decode
assertKey "sig"
sig <- CBOR.decodeBytes
assertKey "x5c"
_ <- CBOR.decodeListLen
certBS <- CBOR.decodeBytes
cert <- either fail pure $ X509.decodeSignedCertificate certBS
return (StmtPacked alg sig cert)

verifyPacked :: StmtPacked -> B.ByteString -> Digest SHA256 -> Either VerificationFailure ()
verifyPacked (StmtPacked _ sig cert) ad clientDataHash = do
case X509.verifySignature (X509.SignatureALG X509.HashSHA256 X509.PubKeyALG_EC)
(X509.certPubKey $ X509.getCertificate cert) (ad <> BA.convert clientDataHash) sig of
X509.SignaturePass -> return ()
X509.SignatureFailed f -> Left $ SignatureFailure f

assertKey :: Text -> CBOR.Decoder s ()
assertKey k = do
k' <- CBOR.decodeString
Expand All @@ -165,12 +190,13 @@ parseAuthenticatorData = do
let attestedCredentialData = CredentialData{..}
return AuthenticatorData{..}

data AttestationStatement = AF_Packed
data AttestationStatement = AF_Packed StmtPacked
| AF_TPM
| AF_AndroidKey
| AF_AndroidSafetyNet
| AF_FIDO_U2F StmtFIDOU2F
| AF_None
deriving Show

verifyFIDOU2F :: StmtFIDOU2F -> AuthenticatorData -> Digest SHA256 -> Either VerificationFailure ()
verifyFIDOU2F (StmtFIDOU2F cert sig) AuthenticatorData{..} clientDataHash = do
Expand Down Expand Up @@ -200,11 +226,12 @@ decodeAttestation = do
assertKey "attStmt"
stmt <- case fmt of
"fido-u2f" -> AF_FIDO_U2F <$> decodeFIDOU2F
_ -> error "decodeAttestation: Unsupported format"
"packed" -> AF_Packed <$> decodePacked
stmt -> error $ "decodeAttestation: Unsupported format: " ++ show fmt
assertKey "authData"
ad <- CBOR.decodeBytes
>>= either fail pure . C.runGet parseAuthenticatorData
return (Attestation ad stmt)
adRaw <- CBOR.decodeBytes
ad <- either fail pure $ C.runGet parseAuthenticatorData adRaw
return (Attestation ad adRaw stmt)

lookupM :: (Ord k, MonadFail m) => k -> Map.Map k a -> m a
lookupM k = maybe (fail "not found") pure . Map.lookup k
Expand All @@ -221,7 +248,7 @@ data CredentialData = CredentialData
{ aaguid :: ByteString
, credentialId :: ByteString
, credentialPublicKey :: ByteString
}
} deriving (Show, Eq)

data Origin = Origin
{ originScheme :: Text
Expand Down Expand Up @@ -253,17 +280,18 @@ False ?? e = Left e
True ?? _ = Right ()
infix 1 ??

registerCredential :: Challenge
verify :: WebAuthnType
-> Challenge
-> RelyingParty
-> Maybe Text -- ^ Token Binding ID in base64
-> Bool -- ^ require user verification?
-> AuthenticatorAttestationResponse
-> Either VerificationFailure CredentialData
registerCredential challenge RelyingParty{..} tbi verificationRequired
verify ty challenge RelyingParty{..} tbi verificationRequired
AuthenticatorAttestationResponse{..} = do
CollectedClientData{..} <- either
(Left . JSONDecodeError) Right $ J.eitherDecode $ BL.fromStrict clientDataJSON
clientType == Create ?? InvalidType
clientType == ty ?? InvalidType
challenge == clientChallenge ?? MismatchedChallenge
rpOrigin == clientOrigin ?? MismatchedOrigin
case clientTokenBinding of
Expand All @@ -274,7 +302,9 @@ registerCredential challenge RelyingParty{..} tbi verificationRequired
Just t'
| t == t' -> pure ()
| otherwise -> Left MismatchedTokenBinding
Attestation{ attestationAuthData = ad, attestationStatement = stmt }
Attestation{ attestationAuthData = ad
, attestationAuthDataRaw = adRaw
, attestationStatement = stmt }
<- either (Left . CBORDecodeError "registerCredential") (pure . snd)
$ CBOR.deserialiseFromBytes decodeAttestation
$ BL.fromStrict $ attestationObject
Expand All @@ -287,5 +317,6 @@ registerCredential challenge RelyingParty{..} tbi verificationRequired

case stmt of
AF_FIDO_U2F s -> verifyFIDOU2F s ad clientDataHash
_ -> error "registerCredential: unsupported format"
AF_Packed s -> verifyPacked s adRaw client
stmt -> error $ "registerCredential: unsupported format: " ++ show stmt
return $ attestedCredentialData ad

0 comments on commit e3610dd

Please sign in to comment.