From de0b0b7527d36b6a606473b4a13f553779e47c16 Mon Sep 17 00:00:00 2001 From: me Date: Tue, 17 Dec 2024 10:47:00 +0200 Subject: [PATCH] a couple of signature tests --- .dir-locals.el | 4 +++- Makefile | 12 ++++++++---- cabal.project | 2 ++ fedi.cabal | 34 +++++++++++++++++++++++++++++++++- src/Fedi.hs | 2 ++ src/Fedi/Crypto.hs | 31 +++++++++++++++++-------------- src/Fedi/Helpers.hs | 3 +-- src/Fedi/Routes.hs | 1 + src/Fedi/Routes/Notes.hs | 1 + src/Fedi/Signature/Check.hs | 19 ++++++++++++++----- src/Fedi/Signature/Sign.hs | 8 +------- src/Fedi/Signature/Types.hs | 1 + src/Fedi/Types.hs | 2 +- src/Fedi/UserDetails.hs | 15 ++++++--------- src/Fedi/Webfinger.hs | 1 + test/Fedi/TestSpec.hs | 37 +++++++++++++++++++++++++++++++++++++ test/Spec.hs | 1 + test/public/details.json | 10 ++++++++++ test/public/private.pem | 28 ++++++++++++++++++++++++++++ test/public/public.pem | 9 +++++++++ 20 files changed, 177 insertions(+), 44 deletions(-) create mode 100644 test/Fedi/TestSpec.hs create mode 100644 test/Spec.hs create mode 100644 test/public/details.json create mode 100644 test/public/private.pem create mode 100644 test/public/public.pem diff --git a/.dir-locals.el b/.dir-locals.el index e17e58e..5241ecb 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -1,5 +1,7 @@ ( ("src" . ((nil . ((dante-target . "fedi"))))) - ("app" . ((nil . ((dante-target . "exe:fedi"))))) + ("app" . ((nil . ((dante-target . "exe:fediserve"))))) + + ("test" . ((nil . ((dante-target . "test:fedi-test"))))) ) diff --git a/Makefile b/Makefile index e23f8b2..0c69a35 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ format: find ./src -type f -name "*.hs" -exec sh -c 'fourmolu -i {}' \; find ./app -type f -name "*.hs" -exec sh -c 'fourmolu -i {}' \; - # find ./test -type f -name "*.hs" -exec sh -c 'fourmolu -i {}' \; + find ./test -type f -name "*.hs" -exec sh -c 'fourmolu -i {}' \; .PHONY: clean clean: @@ -12,15 +12,19 @@ clean: build: fedi.cabal cabal.project cabal build all --enable-tests --enable-benchmarks +.PHONY: test +test: + cabal test --test-show-details=direct + .PHONY: serve serve: - FEDI_DETAILS="test/public/details.json" cabal run fedi -- serve + FEDI_DETAILS="test/public/details.json" cabal run fediserve -- serve .PHONY: insert insert: - FEDI_DETAILS="test/public/details.json" cabal run fedi -- insert note.html + FEDI_DETAILS="test/public/details.json" cabal run fediserve -- insert note.html .PHONY: docker docker: fedi.cabal cabal.project clean DOCKER_BUILDKIT=1 docker build -o ./out/ . - file out/fedi + file out/fediserve diff --git a/cabal.project b/cabal.project index 9792e01..ced48fb 100644 --- a/cabal.project +++ b/cabal.project @@ -1,3 +1,5 @@ packages: *.cabal constraints: cryptostore +use_crypton + +tests: True diff --git a/fedi.cabal b/fedi.cabal index 8f6cd78..eedea6c 100644 --- a/fedi.cabal +++ b/fedi.cabal @@ -83,7 +83,7 @@ library DuplicateRecordFields NoFieldSelectors -executable fedi +executable fediserve import: warnings main-is: Main.hs other-modules: @@ -122,3 +122,35 @@ executable fedi DuplicateRecordFields NoFieldSelectors ghc-options: -Wall -O -threaded -rtsopts -with-rtsopts=-N + +test-suite fedi-test + type: exitcode-stdio-1.0 + hs-source-dirs: test + main-is: Spec.hs + other-modules: + Fedi.TestSpec + default-extensions: + BlockArguments + LambdaCase + OverloadedRecordDot + OverloadedStrings + QuasiQuotes + ViewPatterns + DuplicateRecordFields + NoFieldSelectors + build-depends: + base + , hspec + , hspec-discover + , hspec-wai + , req + , aeson + , text + , raw-strings-qq + , pretty-simple + , fedi + ghc-options: + -threaded -rtsopts -with-rtsopts=-N + build-tool-depends: + hspec-discover:hspec-discover + default-language: GHC2021 diff --git a/src/Fedi.hs b/src/Fedi.hs index 350ed41..8b60b1d 100644 --- a/src/Fedi.hs +++ b/src/Fedi.hs @@ -4,6 +4,8 @@ import Fedi.Crypto as Export import Fedi.Helpers as Export import Fedi.Requests as Export import Fedi.Routes as Export +import Fedi.Signature.Check as Export +import Fedi.Signature.Sign as Export import Fedi.Types as Export import Fedi.Types.Helpers as Export import Fedi.UserDetails as Export diff --git a/src/Fedi/Crypto.hs b/src/Fedi/Crypto.hs index a795de4..87a98b2 100644 --- a/src/Fedi/Crypto.hs +++ b/src/Fedi/Crypto.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE DataKinds #-} module Fedi.Crypto where @@ -12,6 +12,7 @@ import Data.Base64.Types qualified as Base64 import Data.ByteArray qualified as BA import Data.ByteString.Base64 qualified as Base64 import Data.Text qualified as T +import Data.Text.Encoding qualified as T import Data.X509 qualified as Crypto import Fedi.Helpers @@ -31,7 +32,7 @@ verifyPub pubkeypem sig message = do _ -> throw "failed to read pubkey pem" pure $ Crypto.verify (Just Crypto.SHA256) pubkey message (decodeBase64 sig) -sign :: FilePath -> ByteString -> IO Signed +sign :: FilePath -> ByteString -> IO SignedMessage sign privatePemFile message = do -- get private key privkeypem <- Crypto.readKeyFile privatePemFile @@ -45,21 +46,23 @@ sign privatePemFile message = do & either (throw . show) pure -- return - pure Signed {..} + pure (makeSignedMessage signedMessage) -newtype Signed - = Signed - { signedMessage :: ByteString - } +-- | A Base64 encoded string. +newtype SignedMessage + = SignedMessage (Base64.Base64 'Base64.StdPadded ByteString) deriving (Show) -ppSigned :: Signed -> String -ppSigned signed = - unlines - [ "Signature" - , "{ signedMessage = " <> encodeBase64String signed.signedMessage - , "}" - ] +makeSignedMessage :: ByteString -> SignedMessage +makeSignedMessage = SignedMessage . Base64.encodeBase64' + +ppSignedMessage :: SignedMessage -> String +ppSignedMessage (SignedMessage message) = + T.unpack $ T.decodeUtf8 $ Base64.extractBase64 message + +bsSignedMessage :: SignedMessage -> ByteString +bsSignedMessage (SignedMessage message) = + Base64.extractBase64 message encodeBase64 :: ByteString -> ByteString encodeBase64 = Base64.extractBase64 . Base64.encodeBase64' diff --git a/src/Fedi/Helpers.hs b/src/Fedi/Helpers.hs index 047f964..777a7bc 100644 --- a/src/Fedi/Helpers.hs +++ b/src/Fedi/Helpers.hs @@ -9,7 +9,7 @@ import Data.Aeson qualified as A import Data.Aeson.Encode.Pretty qualified as AP import Data.ByteString as Export (ByteString) import Data.Foldable as Export -import Data.Function as Export +import Data.Function as Export hiding (id) import Data.Functor as Export import Data.Maybe as Export (fromMaybe, listToMaybe, maybeToList) import Data.String as Export (fromString) @@ -18,7 +18,6 @@ import Data.Text.Lazy qualified as TL import Data.Text.Lazy.Encoding qualified as TL import Data.Time as Export (UTCTime) import Data.Traversable as Export -import Fedi.UserDetails import GHC.Generics as Export (Generic) import Text.Pretty.Simple qualified as PS diff --git a/src/Fedi/Routes.hs b/src/Fedi/Routes.hs index fc548f2..b015ad5 100644 --- a/src/Fedi/Routes.hs +++ b/src/Fedi/Routes.hs @@ -3,6 +3,7 @@ module Fedi.Routes ( module Export, ) where +import Fedi.Helpers import Fedi.Routes.Follow as Export import Fedi.Routes.Helpers as Export import Fedi.Routes.Inbox as Export diff --git a/src/Fedi/Routes/Notes.hs b/src/Fedi/Routes/Notes.hs index 54839ed..c63175d 100644 --- a/src/Fedi/Routes/Notes.hs +++ b/src/Fedi/Routes/Notes.hs @@ -1,6 +1,7 @@ module Fedi.Routes.Notes where import Data.Aeson qualified as A +import Fedi.Helpers import Fedi.Routes.Helpers import Fedi.Types import Fedi.UserDetails diff --git a/src/Fedi/Signature/Check.hs b/src/Fedi/Signature/Check.hs index 1055451..0aae04e 100644 --- a/src/Fedi/Signature/Check.hs +++ b/src/Fedi/Signature/Check.hs @@ -19,6 +19,7 @@ import Fedi.Routes.Helpers import Fedi.Signature.Types import Fedi.Types import Fedi.UserDetails +import Network.HTTP.Types.Header qualified as HTTP import Network.HTTP.Types.URI qualified as HTTP import Network.Wai qualified as Wai import Text.ParserCombinators.ReadP qualified as P @@ -45,7 +46,7 @@ checkSignatureAndParseBody = do personPublicKey = pemToBS person.otype.publicKey.publicKeyPem signatureString <- - makeSignatureString request sigheader.headers + signatureStringFromRequest request sigheader.headers -- check liftIO $ @@ -54,9 +55,11 @@ checkSignatureAndParseBody = do -- parse the body and return it parseJson body -makeSignatureString - :: forall m. (MonadThrow m) => Wai.Request -> [T.Text] -> m ByteString -makeSignatureString request (map (T.encodeUtf8 . T.toLower) -> headers) = do +signatureStringFromRequest + :: forall m + . (MonadThrow m) + => Wai.Request -> [T.Text] -> m ByteString +signatureStringFromRequest request (map (T.encodeUtf8 . T.toLower) -> headers) = do let requestHeaders = Wai.requestHeaders request method = T.encodeUtf8 $ T.toLower $ T.decodeUtf8 $ Wai.requestMethod request @@ -65,6 +68,13 @@ makeSignatureString request (map (T.encodeUtf8 . T.toLower) -> headers) = do <> T.encodeUtf8 (T.intercalate "/" $ Wai.pathInfo request) <> HTTP.renderQuery True (Wai.queryString request) requestTarget = method <> " " <> path + signatureStringFromSpecifics requestHeaders requestTarget headers + +signatureStringFromSpecifics + :: forall m + . (MonadThrow m) + => HTTP.RequestHeaders -> ByteString -> [ByteString] -> m ByteString +signatureStringFromSpecifics requestHeaders requestTarget headers = do let mylookup :: ByteString -> m ByteString mylookup header @@ -88,7 +98,6 @@ makeSignatureString request (map (T.encodeUtf8 . T.toLower) -> headers) = do case result of Nothing -> throw $ "Missing header '" <> show header <> "'." Just value -> pure $ header <> ": " <> value - BS.intercalate "\n" <$> traverse mylookup headers checkSignature diff --git a/src/Fedi/Signature/Sign.hs b/src/Fedi/Signature/Sign.hs index 3bb2e66..883f2b0 100644 --- a/src/Fedi/Signature/Sign.hs +++ b/src/Fedi/Signature/Sign.hs @@ -21,22 +21,16 @@ signSignature details host requestTarget body = do date <- Time.getCurrentTime <&> Time.formatTime Time.defaultTimeLocale "%a, %d %b %Y %H:%M:%S GMT" - let digest = "SHA-256=" <> encodeBase64 (makeDigest body) keyId = actorUrl details <> "#main-key" headers = ["(request-target)", "host", "date", "digest"] components = [] signatureString = makeSignatureString host requestTarget date digest - - -- BS.putStr $ signatureString <> "\n" - signed <- sign details.privatePem signatureString - let - signature = encodeBase64 signed.signedMessage + signature = bsSignedMessage signed signatureHeader = SignatureHeader {..} - pure HttpSignature {..} makeSignatureString diff --git a/src/Fedi/Signature/Types.hs b/src/Fedi/Signature/Types.hs index 990e724..5d88875 100644 --- a/src/Fedi/Signature/Types.hs +++ b/src/Fedi/Signature/Types.hs @@ -5,6 +5,7 @@ module Fedi.Signature.Types where import Data.ByteString qualified as BS import Data.Text qualified as T import Data.Text.Encoding qualified as T +import Fedi.Helpers import Fedi.UserDetails import Prelude hiding (error) diff --git a/src/Fedi/Types.hs b/src/Fedi/Types.hs index 6e8800c..2bac4c8 100644 --- a/src/Fedi/Types.hs +++ b/src/Fedi/Types.hs @@ -2,10 +2,10 @@ module Fedi.Types where -import Control.Monad (guard) import Data.Aeson qualified as A import Data.Aeson.Types qualified as A import Data.Text qualified as T +import Fedi.Helpers import Fedi.UserDetails import Prelude hiding (id, last) diff --git a/src/Fedi/UserDetails.hs b/src/Fedi/UserDetails.hs index e3c5813..5c6ae34 100644 --- a/src/Fedi/UserDetails.hs +++ b/src/Fedi/UserDetails.hs @@ -1,19 +1,11 @@ module Fedi.UserDetails ( module Fedi.UserDetails, - module Export, ) where import Data.Aeson qualified as A -import Data.ByteString as Export (ByteString) -import Data.Foldable as Export -import Data.Maybe as Export (fromMaybe, listToMaybe, maybeToList) -import Data.String as Export (fromString) -import Data.Text as Export (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as T -import Data.Time as Export (UTCTime) -import Data.Traversable as Export -import GHC.Generics as Export (Generic) +import Fedi.Helpers type Url = String @@ -50,3 +42,8 @@ actorUrl details = fullmention :: UserDetails -> String fullmention details = "@" <> details.username <> "@" <> details.domain + +readUserDetailsFile :: FilePath -> IO UserDetails +readUserDetailsFile detailsFile = + A.eitherDecodeFileStrict detailsFile + <&> either (\err -> error $ "could not read file '" <> detailsFile <> "'.\n" <> err) id diff --git a/src/Fedi/Webfinger.hs b/src/Fedi/Webfinger.hs index a4267f3..219e77a 100644 --- a/src/Fedi/Webfinger.hs +++ b/src/Fedi/Webfinger.hs @@ -1,6 +1,7 @@ module Fedi.Webfinger where import Data.Aeson qualified as A +import Fedi.Helpers import Fedi.UserDetails data Webfinger diff --git a/test/Fedi/TestSpec.hs b/test/Fedi/TestSpec.hs new file mode 100644 index 0000000..5907031 --- /dev/null +++ b/test/Fedi/TestSpec.hs @@ -0,0 +1,37 @@ +module Fedi.TestSpec where + +import Control.Monad.IO.Class +import Data.Aeson qualified as A +import Fedi qualified +import Test.Hspec + +-- * Tests + +spec :: Spec +spec = do + describe "Fedi" do + httpSignature + +httpSignature :: Spec +httpSignature = do + describe "HTTP Signature" do + describe "Sign" do + it "Sign a simple message" do + details <- readDetails + signed <- Fedi.sign details.privatePem "hello world" + shouldBe + (Fedi.ppSignedMessage signed) + "rXu4VgqPA5izfSSznnuHVk68gFqjLmn2gM85LqNsyXgRhk1V6WnLCtSaMR3ZaqefaU37D1Nj2xUnPu6x54HqkW99Iwe64HAddKFdMNtkcyh523+FF9t8CuRcT7l97RfbqcZSkH6D7eF8+eDa/ItpP+uwdGBDzlk8gkgRwXMP63cLVd9gboNOqvFXx11OZLBWHHMCkJuKDbzuu+46fkPXDKnDojuDLLrfd9cWqee3w3qkvigZ2T/2s3iiXrvt4rOb5Gi0oLmXh6/mqDHBIDinkyXb72vEP/HhzZMv+OSa/6tZ+F1DWf9RYlh0tItfnU4qIamRusr/YYC3KDQJzkzPZQ==" + it "Verify a simple message" do + details <- readDetails + let + signedMessage = + "rXu4VgqPA5izfSSznnuHVk68gFqjLmn2gM85LqNsyXgRhk1V6WnLCtSaMR3ZaqefaU37D1Nj2xUnPu6x54HqkW99Iwe64HAddKFdMNtkcyh523+FF9t8CuRcT7l97RfbqcZSkH6D7eF8+eDa/ItpP+uwdGBDzlk8gkgRwXMP63cLVd9gboNOqvFXx11OZLBWHHMCkJuKDbzuu+46fkPXDKnDojuDLLrfd9cWqee3w3qkvigZ2T/2s3iiXrvt4rOb5Gi0oLmXh6/mqDHBIDinkyXb72vEP/HhzZMv+OSa/6tZ+F1DWf9RYlh0tItfnU4qIamRusr/YYC3KDQJzkzPZQ==" + message = "hello world" + result <- Fedi.verifyPub (Fedi.pemToBS details.publicPem) signedMessage message + shouldBe + result + True + +readDetails :: IO Fedi.UserDetails +readDetails = Fedi.readUserDetailsFile "test/public/details.json" diff --git a/test/Spec.hs b/test/Spec.hs new file mode 100644 index 0000000..a824f8c --- /dev/null +++ b/test/Spec.hs @@ -0,0 +1 @@ +{-# OPTIONS_GHC -F -pgmF hspec-discover #-} diff --git a/test/public/details.json b/test/public/details.json new file mode 100644 index 0000000..4c4d231 --- /dev/null +++ b/test/public/details.json @@ -0,0 +1,10 @@ +{ + "domain": "fediserve.example.com", + "username": "me", + "name": "me", + "icon": "https://fedi.example.com/images/icon.png", + "image": "https://fedi.example.com/images/image.png", + "summary": "my summary", + "publicPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAslpzUw8zmQKvQwyyn4H/\n93/KQhUGDXRclE+J7m7gC8PRJZj884uWHjWOdtMdWq2Ec7ZMNzKldjj9MxLulv6p\n+HrEPWFmMpGFVDvNLvgawzt6M8Ygo2yoY2yeUtnLKqJF/vibBr51B75zp9qCNzP4\nMpdwpgppz5uyS28xSt5BWo5SsJ7Cq8H4jvl+qkDHpV/TeLdYDv7nI6zmnvbgsWdh\n9kNJEr9zByvAOMGGyxTWLl0tNTBGxbj0N6js7zac69KKlbUXGSmiGec6ITX7ujr+\n9XbX/KNw1nShi+zHvpUoeSNcY6xGfn0NNmE6PaEHimUeGXB/mkmbZaVcxPP+/vNI\nAQIDAQAB\n-----END PUBLIC KEY-----", + "privatePem": "test/public/private.pem" +} diff --git a/test/public/private.pem b/test/public/private.pem new file mode 100644 index 0000000..d25ddba --- /dev/null +++ b/test/public/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyWnNTDzOZAq9D +DLKfgf/3f8pCFQYNdFyUT4nubuALw9ElmPzzi5YeNY520x1arYRztkw3MqV2OP0z +Eu6W/qn4esQ9YWYykYVUO80u+BrDO3ozxiCjbKhjbJ5S2csqokX++JsGvnUHvnOn +2oI3M/gyl3CmCmnPm7JLbzFK3kFajlKwnsKrwfiO+X6qQMelX9N4t1gO/ucjrOae +9uCxZ2H2Q0kSv3MHK8A4wYbLFNYuXS01MEbFuPQ3qOzvNpzr0oqVtRcZKaIZ5zoh +Nfu6Ov71dtf8o3DWdKGL7Me+lSh5I1xjrEZ+fQ02YTo9oQeKZR4ZcH+aSZtlpVzE +8/7+80gBAgMBAAECggEASLyRrsySzvKk1nPtvxaN9PqFWpjnXIJZpVSg3IAUWEf2 +Wl5/vrVtNgnCfZxQquP8EhLFF3fMuh+4x2UPr73RRNiQgrXfr3zggr9WyTJ++6Mc +jdPeCLguHko0cXaIIG2InVj3JHN1GADoewqms2u666sOUnEJwRTuzEvfLvjgb1TO +MbF7E72CQCO4v+2naH1JLZZC7yCI1GZehDzkRGrsX7DSwysJOBbp81FZvpvRvQGF +oUJImVJSyICB97qOwM5kB3G5Y6auzAOUXpznWrF/cZiMqcQx2WkVohUr6eMt09rJ +qz15pq7aq+qRAuUyoKCi6ZWmnnG+JSFzu1J6fw6gfwKBgQD7f8YM/eguzq2WvGua +AhE1XGpEo10EgNg6g6RHhZlf5VITykxG7iBZUX2srij1+pHMKIFgUPWOPXDpjf2z +MD1O5SVcJOdRdeWyG4uddLjBckh/fUm9KS2a1+FCMS7Kf5RSMCPdAsmAEu+Sfgzw +O0H7KSHS4lTk7uoiQtdUz9qDRwKBgQC1i5B1iDje10A1EyZuaKYOEOOoljoI3PL1 +jkVaCKVzjnxl9FdfbQnyGbSa+p8VndYhF/Kr+cfTxf72FAghtXEk7v+9H/G9Q7pE +J4Ci9dWWiR/VEpi+f8liZyTXcK8GXjM2wjKxhNpuMFkmd7dmkyT18rERrlJVYYFY +JSxU4qqudwKBgHa2pm7Ff05w4oGPaR8mMKdGeybxEdwpUWe1cdsM1gvs/CQ5Tm4A +wyZGEBNtlgod/6olyIQFUxOULvMfwgxODfJquYrYJJRQGV9MH0fwq7EsV8pOAnuW +r18y3bJX8uVE+WceMB+HzEN3LLyyYTsYTZnXnY6kpTv3Bm+rq2buQg/LAoGBAI5T +ax5pfERftkulxsRaMIzoesJpe/gE2Ejgj8hE7QkKFMaNZ449WsW9l1TE1MFKq1vo +fWyL9zg0imz7SWC78YcGA+KOT6OUnEvgOD4Jwrgwqo8N9EeswQHvYPextNWmNjpg +xwNa5y/poYrB4jt9ckWLTXxSEOQ0/AVRp06pISyDAoGBAJGyZp4OsPpBDBka+cN7 +MbuPyEmRuH85Ust3ckBo2W3eWAQE+tyz6GtYbu/Lk+8WFNipOG4dIeGKt8EqlQcY +PM8LSGkgFqvCzXyFgCm0bjFUDez7zkhTCdOtGR7rS8SmPciVuA3HwQd3AiToE9Cb +gpBp7jigV5icO3byxWBSr++j +-----END PRIVATE KEY----- diff --git a/test/public/public.pem b/test/public/public.pem new file mode 100644 index 0000000..07da0dd --- /dev/null +++ b/test/public/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAslpzUw8zmQKvQwyyn4H/ +93/KQhUGDXRclE+J7m7gC8PRJZj884uWHjWOdtMdWq2Ec7ZMNzKldjj9MxLulv6p ++HrEPWFmMpGFVDvNLvgawzt6M8Ygo2yoY2yeUtnLKqJF/vibBr51B75zp9qCNzP4 +Mpdwpgppz5uyS28xSt5BWo5SsJ7Cq8H4jvl+qkDHpV/TeLdYDv7nI6zmnvbgsWdh +9kNJEr9zByvAOMGGyxTWLl0tNTBGxbj0N6js7zac69KKlbUXGSmiGec6ITX7ujr+ +9XbX/KNw1nShi+zHvpUoeSNcY6xGfn0NNmE6PaEHimUeGXB/mkmbZaVcxPP+/vNI +AQIDAQAB +-----END PUBLIC KEY-----