{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module WebAuthn.FIDOU2F where

import Crypto.Hash
import Data.ByteString (ByteString)
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import qualified Codec.CBOR.Term as CBOR
import qualified Codec.Serialise as CBOR
import qualified Data.ByteArray as BA
import qualified Data.Map as Map
import qualified Data.X509 as X509
import qualified Data.X509.Validation as X509
import WebAuthn.Types

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

decode :: CBOR.Term -> Maybe Stmt
decode (CBOR.TMap xs) = do
  let m = Map.fromList xs
  CBOR.TBytes sig <- Map.lookup (CBOR.TString "sig") m
  CBOR.TList [CBOR.TBytes certBS] <- Map.lookup (CBOR.TString "x5c") m
  cert <- either fail pure $ X509.decodeSignedCertificate certBS
  return (Stmt cert sig)
decode _ = Nothing

verify :: Stmt
  -> AuthenticatorData
  -> Digest SHA256
  -> Either VerificationFailure ()
verify (Stmt cert sig) AuthenticatorData{..} clientDataHash = do
  AttestedCredentialData{..} <- maybe (Left MalformedAuthenticatorData) pure attestedCredentialData
  m <- either (Left . CBORDecodeError "verifyFIDOU2F") pure
    $ CBOR.deserialiseOrFail $ BL.fromStrict $ unCredentialPublicKey credentialPublicKey
  pubU2F <- maybe (Left MalformedPublicKey) pure $ do
      CBOR.TBytes x <- Map.lookup (-2 :: Int) m
      CBOR.TBytes y <- Map.lookup (-3) m
      return $ BB.word8 0x04 <> BB.byteString x <> BB.byteString y
  let dat = BL.toStrict $ BB.toLazyByteString $ mconcat
        [ BB.word8 0x00
        , BB.byteString $ BA.convert rpIdHash
        , BB.byteString $ BA.convert clientDataHash
        , BB.byteString $ unCredentialId credentialId
        , pubU2F]
  let pub = X509.certPubKey $ X509.getCertificate cert
  case X509.verifySignature (X509.SignatureALG X509.HashSHA256 X509.PubKeyALG_EC) pub dat sig of
    X509.SignaturePass -> return ()
    X509.SignatureFailed _ -> Left $ SignatureFailure "FIDOU2F"