u2f 0.1.0.1 → 0.1.0.2
raw patch · 4 files changed
+34/−31 lines, 4 filesdep ~aesondep ~basedep ~cryptonite
Dependency ranges changed: aeson, base, cryptonite, hspec
Files
- README.md +3/−1
- src/U2F.hs +24/−23
- src/U2F/Types.hs +2/−2
- u2f.cabal +5/−5
README.md view
@@ -1,3 +1,5 @@ # U2F -Haskell library for conveniently processing U2F registration/sign-in requests.+[](https://hackage.haskell.org/package/u2f) [](https://travis-ci.org/EButlerIV/u2f)++Haskell library for conveniently processing U2F registration/sign-in requests consistent with the FIDO U2F standard. (https://fidoalliance.org/download/)
src/U2F.hs view
@@ -104,17 +104,16 @@ pubKeyShape (BitString (BitArray len _)) = len == 520 pubKeyShape _ = False -getSignatureBase :: BS.ByteString -> BS.ByteString -> BS.ByteString -> BS.ByteString -> BS.ByteString-getSignatureBase appId clientData keyHandle publicKey = sigBase- where sigBase = BS.concat([BS.pack "\NUL", SHA256.hash(appId), SHA256.hash(decodeLenient clientData), keyHandle, publicKey])+formatSignatureBase :: BS.ByteString -> BS.ByteString -> BS.ByteString -> BS.ByteString -> BS.ByteString+formatSignatureBase _appId clientData _keyHandle publicKey = sigBase+ where sigBase = BS.concat([BS.pack "\NUL", SHA256.hash(_appId), SHA256.hash(decodeLenient clientData), _keyHandle, publicKey]) -getSignatureBaseFromRegistration :: Registration -> RegistrationData -> Either U2FError BS.ByteString-getSignatureBaseFromRegistration registration registrationData = do- appId <- pure $ BS.pack $ T.unpack $ registration_appId registration- clientData <- pure $ BS.pack $ T.unpack $ registration_clientData registration- keyHandle <- pure $ registrationData_keyHandle registrationData- publicKey <- pure $ registrationData_publicKey registrationData- pure $ getSignatureBase appId clientData keyHandle publicKey+getSignatureBaseFromRegistration :: Registration -> RegistrationData -> BS.ByteString+getSignatureBaseFromRegistration registration registrationData = formatSignatureBase aId clientData kH publicKey+ where aId = encodeUtf8 $ registration_appId registration+ clientData = encodeUtf8 $ registration_clientData registration+ kH = registrationData_keyHandle registrationData+ publicKey = registrationData_publicKey registrationData -- | Verifies that Registration is a valid response to the Request verifyRegistration :: Request -> Registration -> Either U2FError Request@@ -123,7 +122,7 @@ registrationData <- parseRegistrationData $ encodeUtf8 $ registration_registrationData registration pkey <- getPubKeyFromCertificate $ registrationData_certificate registrationData signature <- parseSignature $ registrationData_signature registrationData- signatureBase <- getSignatureBaseFromRegistration registration registrationData+ let signatureBase = getSignatureBaseFromRegistration registration registrationData case (verifySignature signatureBase pkey signature) of True -> Right (request {keyHandle = Just $ formatOutputBase64 $ registrationData_keyHandle registrationData}) False -> Left FailedVerificationError@@ -140,15 +139,18 @@ Just clientData -> Right clientData Nothing -> Left ClientDataParseError --- | Verifies that Signin response is valid given saved pubkey bytestring, request+-- | Verifies that Signin response is valid given saved pubkey bytestring, request.+-- Warning!: Expects uncompressed public key. verifySignin :: BS.ByteString -> Request -> Signin -> Either U2FError Bool verifySignin savedPubkey request signin = do clientData <- parseClientData $ encodeUtf8 $ signin_clientData signin _ <- u2fComparator (challenge request) (clientData_challenge clientData) ChallengeMismatchError signatureData <- parseSignatureData $ encodeUtf8 $ signin_signatureData signin signature <- parseSignature $ signatureData_signature signatureData- signatureBase <- getSigninSignatureBase request signin signatureData- -- So Gross. TODO: write function that checks first byte for compression state, parses each pubkey format+ let signatureBase = getSigninSignatureBase request signin signatureData+ -- TODO: write function that checks first byte for compression state, parses each pubkey format+ -- Technically, only uncompressed keys are allowed by the FIDO U2F spec, but it's possible that+ -- a compressed key could be added to user data via other means. publicKey <- case (parsePublicKey $ BS.tail $ savedPubkey) of Just key -> Right key Nothing -> Left PubKeyParsingError@@ -164,13 +166,12 @@ Right ([_, IntVal r, IntVal s, _]) -> Right $ ECDSA.Signature r s _ -> Left SignatureParseError -getSigninSignatureBase :: Request -> Signin -> SignatureData -> Either U2FError BS.ByteString-getSigninSignatureBase request signin signatureData = do- appId <- pure $ encodeUtf8 $ appId request- userPresenceFlag <- pure $ signatureData_userPresenceFlag signatureData- counter <- pure $ signatureData_counter signatureData- clientData <- pure $ encodeUtf8 $ signin_clientData signin- Right $ BS.concat([SHA256.hash(appId), userPresenceFlag, counter, SHA256.hash(decodeLenient clientData)])+getSigninSignatureBase :: Request -> Signin -> SignatureData -> BS.ByteString+getSigninSignatureBase request signin signatureData = BS.concat([SHA256.hash(aId), userPresenceFlag, counter, SHA256.hash(decodeLenient clientData)])+ where aId = encodeUtf8 $ appId request+ userPresenceFlag = signatureData_userPresenceFlag signatureData+ counter = signatureData_counter signatureData+ clientData = encodeUtf8 $ signin_clientData signin parsePublicKey :: BS.ByteString -> Maybe ECDSA.PublicKey parsePublicKey keyByteString = case P256.pointFromBinary keyByteString of@@ -195,10 +196,10 @@ reserved <- getByteString 1 publicKey <- getByteString 65 keyHandleLen <- getWord8- keyHandle <- getByteString $ fromIntegral keyHandleLen+ kH <- getByteString $ fromIntegral keyHandleLen cert <- unpackASN1 sign <- unpackASN1- return $ RegistrationData reserved publicKey keyHandle cert sign+ return $ RegistrationData reserved publicKey kH cert sign unpackSignatureData :: Get SignatureData unpackSignatureData = do
src/U2F/Types.hs view
@@ -5,8 +5,8 @@ import GHC.Generics import qualified Data.Text as T import qualified Data.ByteString.Char8 as BS-import Data.Aeson ((.:), (.:?), decode, FromJSON(..),- ToJSON(..), Value(..), genericParseJSON, genericToJSON, defaultOptions)+import Data.Aeson (FromJSON(..),+ ToJSON(..), genericParseJSON, genericToJSON, defaultOptions) import Data.Aeson.Types (fieldLabelModifier) data U2FError =
u2f.cabal view
@@ -2,7 +2,7 @@ -- see http://haskell.org/cabal/users-guide/ name: u2f-version: 0.1.0.1+version: 0.1.0.2 synopsis: Haskell Universal Two Factor helper toolbox library thing description: Library useful for server-side U2F Registration and Signin flows. homepage: https://github.com/EButlerIV/u2f@@ -23,15 +23,15 @@ ghc-options: -Wall -- other-modules: -- other-extensions:- build-depends: base >= 4.8 && < 4.11- , cryptonite >= 0.17 && < 0.21+ build-depends: base >= 4.8 && < 5+ , cryptonite >= 0.17 && <= 0.25 , binary >= 0.8.4.0 && < 0.9 , text >= 1.2 && < 1.3 , asn1-encoding >= 0.9 && < 0.10 , asn1-types >= 0.3.2 && < 0.4 , base64-bytestring >= 1.0.0.1 && < 1.0.1.0 , cryptohash >= 0.11 && < 0.12- , aeson >= 1.0 && < 1.1+ , aeson >= 1.0 && <= 1.4.0.0 , bytestring >= 0.10 && < 0.11 hs-source-dirs: src default-language: Haskell2010@@ -43,7 +43,7 @@ hs-source-dirs: tests default-language: Haskell2010 build-depends: base- , hspec >= 2.2 && < 2.3+ , hspec >= 2.2 && < 2.6 , u2f , text , either-unwrap