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 +5/−1
- aws-sns-verify.cabal +14/−1
- library/Amazon/SNS/Verify.hs +2/−2
- library/Amazon/SNS/Verify/Prelude.hs +6/−2
- library/Amazon/SNS/Verify/ValidURI.hs +38/−0
- library/Amazon/SNS/Verify/Validate.hs +22/−6
- tests/Amazon/SNS/Verify/TestPrelude.hs +3/−5
- tests/Amazon/SNS/Verify/ValidURISpec.hs +22/−0
- tests/Amazon/SNS/Verify/ValidateSpec.hs +21/−1
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"