stripe-signature 1.0.0.10 → 1.0.0.12
raw patch · 4 files changed
+76/−40 lines, 4 filesdep +cryptohash-sha256dep −cryptonitedep −memoryPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: cryptohash-sha256
Dependencies removed: cryptonite, memory
API changes (from Hackage documentation)
- Stripe.Signature: digest :: WebhookSecretKey -> Natural -> ByteString -> HMAC SHA256
+ Stripe.Signature: digest :: WebhookSecretKey -> Natural -> ByteString -> ByteString
Files
- changelog.md +6/−0
- library/Stripe/Signature.hs +5/−11
- stripe-signature.cabal +6/−7
- test/test.hs +59/−22
changelog.md view
@@ -29,3 +29,9 @@ Support GHC 9.0, `cryptonite` 0.29, `bytestring` 0.11, `memory` 0.16 Switch `base16-bytestring` version from 0.1 to 1.0++## 1.0.0.12 - 2022-01-08++Drop `cryptonite` and `memory` dependencies; HMAC is now done using the `cryptohash-sha256` package instead++Add some tests for the `isSigValid` function
library/Stripe/Signature.hs view
@@ -1,5 +1,3 @@-{-# OPTIONS_GHC -Wall #-}- {- | https://stripe.com/docs/webhooks/signatures#verify-manually -} module Stripe.Signature@@ -19,12 +17,8 @@ -- bytestring import Data.ByteString (ByteString) --- cryptonite-import Crypto.Hash (SHA256)-import Crypto.MAC.HMAC as HMAC---- memory-import qualified Data.ByteArray+-- cryptohash-sha256+import qualified Crypto.Hash.SHA256 -- stripe-concepts import Stripe.Concepts (WebhookSecretKey (..))@@ -36,13 +30,13 @@ isSigValid :: Sig -> WebhookSecretKey -> ByteString -> Bool isSigValid x secret body =- Data.List.any (Data.ByteArray.eq correctDigest) (sigV1 x)+ Data.List.any ((==) correctDigest) (sigV1 x) where correctDigest = digest secret (sigTime x) body -digest :: WebhookSecretKey -> Natural -> ByteString -> HMAC SHA256+digest :: WebhookSecretKey -> Natural -> ByteString -> ByteString digest (WebhookSecretKey secret) time body =- HMAC.hmac secret (signedPayload time body)+ Crypto.Hash.SHA256.hmac secret (signedPayload time body) signedPayload :: Natural -> ByteString -> ByteString signedPayload time body =
stripe-signature.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.0 name: stripe-signature-version: 1.0.0.10+version: 1.0.0.12 synopsis: Verification of Stripe webhook signatures category: Web@@ -24,8 +24,6 @@ license-file: license.txt build-type: Simple-tested-with: GHC==8.2.2, GHC==8.4.4, GHC==8.6.3, GHC==8.8.1, GHC==8.10.1,- GHC==9.0.1 extra-source-files: changelog.md@@ -33,6 +31,7 @@ library hs-source-dirs: library default-language: Haskell2010+ ghc-options: -Wall exposed-modules: Stripe.Signature@@ -42,9 +41,7 @@ || ^>= 4.15 , base16-bytestring ^>= 1.0 , bytestring ^>= 0.10 || ^>= 0.11- , cryptonite ^>= 0.25 || ^>= 0.26 || ^>= 0.27 || ^>= 0.28- || ^>= 0.29- , memory ^>= 0.14 || ^>= 0.15 || ^>= 0.16+ , cryptohash-sha256 ^>= 0.11.101 , stripe-concepts ^>= 1.0 , text ^>= 1.2 @@ -53,11 +50,13 @@ hs-source-dirs: test type: exitcode-stdio-1.0 main-is: test.hs- ghc-options: -threaded+ ghc-options: -Wall build-depends: base ^>= 4.10 || ^>= 4.11 || ^>= 4.12 || ^>= 4.13 || ^>= 4.14 || ^>= 4.15+ , base16-bytestring ^>= 1.0 , bytestring ^>= 0.10 || ^>= 0.11+ , stripe-concepts ^>= 1.0 , stripe-signature , text ^>= 1.2
test/test.hs view
@@ -1,40 +1,77 @@-{-# OPTIONS_GHC -Wall #-}- import Stripe.Signature -- base import Control.Monad (unless)+import Data.Foldable (for_) import Data.Word (Word8) import Numeric.Natural (Natural)-import System.Exit (die)+import System.Exit (exitFailure) +-- base16-bytestring+import qualified Data.ByteString.Base16 as Base16+ -- bytestring import qualified Data.ByteString+import qualified Data.ByteString.Char8 +-- stripe-concepts+import Stripe.Concepts (WebhookSecretKey (..))+ -- text import qualified Data.Text -test_string :: String-test_string =- "t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e\- \77a0e56ff536d0ce8e108d8bd,v0=6ffbb59b2300aae63f27240606\- \9a9788598b792a944a07aba816edb039989a39"--test_time :: Natural-test_time = 1492774577--test_v1Bytes :: [Word8]-test_v1Bytes =- [82,87,168,105,231,236,235,237,163,42,255,166,44,220,163,250- ,81,202,215,231,122,14,86,255,83,109,12,232,225,8,216,189]+data Failure = Failure String main :: IO () main =+ unless (null failures) $ do+ for_ failures $ \(Failure x) -> putStrLn ("🔥 " ++ x)+ exitFailure++failures :: [Failure]+failures = parseFailures ++ validationFailures++parseFailures :: [Failure]+parseFailures = case parseSig (Data.Text.pack test_string) of- Nothing -> die "Signature parsing failed"+ Nothing -> [Failure "Signature parsing failed"] Just sig ->- do- unless (sigTime sig == test_time) $- die ("sigTime: " ++ show (sigTime sig))- unless ((Data.ByteString.unpack <$> sigV1 sig) == [test_v1Bytes]) $- die ("sigV1: " ++ show (Data.ByteString.unpack <$> sigV1 sig))+ case sigTime sig == test_time of+ True -> []+ False -> [Failure $ "sigTime: " ++ show (sigTime sig)]+ +++ case (Data.ByteString.unpack <$> sigV1 sig) == [test_v1Bytes] of+ True -> []+ False -> [Failure $ "sigV1: " ++ show (Data.ByteString.unpack <$> sigV1 sig)]++ where+ test_string :: String+ test_string =+ "t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e\+ \77a0e56ff536d0ce8e108d8bd,v0=6ffbb59b2300aae63f27240606\+ \9a9788598b792a944a07aba816edb039989a39"++ test_time :: Natural+ test_time = 1492774577++ test_v1Bytes :: [Word8]+ test_v1Bytes =+ [82,87,168,105,231,236,235,237,163,42,255,166,44,220,163,250+ ,81,202,215,231,122,14,86,255,83,109,12,232,225,8,216,189]++validationFailures :: [Failure]+validationFailures =+ case Base16.decode (Data.ByteString.Char8.pack "f89c0a0e96fa6304a7ebd5ecaf13bc1beb859577cd7cafb7bdbc43c3bc2f6afa") of+ Left _ -> [Failure "Digest decoding failed"]+ Right d ->+ case isSigValid Sig{ sigTime = 123, sigV1 = [d] } (WebhookSecretKey (Data.ByteString.Char8.pack "secret")) (Data.ByteString.Char8.pack "hello") of+ True -> []+ False -> [Failure "isSigValid rejects a valid signature"]+ +++ case isSigValid Sig{ sigTime = 123, sigV1 = [d] } (WebhookSecretKey (Data.ByteString.Char8.pack "secret")) (Data.ByteString.Char8.pack "hello!") of+ False -> []+ True -> [Failure "isSigValid accepts an invalid signature"]+ +++ case isSigValid Sig{ sigTime = 123, sigV1 = [Data.ByteString.Char8.pack "whatever", d] } (WebhookSecretKey (Data.ByteString.Char8.pack "secret")) (Data.ByteString.Char8.pack "hello") of+ True -> []+ False -> [Failure "isSigValid should accept if *any* of the sigs are valid"]