tls 2.0.6 → 2.1.0
raw patch · 27 files changed
+499/−248 lines, 27 filesdep ~data-default-class
Dependency ranges changed: data-default-class
Files
- CHANGELOG.md +8/−0
- Network/TLS.hs +111/−14
- Network/TLS/Context.hs +1/−1
- Network/TLS/Context/Internal.hs +17/−20
- Network/TLS/Core.hs +24/−18
- Network/TLS/Handshake/Client.hs +5/−4
- Network/TLS/Handshake/Client/ClientHello.hs +47/−36
- Network/TLS/Handshake/Client/Common.hs +7/−0
- Network/TLS/Handshake/Client/ServerHello.hs +15/−9
- Network/TLS/Handshake/Client/TLS12.hs +21/−18
- Network/TLS/Handshake/Client/TLS13.hs +13/−11
- Network/TLS/Handshake/Common.hs +11/−0
- Network/TLS/Handshake/Common13.hs +7/−6
- Network/TLS/Handshake/Server.hs +1/−1
- Network/TLS/Handshake/Server/ServerHello12.hs +14/−12
- Network/TLS/Handshake/Server/ServerHello13.hs +8/−5
- Network/TLS/Handshake/Server/TLS12.hs +5/−0
- Network/TLS/Handshake/State.hs +1/−1
- Network/TLS/Parameters.hs +33/−1
- Network/TLS/Session.hs +8/−6
- Network/TLS/State.hs +50/−38
- Network/TLS/Types.hs +4/−0
- test/Session.hs +2/−2
- tls.cabal +3/−1
- util/Client.hs +21/−8
- util/tls-client.hs +48/−32
- util/tls-server.hs +14/−4
CHANGELOG.md view
@@ -1,3 +1,11 @@+## Version 2.1.0++* Breaking change: stop exporting constructors to maintain future+ compatibilities. Use `def` and `noSessionManager` instead.+* `onServerFinished` is added to `ClientHooks`.+* `clientWantSessionResumeList` is added to `ClientParams` to support+ multiple tickets for TLS 1.3.+ ## Version 2.0.6 * Setting `supportedCiphers` in `defaultSupported` to `ciphersuite_default`.
Network/TLS.hs view
@@ -30,25 +30,85 @@ -- intentionally hide the internal methods even haddock warns. TLSParams,- ClientParams (..),++ -- ** Client parameters+ ClientParams, defaultParamsClient,- ServerParams (..),+ clientUseMaxFragmentLength,+ clientServerIdentification,+ clientUseServerNameIndication,+ clientWantSessionResume,+ clientWantSessionResumeList,+ clientShared,+ clientHooks,+ clientSupported,+ clientDebug,+ clientUseEarlyData, + -- ** Server parameters+ ServerParams,+ serverWantClientCert,+ serverCACertificates,+ serverDHEParams,+ serverHooks,+ serverShared,+ serverSupported,+ serverDebug,+ serverEarlyDataSize,+ serverTicketLifetime,+ -- ** Shared- Shared (..),+ Shared,+ sharedCredentials,+ sharedSessionManager,+ sharedCAStore,+ sharedValidationCache,+ sharedHelloExtensions, - -- ** Hooks- ClientHooks (..),+ -- ** Client hooks+ ClientHooks, OnCertificateRequest,+ onCertificateRequest, OnServerCertificate,- ServerHooks (..),- Measurement (..),+ onServerCertificate,+ onSuggestALPN,+ onCustomFFDHEGroup,+ onServerFinished, + -- ** Server hooks+ ServerHooks,+ onClientCertificate,+ onUnverifiedClientCert,+ onCipherChoosing,+ onServerNameIndication,+ onNewHandshake,+ onALPNClientSuggest,+ onEncryptedExtensionsCreating,+ Measurement,+ nbHandshakes,+ bytesReceived,+ bytesSent,+ -- ** Supported- Supported (..),+ Supported,+ supportedVersions,+ supportedCiphers,+ supportedCompressions,+ supportedHashSignatures,+ supportedSecureRenegotiation,+ supportedClientInitiatedRenegotiation,+ supportedExtendedMainSecret,+ supportedSession,+ supportedFallbackScsv,+ supportedEmptyPacket,+ supportedGroups, -- ** Debug parameters- DebugParams (..),+ DebugParams,+ debugSeed,+ debugPrintSeed,+ debugVersionForced,+ debugKeyLogger, -- * Shared parameters @@ -61,14 +121,32 @@ credentialLoadX509ChainFromMemory, -- ** Session manager- SessionManager (..),+ SessionManager, noSessionManager,+ sessionResume,+ sessionResumeOnlyOnce,+ sessionEstablish,+ sessionInvalidate,+ sessionUseTicket, SessionID, SessionIDorTicket, Ticket,- SessionData (..),++ -- ** Session data+ SessionData,+ sessionVersion,+ sessionCipher,+ sessionCompression,+ sessionClientSNI,+ sessionSecret,+ sessionGroup,+ sessionTicketInfo,+ sessionALPN,+ sessionMaxEarlyDataSize,+ sessionFlags, SessionFlag (..), TLS13TicketInfo,+ is0RTTPossible, -- ** Validation Cache ValidationCache (..),@@ -109,8 +187,19 @@ contextClose, -- ** Information gathering- Information (..),+ Information, contextGetInformation,+ infoVersion,+ infoCipher,+ infoCompression,+ infoMainSecret,+ infoExtendedMainSecret,+ infoClientRandom,+ infoServerRandom,+ infoSupportedGroup,+ infoTLS12Resumption,+ infoTLS13HandshakeMode,+ infoIsEarlyDataAccepted, ClientRandom, ServerRandom, unClientRandom,@@ -133,14 +222,22 @@ getPeerFinished, -- ** Modifying hooks in context- Hooks (..),+ Hooks,+ hookRecvHandshake,+ hookRecvHandshake13,+ hookRecvCertificates,+ hookLogging, contextModifyHooks, Handshake, contextHookSetHandshakeRecv, Handshake13, contextHookSetHandshake13Recv, contextHookSetCertificateRecv,- Logging (..),+ Logging,+ loggingPacketSent,+ loggingPacketRecv,+ loggingIOSent,+ loggingIORecv, Header (..), ProtocolType (..), contextHookSetLogging,
Network/TLS/Context.hs view
@@ -266,7 +266,7 @@ exporter :: Context -> ByteString -> ByteString -> Int -> IO (Maybe ByteString) exporter ctx label context outlen = do- msecret <- usingState_ ctx getExporterSecret+ msecret <- usingState_ ctx getTLS13ExporterSecret mcipher <- failOnEitherError $ runRxRecordState ctx $ gets stCipher return $ case (msecret, mcipher) of (Just secret, Just cipher) ->
Network/TLS/Context/Internal.hs view
@@ -80,7 +80,6 @@ import Network.TLS.Backend import Network.TLS.Cipher-import Network.TLS.Compression (Compression) import Network.TLS.Crypto import Network.TLS.Extension import Network.TLS.Handshake.Control@@ -97,24 +96,9 @@ import Network.TLS.Types import Network.TLS.Util --- | Information related to a running context, e.g. current cipher-data Information = Information- { infoVersion :: Version- , infoCipher :: Cipher- , infoCompression :: Compression- , infoMainSecret :: Maybe ByteString- , infoExtendedMainSecret :: Bool- , infoClientRandom :: Maybe ClientRandom- , infoServerRandom :: Maybe ServerRandom- , infoSupportedGroup :: Maybe Group- , infoTLS12Resumption :: Bool- , infoTLS13HandshakeMode :: Maybe HandshakeMode13- , infoIsEarlyDataAccepted :: Bool- }- deriving (Show, Eq)- -- | A TLS Context keep tls specific state, parameters and backend information.-data Context = forall a.+data Context+ = forall a. Monoid a => Context { ctxBackend :: Backend@@ -200,6 +184,7 @@ , tls13stClientExtensions :: [ExtensionRaw] -- client , tls13stChoice :: ~CipherChoice -- client , tls13stHsKey :: Maybe (SecretTriple HandshakeSecret) -- client+ -- Actuall session id for TLS 1.2, random value for TLS 1.3 , tls13stSession :: Session , tls13stSentExtensions :: [ExtensionID] }@@ -301,12 +286,24 @@ let accepted = case hstate of Just st -> hstTLS13RTT0Status st == RTT0Accepted Nothing -> False- tls12resumption <- usingState_ ctx isSessionResuming+ tls12resumption <- usingState_ ctx getTLS12SessionResuming case (ver, cipher) of (Just v, Just c) -> return $ Just $- Information v c comp ms ems cr sr grp tls12resumption hm13 accepted+ Information+ { infoVersion = v+ , infoCipher = c+ , infoCompression = comp+ , infoMainSecret = ms+ , infoExtendedMainSecret = ems+ , infoClientRandom = cr+ , infoServerRandom = sr+ , infoSupportedGroup = grp+ , infoTLS12Resumption = tls12resumption+ , infoTLS13HandshakeMode = hm13+ , infoIsEarlyDataAccepted = accepted+ } _ -> return Nothing contextSend :: Context -> ByteString -> IO ()
Network/TLS/Core.hs view
@@ -298,7 +298,7 @@ loopHandshake13 [] = return () -- fixme: some implementations send multiple NST at the same time. -- Only the first one is used at this moment.- loopHandshake13 (NewSessionTicket13 life add nonce label exts : hs) = do+ loopHandshake13 (NewSessionTicket13 life add nonce ticket exts : hs) = do role <- usingState_ ctx S.getRole unless (role == ClientRole) $ let reason = "Session ticket is allowed for client only"@@ -318,8 +318,8 @@ life7d = min life 604800 -- 7 days max tinfo <- createTLS13TicketInfo life7d (Right add) Nothing sdata <- getSessionData13 ctx usedCipher tinfo maxSize psk- let label' = B.copy label- void $ sessionEstablish (sharedSessionManager $ ctxShared ctx) label' sdata+ let ticket' = B.copy ticket+ void $ sessionEstablish (sharedSessionManager $ ctxShared ctx) ticket' sdata modifyTLS13State ctx $ \st -> st{tls13stRecvNST = True} loopHandshake13 hs loopHandshake13 (KeyUpdate13 mode : hs) = do@@ -379,7 +379,7 @@ loopHandshake13 [] = return () -- fixme: some implementations send multiple NST at the same time. -- Only the first one is used at this moment.- loopHandshake13 (NewSessionTicket13 life add nonce label exts : hs) = do+ loopHandshake13 (NewSessionTicket13 life add nonce ticket exts : hs) = do role <- usingState_ ctx S.getRole unless (role == ClientRole) $ let reason = "Session ticket is allowed for client only"@@ -399,8 +399,8 @@ life7d = min life 604800 -- 7 days max tinfo <- createTLS13TicketInfo life7d (Right add) Nothing sdata <- getSessionData13 ctx usedCipher tinfo maxSize psk- let label' = B.copy label- void $ sessionEstablish (sharedSessionManager $ ctxShared ctx) label' sdata+ let ticket' = B.copy ticket+ void $ sessionEstablish (sharedSessionManager $ ctxShared ctx) ticket' sdata modifyTLS13State ctx $ \st -> st{tls13stRecvNST = True} loopHandshake13 hs loopHandshake13 (h : hs) = do@@ -439,11 +439,11 @@ return True checkAlignment :: Context -> [Handshake13] -> IO ()-checkAlignment ctx hs = do+checkAlignment ctx _hs = do complete <- isRecvComplete ctx- unless (complete && null hs) $+ unless complete $ do let reason = "received message not aligned with record boundary"- in terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason+ terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason -- the other side could have close the connection already, so wrap -- this in a try and ignore all exceptions@@ -470,15 +470,21 @@ -> AlertDescription -> String -> IO a-terminateWithWriteLock ctx send err level desc reason = do- session <- usingState_ ctx getSession- -- Session manager is always invoked with read+write locks, so we merge this- -- with the alert packet being emitted.- withWriteLock ctx $ do- case session of- Session Nothing -> return ()- Session (Just sid) -> sessionInvalidate (sharedSessionManager $ ctxShared ctx) sid- catchException (send [(level, desc)]) (\_ -> return ())+terminateWithWriteLock ctx send err level desc reason = withWriteLock ctx $ do+ tls13 <- tls13orLater ctx+ unless tls13 $ do+ -- TLS 1.2 uses the same session ID and session data+ -- for all resumed sessions.+ --+ -- TLS 1.3 changes session data for every resumed session.+ session <- usingState_ ctx getSession+ withWriteLock ctx $ do+ case session of+ Session Nothing -> return ()+ Session (Just sid) ->+ -- calling even session ticket manager anyway+ sessionInvalidate (sharedSessionManager $ ctxShared ctx) sid+ catchException (send [(level, desc)]) (\_ -> return ()) setEOF ctx E.throwIO (Terminated False reason err)
Network/TLS/Handshake/Client.hs view
@@ -10,6 +10,7 @@ import Network.TLS.Crypto import Network.TLS.Extension import Network.TLS.Handshake.Client.ClientHello+import Network.TLS.Handshake.Client.Common import Network.TLS.Handshake.Client.ServerHello import Network.TLS.Handshake.Client.TLS12 import Network.TLS.Handshake.Client.TLS13@@ -37,9 +38,9 @@ -- values intertwined with response from the server. handshakeClient :: ClientParams -> Context -> IO () handshakeClient cparams ctx = do- groups <- case clientWantSessionResume cparams of- Nothing -> return groupsSupported- Just (_, sdata) -> case sessionGroup sdata of+ groups <- case clientSessions cparams of+ [] -> return groupsSupported+ (_, sdata) : _ -> case sessionGroup sdata of Nothing -> return [] -- TLS 1.2 or earlier Just grp | grp `elem` groupsSupported -> return $ grp : filter (/= grp) groupsSupported@@ -94,7 +95,7 @@ | otherwise -> do recvServerFirstFlight12 cparams ctx hss sendClientSecondFlight12 cparams ctx- recvServerSecondFlight12 ctx+ recvServerSecondFlight12 cparams ctx where groupToSend = listToMaybe groups
Network/TLS/Handshake/Client/ClientHello.hs view
@@ -51,9 +51,9 @@ return crand generateClientHelloParams Nothing = do crand <- clientRandom ctx- let paramSession = case clientWantSessionResume cparams of- Nothing -> Session Nothing- Just (sidOrTkt, sdata)+ let paramSession = case clientSessions cparams of+ [] -> Session Nothing+ (sidOrTkt, sdata) : _ | sessionVersion sdata >= TLS13 -> Session Nothing | ems == RequireEMS && noSessionEMS -> Session Nothing | isTicket sidOrTkt -> Session $ Just $ toSessionID sidOrTkt@@ -178,8 +178,8 @@ -- heartbeatExtension = return $ Just $ toExtensionRaw $ HeartBeat $ HeartBeat_PeerAllowedToSend sessionTicketExtension = do- case clientWantSessionResume cparams of- Just (sidOrTkt, _)+ case clientSessions cparams of+ (sidOrTkt, _) : _ | isTicket sidOrTkt -> return $ Just $ toExtensionRaw $ SessionTicket sidOrTkt _ -> return $ Just $ toExtensionRaw $ SessionTicket "" @@ -210,10 +210,13 @@ preSharedKeyExtension = case pskInfo of Nothing -> return Nothing- Just (identity, _, choice, obfAge) ->+ Just (identities, _, choice, obfAge) -> let zero = cZero choice- pskIdentity = PskIdentity identity obfAge- offeredPsks = PreSharedKeyClientHello [pskIdentity] [zero]+ pskIdentities = map (\x -> PskIdentity x obfAge) identities+ -- [zero] is a place holds.+ -- adjustExtentions will replace them.+ binders = replicate (length pskIdentities) zero+ offeredPsks = PreSharedKeyClientHello pskIdentities binders in return $ Just $ toExtensionRaw offeredPsks pskExchangeModeExtension@@ -238,18 +241,21 @@ adjustExtentions exts ch = case pskInfo of Nothing -> return exts- Just (_, sdata, choice, _) -> do+ Just (identities, sdata, choice, _) -> do let psk = sessionSecret sdata earlySecret = initEarlySecret choice (Just psk) usingHState ctx $ setTLS13EarlySecret earlySecret let ech = encodeHandshake ch h = cHash choice- siz = hashDigestSize h- binder <- makePSKBinder ctx earlySecret h (siz + 3) (Just ech)+ siz = (hashDigestSize h + 1) * length identities + 2+ binder <- makePSKBinder ctx earlySecret h siz (Just ech)+ -- PSK is shared by the previous TLS session.+ -- So, PSK is unique for identities.+ let binders = replicate (length identities) binder let exts' = init exts ++ [adjust (last exts)] adjust (ExtensionRaw eid withoutBinders) = ExtensionRaw eid withBinders where- withBinders = replacePSKBinder withoutBinders binder+ withBinders = replacePSKBinder withoutBinders binders return exts' getEarlySecretInfo choice = do@@ -271,7 +277,10 @@ ---------------------------------------------------------------- type PreSharedKeyInfo =- (Maybe (SessionID, SessionData, CipherChoice, Second), Maybe CipherChoice, Bool)+ ( Maybe ([SessionIDorTicket], SessionData, CipherChoice, Second)+ , Maybe CipherChoice+ , Bool+ ) getPreSharedKeyInfo :: ClientParams@@ -286,30 +295,32 @@ ciphers = supportedCiphers $ ctxSupported ctx highestVer = maximum $ supportedVersions $ ctxSupported ctx tls13 = highestVer >= TLS13- sessionAndCipherToResume13 = do- guard tls13- (sid, sdata) <- clientWantSessionResume cparams- guard (sessionVersion sdata >= TLS13)- let cid = sessionCipher sdata- sCipher <- find (\c -> cipherID c == cid) ciphers- return (sid, sdata, sCipher) - getPskInfo =- case sessionAndCipherToResume13 of- Nothing -> return Nothing- Just (identity, sdata, sCipher) -> do- let tinfo = fromJust $ sessionTicketInfo sdata- age <- getAge tinfo- return $- if isAgeValid age tinfo- then- Just- ( identity- , sdata- , makeCipherChoice TLS13 sCipher- , ageToObfuscatedAge age tinfo- )- else Nothing+ sessions = case clientSessions cparams of+ [] -> Nothing+ (sid, sdata) : xs -> do+ guard tls13+ guard (sessionVersion sdata >= TLS13)+ let cid = sessionCipher sdata+ sids = map fst xs+ sCipher <- find (\c -> cipherID c == cid) ciphers+ Just (sid : sids, sdata, sCipher)++ getPskInfo = case sessions of+ Nothing -> return Nothing+ Just (identity, sdata, sCipher) -> do+ let tinfo = fromJust $ sessionTicketInfo sdata+ age <- getAge tinfo+ return $+ if isAgeValid age tinfo+ then+ Just+ ( identity+ , sdata+ , makeCipherChoice TLS13 sCipher+ , ageToObfuscatedAge age tinfo+ )+ else Nothing get0RTTinfo (_, sdata, choice, _) | clientUseEarlyData cparams && sessionMaxEarlyDataSize sdata > 0 = Just choice
Network/TLS/Handshake/Client/Common.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-} module Network.TLS.Handshake.Client.Common ( throwMiscErrorOnException,@@ -9,6 +10,7 @@ sigAlgsToCertTypes, setALPN, contextSync,+ clientSessions, ) where import Control.Exception (SomeException)@@ -348,3 +350,8 @@ contextSync :: Context -> ClientState -> IO () contextSync ctx ctl = case ctxHandshakeSync ctx of HandshakeSync sync _ -> sync ctx ctl++clientSessions :: ClientParams -> [(SessionID, SessionData)]+clientSessions ClientParams{..} = case clientWantSessionResume of+ Nothing -> clientWantSessionResumeList+ Just ent -> clientWantSessionResumeList ++ [ent]
Network/TLS/Handshake/Client/ServerHello.hs view
@@ -117,6 +117,7 @@ let supportedVers = supportedVersions $ clientSupported cparams when (ver == TLS13) $ do+ -- TLS 1.3 server MUST echo the session id when (clientSession /= serverSession) $ throwCore $ Error_Protocol@@ -140,16 +141,21 @@ throwCore $ Error_Protocol "version downgrade detected" IllegalParameter - let resumingSession =- case clientWantSessionResume cparams of- Just (_, sessionData) ->- if serverSession == clientSession then Just sessionData else Nothing- Nothing -> Nothing- usingState_ ctx $ setSession serverSession (isJust resumingSession)- if ver == TLS13- then updateContext13 ctx cipherAlg- else updateContext12 ctx exts resumingSession+ then do+ -- Session is dummy in TLS 1.3.+ usingState_ ctx $ setSession serverSession+ updateContext13 ctx cipherAlg+ else do+ let resumingSession = case clientSessions cparams of+ (_, sessionData) : _ ->+ if serverSession == clientSession then Just sessionData else Nothing+ _ -> Nothing++ usingState_ ctx $ do+ setSession serverSession+ setTLS12SessionResuming $ isJust resumingSession+ updateContext12 ctx exts resumingSession processServerHello _ _ p = unexpected (show p) (Just "server hello") ----------------------------------------------------------------
Network/TLS/Handshake/Client/TLS12.hs view
@@ -34,7 +34,7 @@ recvServerFirstFlight12 :: ClientParams -> Context -> [Handshake] -> IO () recvServerFirstFlight12 cparams ctx hs = do- resuming <- usingState_ ctx isSessionResuming+ resuming <- usingState_ ctx getTLS12SessionResuming if resuming then recvNSTandCCSandFinished ctx else do@@ -72,32 +72,35 @@ sendClientSecondFlight12 :: ClientParams -> Context -> IO () sendClientSecondFlight12 cparams ctx = do- sessionResuming <- usingState_ ctx isSessionResuming+ sessionResuming <- usingState_ ctx getTLS12SessionResuming if sessionResuming then sendCCSandFinished ctx ClientRole else do sendClientCCC cparams ctx sendCCSandFinished ctx ClientRole -recvServerSecondFlight12 :: Context -> IO ()-recvServerSecondFlight12 ctx = do- sessionResuming <- usingState_ ctx isSessionResuming+recvServerSecondFlight12 :: ClientParams -> Context -> IO ()+recvServerSecondFlight12 cparams ctx = do+ sessionResuming <- usingState_ ctx getTLS12SessionResuming unless sessionResuming $ recvNSTandCCSandFinished ctx mticket <- usingState_ ctx getTLS12SessionTicket- identity <- case mticket of- Just ticket -> return ticket- Nothing -> do- session <- usingState_ ctx getSession- case session of- Session (Just sessionId) -> return $ B.copy sessionId- _ -> return "" -- never reach- sessionData <- getSessionData ctx- void $- sessionEstablish- (sharedSessionManager $ ctxShared ctx)- identity- (fromJust sessionData)+ session <- usingState_ ctx getSession+ let midentity = ticketOrSessionID12 mticket session+ case midentity of+ Nothing -> return ()+ Just identity -> do+ sessionData <- getSessionData ctx+ void $+ sessionEstablish+ (sharedSessionManager $ ctxShared ctx)+ identity+ (fromJust sessionData) handshakeDone12 ctx+ liftIO $ do+ minfo <- contextGetInformation ctx+ case minfo of+ Nothing -> return ()+ Just info -> onServerFinished (clientHooks cparams) info recvNSTandCCSandFinished :: Context -> IO () recvNSTandCCSandFinished ctx = do
Network/TLS/Handshake/Client/TLS13.hs view
@@ -44,7 +44,7 @@ runRecvHandshake13 $ do recvHandshake13 ctx $ expectEncryptedExtensions ctx unless resuming $ recvHandshake13 ctx $ expectCertRequest cparams ctx- recvHandshake13hash ctx $ expectFinished ctx+ recvHandshake13hash ctx $ expectFinished cparams ctx ---------------------------------------------------------------- @@ -228,17 +228,23 @@ expectFinished :: MonadIO m- => Context+ => ClientParams+ -> Context -> ByteString -> Handshake13 -> m ()-expectFinished ctx hashValue (Finished13 verifyData) = do+expectFinished cparams ctx hashValue (Finished13 verifyData) = do st <- liftIO $ getTLS13State ctx let usedHash = cHash $ tls13stChoice st ServerTrafficSecret baseKey = triServer $ fromJust $ tls13stHsKey st checkFinished ctx usedHash baseKey hashValue verifyData+ liftIO $ do+ minfo <- contextGetInformation ctx+ case minfo of+ Nothing -> return ()+ Just info -> onServerFinished (clientHooks cparams) info liftIO $ modifyTLS13State ctx $ \s -> s{tls13stRecvSF = True}-expectFinished _ _ p = unexpected (show p) (Just "server finished")+expectFinished _ _ _ p = unexpected (show p) (Just "server finished") ---------------------------------------------------------------- ----------------------------------------------------------------@@ -372,12 +378,8 @@ setPendingRecvActions ctx [ PendingRecvAction True expectServerHello- , PendingRecvAction- True- (expectEncryptedExtensions ctx)- , PendingRecvActionHash- True- expectFinishedAndSet+ , PendingRecvAction True (expectEncryptedExtensions ctx)+ , PendingRecvActionHash True expectFinishedAndSet ] where expectServerHello sh = do@@ -385,7 +387,7 @@ processServerHello13 cparams ctx sh void $ prepareSecondFlight13 ctx groupSent expectFinishedAndSet h sf = do- expectFinished ctx h sf+ expectFinished cparams ctx h sf liftIO $ writeIORef (ctxPendingSendAction ctx) $ Just $
Network/TLS/Handshake/Common.hs view
@@ -8,6 +8,7 @@ newSession, handshakeDone12, ensureNullCompression,+ ticketOrSessionID12, -- * sending packets sendCCSandFinished,@@ -34,6 +35,7 @@ import Control.Concurrent.MVar import Control.Exception (IOException, fromException, handle, throwIO) import Control.Monad.State.Strict+import qualified Data.ByteString as B import Network.TLS.Cipher import Network.TLS.Compression@@ -310,3 +312,12 @@ usingHState ctx $ setPublicKey pubkey where pubkey = certPubKey $ getCertificate c++-- TLS 1.2 distinguishes session ID and session ticket. session+-- ticket. Session ticket is prioritized over session ID.+ticketOrSessionID12+ :: Maybe Ticket -> Session -> Maybe SessionIDorTicket+ticketOrSessionID12 (Just ticket) _+ | ticket /= "" = Just $ B.copy ticket+ticketOrSessionID12 _ (Session (Just sessionId)) = Just $ B.copy sessionId+ticketOrSessionID12 _ _ = Nothing
Network/TLS/Handshake/Common13.hs view
@@ -217,12 +217,13 @@ totalLen = B.length x takeLen = totalLen - truncLen -replacePSKBinder :: ByteString -> ByteString -> ByteString-replacePSKBinder pskz binder = identities `B.append` binders+replacePSKBinder :: ByteString -> [ByteString] -> ByteString+replacePSKBinder pskz bds = tLidentities <> binders where- bindersSize = B.length binder + 3- identities = B.take (B.length pskz - bindersSize) pskz- binders = runPut $ putOpaque16 $ runPut $ putOpaque8 binder+ tLidentities = B.take (B.length pskz - B.length binders) pskz+ -- See instance Extension PreSharedKey+ binders = runPut $ putOpaque16 $ runPut (mapM_ putBinder bds)+ putBinder = putOpaque8 ---------------------------------------------------------------- @@ -531,7 +532,7 @@ let clientApplicationSecret0 = deriveSecret usedHash applicationSecret "c ap traffic" hChSf serverApplicationSecret0 = deriveSecret usedHash applicationSecret "s ap traffic" hChSf exporterSecret = deriveSecret usedHash applicationSecret "exp master" hChSf- usingState_ ctx $ setExporterSecret exporterSecret+ usingState_ ctx $ setTLS13ExporterSecret exporterSecret let sts0 = ServerTrafficSecret serverApplicationSecret0 :: ServerTrafficSecret ApplicationSecret
Network/TLS/Handshake/Server.hs view
@@ -80,7 +80,7 @@ requestCertificateServer :: ServerParams -> Context -> IO Bool requestCertificateServer sparams ctx = do tls13 <- tls13orLater ctx- supportsPHA <- usingState_ ctx getClientSupportsPHA+ supportsPHA <- usingState_ ctx getTLS13ClientSupportsPHA let ok = tls13 && supportsPHA when ok $ do certReqCtx <- newCertReqContext ctx
Network/TLS/Handshake/Server/ServerHello12.hs view
@@ -38,7 +38,7 @@ case resumeSessionData of Nothing -> do serverSession <- newSession ctx- usingState_ ctx $ setSession serverSession False+ usingState_ ctx $ setSession serverSession serverhello <- makeServerHello sparams ctx usedCipher mcred chExtensions serverSession build <- sendServerFirstFlight sparams ctx usedCipher mcred chExtensions@@ -46,7 +46,9 @@ sendPacket12 ctx $ Handshake ff contextFlush ctx Just sessionData -> do- usingState_ ctx $ setSession chSession True+ usingState_ ctx $ do+ setSession chSession+ setTLS12SessionResuming True serverhello <- makeServerHello sparams ctx usedCipher mcred chExtensions chSession sendPacket12 ctx $ Handshake [serverhello]@@ -60,18 +62,18 @@ recoverSessionData ctx CH{..} = do serverName <- usingState_ ctx getClientSNI ems <- processExtendedMainSecret ctx TLS12 MsgTClientHello chExtensions- let mticket =+ let mSessionTicket = extensionLookup EID_SessionTicket chExtensions >>= extensionDecode MsgTClientHello- case mticket of- Just (SessionTicket ticket) | ticket /= "" -> do- sd <- sessionResume (sharedSessionManager $ ctxShared ctx) ticket+ mticket = case mSessionTicket of+ Nothing -> Nothing+ Just (SessionTicket ticket) -> Just ticket+ midentity = ticketOrSessionID12 mticket chSession+ case midentity of+ Nothing -> return Nothing+ Just identity -> do+ sd <- sessionResume (sharedSessionManager $ ctxShared ctx) identity validateSession chCiphers serverName ems sd- _ -> case chSession of- (Session (Just clientSessionId)) -> do- sd <- sessionResume (sharedSessionManager $ ctxShared ctx) clientSessionId- validateSession chCiphers serverName ems sd- (Session Nothing) -> return Nothing validateSession :: [CipherID]@@ -229,7 +231,7 @@ -> Session -> IO Handshake makeServerHello sparams ctx usedCipher mcred chExts session = do- resuming <- usingState_ ctx isSessionResuming+ resuming <- usingState_ ctx getTLS12SessionResuming srand <- serverRandom ctx TLS12 $ supportedVersions $ serverSupported sparams case mcred of
Network/TLS/Handshake/Server/ServerHello13.hs view
@@ -46,8 +46,8 @@ ) sendServerHello13 sparams ctx clientKeyShare (usedCipher, usedHash, rtt0) CH{..} = do newSession ctx >>= \ss -> usingState_ ctx $ do- setSession ss False- setClientSupportsPHA supportsPHA+ setSession ss+ setTLS13ClientSupportsPHA supportsPHA usingHState ctx $ setSupportedGroup $ keyShareEntryGroup clientKeyShare srand <- setServerParameter -- ALPN is used in choosePSK@@ -141,7 +141,7 @@ choosePSK = case extensionLookup EID_PreSharedKey chExtensions >>= extensionDecode MsgTClientHello of- Just (PreSharedKeyClientHello (PskIdentity sessionId obfAge : _) bnds@(bnd : _)) -> do+ Just (PreSharedKeyClientHello (PskIdentity identity obfAge : _) bnds@(bnd : _)) -> do when (null dhModes) $ throwCore $ Error_Protocol "no psk_key_exchange_modes extension" MissingExtension@@ -149,10 +149,13 @@ then do let len = sum (map (\x -> B.length x + 1) bnds) + 2 mgr = sharedSessionManager $ serverShared sparams+ -- sessionInvalidate is not used for TLS 1.3+ -- because PSK is always changed.+ -- So, identity is not stored in Context. msdata <- if rtt0- then sessionResumeOnlyOnce mgr sessionId- else sessionResume mgr sessionId+ then sessionResumeOnlyOnce mgr identity+ else sessionResume mgr identity case msdata of Just sdata -> do let tinfo = fromJust $ sessionTicketInfo sdata
Network/TLS/Handshake/Server/TLS12.hs view
@@ -60,6 +60,11 @@ Session (Just sessionId) -> do sessionData <- getSessionData ctx let sessionId' = B.copy sessionId+ -- SessionID method: SessionID is used as key to store+ -- SessionData. Nothing is returned.+ --+ -- Session ticket method: SessionID is ignored. SessionData+ -- is encrypted and returned. sessionEstablish (sharedSessionManager $ ctxShared ctx) sessionId'
Network/TLS/Handshake/State.hs view
@@ -131,7 +131,7 @@ , hstSupportedGroup :: Maybe Group , hstTLS13HandshakeMode :: HandshakeMode13 , hstTLS13RTT0Status :: RTT0Status- , hstTLS13EarlySecret :: Maybe (BaseSecret EarlySecret)+ , hstTLS13EarlySecret :: Maybe (BaseSecret EarlySecret) -- xxx , hstTLS13ResumptionSecret :: Maybe (BaseSecret ResumptionSecret) , hstCCS13Sent :: Bool }
Network/TLS/Parameters.hs view
@@ -19,6 +19,7 @@ GroupUsage (..), CertificateUsage (..), CertificateRejectReason (..),+ Information (..), ) where import qualified Data.ByteString as B@@ -29,6 +30,7 @@ import Network.TLS.Crypto import Network.TLS.Extension import Network.TLS.Extra.Cipher+import Network.TLS.Handshake.State import Network.TLS.Imports import Network.TLS.Measurement import Network.TLS.RNG (Seed)@@ -100,9 +102,19 @@ -- -- Default: 'True' , clientWantSessionResume :: Maybe (SessionID, SessionData)- -- ^ try to establish a connection using this session.+ -- ^ try to establish a connection using this session for TLS 1.2/TLS 1.3.+ -- This can be used for TLS 1.3 but for backward compatibility purpose only.+ -- Use 'clientWantSessionResume13' instead for TLS 1.3. -- -- Default: 'Nothing'+ , clientWantSessionResumeList :: [(SessionID, SessionData)]+ -- ^ try to establish a connection using one of this sessions+ -- especially for TLS 1.3.+ -- This take precedence over 'clientWantSessionResume'.+ -- For convenience, this can be specified for TLS 1.2 but only the first+ -- entry is used.+ --+ -- Default: '[]' , clientShared :: Shared -- ^ See the default value of 'Shared'. , clientHooks :: ClientHooks@@ -131,6 +143,7 @@ , clientServerIdentification = (serverName, serverId) , clientUseServerNameIndication = True , clientWantSessionResume = Nothing+ , clientWantSessionResumeList = [] , clientShared = def , clientHooks = def , clientSupported = def@@ -535,6 +548,8 @@ -- (4) rejecting if dh_size < 1024 (to prevent Logjam attack) -- -- See RFC 7919 section 3.1 for recommandations.+ , onServerFinished :: Information -> IO ()+ -- ^ When a handshake is done, this hook can check `Information`. } defaultClientHooks :: ClientHooks@@ -544,6 +559,7 @@ , onServerCertificate = validateDefault , onSuggestALPN = return Nothing , onCustomFFDHEGroup = defaultGroupUsage 1024+ , onServerFinished = \_ -> return () } instance Show ClientHooks where@@ -634,3 +650,19 @@ show _ = "ServerHooks" instance Default ServerHooks where def = defaultServerHooks++-- | Information related to a running context, e.g. current cipher+data Information = Information+ { infoVersion :: Version+ , infoCipher :: Cipher+ , infoCompression :: Compression+ , infoMainSecret :: Maybe ByteString+ , infoExtendedMainSecret :: Bool+ , infoClientRandom :: Maybe ClientRandom+ , infoServerRandom :: Maybe ServerRandom+ , infoSupportedGroup :: Maybe Group+ , infoTLS12Resumption :: Bool+ , infoTLS13HandshakeMode :: Maybe HandshakeMode13+ , infoIsEarlyDataAccepted :: Bool+ }+ deriving (Show, Eq)
Network/TLS/Session.hs view
@@ -5,18 +5,20 @@ import Network.TLS.Types --- | A session manager+-- | A session manager.+-- In the server side, all fields are used.+-- In the client side, only 'sessionEstablish' is used. data SessionManager = SessionManager { sessionResume :: SessionIDorTicket -> IO (Maybe SessionData) -- ^ Used on TLS 1.2\/1.3 servers to lookup 'SessionData' with 'SessionID' or to decrypt 'Ticket' to get 'SessionData'. , sessionResumeOnlyOnce :: SessionIDorTicket -> IO (Maybe SessionData) -- ^ Used for 0RTT on TLS 1.3 servers to lookup 'SessionData' with 'SessionID' or to decrypt 'Ticket' to get 'SessionData'.- , sessionEstablish :: SessionID -> SessionData -> IO (Maybe Ticket)- -- ^ Used TLS 1.2\/1.3 servers\/clients to store 'SessionData' with 'SessionID' or to encrypt 'SessionData' to get 'Ticket'. In the client side, 'Nothing' should be returned. For clients, only this field should be set with 'noSessionManager'.- , sessionInvalidate :: SessionID -> IO ()- -- ^ Used TLS 1.2\/1.3 servers to delete 'SessionData' with 'SessionID' if @sessionUseTicket@ is 'True'.+ , sessionEstablish :: SessionIDorTicket -> SessionData -> IO (Maybe Ticket)+ -- ^ Used on TLS 1.2\/1.3 servers to store 'SessionData' with 'SessionID' or to encrypt 'SessionData' to get 'Ticket' ignoring 'SessionID'. Used on TLS 1.2\/1.3 clients to store 'SessionData' with 'SessionIDorTicket' and then return 'Nothing'. For clients, only this field should be set with 'noSessionManager'.+ , sessionInvalidate :: SessionIDorTicket -> IO ()+ -- ^ Used TLS 1.2 servers to delete 'SessionData' with 'SessionID' on errors. , sessionUseTicket :: Bool- -- ^ Used on TLS 1.2 servers to decide to use 'SessionID' or 'Ticket'. Note that TLS 1.3 servers always use session tickets.+ -- ^ Used on TLS 1.2 servers to decide to use 'SessionID' or 'Ticket'. Note that 'SessionID' and 'Ticket' are integrated as identity in TLS 1.3. } -- | The session manager to do nothing.
Network/TLS/State.hs view
@@ -44,10 +44,13 @@ setServerCertificateChain, setSession, getSession,- isSessionResuming, getRole,- setExporterSecret,- getExporterSecret,+ --+ setTLS12SessionResuming,+ getTLS12SessionResuming,+ --+ setTLS13ExporterSecret,+ getTLS13ExporterSecret, setTLS13KeyShare, getTLS13KeyShare, setTLS13PreSharedKey,@@ -56,8 +59,8 @@ getTLS13HRR, setTLS13Cookie, getTLS13Cookie,- setClientSupportsPHA,- getClientSupportsPHA,+ setTLS13ClientSupportsPHA,+ getTLS13ClientSupportsPHA, setTLS12SessionTicket, getTLS12SessionTicket, @@ -80,7 +83,6 @@ data TLSState = TLSState { stSession :: Session- , stSessionResuming :: Bool , -- RFC 5746, Renegotiation Indication Extension -- RFC 5929, Channel Bindings for TLS, "tls-unique" stSecureRenegotiation :: Bool@@ -98,15 +100,18 @@ , stClientCertificateChain :: Maybe CertificateChain , stClientSNI :: Maybe HostName , stRandomGen :: StateRNG- , stVersion :: Maybe Version , stClientContext :: Role- , stTLS13KeyShare :: Maybe KeyShare+ , stVersion :: Maybe Version+ , --+ stTLS12SessionResuming :: Bool+ , stTLS12SessionTicket :: Maybe Ticket+ , --+ stTLS13KeyShare :: Maybe KeyShare , stTLS13PreSharedKey :: Maybe PreSharedKey , stTLS13HRR :: Bool , stTLS13Cookie :: Maybe Cookie- , stExporterSecret :: Maybe ByteString -- TLS 1.3- , stClientSupportsPHA :: Bool -- Post-Handshake Authentication (TLS 1.3)- , stTLS12SessionTicket :: Maybe Ticket+ , stTLS13ExporterSecret :: Maybe ByteString+ , stTLS13ClientSupportsPHA :: Bool -- Post-Handshake Authentication } newtype TLSSt a = TLSSt {runTLSSt :: ErrT TLSError (State TLSState) a}@@ -124,7 +129,6 @@ newTLSState rng clientContext = TLSState { stSession = Session Nothing- , stSessionResuming = False , stSecureRenegotiation = False , stClientVerifyData = Nothing , stServerVerifyData = Nothing@@ -139,15 +143,16 @@ , stClientCertificateChain = Nothing , stClientSNI = Nothing , stRandomGen = rng- , stVersion = Nothing , stClientContext = clientContext+ , stVersion = Nothing+ , stTLS12SessionResuming = False+ , stTLS12SessionTicket = Nothing , stTLS13KeyShare = Nothing , stTLS13PreSharedKey = Nothing , stTLS13HRR = False , stTLS13Cookie = Nothing- , stExporterSecret = Nothing- , stClientSupportsPHA = False- , stTLS12SessionTicket = Nothing+ , stTLS13ExporterSecret = Nothing+ , stTLS13ClientSupportsPHA = False } setVerifyDataForSend :: VerifyData -> TLSSt ()@@ -197,15 +202,18 @@ certVerifyHandshakeMaterial :: Handshake -> Bool certVerifyHandshakeMaterial = certVerifyHandshakeTypeMaterial . typeOfHandshake -setSession :: Session -> Bool -> TLSSt ()-setSession session resuming = modify (\st -> st{stSession = session, stSessionResuming = resuming})+setSession :: Session -> TLSSt ()+setSession session = modify (\st -> st{stSession = session}) getSession :: TLSSt Session getSession = gets stSession -isSessionResuming :: TLSSt Bool-isSessionResuming = gets stSessionResuming+setTLS12SessionResuming :: Bool -> TLSSt ()+setTLS12SessionResuming b = modify (\st -> st{stTLS12SessionResuming = b}) +getTLS12SessionResuming :: TLSSt Bool+getTLS12SessionResuming = gets stTLS12SessionResuming+ setVersion :: Version -> TLSSt () setVersion ver = modify (\st -> st{stVersion = Just ver}) @@ -294,10 +302,14 @@ getFirstVerifyData :: TLSSt (Maybe ByteString) getFirstVerifyData = do- resuming <- isSessionResuming- if resuming- then gets stServerVerifyData- else gets stClientVerifyData+ ver <- getVersion+ case ver of+ TLS13 -> gets stServerVerifyData+ _ -> do+ resuming <- getTLS12SessionResuming+ if resuming+ then gets stServerVerifyData+ else gets stClientVerifyData getRole :: TLSSt Role getRole = gets stClientContext@@ -313,12 +325,18 @@ put (st{stRandomGen = rng'}) return a -setExporterSecret :: ByteString -> TLSSt ()-setExporterSecret key = modify (\st -> st{stExporterSecret = Just key})+setTLS12SessionTicket :: Ticket -> TLSSt ()+setTLS12SessionTicket t = modify (\st -> st{stTLS12SessionTicket = Just t}) -getExporterSecret :: TLSSt (Maybe ByteString)-getExporterSecret = gets stExporterSecret+getTLS12SessionTicket :: TLSSt (Maybe Ticket)+getTLS12SessionTicket = gets stTLS12SessionTicket +setTLS13ExporterSecret :: ByteString -> TLSSt ()+setTLS13ExporterSecret key = modify (\st -> st{stTLS13ExporterSecret = Just key})++getTLS13ExporterSecret :: TLSSt (Maybe ByteString)+getTLS13ExporterSecret = gets stTLS13ExporterSecret+ setTLS13KeyShare :: Maybe KeyShare -> TLSSt () setTLS13KeyShare mks = modify (\st -> st{stTLS13KeyShare = mks}) @@ -343,14 +361,8 @@ getTLS13Cookie :: TLSSt (Maybe Cookie) getTLS13Cookie = gets stTLS13Cookie -setClientSupportsPHA :: Bool -> TLSSt ()-setClientSupportsPHA b = modify (\st -> st{stClientSupportsPHA = b})--getClientSupportsPHA :: TLSSt Bool-getClientSupportsPHA = gets stClientSupportsPHA--setTLS12SessionTicket :: Ticket -> TLSSt ()-setTLS12SessionTicket t = modify (\st -> st{stTLS12SessionTicket = Just t})+setTLS13ClientSupportsPHA :: Bool -> TLSSt ()+setTLS13ClientSupportsPHA b = modify (\st -> st{stTLS13ClientSupportsPHA = b}) -getTLS12SessionTicket :: TLSSt (Maybe Ticket)-getTLS12SessionTicket = gets stTLS12SessionTicket+getTLS13ClientSupportsPHA :: TLSSt Bool+getTLS13ClientSupportsPHA = gets stTLS13ClientSupportsPHA
Network/TLS/Types.hs view
@@ -10,6 +10,7 @@ isTicket, toSessionID, SessionData (..),+ is0RTTPossible, SessionFlag (..), CertReqContext, TLS13TicketInfo (..),@@ -102,6 +103,9 @@ , sessionFlags :: [SessionFlag] } -- sessionFromTicket :: Bool deriving (Show, Eq, Generic)++is0RTTPossible :: SessionData -> Bool+is0RTTPossible sd = sessionMaxEarlyDataSize sd > 0 -- | Some session flags data SessionFlag
test/Session.hs view
@@ -32,7 +32,7 @@ -- a Real concurrent session manager would use an MVar and have multiples items. oneSessionManager :: IORef (Maybe (SessionID, SessionData)) -> SessionManager oneSessionManager ref =- SessionManager+ noSessionManager { sessionResume = \myId -> readIORef ref >>= maybeResume False myId , sessionResumeOnlyOnce = \myId -> readIORef ref >>= maybeResume True myId , sessionEstablish = \myId dat -> writeIORef ref (Just (myId, dat)) >> return Nothing@@ -78,7 +78,7 @@ oneSessionTicket :: SessionManager oneSessionTicket =- SessionManager+ noSessionManager { sessionResume = resume , sessionResumeOnlyOnce = resume , sessionEstablish = \_ dat -> return $ Just $ L.toStrict $ serialise dat
tls.cabal view
@@ -1,6 +1,6 @@ cabal-version: >=1.10 name: tls-version: 2.0.6+version: 2.1.0 license: BSD3 license-file: LICENSE copyright: Vincent Hanquez <vincent@snarc.org>@@ -180,6 +180,7 @@ build-depends: base >=4.9 && <5, bytestring,+ base16-bytestring, containers, crypton, crypton-x509-store,@@ -208,6 +209,7 @@ ghc-options: -Wall -threaded -rtsopts build-depends: base >=4.9 && <5,+ base16-bytestring, bytestring, crypton, crypton-x509-store,
util/Client.hs view
@@ -4,9 +4,11 @@ module Client ( Aux (..), Cli,- client,+ clientHTTP11,+ clientDNS, ) where +import qualified Data.ByteString.Base16 as BS16 import qualified Data.ByteString.Lazy.Char8 as CL8 import Network.Socket import Network.TLS@@ -18,13 +20,13 @@ , auxPort :: ServiceName , auxDebug :: String -> IO () , auxShow :: ByteString -> IO ()- , auxReadResumptionData :: IO (Maybe (SessionID, SessionData))+ , auxReadResumptionData :: IO [(SessionID, SessionData)] } type Cli = Aux -> [ByteString] -> Context -> IO () -client :: Cli-client Aux{..} paths ctx = do+clientHTTP11 :: Cli+clientHTTP11 aux@Aux{..} paths ctx = do sendData ctx $ "GET " <> CL8.fromStrict (head paths)@@ -34,11 +36,22 @@ <> "\r\n" <> "Connection: close\r\n" <> "\r\n"- loop+ consume ctx aux++clientDNS :: Cli+clientDNS Aux{..} _paths ctx = do+ sendData+ ctx+ "\x00\x2c\xdc\xe3\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\x03\x77\x77\x77\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f\x6d\x00\x00\x01\x00\x01\x00\x00\x29\x04\xd0\x00\x00\x00\x00\x00\x00"+ bs <- recvData ctx+ auxShow $ "Reply: " <> BS16.encode bs auxShow "\n"++consume :: Context -> Aux -> IO ()+consume ctx Aux{..} = loop where loop = do bs <- recvData ctx- when (bs /= "") $ do- auxShow bs- loop+ if bs == ""+ then auxShow "\n"+ else auxShow bs >> loop
util/tls-client.hs view
@@ -5,6 +5,7 @@ module Main where import Control.Concurrent+import qualified Data.ByteString.Base16 as BS16 import qualified Data.ByteString.Char8 as C8 import Data.Default.Class (def) import Data.IORef@@ -33,6 +34,7 @@ , opt0RTT :: Bool , optRetry :: Bool , optVersions :: [Version]+ , optALPN :: String } deriving (Show) @@ -49,6 +51,7 @@ , opt0RTT = False , optRetry = False , optVersions = supportedVersions def+ , optALPN = "http/1.1" } usage :: String@@ -106,6 +109,11 @@ ["tls13"] (NoArg (\o -> o{optVersions = [TLS13]})) "use TLS 1.3"+ , Option+ ['a']+ ["alpn"]+ (ReqArg (\a o -> o{optALPN = a}) "<alpn>")+ "set ALPN" ] showUsageAndExit :: String -> IO a@@ -133,7 +141,7 @@ when (null optGroups) $ do putStrLn "Error: unsupported groups" exitFailure- ref <- newIORef Nothing+ ref <- newIORef [] let debug | optDebugLog = putStrLn | otherwise = \_ -> return ()@@ -150,15 +158,14 @@ } mstore <- if optValidate then Just <$> getSystemCertificateStore else return Nothing- let keyLog = getLogger optKeyLogFile- groups- | optRetry = FFDHE8192 : optGroups- | otherwise = optGroups- cparams = getClientParams optVersions host port groups (smIORef ref) mstore keyLog- runClient opts cparams aux paths+ let cparams = getClientParams opts host port (smIORef ref) mstore+ client+ | optALPN == "dot" = clientDNS+ | otherwise = clientHTTP11+ runClient opts client cparams aux paths -runClient :: Options -> ClientParams -> Aux -> [ByteString] -> IO ()-runClient opts@Options{..} cparams aux@Aux{..} paths = do+runClient :: Options -> Cli -> ClientParams -> Aux -> [ByteString] -> IO ()+runClient opts@Options{..} client cparams aux@Aux{..} paths = do auxDebug "------------------------" (info1, msd) <- runTLS cparams aux $ \ctx -> do i1 <- getInfo ctx@@ -171,7 +178,7 @@ if isResumptionPossible msd then do let cparams2 = modifyClientParams cparams msd False- info2 <- runClient2 opts cparams2 aux paths+ info2 <- runClient2 opts client cparams2 aux paths if infoVersion info1 == TLS12 then do if infoTLS12Resumption info2@@ -196,7 +203,7 @@ if is0RTTPossible info1 msd then do let cparams2 = modifyClientParams cparams msd True- info2 <- runClient2 opts cparams2 aux paths+ info2 <- runClient2 opts client cparams2 aux paths if infoTLS13HandshakeMode info2 == Just RTT0 then do putStrLn "Result: (Z) 0-RTT ... OK"@@ -217,18 +224,18 @@ exitFailure | otherwise -> do putStrLn "Result: (H) handshake ... OK"- let malpn = (snd <$> msd) >>= sessionALPN- when (malpn == Just "http/1.1") $+ when (optALPN == "http/1.1") $ putStrLn "Result: (1) HTTP/1.1 transaction ... OK" exitSuccess runClient2 :: Options+ -> Cli -> ClientParams -> Aux -> [ByteString] -> IO Information-runClient2 Options{..} cparams aux@Aux{..} paths = do+runClient2 Options{..} client cparams aux@Aux{..} paths = do threadDelay 100000 auxDebug "<<<< next connection >>>>" auxDebug "------------------------"@@ -257,23 +264,21 @@ action ctx modifyClientParams- :: ClientParams -> Maybe (SessionID, SessionData) -> Bool -> ClientParams-modifyClientParams cparams wantResume early =+ :: ClientParams -> [(SessionID, SessionData)] -> Bool -> ClientParams+modifyClientParams cparams ts early = cparams- { clientWantSessionResume = wantResume+ { clientWantSessionResumeList = ts , clientUseEarlyData = early } getClientParams- :: [Version]+ :: Options -> HostName -> ServiceName- -> [Group] -> SessionManager -> Maybe CertificateStore- -> (String -> IO ()) -> ClientParams-getClientParams vers serverName port groups sm mstore keyLog =+getClientParams Options{..} serverName port sm mstore = (defaultParamsClient serverName (C8.pack port)) { clientSupported = supported , clientUseServerNameIndication = True@@ -282,6 +287,9 @@ , clientDebug = debug } where+ groups+ | optRetry = FFDHE8192 : optGroups+ | otherwise = optGroups shared = def { sharedSessionManager = sm@@ -292,12 +300,12 @@ } supported = def- { supportedVersions = vers+ { supportedVersions = optVersions , supportedGroups = groups } hooks = def- { onSuggestALPN = return $ Just ["http/1.1"]+ { onSuggestALPN = return $ Just [C8.pack optALPN] } validateCache | isJust mstore = def@@ -307,20 +315,28 @@ (\_ _ _ -> return ()) debug = def- { debugKeyLogger = keyLog+ { debugKeyLogger = getLogger optKeyLogFile } -smIORef :: IORef (Maybe (SessionID, SessionData)) -> SessionManager+smIORef :: IORef [(SessionID, SessionData)] -> SessionManager smIORef ref = noSessionManager- { sessionEstablish = \sid sdata -> writeIORef ref (Just (sid, sdata)) >> return Nothing+ { sessionEstablish = \sid sdata ->+ modifyIORef' ref (\xs -> (sid, sdata) : xs)+ >> printTicket sid sdata+ >> return Nothing } -isResumptionPossible :: Maybe (SessionID, SessionData) -> Bool-isResumptionPossible = isJust+printTicket :: SessionID -> SessionData -> IO ()+printTicket sid sdata = do+ C8.putStr $ "Ticket: " <> C8.take 16 (BS16.encode sid) <> "..., "+ putStrLn $ "0-RTT: " <> if sessionMaxEarlyDataSize sdata > 0 then "OK" else "NG" -is0RTTPossible :: Information -> Maybe (SessionID, SessionData) -> Bool-is0RTTPossible _ Nothing = False-is0RTTPossible info (Just (_, sd)) =+isResumptionPossible :: [(SessionID, SessionData)] -> Bool+isResumptionPossible = not . null++is0RTTPossible :: Information -> [(SessionID, SessionData)] -> Bool+is0RTTPossible _ [] = False+is0RTTPossible info xs = infoVersion info == TLS13- && sessionMaxEarlyDataSize sd > 0+ && any (\(_, sd) -> sessionMaxEarlyDataSize sd > 0) xs
util/tls-server.hs view
@@ -5,6 +5,8 @@ module Main where +import qualified Data.ByteString.Base16 as BS16+import qualified Data.ByteString.Char8 as C8 import Data.Default.Class (def) import Data.IORef import qualified Data.Map.Strict as M@@ -152,9 +154,17 @@ ref <- newIORef M.empty return $ noSessionManager- { sessionResume = \key -> M.lookup key <$> readIORef ref- , sessionResumeOnlyOnce = \key -> M.lookup key <$> readIORef ref- , sessionEstablish = \key val -> atomicModifyIORef' ref $ \m -> (M.insert key val m, Nothing)- , sessionInvalidate = \key -> atomicModifyIORef' ref $ \m -> (M.delete key m, ())+ { sessionResume = \key -> do+ C8.putStrLn $ "sessionResume: " <> BS16.encode key+ M.lookup key <$> readIORef ref+ , sessionResumeOnlyOnce = \key -> do+ C8.putStrLn $ "sessionResumeOnlyOnce: " <> BS16.encode key+ M.lookup key <$> readIORef ref+ , sessionEstablish = \key val -> do+ C8.putStrLn $ "sessionEstablish: " <> BS16.encode key+ atomicModifyIORef' ref $ \m -> (M.insert key val m, Nothing)+ , sessionInvalidate = \key -> do+ C8.putStrLn $ "sessionEstablish: " <> BS16.encode key+ atomicModifyIORef' ref $ \m -> (M.delete key m, ()) , sessionUseTicket = False }