packages feed

aws-sns-verify 0.0.0.1 → 0.0.0.2

raw patch · 9 files changed

+133/−18 lines, 9 filesdep +network-uridep +regex-tdfaPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: network-uri, regex-tdfa

API changes (from Hackage documentation)

+ Amazon.SNS.Verify: BadUri :: String -> SNSNotificationValidationError
+ Amazon.SNS.Verify.ValidURI: devRegPattern :: String
+ Amazon.SNS.Verify.ValidURI: prodRegPattern :: String
+ Amazon.SNS.Verify.ValidURI: validRegPattern :: String
+ Amazon.SNS.Verify.ValidURI: validScheme :: String
+ Amazon.SNS.Verify.Validate: BadUri :: String -> SNSNotificationValidationError
- Amazon.SNS.Verify: BadSubscription :: () -> SNSNotificationValidationError
+ Amazon.SNS.Verify: BadSubscription :: SNSNotificationValidationError
- Amazon.SNS.Verify.Validate: BadSubscription :: () -> SNSNotificationValidationError
+ Amazon.SNS.Verify.Validate: BadSubscription :: SNSNotificationValidationError

Files

CHANGELOG.md view
@@ -1,4 +1,8 @@-## [_Unreleased_](https://github.com/freckle/aws-sns-verify/compare/v0.0.0.0...main)+## [_Unreleased_](https://github.com/freckle/aws-sns-verify/compare/v0.0.0.2...main)++## [v0.0.0.2](https://github.com/freckle/aws-sns-verify/compare/v0.0.0.2...v0.0.0.1)++- Validate PEM has come from AWS before checking signature.  ## [v0.0.0.1](https://github.com/freckle/aws-sns-verify/compare/v0.0.0.1...v0.0.0.0) 
aws-sns-verify.cabal view
@@ -1,6 +1,6 @@ cabal-version:      1.18 name:               aws-sns-verify-version:            0.0.0.1+version:            0.0.0.2 license:            MIT license-file:       LICENSE copyright:          2022 Freckle By Renaissance@@ -28,11 +28,17 @@     type:     git     location: https://github.com/freckle/aws-sns-verify +flag development+    description: Configure of testing+    default:     False+    manual:      True+ library     exposed-modules:         Amazon.SNS.Verify         Amazon.SNS.Verify.Payload         Amazon.SNS.Verify.Validate+        Amazon.SNS.Verify.ValidURI      hs-source-dirs:     library     other-modules:      Amazon.SNS.Verify.Prelude@@ -59,7 +65,9 @@         errors >=2.3.0,         http-conduit >=2.3.2,         memory >=0.14.18,+        network-uri >=2.6.1.0,         pem >=0.2.4,+        regex-tdfa >=1.2.3.1,         text >=1.2.3.1,         x509 >=1.7.5,         x509-validation >=1.6.11@@ -71,6 +79,9 @@     if impl(ghc >=9.2.2)         ghc-options: -Wno-missing-kind-signatures +    if flag(development)+        cpp-options: -DDEVELOPMENT+ test-suite aws-sns-verify-test     type:               exitcode-stdio-1.0     main-is:            Main.hs@@ -78,6 +89,7 @@     other-modules:         Amazon.SNS.Verify.TestPrelude         Amazon.SNS.Verify.ValidateSpec+        Amazon.SNS.Verify.ValidURISpec         Amazon.SNS.VerifySpec         Paths_aws_sns_verify @@ -104,6 +116,7 @@         base >=4.7 && <5,         hspec >=2.5.5,         http-types >=0.12.2,+        regex-tdfa >=1.2.3.1,         text >=1.2.3.1,         wai >=3.2.1.2,         warp >=3.2.25,
library/Amazon/SNS/Verify.hs view
@@ -22,7 +22,7 @@ -- The same as 'verifySNSMessage', but decodes the message as `JSON`. -- verifySNSMessageJSON :: (FromJSON a, MonadIO m) => Value -> m a-verifySNSMessageJSON = unTry id <=< verifySNSMessageJSONEither+verifySNSMessageJSON = unTryIO id <=< verifySNSMessageJSONEither  verifySNSMessageJSONEither   :: (FromJSON a, MonadIO m)@@ -47,7 +47,7 @@ -- 3. And in the case of subscription events responded to. -- verifySNSMessage :: MonadIO m => Value -> m Text-verifySNSMessage = unTry id <=< verifySNSMessageEither+verifySNSMessage = unTryIO id <=< verifySNSMessageEither  verifySNSMessageEither   :: MonadIO m => Value -> m (Either SNSNotificationValidationError Text)
library/Amazon/SNS/Verify/Prelude.hs view
@@ -5,6 +5,7 @@  import Prelude as X +import Control.Error (ExceptT, throwE) import Control.Exception as X (Exception) import qualified Control.Exception import Control.Monad as X (join, (<=<))@@ -16,8 +17,11 @@ throwIO :: (MonadIO m, Exception e) => e -> m a throwIO = liftIO . Control.Exception.throwIO -unTry :: (MonadIO m, Exception e) => (a -> e) -> Either a b -> m b-unTry e = either (throwIO . e) pure+unTryIO :: (MonadIO m, Exception e) => (a -> e) -> Either a b -> m b+unTryIO e = either (throwIO . e) pure++unTryE :: (Monad m) => (a -> e) -> Either a b -> ExceptT e m b+unTryE e = either (throwE . e) pure  fromMaybeM :: Monad m => m a -> Maybe a -> m a fromMaybeM f = maybe f pure
+ library/Amazon/SNS/Verify/ValidURI.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE CPP #-}++module Amazon.SNS.Verify.ValidURI+  ( validScheme+  , validRegPattern+  , devRegPattern+  , prodRegPattern+  ) where++import Amazon.SNS.Verify.Prelude++validScheme :: String+validScheme =+#ifdef DEVELOPMENT+  _devScheme+#else+  _prodScheme+#endif++_devScheme :: String+_devScheme = "http:"++_prodScheme :: String+_prodScheme = "https:"++validRegPattern :: String+validRegPattern =+#ifdef DEVELOPMENT+  devRegPattern+#else+  prodRegPattern+#endif++devRegPattern :: String+devRegPattern = "^localhost$"++prodRegPattern :: String+prodRegPattern = "^sns\\.[a-zA-Z0-9\\-]{3,}\\.amazonaws\\.com(\\.cn)?$"
library/Amazon/SNS/Verify/Validate.hs view
@@ -8,6 +8,7 @@ import Amazon.SNS.Verify.Prelude  import Amazon.SNS.Verify.Payload+import Amazon.SNS.Verify.ValidURI (validRegPattern, validScheme) import Control.Error (ExceptT, catMaybes, headMay, runExceptT, throwE) import Control.Monad (when) import Data.ByteArray.Encoding (Base(Base64), convertFromBase)@@ -27,6 +28,8 @@   (SignatureFailure, SignatureVerification(..), verifySignature) import Network.HTTP.Simple   (getResponseBody, getResponseStatusCode, httpLbs, parseRequest_)+import Network.URI (parseURI, uriAuthority, uriRegName, uriScheme)+import Text.Regex.TDFA ((=~))  data ValidSNSMessage   = SNSMessage Text@@ -46,7 +49,7 @@   => SNSPayload   -> m (Either SNSNotificationValidationError ValidSNSMessage) validateSnsMessage payload@SNSPayload {..} = runExceptT $ do-  signature <- unTry BadSignature $ convertFromBase Base64 $ encodeUtf8+  signature <- unTryE BadSignature $ convertFromBase Base64 $ encodeUtf8     snsSignature   signedCert <- retrieveCertificate payload   let@@ -67,12 +70,24 @@   => SNSPayload   -> ExceptT SNSNotificationValidationError m SignedCertificate retrieveCertificate SNSPayload {..} = do-  response <- httpLbs $ parseRequest_ $ T.unpack snsSigningCertURL-  pems <- unTry BadPem $ pemParseLBS $ getResponseBody response+  certUrlStr <- unTryE id $ validateCertUrl snsSigningCertURL+  response <- httpLbs $ parseRequest_ certUrlStr+  pems <- unTryE BadPem $ pemParseLBS $ getResponseBody response   cert <-     fromMaybeM (throwE $ BadPem "Empty List") $ pemContent <$> headMay pems-  unTry BadCert $ decodeSignedCertificate cert+  unTryE BadCert $ decodeSignedCertificate cert +validateCertUrl :: Text -> Either SNSNotificationValidationError String+validateCertUrl certUrl = do+  uri <- fromMaybeM (Left $ BadUri certUrlStr) $ parseURI certUrlStr+  if uriScheme uri+      == validScheme+      && maybe "" uriRegName (uriAuthority uri)+      =~ validRegPattern+    then Right certUrlStr+    else Left $ BadUri certUrlStr+  where certUrlStr = T.unpack certUrl+ unsignedSignature :: SNSPayload -> ByteString unsignedSignature SNSPayload {..} =   encodeUtf8 $ mconcat $ (<> "\n") <$> catMaybes@@ -110,16 +125,17 @@   SNSSubscribe SNSSubscription {..} -> do     response <- httpLbs $ parseRequest_ $ T.unpack snsSubscribeURL     when (getResponseStatusCode response >= 300) $ do-      throwE $ BadSubscription ()+      throwE BadSubscription     throwE SubscribeMessageResponded   SNSUnsubscribe{} -> throwE UnsubscribeMessage  data SNSNotificationValidationError   = BadPem String+  | BadUri String   | BadSignature String   | BadCert String   | BadJSONParse String-  | BadSubscription ()+  | BadSubscription   | InvalidPayload SignatureFailure   | MissingMessageTypeHeader   | UnsubscribeMessage
tests/Amazon/SNS/Verify/TestPrelude.hs view
@@ -18,11 +18,9 @@   (setReady, whenReady) <- initReadyState   race_ (whenReady action)     $ runSettings (setBeforeMainLoop setReady . setPort 3000 $ defaultSettings)-    $ \_req send -> send $ responseFile-        ok200-        [("Content-Type", "text/plain")]-        "./tests/cert.pem"-        Nothing+    $ \req send -> if rawPathInfo req == "/404"+        then send $ responseLBS notFound404 [] ""+        else send $ responseFile ok200 [] "./tests/cert.pem" Nothing  where   initReadyState = do     ready <- newEmptyMVar
+ tests/Amazon/SNS/Verify/ValidURISpec.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE NoOverloadedStrings #-}++module Amazon.SNS.Verify.ValidURISpec+  ( spec+  ) where++import Amazon.SNS.Verify.TestPrelude++import Amazon.SNS.Verify.ValidURI+import Text.Regex.TDFA ((=~))++spec :: Spec+spec = around_ useCertServer $ do+  describe "verifySNSMessage" $ do+    it "validates a prod schema" $ do+      "sns.us-east-2.amazonaws.com" `shouldSatisfy` (=~ prodRegPattern)++    it "validates a prod schema" $ do+      "sns.us-west-1b.amazonaws.com" `shouldSatisfy` (=~ prodRegPattern)++    it "validates a prod schema" $ do+      "www.google.com" `shouldNotSatisfy` (=~ prodRegPattern)
tests/Amazon/SNS/Verify/ValidateSpec.hs view
@@ -62,7 +62,7 @@           , snsSignatureVersion = "1"           , snsSignature =             "Dg24trcOUiLjclt5JwyJS0JEOnEEbi6P30XS6KBxMCwzZ08a04UwjaFTW9Ae8xurhBS5YESz1fY28vTwvEmxh/20WmB3bWIDOMp9v5RI8XSZOvpMm+hdQJ43VqGhEDyAvRU6iCDLihDlZNc/sBCwl9X0H4kh/8vIElRif9gFBbYI94ZHGgqEV+Zc3gVKo9Udrl/MxNvMVadsO/+/oPVUeWibQr3xfGK95oc/ocuNAgi0MOxZmLVnibHu36KOTSvy2qSLonnRRFcbaauYZJ4js7oTq+1ujXNO72oPLaeG3pVJ2grqMc5z8tKQxFnSTE3es7wQarU/CLrbO8j0isbnWw=="-          , snsSigningCertURL = "https://freckle.com/404.pem"+          , snsSigningCertURL = "http://localhost:3000/404"           , snsTypePayload = Notification $ SNSNotification             { snsSubject =               Just@@ -70,6 +70,26 @@             }           }       go `shouldReturn` Left (BadPem "Empty List")++    it "fails to validate an unexpected url" $ do+      let+        go = validateSnsMessage $ SNSPayload+          { snsMessage = "Some message"+          , snsMessageId = "corrupt"+          , snsTimestamp = "2022-05-18T14:52:26.952Z"+          , snsTopicArn = "arn:aws:sns:us-west-2:123456789012:MyTopic"+          , snsType = "Notification"+          , snsSignatureVersion = "1"+          , snsSignature =+            "Dg24trcOUiLjclt5JwyJS0JEOnEEbi6P30XS6KBxMCwzZ08a04UwjaFTW9Ae8xurhBS5YESz1fY28vTwvEmxh/20WmB3bWIDOMp9v5RI8XSZOvpMm+hdQJ43VqGhEDyAvRU6iCDLihDlZNc/sBCwl9X0H4kh/8vIElRif9gFBbYI94ZHGgqEV+Zc3gVKo9Udrl/MxNvMVadsO/+/oPVUeWibQr3xfGK95oc/ocuNAgi0MOxZmLVnibHu36KOTSvy2qSLonnRRFcbaauYZJ4js7oTq+1ujXNO72oPLaeG3pVJ2grqMc5z8tKQxFnSTE3es7wQarU/CLrbO8j0isbnWw=="+          , snsSigningCertURL = "http://attacker.com/evil.pem"+          , snsTypePayload = Notification $ SNSNotification+            { snsSubject =+              Just+                "SynthesisTaskNotification { TaskId: 680a1f1b-f3ae-4474-aa8f-3b6dfe52e656, Status: COMPLETED }"+            }+          }+      go `shouldReturn` Left (BadUri "http://attacker.com/evil.pem")      it "successfully validates an SNS subscription" $ do       -- pendingWith "need valid subscription payload"