Skip to content

Commit f632ff2

Browse files
committed
\#2: Provide utilities to validate the secret key
1 parent dbfbc79 commit f632ff2

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

github-webhooks.cabal

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
--
33
-- see: https://github.com/sol/hpack
44
--
5-
-- hash: 5615459135d4ff74ceec05176fb1708f3f5c7b50dd4184b4d462e62af6bbdb17
5+
-- hash: f7a90de8da51ba7bfe8b932cd2e197309c530257d3ed031c1d776ccb5cd7f39e
66

77
name: github-webhooks
88
version: 0.9.0
@@ -36,14 +36,19 @@ library
3636
build-depends:
3737
aeson
3838
, base ==4.*
39+
, base16-bytestring
40+
, bytestring
41+
, cryptonite
3942
, deepseq
4043
, deepseq-generics
44+
, memory
4145
, text
4246
, time
4347
, vector
4448
exposed-modules:
4549
GitHub.Data.Webhooks.Events
4650
GitHub.Data.Webhooks.Payload
51+
GitHub.Data.Webhooks.Secure
4752
other-modules:
4853
Paths_github_webhooks
4954
default-language: Haskell2010

package.yaml

+4-1
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,18 @@ default-extensions:
2929
dependencies:
3030
- base == 4.*
3131
- aeson
32+
- bytestring
3233
- text
3334
- vector
3435

3536
library:
3637
source-dirs: src
3738
dependencies:
39+
- base16-bytestring
40+
- cryptonite
3841
- deepseq
3942
- deepseq-generics
43+
- memory
4044
- time
4145

4246
tests:
@@ -46,6 +50,5 @@ tests:
4650
dependencies:
4751
- github-webhooks
4852
- hspec == 2.*
49-
- bytestring
5053
default-extensions:
5154
- ScopedTypeVariables

src/GitHub/Data/Webhooks/Secure.hs

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
-- See <https://developer.github.com/webhooks/securing/>.
2+
module GitHub.Data.Webhooks.Secure
3+
( isSecurePayload
4+
, assertSecurePayload
5+
) where
6+
7+
import Crypto.Hash.Algorithms (SHA1)
8+
import Crypto.MAC.HMAC (HMAC(..), hmac)
9+
import Control.Monad (unless)
10+
import Control.Exception (Exception, throwIO)
11+
import Data.ByteArray (convert, constEq)
12+
import Data.Monoid ((<>))
13+
import Data.ByteString (ByteString)
14+
import Data.Text (Text)
15+
import qualified Data.ByteString.Base16 as B16
16+
import qualified Data.Text.Encoding as E
17+
18+
19+
-- The implementation of this module is partially lifted from the @github@ package.
20+
21+
22+
-- | Returns 'True' if the given HMAC digest (passed in the @X-Hub-Signature@ header)
23+
-- agrees with the provided secret and request body. If not, this request may be forged.
24+
isSecurePayload
25+
:: Text
26+
-> Maybe Text
27+
-> ByteString
28+
-> Bool
29+
isSecurePayload secret shaOpt payload = maybe False (constEq ourSig) theirSig
30+
where
31+
hexDigest = B16.encode . convert . hmacGetDigest
32+
theirSig = E.encodeUtf8 <$> shaOpt
33+
ourSig = "sha1=" <> hexDigest (hmac (E.encodeUtf8 secret) payload :: HMAC SHA1)
34+
35+
36+
-- | An exception indicating that the given payload is not secure.
37+
data PayloadNotSecure = PayloadNotSecure
38+
39+
instance Exception PayloadNotSecure
40+
41+
instance Show PayloadNotSecure where
42+
showsPrec _ PayloadNotSecure = showString "the origin of this request may not originate from GitHub"
43+
44+
-- | Like 'isSecurePayload', but throws 'PayloadNotSecure' if the payload is not secure.
45+
assertSecurePayload
46+
:: Text
47+
-> Maybe Text
48+
-> ByteString
49+
-> IO ()
50+
assertSecurePayload secret shaOpt payload = do
51+
let secure = isSecurePayload secret shaOpt payload
52+
unless secure $! throwIO PayloadNotSecure

0 commit comments

Comments
 (0)