Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accepting specific certificate with http-client-tls or tls?

I'm probably just overlooking something basic in the documentation of http-client-tls and tls, but: how can I establish an HTTPS connection to a server and only accept one particular certificate, specified by me, that is potentially not in the system certificate store?

like image 622
gspr Avatar asked Oct 21 '25 06:10

gspr


1 Answers

I see that this is an old question, but I just spent some time writing code to do this and figured I'd post it here for posterity... and in the hopes of getting some code review from the community. Snoyman's comment is helpful, but there are so many code interdependencies here, and X.509 and TLS are so boil the ocean, that it's hard to debug and to know for sure that you're not screwing something up without digging pretty deep into the various libraries. I figured a more complete explanation with working code was in order.

Anyways, here's what I've come up with (this is a stack script so you can run it easily yourself) --

#!/usr/bin/env stack
{- stack --resolver lts-7.16 runghc -}

import qualified Data.ByteString           as B
import           Data.ByteString.Lazy         (ByteString)
import           Data.Default.Class           (def)
import           Data.String                  (fromString)
import           Data.X509.CertificateStore   (CertificateStore, readCertificateStore)
import           Network.HTTP.Client          (httpLbs, newManager, ManagerSettings)
import           Network.HTTP.Client.TLS      (mkManagerSettings)
import           Network.Connection           (TLSSettings(TLSSettings))
import qualified Network.TLS               as TLS
import qualified Network.TLS.Extra.Cipher  as TLS
import           System.Environment           (getArgs, getProgName)

managerSettings :: CertificateStore -> ManagerSettings
managerSettings store = mkManagerSettings settings Nothing
  where settings = TLSSettings params
        params   = (TLS.defaultParamsClient "" B.empty) {
                       TLS.clientUseServerNameIndication = True
                     , TLS.clientShared = def {
                           TLS.sharedCAStore = store
                       }
                     , TLS.clientSupported = def {
                           TLS.supportedCiphers = TLS.ciphersuite_default
                       }
                   }

get :: FilePath -> String -> IO ()
get ca url = do
    mstore <- readCertificateStore ca
    case mstore of
        Just store -> do
            manager  <- newManager $ managerSettings store
            response <- httpLbs (fromString url) manager
            putStrLn (show response)
        Nothing    -> do
            putStrLn $ "error: invalid certificate store " ++ ca

main :: IO ()
main = do
    args <- getArgs
    case args of
        ca:url:[] -> get ca url
        _         -> do
            name <- getProgName
            putStrLn $ "usage: " ++ name ++ " ca url"

A couple notes:

  • The TLS.sharedCAStore settings is where the magic happens. If you want to add your CA to the system store (vs. using only your CA) you can load the system store using getSystemCertificateStore from System.X509, then use Data.X509.CertificateStore to convert back and forth between CertificateStore and [SignedCertificate] to create a store with the system certificates along with your own.
  • TLS.defaultParamsClient takes a hostname and server id, used for TLS server name indication (SNI), a TLS extension that allows a server to host multiple sites on a single IP (similar to how HTTP/1.1 host headers work). We don't necessarily know what to set this to when we're creating a manager. Fortunately, Network.Connection (used by http-client-tls) appears to override whatever settings we use, so it doesn't matter.
  • The default for TLS.supportedCiphers is an empty list, so this setting is required (unless you turn off validation or something). Network.Connection defaults to ciphersuite_all but that includes some "not recommended last resource cipher suites" so I opted to use ciphersuite_default instead.
like image 168
mmalone Avatar answered Oct 24 '25 01:10

mmalone



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!