tcp-streams-openssl (empty) → 0.6.0.0
raw patch · 14 files changed
+623/−0 lines, 14 filesdep +HUnitdep +HsOpenSSLdep +HsOpenSSL-x509-systemsetup-changed
Dependencies added: HUnit, HsOpenSSL, HsOpenSSL-x509-system, QuickCheck, base, bytestring, directory, io-streams, network, tcp-streams, tcp-streams-openssl, test-framework, test-framework-hunit, test-framework-quickcheck2
Files
- ChangeLog.md +5/−0
- Data/OpenSSLSetting.hs +88/−0
- LICENSE +30/−0
- README.md +47/−0
- Setup.hs +2/−0
- System/IO/Streams/OpenSSL.hs +186/−0
- tcp-streams-openssl.cabal +58/−0
- test/Main.hs +116/−0
- test/cert/ca.key +16/−0
- test/cert/ca.pem +15/−0
- test/cert/client.crt +14/−0
- test/cert/client.key +15/−0
- test/cert/server.crt +16/−0
- test/cert/server.key +15/−0
+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for tcp-streams-openssl++## 0.1.0.0 -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ Data/OpenSSLSetting.hs view
@@ -0,0 +1,88 @@+-- | Helpers for setting up a tls connection with @HsOpenSSL@ package,+-- for further customization, please refer to @HsOpenSSL@ package.+--+-- Note, functions in this module will throw error if can't load certificates or CA store.+--+module Data.OpenSSLSetting+ ( -- * choose a CAStore+ TrustedCAStore(..)+ -- * make TLS settings+ , makeClientSSLContext+ , makeClientSSLContext'+ , makeServerSSLContext+ , makeServerSSLContext'+ ) where++import qualified OpenSSL.X509.SystemStore as X509+import qualified OpenSSL.Session as SSL+import OpenSSL (withOpenSSL)+import Data.TLSSetting (TrustedCAStore(..), mozillaCAStorePath)+++makeCAStore :: TrustedCAStore -> SSL.SSLContext -> IO ()+makeCAStore SystemCAStore ctx = X509.contextLoadSystemCerts ctx+makeCAStore MozillaCAStore ctx = SSL.contextSetCAFile ctx =<< mozillaCAStorePath+makeCAStore (CustomCAStore fp) ctx = SSL.contextSetCAFile ctx fp++-- | make a simple 'SSL.SSLContext' that will validate server and use tls connection+-- without providing client's own certificate. suitable for connecting server which don't+-- validate clients.+--+makeClientSSLContext :: TrustedCAStore -- ^ trusted certificates.+ -> IO SSL.SSLContext+makeClientSSLContext tca = withOpenSSL $ do+ let caStore = makeCAStore tca+ ctx <- SSL.context+ caStore ctx+ SSL.contextSetDefaultCiphers ctx+ SSL.contextSetVerificationMode ctx (SSL.VerifyPeer True True Nothing)+ return ctx++-- | make a simple 'SSL.SSLContext' that will validate server and use tls connection+-- while providing client's own certificate. suitable for connecting server which+-- validate clients.+--+-- The chain certificate must be in PEM format and must be sorted starting with the subject's certificate+-- (actual client or server certificate), followed by intermediate CA certificates if applicable,+-- and ending at the highest level (root) CA.+--+makeClientSSLContext' :: FilePath -- ^ public certificate (X.509 format).+ -> [FilePath] -- ^ chain certificate (X.509 format).+ -> FilePath -- ^ private key associated.+ -> TrustedCAStore -- ^ server will use these certificates to validate clients.+ -> IO SSL.SSLContext+makeClientSSLContext' pub certs priv tca = withOpenSSL $ do+ let caStore = makeCAStore tca+ ctx <- SSL.context+ caStore ctx+ SSL.contextSetDefaultCiphers ctx+ SSL.contextSetCertificateFile ctx pub+ SSL.contextSetPrivateKeyFile ctx priv+ mapM_ (SSL.contextSetCertificateChainFile ctx) certs+ SSL.contextSetVerificationMode ctx (SSL.VerifyPeer True True Nothing)+ return ctx++-- | make a simple 'SSL.SSLContext' for server without validating client's certificate.+--+makeServerSSLContext :: FilePath -- ^ public certificate (X.509 format).+ -> [FilePath] -- ^ chain certificate (X.509 format).+ -> FilePath -- ^ private key associated.+ -> IO SSL.SSLContext+makeServerSSLContext pub certs priv = withOpenSSL $ do+ ctx <- SSL.context+ SSL.contextSetDefaultCiphers ctx+ SSL.contextSetCertificateFile ctx pub+ SSL.contextSetPrivateKeyFile ctx priv+ mapM_ (SSL.contextSetCertificateChainFile ctx) certs+ return ctx++-- | make a 'SSL.SSLConext' that also validating client's certificate.+--+-- This's an alias to 'makeClientSSLContext''.+--+makeServerSSLContext' :: FilePath -- ^ public certificate (X.509 format).+ -> [FilePath] -- ^ chain certificates (X.509 format).+ -> FilePath -- ^ private key associated.+ -> TrustedCAStore -- ^ server will use these certificates to validate clients.+ -> IO SSL.SSLContext+makeServerSSLContext' = makeClientSSLContext'
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2016, winterland1989++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of winterland1989 nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,47 @@+tcp-streams-openssl+===================++[](http://hackage.haskell.org/package/tcp-streams-openssl)+[](https://travis-ci.org/winterland1989/tcp-streams)++This package provides TLS support based on [tcp-streams](http://hackage.haskell.org/package/tcp-streams) using [HsOpenSSL](http://hackage.haskell.org/package/HsOpenSSL). By default `tcp-streams` use `tls` package which is much easier to install but slightly slower.++This package is split from `tcp-streams` due to the difficulties of setting up openssl on many platform. You may need manually passing openssl library path if linker can't find them:++```+cabal install --extra-include-dirs=/usr/local/opt/openssl/include --extra-lib-dirs=/usr/local/opt/openssl/lib tcp-streams+```++License+-------++Copyright (c) 2016, Winterland++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of winterland1989 nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ System/IO/Streams/OpenSSL.hs view
@@ -0,0 +1,186 @@+{-# LANGUAGE ScopedTypeVariables #-}++-- | This module provides convenience functions for interfacing @io-streams@+-- with @HsOpenSSL@. @ssl/SSL@ here stand for @HsOpenSSL@ library, not the+-- deprecated SSL 2.0/3.0 protocol. the receive buffer size is 32752.+-- sending is unbuffered, anything write into 'OutputStream' will be immediately+-- send to underlying socket.+--+-- The same exceptions rule which applied to TCP apply here, with addtional+-- 'SSL.SomeSSLException` to be watched out.+--+-- This module is intended to be imported @qualified@, e.g.:+--+-- @+-- import qualified "System.IO.Streams.OpenSSL" as SSL+-- @+--+module System.IO.Streams.OpenSSL+ ( -- * client+ connect+ , connectWithVerifier+ , withConnection+ -- * server+ , accept+ -- * helpers+ , sslToStreams+ , close+ -- * re-export helpers+ , module Data.OpenSSLSetting+ ) where++import qualified Control.Exception as E+import Control.Monad (unless, void)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S+import Data.OpenSSLSetting+import Network.Socket (HostName, PortNumber, Socket)+import qualified Network.Socket as N+import OpenSSL (withOpenSSL)+import OpenSSL.Session (SSL, SSLContext)+import qualified OpenSSL.Session as SSL+import qualified OpenSSL.X509 as X509+import System.IO.Streams (InputStream, OutputStream)+import qualified System.IO.Streams as Streams+import qualified System.IO.Streams.TCP as TCP++bUFSIZ :: Int+bUFSIZ = 32752++-- | Given an existing HsOpenSSL 'SSL' connection, produces an 'InputStream' \/+-- 'OutputStream' pair.+--+sslToStreams :: SSL -- ^ SSL connection object+ -> IO (InputStream ByteString, OutputStream ByteString)+sslToStreams ssl = do+ is <- Streams.makeInputStream input+ os <- Streams.makeOutputStream output+ return (is, os)++ where+ input = ( do+ s <- SSL.read ssl bUFSIZ+ return $! if S.null s then Nothing else Just s+ ) `E.catch` (\(_::E.SomeException) -> return Nothing)++ output Nothing = return ()+ output (Just s) = SSL.write ssl s+{-# INLINABLE sslToStreams #-}++close :: SSL.SSL -> IO ()+close ssl = withOpenSSL $ do+ SSL.shutdown ssl SSL.Unidirectional+ maybe (return ()) N.close (SSL.sslSocket ssl)++-- | Convenience function for initiating an SSL connection to the given+-- @('HostName', 'PortNumber')@ combination.+--+-- This function will try to verify server's identity using a very simple algorithm,+-- which may not suit your need:+--+-- @+-- matchDomain :: String -> String -> Bool+-- matchDomain n1 n2 =+-- let n1' = reverse (splitDot n1)+-- n2' = reverse (splitDot n2)+-- cmp src target = src == "*" || target == "*" || src == target+-- in and (zipWith cmp n1' n2')+-- @+--+-- If the certificate or hostname is not verified, a 'SSL.ProtocolError' will be thrown.+--+connect :: SSLContext -- ^ SSL context. See the @HsOpenSSL@+ -- documentation for information on creating+ -- this.+ -> Maybe String -- ^ Optional certificate subject name, if set to 'Nothing'+ -- then we will try to verify 'HostName' as subject name.+ -> HostName -- ^ hostname to connect to+ -> PortNumber -- ^ port number to connect to+ -> IO (InputStream ByteString, OutputStream ByteString, SSL)+connect ctx vhost host port = withOpenSSL $ do+ connectWithVerifier ctx verify host port+ where+ verify trusted cnname = trusted+ && maybe False (matchDomain verifyHost) cnname+ verifyHost = maybe host id vhost+ matchDomain :: String -> String -> Bool+ matchDomain n1 n2 =+ let n1' = reverse (splitDot n1)+ n2' = reverse (splitDot n2)+ cmp src target = src == "*" || target == "*" || src == target+ in and (zipWith cmp n1' n2')+ splitDot :: String -> [String]+ splitDot "" = [""]+ splitDot x =+ let (y, z) = break (== '.') x in+ y : (if z == "" then [] else splitDot $ drop 1 z)++-- | Connecting with a custom verification callback.+--+-- @since 0.6.0.0@+--+connectWithVerifier :: SSLContext -- ^ SSL context. See the @HsOpenSSL@+ -- documentation for information on creating+ -- this.+ -> (Bool -> Maybe String -> Bool) -- ^ A verify callback, the first param is+ -- the result of certificate verification, the+ -- second param is the certificate's subject name.+ -> HostName -- ^ hostname to connect to+ -> PortNumber -- ^ port number to connect to+ -> IO (InputStream ByteString, OutputStream ByteString, SSL)+connectWithVerifier ctx f host port = withOpenSSL $ do+ sock <- TCP.connectSocket host port+ E.bracketOnError (SSL.connection ctx sock) close $ \ ssl -> do+ SSL.connect ssl+ trusted <- SSL.getVerifyResult ssl+ cert <- SSL.getPeerCertificate ssl+ subnames <- maybe (return []) (`X509.getSubjectName` False) cert+ let cnname = lookup "CN" subnames+ verified = f trusted cnname+ unless verified (E.throwIO $ SSL.ProtocolError "fail to verify certificate")+ (is, os) <- sslToStreams ssl+ return (is, os, ssl)+++-- | Convenience function for initiating an SSL connection to the given+-- @('HostName', 'PortNumber')@ combination. The socket and SSL connection are+-- closed and deleted after the user handler runs.+--+withConnection :: SSLContext+++ -> Maybe String+ -> HostName+ -> PortNumber+ -> (InputStream ByteString -> OutputStream ByteString -> SSL -> IO a)+ -- ^ Action to run with the new connection+ -> IO a+withConnection ctx subname host port action =+ E.bracket (connect ctx subname host port) cleanup go++ where+ go (is, os, ssl) = action is os ssl++ cleanup (_, os, ssl) = E.mask_ $+ eatException $! Streams.write Nothing os >> close ssl++ eatException m = void m `E.catch` (\(_::E.SomeException) -> return ())+++-- | Accept a new connection from remote client, return a 'InputStream' / 'OutputStream'+-- pair and remote 'N.SockAddr', you should call 'TCP.bindAndListen' first.+--+-- this operation will throw 'SSL.SomeSSLException' on failure.+--+accept :: SSL.SSLContext -- ^ check "Data.OpenSSLSetting".+ -> Socket -- ^ the listening 'Socket'.+ -> IO (InputStream ByteString, OutputStream ByteString, SSL.SSL, N.SockAddr)+accept ctx sock = withOpenSSL $ do+ (sock', sockAddr) <- N.accept sock+ E.bracketOnError (SSL.connection ctx sock') close $ \ ssl -> do+ SSL.accept ssl+ trusted <- SSL.getVerifyResult ssl+ unless trusted (E.throwIO $ SSL.ProtocolError "fail to verify certificate")+ (is, os) <- sslToStreams ssl+ return (is, os, ssl, sockAddr)+
+ tcp-streams-openssl.cabal view
@@ -0,0 +1,58 @@+name: tcp-streams-openssl+version: 0.6.0.0+synopsis: Tcp streams using openssl for tls support.+description: Tcp streams using openssl for tls support.+license: BSD3+license-file: LICENSE+author: winterland1989+maintainer: winterland1989@gmail.com+homepage: https://github.com/winterland1989/tcp-streams+copyright: (c) Winterland 2016 +category: Network+build-type: Simple+extra-source-files: ChangeLog.md+ , README.md+ , test/cert/*.pem+ , test/cert/*.crt+ , test/cert/*.key+cabal-version: >=1.10++source-repository head+ type: git+ location: git://github.com/winterland1989/tcp-streams.git++library+ exposed-modules: Data.OpenSSLSetting+ , System.IO.Streams.OpenSSL++ -- other-extensions: + build-depends: base >=4.7 && < 5.0+ , network >=2.3 && < 3.0+ , bytestring >= 0.10.2.0+ , io-streams >= 1.2 && < 2.0+ , tcp-streams >= 0.6 && < 0.7+ , HsOpenSSL >=0.10.3 && <0.12+ , HsOpenSSL-x509-system == 0.1.*++ ghc-options: -Wall+ -- hs-source-dirs: + default-language: Haskell2010++test-suite testsuite+ type: exitcode-stdio-1.0+ Hs-source-dirs: src test+ main-is: Main.hs+ default-language: Haskell2010+ ghc-options: -Wall -threaded+ build-depends: base+ , io-streams+ , tcp-streams+ , tcp-streams-openssl+ , network+ , bytestring+ , HUnit >= 1.2 && <2+ , QuickCheck >= 2.3.0.2 && <3+ , test-framework >= 0.6 && <0.9+ , test-framework-hunit >= 0.2.7 && <0.4+ , test-framework-quickcheck2 >= 0.2.12.1 && <0.4+ , directory == 1.*
+ test/Main.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main (main) where++------------------------------------------------------------------------------+import Control.Concurrent (forkIO, newEmptyMVar, putMVar,+ takeMVar)+import qualified Control.Exception as E+import qualified Network.Socket as N+import System.Timeout (timeout)+import Test.Framework+import Test.Framework.Providers.HUnit+import Test.HUnit hiding (Test)+import qualified Data.ByteString as B+import System.Directory (removeFile)+------------------------------------------------------------------------------+import qualified Data.OpenSSLSetting as SSL+import qualified System.IO.Streams as Stream+import qualified System.IO.Streams.TCP as Raw+import qualified System.IO.Streams.OpenSSL as SSL+------------------------------------------------------------------------------++main :: IO ()+main = defaultMain tests+ where+ tests = [ testGroup "TCP" rawTests+ , testGroup "OpenSSL" sslTests+ ]++------------------------------------------------------------------------------++rawTests :: [Test]+rawTests = [ testRawSocket ]++testRawSocket :: Test+testRawSocket = testCase "network/socket" $+ N.withSocketsDo $ do+ x <- timeout (10 * 10^(6::Int)) go+ assertEqual "ok" (Just ()) x++ where+ go = do+ portMVar <- newEmptyMVar+ resultMVar <- newEmptyMVar+ forkIO $ client portMVar resultMVar+ server portMVar+ l <- takeMVar resultMVar+ assertEqual "testSocket" l ["ok"]++ client mvar resultMVar = do+ _ <- takeMVar mvar+ (is, os, sock) <- Raw.connect "127.0.0.1" 8888+ Stream.fromList ["", "ok"] >>= Stream.connectTo os+ N.shutdown sock N.ShutdownSend+ Stream.toList is >>= putMVar resultMVar+ N.close sock++ server mvar = do+ sock <- Raw.bindAndListen 8888 1024+ putMVar mvar ()+ (is, os, csock, _) <- Raw.accept sock+ os' <- Stream.atEndOfOutput (N.close csock) os+ os' `Stream.connectTo` is++------------------------------------------------------------------------------++sslTests :: [Test]+sslTests = [ testSSLSocket, testHTTPS' ]++testSSLSocket :: Test+testSSLSocket = testCase "network/socket" $+ N.withSocketsDo $ do+ x <- timeout (10 * 10^(6::Int)) go+ assertEqual "ok" (Just ()) x++ where+ go = do+ portMVar <- newEmptyMVar+ resultMVar <- newEmptyMVar+ forkIO $ client portMVar resultMVar+ server portMVar+ l <- takeMVar resultMVar+ assertEqual "testSocket" l (Just "ok")++ client mvar resultMVar = do+ _ <- takeMVar mvar+ cp <- SSL.makeClientSSLContext (SSL.CustomCAStore "./test/cert/ca.pem")+ (is, os, ctx) <- SSL.connect cp (Just "Winter") "127.0.0.1" 8890+ Stream.fromList ["", "ok"] >>= Stream.connectTo os+ Stream.read is >>= putMVar resultMVar -- There's no shutdown in tls, so we won't get a 'Nothing'+ SSL.close ctx++ server mvar = do+ sp <- SSL.makeServerSSLContext "./test/cert/server.crt" [] "./test/cert/server.key"+ sock <- Raw.bindAndListen 8890 1024+ putMVar mvar ()+ (is, os, ssl, _) <- SSL.accept sp sock+ os' <- Stream.atEndOfOutput (SSL.close ssl) os+ os' `Stream.connectTo` is++testHTTPS' :: Test+testHTTPS' = testCase "network/https" $+ N.withSocketsDo $ do+ x <- timeout (10 * 10^(6::Int)) go+ assertEqual "ok" (Just 1024) x+ where+ go = do+ cp <- SSL.makeClientSSLContext SSL.SystemCAStore+ (is, os, ctx) <- SSL.connect cp (Just "*.google.com") "www.google.com" 443+ Stream.write (Just "GET / HTTP/1.1\r\n") os+ Stream.write (Just "Host: www.google.com\r\n") os+ Stream.write (Just "\r\n") os+ bs <- Stream.readExactly 1024 is+ SSL.close ctx+ return (B.length bs)
+ test/cert/ca.key view
@@ -0,0 +1,16 @@+-----BEGIN PRIVATE KEY-----+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMprzly4sBhDqgmd+zwuXs7rlPZvRiZ3ySYmgGpTb9QZIPPTVTQ69GUC0WmeLtW/zni0YPUgowDUTf16X+LDjC3wT0kUvk6ek/rQgs/AToJcWBfYxCSElPRSGb5eiXQX8N+HPNXdViJEUvhoDv+3LgltliFZiY3BxDJ3tkWJOL7lshtAgMBAAECgYBnM4GZyluVc2IM1xVJXsW2gsvf+VnxoN7AAZ81FmcMZudjCJsHLwuNOFuWEpzkQ488ARLxxvr0IEnG8wyw7oDbdQWGd+AKjdTZgdkfAR9MIQCE4L3C5ZFfw4Slq6JvmucIsw7ERiulShcWrVMvseVAEYMNTm+40OWTiWfA1qwc+ANrQJBAP4UpSgD3FY8rJSznUXJCXwougrWwZQYuaYx4MmR2QGV+GIq8HF6QVVr9XzFAD/WIjtVm7nM+3pIS1ST9HPnQOncCQQDL80JMeNdMrXNqJZsy+3oR0mAFXhXi2MObBnnOHce44QwNO6TqyqPhLx7M55MwNNyA9/YTi1MmhWkLWRdTt+dek7AkEA9JoDdUZiNFMtAer4mVo022aJ1C1zJpO3Bhw2f1b9Rty2R7lYxmDFC1eo+8MzvkDzq5N626BO6SX3/3CAgaQ7heQJAHHyz6/6NBBbOIityjB5sneSFe3YXMEuQ+T8cUF/0f6xfhJGqLWl0joWIZdKKypb3ncQEySIS0TSdQYqGKZkir9QJAbW1uPEy4+7g0yXwG3YtJNE2UD9JIb+qzejeAAKcdglqrEZpJNdGpjG6hlp3XWqM6YXexrEfTs+CCskhGwSQe/RdA==+-----END PRIVATE KEY-----
+ test/cert/ca.pem view
@@ -0,0 +1,15 @@+-----BEGIN TRUSTED CERTIFICATE-----+MIICTTCCAbYCCQCy3IPKsROAWjANBgkqhkiG9w0BAQsFADBrMQswCQYDVQQGEwJD+TjETMBEGA1UECgwKV2ludGVybGFuZDENMAsGA1UECwwEVGVzdDEPMA0GA1UEAwwG+V2ludGVyMScwJQYJKoZIhvcNAQkBFhh3aW50ZXJsYW5kMTk4OUBnbWFpbC5jb20w+HhcNMTYwNzE5MDYzNjA5WhcNMTcwNzE5MDYzNjA5WjBrMQswCQYDVQQGEwJDTjET+MBEGA1UECgwKV2ludGVybGFuZDENMAsGA1UECwwEVGVzdDEPMA0GA1UEAwwGV2lu+dGVyMScwJQYJKoZIhvcNAQkBFhh3aW50ZXJsYW5kMTk4OUBnbWFpbC5jb20wgZ8w+DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMprzly4sBhDqgmdzwuXs7rlPZvRiZ3y+SYmgGpTb9QZIPPTVTQ69GUC0WmeLtW/zni0YPUgowDUTf16XLDjC3wT0kUvk6ek/+rQgs/AToJcWBfYxCSElPRSGb5eiXQX8N+HPNXdViJEUvhoDv3LgltliFZiY3BxDJ+3tkWJOL7lshtAgMBAAEwDQYJKoZIhvcNAQELBQADgYEATPckjNo9SmFuSQWI74ec+xvghpo/wG/dR7JHBcPMCftRmK5eKPkqrrtHhQz5LA61jhW+uL0q2+W6/oE+GvZnv+QWaMtuN0YKdPM4yVQR51AMZwwQd7OXo8qgaCAoI7Be8JMzOlS3pERLlupFpn8D9e+Te0iF3xlZX1kDbWUnS++oGM=+-----END TRUSTED CERTIFICATE-----
+ test/cert/client.crt view
@@ -0,0 +1,14 @@+-----BEGIN CERTIFICATE-----+MIICJTCCAY4CAQEwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCQ04xEzARBgNV+BAoMCldpbnRlcmxhbmQxDTALBgNVBAsMBFRlc3QxDzANBgNVBAMMBldpbnRlcjEn+MCUGCSqGSIb3DQEJARYYd2ludGVybGFuZDE5ODlAZ21haWwuY29tMB4XDTE2MDcx+ODA5NDYwNloXDTE3MDcxODA5NDYwNlowSzELMAkGA1UEBhMCQ04xEzARBgNVBAMM+CldpbnRlcmxhbmQxJzAlBgkqhkiG9w0BCQEWGHdpbnRlcmxhbmQxOTg5QGdtYWls+LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA3fb1pYRCExJFpKc2ai2i+B3CRsosZS4aCWAghVovCdmq8GOPcfH+IMxhX0hdUluKWOltj4Kl4Chi7iiZUyGNq+kV8CkKJ62WTwqgXUqJ4ku1eHKYXb0WSBj17Y4vCtMMAF/mmB9E/cxY08KOH3/Smp+RqmwDNoPymxmU7VCir256N0CAwEAATANBgkqhkiG9w0BAQsFAAOBgQDAQB1VMXZd+nK/4gW5/vnoq/UOb+kmvoUi9ZPmGSopW2BHop3YEL5IkPn2UaHZH+edU8dkj79LV+xpg0yI3CpTJE+purqNBk799mpLii2pkoDwbnIn1hf7XmPInBNJJvRjqTMlfpaWb4+fGI6U0A27XtWi+6zy5s94arB4uoCrdvUTA==+-----END CERTIFICATE-----
+ test/cert/client.key view
@@ -0,0 +1,15 @@+-----BEGIN RSA PRIVATE KEY-----+MIICXAIBAAKBgQDd9vWlhEITEkWkpzZqLaIHcJGyixlLhoJYCCFWi8J2arwY49x8+f4gzGFfSF1SW4pY6W2PgqXgKGLuKJlTIY2qRXwKQonrZZPCqBdSoniS7V4cphdvR+ZIGPXtji8K0wwAX+aYH0T9zFjTwo4ff9KalGqbAM2g/KbGZTtUKKvbno3QIDAQAB+AoGAevoWw2txiEt5Vm5mUZGS0fhmYLt8ekG9+bQXdHHefelI6allQX0PIu/7yCSw+8x/7B74WqiR6o21sinAOTS/3nCcRKoaass20VIOI0DJLwOXJNliuN9DEr02uoifs+hRUD7MTYAETCEQ58HFldgGxU98uEPcCi1HFEhrymK9EUjYECQQDyFLJ+qK+vu/m5+ydtE+NKu5n8MyFWuMgHeE3FEhGlbQvUOeMwj82XAQN7g8XXiSkpIrHevA7iOJ3jO+0NcXS+wxAkEA6ropuMmJk4oKDLEqTpxBbS2ILjmfVoGjZiKjKZEo9dZ/RnFz4zEB+8SC4h55hpRp0gBBOh/QFNHcPko1WBq3YbQJAPlNX1UZG3T7PP6cZvfs1+vO7GCZn+8M5NLsjgq5xPp8BoaU5ueH8M3l+VQmLIT/eCgo1szvFtTaQZ9V1NU2EnsQJAQJGt+YHOwMLHMSemCZdV9+faIe47GUhmfRT1J/Ok9h0LWCB61bk3Q5u/FUykyWySH36Kc+t7FcrS4DTqEqhPPVkQJBAPFKvcd9z+AuUlnB9/VQbaJGSJ3VDqInJkRu6GVXMvH/+6VP3gdjAf4Jdu2sdTwQv/rSuvZg/6ngWcj6CXfbb0kw=+-----END RSA PRIVATE KEY-----
+ test/cert/server.crt view
@@ -0,0 +1,16 @@+-----BEGIN CERTIFICATE-----+MIIChDCCAe2gAwIBAgIJAMddlD79DkOBMA0GCSqGSIb3DQEBCwUAMGsxCzAJBgNV+BAYTAkNOMRMwEQYDVQQKDApXaW50ZXJsYW5kMQ0wCwYDVQQLDARUZXN0MQ8wDQYD+VQQDDAZXaW50ZXIxJzAlBgkqhkiG9w0BCQEWGHdpbnRlcmxhbmQxOTg5QGdtYWls+LmNvbTAeFw0xNjA3MTkwNzQwMjJaFw0xNzA3MTkwNzQwMjJaMIGAMQswCQYDVQQG+EwJDTjETMBEGA1UECAwKU29tZS1TdGF0ZTETMBEGA1UECgwKV2ludGVybGFuZDEN+MAsGA1UECwwEVGVzdDEPMA0GA1UEAwwGV2ludGVyMScwJQYJKoZIhvcNAQkBFhh3+aW50ZXJsYW5kMTk4OUBnbWFpbC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ+AoGBALVw5Aspmpujd76UGQf6mjutf6DWlmnXCfhiLDOMj+QGonBvbxrbRpIfjpWa+PfGDS/9mwYgDNAaU7cfUqEziwSgjTSVPF5PR721fxYFmG/269qGIuJ6i5ds9t7nQ+JcaBZAxN22Kw3mydZAJlGdG75pO5tTQRQYsG73XcX0n1wfBXAgMBAAGjGjAYMAkG+A1UdEwQCMAAwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4GBAGAcC0nVZYb5+XnS6VXF/HO9mLGb8B67GHDROND0JdS80s2sS063P4Lfks1Sg8KddybYfX+NPGliR+bo9VWBKe8gpegyyCrEb9F60+/SqTAlg3tO4q0AMkjFiyeOalvPz4cp464eatJ9s2+vNCJSOqZ6nYIoDl0K9vmVckYaQxS+2p4+-----END CERTIFICATE-----
+ test/cert/server.key view
@@ -0,0 +1,15 @@+-----BEGIN RSA PRIVATE KEY-----+MIICXAIBAAKBgQC1cOQLKZqbo3e+lBkH+po7rX+g1pZp1wn4YiwzjI/kBqJwb28a+20aSH46Vmj3xg0v/ZsGIAzQGlO3H1KhM4sEoI00lTxeT0e9tX8WBZhv9uvahiLie+ouXbPbe50CXGgWQMTdtisN5snWQCZRnRu+aTubU0EUGLBu913F9J9cHwVwIDAQAB+AoGAOr7RZvfSch/sLk0/CYFPHJFhKE8yJhSUytHmY85X7ElloKfDvltOrxL+lFt7+QM309j1r3cDDwHnJvxCQaUxlozB0JMDqWRVtTx//0NXNW8495H8Rn1Xwwyd1LY+e+2+jGTSKNhvSDXHPHJS93FgbbYoCXvGkVGx2mpDNksDrWDpECQQDkuV7U4KqtapU9+73u2GKnIpJDx3Hjm8N7DDfaA5SS/3kfIABZBfWJMCIbsSRTSvN9s3XsVUEvXpjX1+4uhW4Ck/AkEAyxQIPbuuqv+6bN3qHMLxjVGiNc92anWCifsy+4THWpWV5Q0F4WX5+pIxL3pbuIqJMXQfTCLBAifR5GTT/0XQa6QJBAImWFLyblTASQEpsiA+HEIL4s5Q6+GqRZWrcc7B6nOI8OaEGgA8NLaFjyfC8g2xzVrtTu+j5c+fJ0MluCLl8sIsMCQAVk+RsO80+peV8jEK48P7fHelPvwwigZbpnTPYtH/zL8fbpTGjDd0D76KpmCUFhDDtv5+dTTp2QzQnNZ6fcBF4OECQCkeFGnhs/YLyev2WsFx0rI8AlrjCeGq+t2HSv+/vc8M+Zv4P6QLK46sX0wy7xkCdtSzhd6tD/HCl/KT0y45q+DY=+-----END RSA PRIVATE KEY-----