@@ -22,7 +22,9 @@ module Federator.Remote where
2222import Data.Default (def )
2323import Data.Domain (Domain , domainText )
2424import Data.String.Conversions (cs )
25+ import qualified Data.X509 as X509
2526import Data.X509.CertificateStore
27+ import qualified Data.X509.Validation as X509
2628import Federator.Discovery (DiscoverFederator , LookupError , discoverFederator )
2729import Federator.Options
2830import Imports
@@ -77,7 +79,9 @@ callInward :: MonadIO m => GrpcClient -> Request -> m (GRpcReply InwardResponse)
7779callInward client request =
7880 liftIO $ gRpcCall @ 'MsgProtoBuf @ Inward @ " Inward" @ " call" client request
7981
80- -- FUTUREWORK(federation): Make this use TLS with real certificate validation
82+ -- FUTUREWORK(federation): Consider using HsOpenSSL instead of tls for better
83+ -- security and to avoid having to depend on cryptonite and override validation
84+ -- hooks. This might involve forking http2-client: https://github.com/lucasdicioccio/http2-client/issues/76
8185-- FUTUREWORK(federation): Allow a configurable trust store to be used in TLS certificate validation
8286-- See also https://github.com/lucasdicioccio/http2-client/issues/76
8387-- FUTUREWORK(federation): Cache this client and use it for many requests
@@ -116,10 +120,31 @@ mkGrpcClient target@(SrvTarget host port) = Polysemy.runError $ do
116120
117121 let caStore = customCAStore <> systemCAStore
118122
123+ -- strip trailing dot to workaround issue in tls domain verification
124+ let stripDot hostname
125+ | isSuffixOf " ." hostname = take (length hostname - 1 ) hostname
126+ | otherwise = hostname
127+ -- try validating the hostname without a trailing dot, and if that fails, try
128+ -- again with the original hostname
129+ let validateName hostname cert
130+ | null validation = []
131+ | isSuffixOf " ." hostname = TLS. hookValidateName X509. defaultHooks hostname cert
132+ | otherwise = validation
133+ where
134+ validation = TLS. hookValidateName X509. defaultHooks (stripDot hostname) cert
135+
119136 let betterTLSConfig =
120137 (defaultParamsClient (cs host) (cs $ show port))
121138 { TLS. clientSupported = def {TLS. supportedCiphers = blessed_ciphers},
122- TLS. clientHooks = def, -- FUTUREWORK: use onCertificateRequest to provide client certificates
139+ TLS. clientHooks =
140+ def
141+ { TLS. onServerCertificate =
142+ X509. validate
143+ X509. HashSHA256
144+ (X509. defaultHooks {TLS. hookValidateName = validateName})
145+ X509. defaultChecks
146+ },
147+ -- FUTUREWORK: use onCertificateRequest to provide client certificates
123148 TLS. clientShared = def {TLS. sharedCAStore = caStore}
124149 }
125150 let cfg' = cfg {_grpcClientConfigTLS = Just betterTLSConfig}
0 commit comments