packages feed

quic-0.3.21: Network/QUIC/TLS.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Network.QUIC.TLS (
    clientHandshaker,
    serverHandshaker,
) where

import Data.X509.CertificateStore (isEmptyCertificateStore)
import Network.TLS hiding (Version, defaultSupported)
import Network.TLS.QUIC
import System.X509

import Network.QUIC.Config
import Network.QUIC.Imports
import Network.QUIC.Parameters
import Network.QUIC.Types

sessionManager :: SessionEstablish -> SessionManager
sessionManager establish = noSessionManager{sessionEstablish = establish}

clientHandshaker
    :: QUICCallbacks
    -> ClientConfig
    -> Version
    -> AuthCIDs
    -> SessionEstablish
    -> Bool
    -> IO ()
clientHandshaker callbacks ClientConfig{..} ver myAuthCIDs establish use0RTT = do
    caStore <-
        if ccValidate
            then
                if isEmptyCertificateStore ccCAStore
                    then getSystemCertificateStore
                    else return ccCAStore
            else return mempty
    tlsQUICClient (cparams caStore) callbacks
  where
    sni = fromMaybe ccServerName ccServerNameOverride
    cparams caStore =
        (defaultParamsClient sni "")
            { clientShared = cshared caStore
            , clientHooks = hook
            , clientSupported = supported
            , clientDebug = debug
            , clientWantSessionResumeList = resumptionSession ccResumption
            , clientUseEarlyData = use0RTT
            , clientUseServerNameIndication = ccUseServerNameIndication
            }
    convTP = onTransportParametersCreated ccHooks
    params = convTP $ setCIDsToParameters myAuthCIDs ccParameters
    convExt = onTLSExtensionCreated ccHooks
    skipValidation = ValidationCache (\_ _ _ -> return ValidationCachePass) (\_ _ _ -> return ())
    cshared caStore =
        defaultShared
            { sharedValidationCache =
                if ccValidate then sharedValidationCache defaultShared else skipValidation
            , sharedCAStore = caStore
            , sharedHelloExtensions = convExt $ parametersToExtensionRaw ver params
            , sharedSessionManager = sessionManager establish
            }
    hook =
        ccTlsHooks
            { onSuggestALPN = (<|>) <$> ccALPN ver <*> onSuggestALPN ccTlsHooks
            , onServerCertificate = ccOnServerCertificate
            }
    supported =
        defaultSupported
            { supportedCiphers = ccCiphers
            , supportedGroups = ccGroups
            }
    debug =
        defaultDebugParams
            { debugKeyLogger = ccKeyLog
            }

-- | The parameters a server accepting 0-RTT must not lower (RFC 9000
--   Sec 7.4.1, and RFC 9221 Sec 3 for max_datagram_frame_size).
zeroRTTLimits :: Parameters -> Parameters
zeroRTTLimits p =
    baseParameters
        { activeConnectionIdLimit = activeConnectionIdLimit p
        , initialMaxData = initialMaxData p
        , initialMaxStreamDataBidiLocal = initialMaxStreamDataBidiLocal p
        , initialMaxStreamDataBidiRemote = initialMaxStreamDataBidiRemote p
        , initialMaxStreamDataUni = initialMaxStreamDataUni p
        , initialMaxStreamsBidi = initialMaxStreamsBidi p
        , initialMaxStreamsUni = initialMaxStreamsUni p
        , maxDatagramFrameSize = maxDatagramFrameSize p
        }

-- | Whether the parameters now keep to those remembered.
keeps0RTTLimits :: Parameters -> Parameters -> Bool
keeps0RTTLimits remembered now =
    and
        [ f now >= f remembered
        | f <-
            [ activeConnectionIdLimit
            , initialMaxData
            , initialMaxStreamDataBidiLocal
            , initialMaxStreamDataBidiRemote
            , initialMaxStreamDataUni
            , initialMaxStreamsBidi
            , initialMaxStreamsUni
            , maxDatagramFrameSize
            ]
        ]

parametersToExtensionRaw :: Version -> Parameters -> [ExtensionRaw]
parametersToExtensionRaw ver params = [ExtensionRaw tpId eParams]
  where
    tpId = extensionIDForTtransportParameter ver
    eParams = encodeParameters params

serverHandshaker
    :: QUICCallbacks
    -> ServerConfig
    -> Version
    -> IO Parameters
    -> IO ()
serverHandshaker callbacks ServerConfig{..} ver getParams =
    tlsQUICServer sparams callbacks
  where
    sparams =
        defaultParamsServer
            { serverWantClientCert = scWantClientCert
            , serverShared = sshared
            , serverHooks = hook
            , serverSupported = supported
            , serverDebug = debug
            , serverEarlyDataSize = if scUse0RTT then quicMaxEarlyDataSize else 0
            , serverTicketLifetime = scTicketLifetime
            }
    convTP = onTransportParametersCreated scHooks
    convExt = onTLSExtensionCreated scHooks
    sshared =
        defaultShared
            { sharedCredentials = scCredentials
            , sharedSessionManager = mgr
            }
    -- RFC 9000 Sec 7.4.1: what 0-RTT is held to is kept with the session,
    -- and 0-RTT is refused if the server now allows less.
    mgr =
        scSessionManager
            { sessionEstablish = \sid sdata -> do
                remembered <- zeroRTTLimits <$> getParams
                sessionEstablish scSessionManager sid $
                    sdata{sessionAppData = Just $ encodeParameters remembered}
            }
    accept0RTT sdata = do
        ok <- onAccept0RTT scTlsHooks sdata
        now <- getParams
        return $ case sessionAppData sdata >>= decodeParameters of
            Just remembered -> ok && keeps0RTTLimits remembered now
            Nothing -> False
    hook =
        scTlsHooks
            { onAccept0RTT = accept0RTT
            , onALPNClientSuggest = case scALPN of
                Nothing -> onALPNClientSuggest scTlsHooks
                Just io -> Just $ io ver
            , onEncryptedExtensionsCreating = \exts0 -> do
                exts0' <- onEncryptedExtensionsCreating scTlsHooks exts0
                params <- getParams
                let exts = convExt $ parametersToExtensionRaw ver $ convTP params
                return $ exts ++ exts0'
            }
    supported =
        defaultSupported
            { supportedVersions = [TLS13]
            , supportedCiphers = scCiphers
            , supportedGroups = scGroups
            , supportedGroupsTLS13 = scGroupsTLS13
            }
    debug =
        defaultDebugParams
            { debugKeyLogger = scKeyLog
            }