packages feed

tls-sslkeylogfile (empty) → 0.1.0.0

raw patch · 5 files changed

+430/−0 lines, 5 filesdep +basedep +bytestringdep +crypton-connection

Dependencies added: base, bytestring, crypton-connection, crypton-x509-system, data-default, http-client, http-client-tls, tls, tls-sslkeylogfile

Files

+ CHANGELOG.md view
@@ -0,0 +1,9 @@+<!--+SPDX-FileCopyrightText: 2025 Mercury Technologies, Inc++SPDX-License-Identifier: MIT+-->++## 0.1.0.0 (2025-03-07)++- Initial release
+ README.md view
@@ -0,0 +1,99 @@+<!--+SPDX-FileCopyrightText: 2025 Mercury Technologies, Inc++SPDX-License-Identifier: MIT+-->++# tls-sslkeylogfile++This package adds support for the [`SSLKEYLOGFILE` standard](https://www.ietf.org/archive/id/draft-thomson-tls-keylogfile-00.html) to Haskell programs using the `tls` and/or `http-client` libraries.++This packages uses the functionality built into the Haskell `tls` library to extract the *session keys* of TLS sessions and store them in the `SSLKEYLOGFILE` format.+In TLS, the session keys are ephemeral *symmetric* keys used to encrypt just the data for each session.+They are generated and exchanged during the TLS handshake, and with modern TLS cipher suites with [Perfect Forward Secrecy], are handled via completely separate machinery (Diffie-Hellman) from the server's private key, the latter only being used to sign the handshake.+As far as key material is concerned, session keys are not very sensitive: they are generated for every session and grabbing them for debugging only allows access to the traffic for which the keys are known.++[Perfect Forward Secrecy]: https://en.wikipedia.org/wiki/Forward_secrecy++Using session keys allows for (relatively) easy debugging of HTTPS traffic using commonly-available packet sniffers like Wireshark *without altering the traffic at all*, and without wider security impacts like e.g. adding local TLS certification authorities to the system keyring.++For more details on TLS interception using session keys, see: <https://jade.fyi/blog/announcing-clipper/>.++## Related work++- [mitmproxy](https://mitmproxy.org/) - an intercepting TLS proxy with a lot of features.++  It can operate as a proxy with fake certificates in various modes including capturing raw traffic and tampering with it, acting as a traditional HTTPS proxy, and more.+  It's really cool.++  However, the fake certificates mode can be unfortunate since they require certificate configuration in the application and are not transparent: you can't use mitmproxy to debug TLS implementation bugs, for instance, since it changes the traffic to intercept it.+- [clipper](https://github.com/lf-/clipper) - a fully integrated TLS session-key-log based packet sniffer for Linux by the same author as this package.++  Clipper does the same thing as this package for rustls and OpenSSL with no application-level changes, implemented by injecting code into the process.+  It captures traffic transparently and can either generate pcapng files with included keys or decode traffic on-the-fly to display it in Chrome DevTools.++  However, it does not support either extracting keys from Haskell or capturing traffic on macOS.++## Usage++Set up tls-sslkeylogfile on a simple program (see [examples/demo/Demo.hs](./examples/demo/Demo.hs)):++```haskell+import Network.TLS.SSLKeyLogFile+import Network.HTTP.Client (parseRequest, httpLbs, Response (..))++main :: IO ()+main = do+  man <- makeManager++  req <- parseRequest "https://example.com/index.html"+  resp <- httpLbs req man+  putStrLn $ "The status code was: " ++ show (responseStatus resp)+```++Then run it with some interception:++In one terminal, capture some packets (tcpdump can equally be used):++```+$ tshark -w pakits.pcapng -i en9 'port 443'+Capturing on 'REDACTED: en9'+322 ^C+```++In another, run the program with `SSLKEYLOGFILE` set:++```+$ SSLKEYLOGFILE=keys.log cabal run keylogfile-demo+The status code was: Status {statusCode = 200, statusMessage = "OK"}+```++After the debugee finishes, the `tshark` command should be CTRL-C'd.++`keys.log` will look like the following:++```+SERVER_HANDSHAKE_TRAFFIC_SECRET aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb+CLIENT_HANDSHAKE_TRAFFIC_SECRET aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc+SERVER_TRAFFIC_SECRET_0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd+CLIENT_TRAFFIC_SECRET_0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee+```++The actual keys are replaced with placeholders here for readability.+This is a TLS 1.3 session because there are lines that are not tagged `CLIENT_RANDOM`.++Put the keys into the pcapng so the analysis tools work nicely:++```+$ editcap --inject-secrets tls,keys.log pakits.pcapng pakits2.pcapng+```++Then, look at the packets with tshark:++```+$ tshark -r pakits2.pcapng -T fields -e '_ws.col.Info' --display-filter http+GET /index.html HTTP/1.1+HTTP/1.1 200 OK  (text/html)+```++Here's our traffic! If you wanted a nicer UX of looking at it, just open the pcapng file in Wireshark, the encrypted traffic will be right there decrypted for you.
+ examples/demo/Demo.hs view
@@ -0,0 +1,14 @@+-- SPDX-FileCopyrightText: 2025 Mercury Technologies, Inc+--+-- SPDX-License-Identifier: MIT++import Network.HTTP.Client (Response (..), httpLbs, parseRequest)+import Network.TLS.SSLKeyLogFile++main :: IO ()+main = do+  man <- makeManager++  req <- parseRequest "https://example.com/index.html"+  resp <- httpLbs req man+  putStrLn $ "The status code was: " ++ show (responseStatus resp)
+ src/Network/TLS/SSLKeyLogFile.hs view
@@ -0,0 +1,165 @@+-- SPDX-FileCopyrightText: 2025 Mercury Technologies, Inc+--+-- SPDX-License-Identifier: MIT++-- | Implements @SSLKEYLOGFILE@ support: logs keys to the file specified in the+-- @SSLKEYLOGFILE@ environment variable. Completely inactive when the+-- environment variable is not set.+--+-- This is used for inspecting TLS traffic in-flight in combination with a+-- packet dump, with a primary use case of debugging.+--+-- For easier usage, you can embed the key log in the pcap and Wireshark will+-- decrypt it without having to fiddle with any settings:+--+-- > $ editcap --inject-secrets tls,mykeylog.tls_keys input.pcap output.pcapng+--+-- - See: <https://www.ietf.org/archive/id/draft-thomson-tls-keylogfile-00.html>+-- - See: <https://jade.fyi/blog/announcing-clipper/>+module Network.TLS.SSLKeyLogFile (+  -- * Demo and workflow++  -- | There is a demonstration of the workflow of using tls-sslkeylogfile in+  -- the [README on GitHub](https://github.com/MercuryTechnologies/tls-sslkeylogfile#readme)+  -- including all the steps necessary to capture packets and analyze their+  -- decrypted versions.++  -- * Security considerations++  -- | See the RFC: <https://www.ietf.org/archive/id/draft-thomson-tls-keylogfile-00.html#name-security-considerations-8>+  --+  -- This package should be thought of the same as @gdb@ in terms of security. If+  -- the person invoking your software is passing @SSLKEYLOGFILE@, they could+  -- have just as well run it under @gdb@ and gotten the keys out of it without+  -- this package.+  --+  -- The combination of keys extracted from this and traffic decrypted using+  -- those keys, in the absence of session resumption (off by default in hs-tls),+  -- only impacts encryption sessions that were executed while keys were being+  -- logged and no past or future sessions. This assumes that obscure+  -- features like exporters (not supported by hs-tls anyhow as of+  -- 2025-03-06) are not used in a manner that impacts confidentiality of+  -- past or future sessions.+  --+  -- Logging the session keys for debugging has no impact on the security+  -- properties or behaviour of TLS as visible to other hosts; it just allows you+  -- to decrypt traffic as someone who already has full access to the process.++  -- * Functions+  makeManager,+  MakeTLSSettingsParams (..),+  makeTLSSettingsWithKeyLogging,+  addKeyLoggingToClientParams,+) where++import Control.Monad.IO.Class (MonadIO (..))+import Data.Default (Default (..))+import Network.Connection (TLSSettings (..))+import Network.HTTP.Client (Manager, newManager)+import Network.HTTP.Client.TLS (mkManagerSettings)+import Network.TLS (ClientParams (..), DebugParams (..), Shared (..), Supported (..), defaultParamsClient)+import Network.TLS qualified as TLS+import Network.TLS.Extra.Cipher qualified as TLS+import System.Environment (lookupEnv)+import System.IO (IOMode (..), hFlush, hPutStrLn, openFile)+import System.X509 (getSystemCertificateStore)++-- | This is the equivalent of 'TLSSettingsSimple' with only the actually-used+-- fields implemented.+data MakeTLSSettingsParams = MakeTLSSettingsParams+  { disableCertificateValidation :: Bool+  -- ^ Whether to ignore server certificates and not validate them. This is+  -- obviously insecure to set to 'True'.+  --+  -- __Default__: 'False'.+  , clientSupported :: Supported+  -- ^ Value of 'Supported' to use when constructing the 'ClientParams'.+  -- Used for e.g. allowing legacy TLS 1.2 services without Extended Main+  -- Secret support in versions of the Haskell @tls@ library >= 2.0.+  --+  -- __Default__: @def \@Supported {supportedCiphers = TLS.ciphersuite_default}@+  }+  deriving stock (Show)++instance Default MakeTLSSettingsParams where+  def =+    MakeTLSSettingsParams+      { disableCertificateValidation = False+      , clientSupported = def {supportedCiphers = TLS.ciphersuite_default}+      }++-- | Creates a 'TLSSettings' with the system CA trust store and the default+-- cipher suites. This function is for those who want a 'TLSSettings' value that+-- works in normal use cases.+--+-- See @Network.Connection.makeTLSParams@ in @crypton-connection@ for the code+-- this function replicates.+makeTLSSettingsWithKeyLogging :: MakeTLSSettingsParams -> IO TLSSettings+makeTLSSettingsWithKeyLogging MakeTLSSettingsParams {disableCertificateValidation, clientSupported} = do+  caStore <- getSystemCertificateStore++  TLSSettings+    <$> addKeyLoggingToClientParams+      ( (defaultParamsClient "" "")+          { clientShared =+              (def @Shared)+                { sharedCAStore = caStore+                , sharedValidationCache = validationCache+                }+          , TLS.clientSupported+          }+      )+  where+    validationCache+      | disableCertificateValidation =+          TLS.ValidationCache+            (\_ _ _ -> return TLS.ValidationCachePass)+            (\_ _ _ -> return ())+      | otherwise = def++-- | Creates a 'Manager' with TLS support, with the default configuration, with+-- key logging available.+makeManager :: IO Manager+makeManager = do+  tlsSettings <- makeTLSSettingsWithKeyLogging def+  newManager $ mkManagerSettings tlsSettings Nothing++-- | Adds key logging support to the given ClientParams using the+-- @SSLKEYLOGFILE@ environment variable. This is an advanced function. Most use+-- cases should use 'makeTLSSettings' or 'makeManager' to create a+-- 'TLSSettings' or a 'Manager' which works.+--+-- __IMPORTANT NOTE__: If you haven't put a 'sharedCAStore' or 'supportedCiphers'+-- into the given ClientParams, it will not make a connection successfully!+--+-- By default, @crypton-connection@ uses a 'TLSSettingsSimple' into which the CA+-- store and cipher suites are injected during connection setup to make a+-- 'TLSSettings' containing an appropriate 'ClientParams' that can actually+-- establish a TLS connection. By using this function, you are necessarily+-- bypassing this logic and constructing your /own/ 'ClientParams', which,+-- unless you specifically add some, will not have any trusted certification+-- authorities or allowed cipher suites and will thus fail to establish any+-- connection.+addKeyLoggingToClientParams :: ClientParams -> IO ClientParams+addKeyLoggingToClientParams params = do+  keyLogFileEnv <- lookupEnv "SSLKEYLOGFILE"++  -- XXX(jade): due to API design limitations in hs-tls, there's no way to have+  -- a properly managed lifetime for the file handle. we do just leak it. it's+  -- fine, it either gets closed or not (in which case it gets closed on process+  -- exit), but it won't get eaten by the GC or anything either way.++  keyLog <- case keyLogFileEnv of+    Just f -> do+      hand <- openFile f AppendMode+      pure $ \line -> liftIO (hPutStrLn hand line >> hFlush hand)+    Nothing -> do+      pure $ \_ -> pure ()++  pure+    params+      { clientDebug =+          (def @DebugParams)+            { debugKeyLogger = keyLog+            }+      }
+ tls-sslkeylogfile.cabal view
@@ -0,0 +1,143 @@+cabal-version: 3.0++-- This file has been generated from package.yaml by hpack version 0.36.1.+--+-- see: https://github.com/sol/hpack++name:           tls-sslkeylogfile+version:        0.1.0.0+synopsis:       SSLKEYLOGFILE support for Haskell+description:    See README at <https://github.com/MercuryTechnologies/tls-sslkeylogfile#readme>.+category:       Network+homepage:       https://github.com/MercuryTechnologies/tls-sslkeylogfile#readme+bug-reports:    https://github.com/MercuryTechnologies/tls-sslkeylogfile/issues+maintainer:     Jade Lovelace <jadel@mercury.com>+license:        MIT+build-type:     Simple+tested-with:+    GHC == { 9.2, 9.4, 9.6, 9.8, 9.10 }+extra-source-files:+    CHANGELOG.md+    README.md++source-repository head+  type: git+  location: https://github.com/MercuryTechnologies/tls-sslkeylogfile++flag examples+  description: Build example executables.+  manual: True+  default: False++library+  exposed-modules:+      Network.TLS.SSLKeyLogFile+  other-modules:+      Paths_tls_sslkeylogfile+  autogen-modules:+      Paths_tls_sslkeylogfile+  hs-source-dirs:+      src+  default-extensions:+      AllowAmbiguousTypes+      BlockArguments+      DataKinds+      DeriveAnyClass+      DeriveFoldable+      DeriveFunctor+      DeriveGeneric+      DeriveLift+      DeriveTraversable+      DerivingVia+      FlexibleContexts+      FlexibleInstances+      FunctionalDependencies+      GADTs+      GeneralizedNewtypeDeriving+      ImportQualifiedPost+      InstanceSigs+      LambdaCase+      MonoLocalBinds+      MultiWayIf+      NamedFieldPuns+      NumericUnderscores+      OverloadedStrings+      PatternSynonyms+      PolyKinds+      RankNTypes+      RecordWildCards+      RecursiveDo+      ScopedTypeVariables+      StandaloneDeriving+      StandaloneKindSignatures+      TypeApplications+      TypeFamilies+      ViewPatterns+  ghc-options: -Weverything -Wno-missing-exported-signatures -Wno-missing-export-lists -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module -Wno-unused-packages -Wno-missing-kind-signatures -Wno-implicit-prelude+  build-depends:+      base >=4.16 && <4.22+    , crypton-connection+    , crypton-x509-system+    , data-default+    , http-client+    , http-client-tls+    , tls >=1.8 && <2.0 || >=2.0 && <2.3+  default-language: Haskell2010++executable keylogfile-demo+  main-is: Demo.hs+  other-modules:+      Paths_tls_sslkeylogfile+  autogen-modules:+      Paths_tls_sslkeylogfile+  hs-source-dirs:+      examples/demo+  default-extensions:+      AllowAmbiguousTypes+      BlockArguments+      DataKinds+      DeriveAnyClass+      DeriveFoldable+      DeriveFunctor+      DeriveGeneric+      DeriveLift+      DeriveTraversable+      DerivingVia+      FlexibleContexts+      FlexibleInstances+      FunctionalDependencies+      GADTs+      GeneralizedNewtypeDeriving+      ImportQualifiedPost+      InstanceSigs+      LambdaCase+      MonoLocalBinds+      MultiWayIf+      NamedFieldPuns+      NumericUnderscores+      OverloadedStrings+      PatternSynonyms+      PolyKinds+      RankNTypes+      RecordWildCards+      RecursiveDo+      ScopedTypeVariables+      StandaloneDeriving+      StandaloneKindSignatures+      TypeApplications+      TypeFamilies+      ViewPatterns+  ghc-options: -Weverything -Wno-missing-exported-signatures -Wno-missing-export-lists -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module -Wno-unused-packages -Wno-missing-kind-signatures -Wno-implicit-prelude+  build-depends:+      base >=4.16 && <4.22+    , bytestring+    , crypton-connection+    , crypton-x509-system+    , data-default+    , http-client+    , http-client-tls+    , tls >=1.8 && <2.0 || >=2.0 && <2.3+    , tls-sslkeylogfile+  default-language: Haskell2010+  if !flag(examples)+    buildable: False