1

I have created a program using http-conduit and it needs to talk to a server that doesn't have a valid TLS certificate. It's a self-signed certificate in this case.

https-test.hs:

#!/usr/bin/env stack
-- stack --install-ghc --resolver lts-5.13 runghc --package http-conduit
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy.Char8 as L8
import           Network.HTTP.Client
import           Network.HTTP.Simple
import           Network.Connection
                 ( TLSSettings(..) )

main :: IO ()
main = do
  authenticate "self-signed.badssl.com" "" ""

authenticate :: S8.ByteString
             -> L8.ByteString
             -> L8.ByteString
             -> IO ()
authenticate hostname username password = do
  let request
        = setRequestMethod "GET"
        $ setRequestSecure True
        $ setRequestPort 443
        $ setRequestHost hostname
        $ setRequestPath "/"
        $ defaultRequest
  response <- httpLBS request
  putStrLn $ "The status code was: " ++
             show (getResponseStatusCode response)
  print $ getResponseHeader "Content-Type" response
  L8.putStrLn $ getResponseBody response

Expected output

The status code was: 200
["text/html"]
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="shortcut icon" href="/icons/favicon-red.ico"/>
  <link rel="apple-touch-icon" href="/icons/icon-red.png"/>
  <title>self-signed.badssl.com</title>
  <link rel="stylesheet" href="/style.css">
  <style>body { background: red; }</style>
</head>
<body>
<div id="content">
  <h1 style="font-size: 12vw;">
    self-signed.<br>badssl.com
  </h1>
</div>

</body>
</html>

Actual output:

https-test.hs: TlsExceptionHostPort (HandshakeFailed (Error_Protocol ("certificate rejected: [SelfSigned]",True,CertificateUnknown))) "self-signed.badssl.com" 443
Alain O'Dea
  • 21,033
  • 1
  • 58
  • 84

1 Answers1

3

This is very bad idea for many reasons. You are much better off fixing the server (if you can) or encouraging the people who run it to fix it.

Bypassing TLS certificate validation removes all useful aspects of TLS, because it makes it trivial for an attacker in a man-in-the-middle position to pretend to be the server and manipulate data. All the attacker needs to do it re-encrypt their intercepted, manipulated content with another equally bad self-signed cert. Your client software will be none the wiser.

http-conduit supports the concept of a request manager. Using a request manager you can supply an alternative.

First you can construct a TLSSettingsSimple that disables server certificate validation (TLSSettingsSimple is defined in Network.Connection in the connection package):

noVerifyTlsSettings :: TLSSettings
noVerifyTlsSettings = TLSSettingsSimple
  { settingDisableCertificateValidation = True
  , settingDisableSession = True
  , settingUseServerName = False
  }

Then you can make a request manager that uses that (mkManagerSettings comes from the Network.HTTP.Client.TLS module in the http-client-tls package):

noVerifyTlsManagerSettings :: ManagerSettings
noVerifyTlsManagerSettings = mkManagerSettings noVerifyTlsSettings Nothing

Then you can initialize this request manager and set it on the request:

manager <- newManager noVerifyTlsManagerSettings
-- ...
$ setRequestManager manager
-- ...

You'll also need to have the http-client-tls package available for this so you need to modify the arguments for stack to include this:

--package http-client-tls

Here's the complete solution:

#!/usr/bin/env stack
-- stack --install-ghc --resolver lts-5.13 runghc --package http-client-tls
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy.Char8 as L8
import           Network.HTTP.Client
import           Network.HTTP.Client.TLS (mkManagerSettings)
import           Network.HTTP.Simple
import           Network.Connection (TLSSettings(..))

main :: IO ()
main = do
  authenticate "self-signed.badssl.com" "" ""

authenticate :: S8.ByteString
             -> L8.ByteString
             -> L8.ByteString
             -> IO ()
authenticate hostname username password = do
  manager <- newManager noVerifyTlsManagerSettings
  let request
        = setRequestMethod "GET"
        $ setRequestSecure True
        $ setRequestPort 443
        $ setRequestHost hostname
        $ setRequestPath "/"
        $ setRequestManager manager
        $ defaultRequest
  response <- httpLBS request
  putStrLn $ "The status code was: " ++
             show (getResponseStatusCode response)
  print $ getResponseHeader "Content-Type" response
  L8.putStrLn $ getResponseBody response

noVerifyTlsManagerSettings :: ManagerSettings
noVerifyTlsManagerSettings = mkManagerSettings noVerifyTlsSettings Nothing

noVerifyTlsSettings :: TLSSettings
noVerifyTlsSettings = TLSSettingsSimple
  { settingDisableCertificateValidation = True
  , settingDisableSession = True
  , settingUseServerName = False
  }
Alain O'Dea
  • 21,033
  • 1
  • 58
  • 84
  • "This is very bad idea for many reasons". This is not assuming you add your own certificate to the trusted ones. In fact, this is much better then trusting "some one else's certificates" –  Oct 10 '19 at 08:19
  • @Igor systems are deployed for an audience. That audience rarely has the knowledge to make appropriate decisions about self-signed certificates. CA signed certificates are in no way perfect but are substantially less damaging than deploying self-signed certs and training users to trust them. – Alain O'Dea Oct 11 '19 at 21:53
  • In fairness, this is server to server, but your point is still missing the point. You install the server cert as trusted by the OS, you don't carte blanche disable TLS verification. It's never the right choice. – Alain O'Dea Oct 11 '19 at 21:55