metro-transport-tls (empty) → 0.1.0.0
raw patch · 7 files changed
+295/−0 lines, 7 filesdep +basedep +bytestringdep +data-default-classsetup-changed
Dependencies added: base, bytestring, data-default-class, metro, pem, tls, x509, x509-store, x509-validation
Files
- ChangeLog.md +3/−0
- LICENSE +30/−0
- README.md +3/−0
- Setup.hs +2/−0
- metro-transport-tls.cabal +48/−0
- src/Metro/TP/TLS.hs +72/−0
- src/Metro/TP/TLSSetting.hs +137/−0
+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for metro-transport-xor++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Lupino (c) 2020++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 Lupino 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,3 @@+# metro-transport-tls++TLS transport for metro
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ metro-transport-tls.cabal view
@@ -0,0 +1,48 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 4800ce3a672404ff6894c0407fa75e3d83ebb0d5c936fd2d651342365244eebb++name: metro-transport-tls+version: 0.1.0.0+synopsis: TLS transport for metro+description: Please see the README on GitHub at <https://github.com/Lupino/metro/tree/master/metro-transport-tls#readme>+category: Web+homepage: https://github.com/Lupino/metro#readme+bug-reports: https://github.com/Lupino/metro/issues+author: Lupino+maintainer: lmjubuntu@gmail.com+copyright: MIT+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ ChangeLog.md++source-repository head+ type: git+ location: https://github.com/Lupino/metro++library+ exposed-modules:+ Metro.TP.TLS+ Metro.TP.TLSSetting+ other-modules:+ Paths_metro_transport_tls+ hs-source-dirs:+ src+ build-depends:+ base >=4.7 && <5+ , bytestring+ , data-default-class+ , metro+ , pem+ , tls+ , x509+ , x509-store+ , x509-validation+ default-language: Haskell2010
+ src/Metro/TP/TLS.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++-- | This module provides convenience functions for interfacing @tls@.+--+-- This module is intended to be imported @qualified@, e.g.:+--+module Metro.TP.TLS+ ( TLS+ -- * re-export+ , module Metro.TP.TLSSetting+ , tlsConfig+ ) where++import Control.Exception (SomeException, bracketOnError, catch)+import qualified Data.ByteString.Char8 as B (append, length, null)+import qualified Data.ByteString.Lazy as BL (fromStrict)+import Metro.Class (Transport (..))+import Metro.TP.TLSSetting+import Network.TLS (Context, TLSParams)+import qualified Network.TLS as TLS+++newtype TLS = TLS Context++instance Transport TLS where+ data TransportConfig TLS = forall params tp. (Transport tp, TLSParams params) => TLSConfig params (TransportConfig tp)++ -- | Convenience function for initiating an TLS transport+ --+ -- This operation may throw 'TLS.TLSException' on failure.+ --+ newTransport (TLSConfig params config) = do+ transport <- newTransport config+ bracketOnError (TLS.contextNew (transportBackend transport) params) closeTLS $ \ctx -> do+ TLS.handshake ctx+ return $ TLS ctx++ recvData (TLS ctx) = const $ TLS.recvData ctx+ sendData (TLS ctx) = TLS.sendData ctx . BL.fromStrict+ closeTransport (TLS ctx) = closeTLS ctx++transportBackend :: Transport tp => tp -> TLS.Backend+transportBackend transport = TLS.Backend+ { TLS.backendFlush = return ()+ , TLS.backendClose = closeTransport transport+ , TLS.backendSend = sendData transport+ , TLS.backendRecv = recvData'+ }++ where recvData' nbytes = do+ s <- recvData transport nbytes+ if loadMore nbytes s then do+ s' <- recvData' (nbytes - B.length s)+ return $ s `B.append` s'+ else return s++ loadMore nbytes bs | B.null bs = False+ | B.length bs < nbytes = True+ | otherwise = False+++-- | Close a TLS 'Context' and its underlying socket.+--+closeTLS :: Context -> IO ()+closeTLS ctx = (TLS.bye ctx >> TLS.contextClose ctx) -- sometimes socket was closed before 'TLS.bye'+ `catch` (\(_::SomeException) -> return ()) -- so we catch the 'Broken pipe' error here+++tlsConfig :: (Transport tp, TLSParams params) => params -> TransportConfig tp -> TransportConfig TLS+tlsConfig = TLSConfig
+ src/Metro/TP/TLSSetting.hs view
@@ -0,0 +1,137 @@+-- | Helpers for setting up a tls connection with @tls@ package,+-- for further customization, please refer to @tls@ package.+--+-- Note, functions in this module will throw error if can't load certificates or CA store.+--+module Metro.TP.TLSSetting+ (+ -- * Make TLS settings+ makeClientParams+ , makeClientParams'+ , makeServerParams+ , makeServerParams'+ ) where++import qualified Data.ByteString as B (empty, readFile)+import Data.Default.Class (def)+import qualified Data.PEM as X509 (pemContent, pemParseBS)+import qualified Data.X509 as X509 (CertificateChain (..),+ HashALG (..),+ decodeSignedCertificate)+import qualified Data.X509.CertificateStore as X509 (CertificateStore,+ makeCertificateStore)+import qualified Data.X509.Validation as X509 (ServiceID, checkFQHN,+ validate)+import qualified Network.TLS as TLS+import qualified Network.TLS.Extra as TLS (ciphersuite_strong)++++makeCAStore :: FilePath -> IO X509.CertificateStore+makeCAStore fp = do+ bs <- B.readFile fp+ let Right pems = X509.pemParseBS bs+ case mapM (X509.decodeSignedCertificate . X509.pemContent) pems of+ Right cas -> return (X509.makeCertificateStore cas)+ Left err -> error err++-- | make a simple tls 'TLS.ClientParams' that will validate server and use tls connection+-- without providing client's own certificate. suitable for connecting server which don't+-- validate clients.+--+-- we defer setting of 'TLS.clientServerIdentification' to connecting phase.+--+-- Note, tls's default validating method require server has v3 certificate.+-- you can use openssl's V3 extension to issue such a certificate. or change 'TLS.ClientParams'+-- before connecting.+--+makeClientParams :: FilePath -- ^ trusted certificates.+ -> X509.ServiceID+ -> IO TLS.ClientParams+makeClientParams tca servid = do+ caStore <- makeCAStore tca+ return (TLS.defaultParamsClient "" B.empty)+ { TLS.clientSupported = def { TLS.supportedCiphers = TLS.ciphersuite_strong }+ , TLS.clientServerIdentification = servid+ , TLS.clientShared = def+ { TLS.sharedCAStore = caStore+ , TLS.sharedValidationCache = def+ }+ }++-- | make a simple tls 'TLS.ClientParams' that will validate server and use tls connection+-- while providing client's own certificate as well. suitable for connecting server which+-- validate clients.+--+-- Also only accept v3 certificate.+--+makeClientParams' :: FilePath -- ^ public certificate (X.509 format).+ -> [FilePath] -- ^ chain certificates (X.509 format).+ -- the root of your certificate chain should be+ -- already trusted by server, or tls will fail.+ -> FilePath -- ^ private key associated.+ -> FilePath -- ^ trusted certificates.+ -> X509.ServiceID+ -> IO TLS.ClientParams+makeClientParams' pub certs priv tca servid = do+ p <- makeClientParams tca servid+ c <- TLS.credentialLoadX509Chain pub certs priv+ case c of+ Right c' ->+ return p+ { TLS.clientShared = (TLS.clientShared p)+ {+ TLS.sharedCredentials = TLS.Credentials [c']+ }+ , TLS.clientHooks = (TLS.clientHooks p)+ {+ TLS.onCertificateRequest = const . return $ Just c'+ }+ }+ Left err -> error err++-- | make a simple tls 'TLS.ServerParams' without validating client's certificate.+--+makeServerParams :: FilePath -- ^ public certificate (X.509 format).+ -> [FilePath] -- ^ chain certificates (X.509 format).+ -- the root of your certificate chain should be+ -- already trusted by client, or tls will fail.+ -> FilePath -- ^ private key associated.+ -> IO TLS.ServerParams+makeServerParams pub certs priv = do+ c <- TLS.credentialLoadX509Chain pub certs priv+ case c of+ Right c'@(X509.CertificateChain c'', _) ->+ return def+ { TLS.serverCACertificates = c''+ , TLS.serverShared = def+ {+ TLS.sharedCredentials = TLS.Credentials [c']+ }+ , TLS.serverSupported = def { TLS.supportedCiphers = TLS.ciphersuite_strong }+ }+ Left err -> error err++-- | make a tls 'TLS.ServerParams' that also validating client's certificate.+--+makeServerParams' :: FilePath -- ^ public certificate (X.509 format).+ -> [FilePath] -- ^ chain certificates (X.509 format).+ -> FilePath -- ^ private key associated.+ -> FilePath -- ^ server will use these certificates to validate clients.+ -> IO TLS.ServerParams+makeServerParams' pub certs priv tca = do+ caStore <- makeCAStore tca+ p <- makeServerParams pub certs priv+ return p+ { TLS.serverWantClientCert = True+ , TLS.serverShared = (TLS.serverShared p)+ { TLS.sharedCAStore = caStore+ }+ , TLS.serverHooks = def+ { TLS.onClientCertificate = \chain -> do+ errs <- X509.validate X509.HashSHA256 def (def { X509.checkFQHN = False }) caStore def ("", B.empty) chain+ case errs of+ [] -> return TLS.CertificateUsageAccept+ xs -> return . TLS.CertificateUsageReject . TLS.CertificateRejectOther $ show xs+ }+ }