tls 2.3.1 → 2.4.12
raw patch · 62 files changed
Files
- Benchmarks/Benchmarks.hs +0/−187
- CHANGELOG.md +164/−4
- Network/TLS.hs +10/−1
- Network/TLS/Compression.hs +1/−1
- Network/TLS/Context.hs +11/−5
- Network/TLS/Context/Internal.hs +52/−7
- Network/TLS/Core.hs +90/−18
- Network/TLS/Credentials.hs +3/−0
- Network/TLS/Crypto.hs +96/−39
- Network/TLS/Crypto/IES.hs +116/−76
- Network/TLS/Error.hs +4/−5
- Network/TLS/Extension.hs +65/−29
- Network/TLS/Extra/Cipher.hs +65/−44
- Network/TLS/Extra/CipherCBC.hs +201/−0
- Network/TLS/Handshake/Certificate.hs +23/−3
- Network/TLS/Handshake/Client.hs +18/−8
- Network/TLS/Handshake/Client/ClientHello.hs +15/−6
- Network/TLS/Handshake/Client/Common.hs +28/−11
- Network/TLS/Handshake/Client/ServerHello.hs +13/−3
- Network/TLS/Handshake/Client/TLS12.hs +9/−2
- Network/TLS/Handshake/Client/TLS13.hs +59/−14
- Network/TLS/Handshake/Common.hs +8/−5
- Network/TLS/Handshake/Common13.hs +20/−13
- Network/TLS/Handshake/Control.hs +24/−0
- Network/TLS/Handshake/Key.hs +9/−0
- Network/TLS/Handshake/Server.hs +4/−4
- Network/TLS/Handshake/Server/ClientHello.hs +17/−2
- Network/TLS/Handshake/Server/ClientHello12.hs +77/−5
- Network/TLS/Handshake/Server/ClientHello13.hs +34/−24
- Network/TLS/Handshake/Server/Common.hs +30/−3
- Network/TLS/Handshake/Server/ServerHello12.hs +19/−5
- Network/TLS/Handshake/Server/ServerHello13.hs +12/−4
- Network/TLS/Handshake/Server/TLS12.hs +20/−4
- Network/TLS/Handshake/Server/TLS13.hs +19/−7
- Network/TLS/Handshake/Signature.hs +98/−15
- Network/TLS/Handshake/State13.hs +4/−0
- Network/TLS/HashAndSignature.hs +60/−3
- Network/TLS/IO.hs +35/−6
- Network/TLS/IO/Decode.hs +55/−18
- Network/TLS/Packet.hs +36/−3
- Network/TLS/Packet13.hs +34/−9
- Network/TLS/Parameters.hs +32/−10
- Network/TLS/PostHandshake.hs +3/−3
- Network/TLS/QUIC.hs +3/−3
- Network/TLS/Record/Decrypt.hs +55/−13
- Network/TLS/Record/Recv.hs +23/−2
- Network/TLS/State.hs +9/−0
- Network/TLS/Types.hs +17/−0
- Network/TLS/Types/Secret.hs +29/−5
- Network/TLS/Types/Session.hs +37/−1
- Network/TLS/Util.hs +1/−2
- test/Arbitrary.hs +17/−1
- test/Certificate.hs +23/−0
- test/EncodeSpec.hs +251/−0
- test/HandshakeSpec.hs +2831/−1035
- test/PubKey.hs +26/−0
- test/Run.hs +52/−5
- test/SecretSpec.hs +63/−0
- tls.cabal +15/−53
- util/Common.hs +1/−1
- util/Server.hs +5/−2
- util/tls-server.hs +175/−18
− Benchmarks/Benchmarks.hs
@@ -1,187 +0,0 @@-{-# LANGUAGE BangPatterns #-}--module Main where--import Certificate-import Control.Concurrent.Chan-import Data.Default (def)-import Data.IORef-import Data.X509-import Data.X509.Validation-import Test.Tasty.Bench-import Network.TLS-import Network.TLS.Extra.Cipher-import Session-import Run-import PubKey--import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as L--blockCipher :: Cipher-blockCipher =- Cipher- { cipherID = 0xff12- , cipherName = "rsa-id-const"- , cipherBulk =- Bulk- { bulkName = "id"- , bulkKeySize = 16- , bulkIVSize = 16- , bulkExplicitIV = 0- , bulkAuthTagLen = 0- , bulkBlockSize = 16- , bulkF = BulkBlockF $ \_ _ _ m -> (m, B.empty)- }- , cipherHash = MD5- , cipherPRFHash = Nothing- , cipherKeyExchange = CipherKeyExchange_RSA- , cipherMinVer = Nothing- }--getParams :: Version -> Cipher -> (ClientParams, ServerParams)-getParams connectVer cipher = (cParams, sParams)- where- sParams =- def- { serverSupported = supported- , serverShared =- def- { sharedCredentials =- Credentials- [(CertificateChain [simpleX509 $ PubKeyRSA pubKey], PrivKeyRSA privKey)]- }- }- cParams =- (defaultParamsClient "" B.empty)- { clientSupported = supported- , clientShared =- def- { sharedValidationCache =- ValidationCache- { cacheAdd = \_ _ _ -> return ()- , cacheQuery = \_ _ _ -> return ValidationCachePass- }- }- }- supported =- def- { supportedCiphers = [cipher]- , supportedVersions = [connectVer]- , supportedGroups = [X25519, FFDHE2048]- }- (pubKey, privKey) = getGlobalRSAPair--runTLSPipe- :: (ClientParams, ServerParams)- -> (Context -> Chan b -> IO ())- -> (Chan a -> Context -> IO ())- -> a- -> IO b-runTLSPipe params tlsServer tlsClient d = do- withDataPipe params tlsServer tlsClient $ \(writeStart, readResult) -> do- writeStart d- readResult--runTLSPipeSimple- :: (ClientParams, ServerParams) -> B.ByteString -> IO B.ByteString-runTLSPipeSimple params = runTLSPipe params tlsServer tlsClient- where- tlsServer ctx queue = do- handshake ctx- d <- recvData ctx- writeChan queue d- bye ctx- tlsClient queue ctx = do- handshake ctx- d <- readChan queue- sendData ctx (L.fromChunks [d])- byeBye ctx--benchConnection- :: (ClientParams, ServerParams) -> B.ByteString -> String -> Benchmark-benchConnection params !d name = bench name . nfIO $ runTLSPipeSimple params d--benchResumption- :: (ClientParams, ServerParams) -> B.ByteString -> String -> Benchmark-benchResumption params !d name = env initializeSession runResumption- where- initializeSession = do- sessionRefs <- twoSessionRefs- let sessionManagers = twoSessionManagers sessionRefs- params1 = setPairParamsSessionManagers sessionManagers params- _ <- runTLSPipeSimple params1 d-- Just sessionParams <- readClientSessionRef sessionRefs- let params2 = setPairParamsSessionResuming sessionParams params1- newIORef params2-- runResumption paramsRef = bench name . nfIO $ do- params2 <- readIORef paramsRef- runTLSPipeSimple params2 d--benchResumption13- :: (ClientParams, ServerParams) -> B.ByteString -> String -> Benchmark-benchResumption13 params !d name = env initializeSession runResumption- where- initializeSession = do- sessionRefs <- twoSessionRefs- let sessionManagers = twoSessionManagers sessionRefs- params1 = setPairParamsSessionManagers sessionManagers params- _ <- runTLSPipeSimple params1 d- newIORef (params1, sessionRefs)-- -- with TLS13 the sessionId is constantly changing so we must update- -- our parameters at each iteration unfortunately- runResumption paramsRef = bench name . nfIO $ do- (params1, sessionRefs) <- readIORef paramsRef- Just sessionParams <- readClientSessionRef sessionRefs- let params2 = setPairParamsSessionResuming sessionParams params1- runTLSPipeSimple params2 d--benchCiphers :: String -> Version -> B.ByteString -> [Cipher] -> Benchmark-benchCiphers name connectVer d = bgroup name . map doBench- where- doBench cipher =- benchResumption13 (getParams connectVer cipher) d (cipherName cipher)--main :: IO ()-main =- defaultMain- [ bgroup- "connection"- -- not sure the number actually make sense for anything. improve ..- [ benchConnection (getParams TLS12 blockCipher) small "TLS12-256 bytes"- ]- , bgroup- "resumption"- [ benchResumption (getParams TLS12 blockCipher) small "TLS12-256 bytes"- ]- , -- Here we try to measure TLS12 and TLS13 performance with AEAD ciphers.- -- Resumption and a larger message can be a demonstration of the symmetric- -- crypto but for TLS13 this does not work so well because of dhe_psk.- benchCiphers- "TLS12"- TLS12- large- [ cipher_DHE_RSA_WITH_AES_128_GCM_SHA256- , cipher_DHE_RSA_WITH_AES_256_GCM_SHA384- , cipher_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256- , cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256- , cipher_ECDHE_RSA_WITH_AES_256_GCM_SHA384- , cipher_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256- ]- , benchCiphers- "TLS13"- TLS13- large- [ cipher13_AES_128_GCM_SHA256- , cipher13_AES_256_GCM_SHA384- , cipher13_CHACHA20_POLY1305_SHA256- , cipher13_AES_128_CCM_SHA256- , cipher13_AES_128_CCM_8_SHA256- ]- ]- where- small = B.replicate 256 0- large = B.replicate 102400 0
CHANGELOG.md view
@@ -1,7 +1,167 @@ # Change log for "tls" -## Version 2.3.1+## Version 2.4.12 +* Authenticate a server that rejects the PSK of 0-RTT.+ [#590](https://github.com/haskell-tls/hs-tls/pull/590)+* Require the client Certificate a TLS 1.2 server asked for.+ [#591](https://github.com/haskell-tls/hs-tls/pull/591)+* Refuse a TLS 1.3 alert in clear after the handshake.+ [#592](https://github.com/haskell-tls/hs-tls/pull/592)+* Require Certificate and ServerKeyExchange from a TLS 1.2 server.+ [#593](https://github.com/haskell-tls/hs-tls/pull/593)+* Refuse a TLS 1.2 ChangeCipherSpec before its keys exist.+ [#594](https://github.com/haskell-tls/hs-tls/pull/594)+* Answer an unfit ServerKeyExchange algorithm with an alert.+ [#595](https://github.com/haskell-tls/hs-tls/pull/595)++## Version 2.4.11++* Keep preferring AES-GCM on AArch64 with crypton 2.2, which names the+ ARMv8 instructions rather than x86's.+ [#584](https://github.com/haskell-tls/hs-tls/pull/584)+* Sign and verify RSA PKCS#1 v1.5 through crypton's digest-taking+ operations, which deprecate the ones used before. crypton >= 2.1.9 is+ now required.+ [#585](https://github.com/haskell-tls/hs-tls/pull/585)+* Remove the `tls-bench` benchmark.++## Version 2.4.10++* Support ML-DSA certificates and CertificateVerify, TLS 1.3 only+ (draft-ietf-tls-mldsa).+ [#583](https://github.com/haskell-tls/hs-tls/pull/583)+* Take ML-KEM from `crypton` instead of the `mlkem` package.+ [#583](https://github.com/haskell-tls/hs-tls/pull/583)+* `tls-server`: let `-g` choose the TLS 1.3 groups too.+ [#583](https://github.com/haskell-tls/hs-tls/pull/583)++## Version 2.4.9++* Include CertificateRequest in the post-handshake auth transcript.+ This breaks PHA with hs-tls 2.4.8 or earlier.+ [#565](https://github.com/haskell-tls/hs-tls/pull/565)+* Send a TLS 1.2 session ticket only to a client that asked for one.+ [#575](https://github.com/haskell-tls/hs-tls/pull/575)+* Fall back to a full handshake when a PSK finds a TLS 1.2 session.+ [#576](https://github.com/haskell-tls/hs-tls/pull/576)+* Refuse SCSV or a missing renegotiation_info in secure renegotiation.+ [#566](https://github.com/haskell-tls/hs-tls/pull/566)+* Reject a hybrid key share whose classical part fails to derive.+ [#550](https://github.com/haskell-tls/hs-tls/pull/550)+* Send no SNI for an empty server name, and refuse a malformed or+ invalid server_name.+ [#568](https://github.com/haskell-tls/hs-tls/pull/568)+* Refuse a CertificateVerify algorithm that is not offered or does not+ fit the key with illegal_parameter.+ [#562](https://github.com/haskell-tls/hs-tls/pull/562)+ [#578](https://github.com/haskell-tls/hs-tls/pull/578)+* Accept an empty TLS 1.2 client certificate when the hook does.+ [#552](https://github.com/haskell-tls/hs-tls/pull/552)+* Request EdDSA client certificates with ecdsa_sign in TLS 1.2.+ [#553](https://github.com/haskell-tls/hs-tls/pull/553)+* Accept a ClientHello whose legacy_version is above TLS 1.2.+ [#564](https://github.com/haskell-tls/hs-tls/pull/564)+* Answer malformed or misplaced messages with the alerts the RFCs ask for.+ [#554](https://github.com/haskell-tls/hs-tls/pull/554)+ [#555](https://github.com/haskell-tls/hs-tls/pull/555)+ [#556](https://github.com/haskell-tls/hs-tls/pull/556)+ [#557](https://github.com/haskell-tls/hs-tls/pull/557)+ [#558](https://github.com/haskell-tls/hs-tls/pull/558)+ [#559](https://github.com/haskell-tls/hs-tls/pull/559)+ [#560](https://github.com/haskell-tls/hs-tls/pull/560)+ [#561](https://github.com/haskell-tls/hs-tls/pull/561)+ [#563](https://github.com/haskell-tls/hs-tls/pull/563)+ [#567](https://github.com/haskell-tls/hs-tls/pull/567)+ [#569](https://github.com/haskell-tls/hs-tls/pull/569)+ [#570](https://github.com/haskell-tls/hs-tls/pull/570)+ [#571](https://github.com/haskell-tls/hs-tls/pull/571)+* tls-server: new options for tlsfuzzer, which CI runs weekly.+ [#551](https://github.com/haskell-tls/hs-tls/pull/551)+ [#572](https://github.com/haskell-tls/hs-tls/pull/572)+ [#573](https://github.com/haskell-tls/hs-tls/pull/573)+ [#574](https://github.com/haskell-tls/hs-tls/pull/574)+ [#577](https://github.com/haskell-tls/hs-tls/pull/577)++## Version 2.4.8++* Stop printing traffic secrets and the session secret.+ [#549](https://github.com/haskell-tls/hs-tls/pull/549)++## Version 2.4.7++* Use the one-call AES-GCM and ChaCha20-Poly1305 interfaces of crypton.+ This needs crypton 2.1.1 or later.+ [#548](https://github.com/haskell-tls/hs-tls/pull/548)++## Version 2.4.6++* Accept crypton 2.1.++## Version 2.4.5++* Fix the TLS 1.3 0-RTT session tests racing the NewSessionTicket.+ [#547](https://github.com/haskell-tls/hs-tls/pull/547)+* CI: drop macOS with GHC 9.12.+ [#546](https://github.com/haskell-tls/hs-tls/pull/546)+* CI: retry Hackage downloads, keep the cache when a test fails, and run+ doctest on one job.+ [#545](https://github.com/haskell-tls/hs-tls/pull/545)+* `extensionDecode` returns `Nothing` instead of calling `error`.+ [#544](https://github.com/haskell-tls/hs-tls/pull/544)+* `getTLSUnique` and `getTLSExporter` return `Nothing` before a handshake.+ [#543](https://github.com/haskell-tls/hs-tls/pull/543)+* Take two timing signals out of the CBC record path.+ [#542](https://github.com/haskell-tls/hs-tls/pull/542)+* Bound the size of a handshake message reassembled from records.+ [#541](https://github.com/haskell-tls/hs-tls/pull/541)+* CI: speed up.+ [#540](https://github.com/haskell-tls/hs-tls/pull/540)+* Limit consecutive TLS 1.3 KeyUpdate messages with the new+ `limitKeyUpdate` parameter.+ [#539](https://github.com/haskell-tls/hs-tls/pull/539)+* Validate the negotiated ALPN protocol.+ [#538](https://github.com/haskell-tls/hs-tls/pull/538)+* Validate the negotiated cipher suite.+ [#537](https://github.com/haskell-tls/hs-tls/pull/537)+* Make the session ticket tests deterministic.+ [#536](https://github.com/haskell-tls/hs-tls/pull/536)++## Version 2.4.4++* Enforce server certificate purpose+ [#534](https://github.com/haskell-tls/hs-tls/pull/534)+* Use dedicated doctest REPL+ [#533](https://github.com/haskell-tls/hs-tls/pull/533)+* Bind early data to ALPN+ [#532](https://github.com/haskell-tls/hs-tls/pull/532)+* Bound certificate decompression+ [#531](https://github.com/haskell-tls/hs-tls/pull/531)+* Fix RecordOverflow race after TLS 1.3 client authentication+ [#530](https://github.com/haskell-tls/hs-tls/pull/530)++## Version 2.4.3++* A server checks clientAuth of ExtendedKeyUsage in a client+ certificate on client authentication.++## Version 2.4.2++* The `Network.TLS.Extra.CipherCBC` module is added.+ [#526](https://github.com/haskell-tls/hs-tls/pull/526)++## Version 2.4.1++* Ensure same `supported_groups` before/after HRR.+* New `clientWantTicket` parameter makes it possible to opt-out of soliciting+ session tickets from servers.++## Version 2.4.0++* Identical to v2.3.1 but major version up as v2.3.1 breaks "quic".++## Version 2.3.1 (deprecated)+ * Using ScrubbedBytes for secrets. * Key echange with ML-KEM. [#517](https://github.com/haskell-tls/hs-tls/pull/517)@@ -81,14 +241,14 @@ This feature is automatically used if the peer supports it. * More tests with `tlsfuzzer` especially for client authentication and 0-RTT.-* Implementing a utility funcation, `validateClientCertificate`, for+* Implementing a utility function, `validateClientCertificate`, for client authentication. * Bug fix for echo back logic of Cookie extension. * More pretty show for the internal `Handshake` structure for debugging. ## Version 2.1.6 -* Testing with "tlsfuzzer" again. Now don't send an alert agaist to+* Testing with "tlsfuzzer" again. Now don't send an alert against to peer's alert. Double locking (aka self dead-lock) is fixed. Sending an alert for known-but-cannot-parse extensions. Other corner cases are also fixed.@@ -352,7 +512,7 @@ API CHANGES: - `SessionManager` implementations need to provide a `sessionResumeOnlyOnce`- function to accomodate resumption scenarios with 0-RTT data. The function is+ function to accommodate resumption scenarios with 0-RTT data. The function is called only on the server side. - Data type `SessionData` is extended with four new fields for TLS version 1.3. `SessionManager` implementations that serializes/deserializes `SessionData`
Network/TLS.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE PatternSynonyms #-} -- | -- Native Haskell TLS protocol implementation for servers and -- clients.@@ -11,7 +12,7 @@ -- protocol, and support RSA and Ephemeral (Elliptic curve and -- regular) Diffie Hellman key exchanges, and many extensions. ----- The tipical usage is:+-- The typical usage is: -- -- > socket <- ... -- > ctx <- contextNew socket <params>@@ -46,6 +47,7 @@ clientUseServerNameIndication, clientWantSessionResume, clientWantSessionResumeList,+ clientWantTicket, clientShared, clientHooks, clientSupported,@@ -138,6 +140,7 @@ Limit, defaultLimit, limitHandshakeFragment,+ limitKeyUpdate, limitRecordSize, -- * Shared parameters@@ -196,6 +199,9 @@ supportedSignatureSchemes, HashAlgorithm (..), SignatureAlgorithm (..),+ pattern MLDSA44,+ pattern MLDSA65,+ pattern MLDSA87, Group (..), supportedNamedGroups, EMSMode (..),@@ -350,6 +356,9 @@ TLSError (..), TLSException (..), supportedSignatureSchemes,+ pattern MLDSA44,+ pattern MLDSA65,+ pattern MLDSA87, ) import Network.TLS.Struct13 (Handshake13) import Network.TLS.Types
Network/TLS/Compression.hs view
@@ -51,7 +51,7 @@ (==) c1 c2 = compressionID c1 == compressionID c2 -- | intersect a list of ids commonly given by the other side with a list of compression--- the function keeps the list of compression in order, to be able to find quickly the prefered+-- the function keeps the list of compression in order, to be able to find quickly the preferred -- compression. compressionIntersectID :: [Compression] -> [Word8] -> [Compression] compressionIntersectID l ids = filter (\c -> compressionID c `elem` ids) l
Network/TLS/Context.hs view
@@ -8,6 +8,7 @@ Context (..), Hooks (..), Established (..),+ PendingRecv (..), RecordLayer (..), ctxEOF, ctxEstablished,@@ -23,6 +24,7 @@ updateMeasure, withMeasure, withReadLock,+ tryWithReadLock, withWriteLock, withStateLock, withRWLock,@@ -114,7 +116,7 @@ doHandshake :: a -> Context -> IO () doHandshakeWith :: a -> Context -> HandshakeR -> IO () doRequestCertificate :: a -> Context -> IO Bool- doPostHandshakeAuthWith :: a -> Context -> Handshake13 -> IO ()+ doPostHandshakeAuthWith :: a -> Context -> Handshake13R -> IO () instance TLSParams ClientParams where getTLSCommonParams cparams =@@ -265,8 +267,11 @@ -- and use the "tls-exporter" channel binding via 'getTLSExporter'. getTLSUnique :: Context -> IO (Maybe ByteString) getTLSUnique ctx = do- ver <- liftIO $ usingState_ ctx getVersion- if ver == TLS12+ -- Nothing rather than error before a version has been negotiated: this+ -- can be called on a context whose handshake has not run, and it already+ -- answers with Maybe.+ mver <- liftIO $ usingState_ ctx getVersionMaybe+ if mver == Just TLS12 then do mx <- usingState_ ctx getFirstVerifyData case mx of@@ -278,8 +283,9 @@ -- For TLS 1.2, 'Nothing' is returned. getTLSExporter :: Context -> IO (Maybe ByteString) getTLSExporter ctx = do- ver <- liftIO $ usingState_ ctx getVersion- if ver == TLS13+ -- As in 'getTLSUnique'.+ mver <- liftIO $ usingState_ ctx getVersionMaybe+ if mver == Just TLS13 then exporter ctx "EXPORTER-Channel-Binding" "" 32 else return Nothing
Network/TLS/Context/Internal.hs view
@@ -17,6 +17,7 @@ Hooks (..), Limit (..), Established (..),+ PendingRecv (..), PendingRecvAction (..), RecordLayer (..), Locks (..),@@ -36,6 +37,7 @@ updateMeasure, withMeasure, withReadLock,+ tryWithReadLock, withWriteLock, withStateLock, withRWLock,@@ -65,6 +67,8 @@ defaultTLS13State, getTLS13State, modifyTLS13State,+ incrementTLS13KeyUpdateCount,+ resetTLS13KeyUpdateCount, CipherChoice (..), makeCipherChoice, @@ -86,7 +90,7 @@ ) where import Control.Concurrent.MVar-import Control.Exception (throwIO)+import qualified Control.Exception as E import Control.Monad.State.Strict import Data.ByteArray (convert) import qualified Data.ByteArray as BA@@ -170,7 +174,7 @@ { doHandshake_ :: Context -> IO () , doHandshakeWith_ :: Context -> HandshakeR -> IO () , doRequestCertificate_ :: Context -> IO Bool- , doPostHandshakeAuthWith_ :: Context -> Handshake13 -> IO ()+ , doPostHandshakeAuthWith_ :: Context -> Handshake13R -> IO () } data Locks = Locks@@ -199,11 +203,12 @@ data TLS13State = TLS13State { tls13stRecvNST :: Bool -- client+ , tls13stKeyUpdateCount :: Int , tls13stSentClientCert :: Bool -- client , tls13stRecvSF :: Bool -- client , tls13stSentCF :: Bool -- client , tls13stRecvCF :: Bool -- server- , tls13stPendingRecvData :: Maybe ByteString -- client+ , tls13stPendingRecv :: PendingRecv -- client , tls13stPendingSentData :: [ByteString] -> [ByteString] -- client , tls13stRTT :: Millisecond , tls13st0RTT :: Bool -- client@@ -211,7 +216,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+ -- Actual session id for TLS 1.2, random value for TLS 1.3 , tls13stSession :: Session , tls13stSentExtensions :: [ExtensionID] }@@ -220,11 +225,12 @@ defaultTLS13State = TLS13State { tls13stRecvNST = False+ , tls13stKeyUpdateCount = 0 , tls13stSentClientCert = False , tls13stRecvSF = False , tls13stSentCF = False , tls13stRecvCF = False- , tls13stPendingRecvData = Nothing+ , tls13stPendingRecv = NoPendingRecv , tls13stPendingSentData = id , tls13stRTT = 0 , tls13st0RTT = False@@ -242,6 +248,16 @@ modifyTLS13State :: Context -> (TLS13State -> TLS13State) -> IO () modifyTLS13State Context{..} f = atomicModifyIORef' ctxTLS13State $ \st -> (f st, ()) +incrementTLS13KeyUpdateCount :: Context -> IO Int+incrementTLS13KeyUpdateCount Context{..} =+ atomicModifyIORef' ctxTLS13State $ \st ->+ let count = tls13stKeyUpdateCount st + 1+ in (st{tls13stKeyUpdateCount = count}, count)++resetTLS13KeyUpdateCount :: Context -> IO ()+resetTLS13KeyUpdateCount ctx =+ modifyTLS13State ctx $ \st -> st{tls13stKeyUpdateCount = 0}+ data HandshakeSync = HandshakeSync (Context -> ClientState -> IO ())@@ -271,6 +287,15 @@ | Established deriving (Eq, Show) +-- | Outcome of a read that was started on behalf of a caller who is no longer+-- waiting for it, held until the next receive hands it over. Reads cannot be+-- abandoned once started -- see 'Network.TLS.Core.handshake' -- so a reader that+-- outlives its caller leaves its result here instead.+data PendingRecv+ = NoPendingRecv+ | PendingRecvData ByteString+ | PendingRecvError E.SomeException+ data PendingRecvAction = -- | simple pending action. The first 'Bool' is necessity of alignment. PendingRecvAction Bool (Handshake13 -> IO ())@@ -367,7 +392,7 @@ withLog ctx f = ctxWithHooks ctx (f . hookLogging) throwCore :: MonadIO m => TLSError -> m a-throwCore = liftIO . throwIO . Uncontextualized+throwCore = liftIO . E.throwIO . Uncontextualized failOnEitherError :: MonadIO m => m (Either TLSError a) -> m a failOnEitherError f = do@@ -387,7 +412,7 @@ usingHState :: MonadIO m => Context -> HandshakeM a -> m a usingHState ctx f = liftIO $ modifyMVar (ctxHandshakeState ctx) $ \case- Nothing -> liftIO $ throwIO MissingHandshake+ Nothing -> liftIO $ E.throwIO MissingHandshake Just st -> return $ swap (Just <$> runHandshake st f) getHState :: MonadIO m => Context -> m (Maybe HandshakeState)@@ -449,6 +474,26 @@ withReadLock :: Context -> IO a -> IO a withReadLock ctx f = withMVar (lockRead $ ctxLocks ctx) (const f)++-- | Like 'withReadLock', but returns 'Nothing' immediately instead of waiting+-- when another thread already holds the read lock.+--+-- The read lock is what keeps a single thread reading the connection at a time.+-- Records arrive length-prefixed, so two threads reading in parallel would each+-- take a piece of whatever record the other was in the middle of, and neither+-- would end up with a usable message.+--+-- Use this instead of 'withReadLock' when the read is optional and skipping it+-- is better than waiting for the current reader, which may hold the lock for+-- arbitrarily long. 'bye' is the only such caller; see the note there.+tryWithReadLock :: Context -> IO a -> IO (Maybe a)+tryWithReadLock ctx f = E.bracket acquire release $ \mlock -> case mlock of+ Nothing -> return Nothing+ Just _ -> Just <$> f+ where+ lock = lockRead $ ctxLocks ctx+ acquire = tryTakeMVar lock+ release = mapM_ (putMVar lock) withWriteLock :: Context -> IO a -> IO a withWriteLock ctx f = withMVar (lockWrite $ ctxLocks ctx) (const f)
Network/TLS/Core.hs view
@@ -27,6 +27,8 @@ requestCertificate, ) where +import Control.Concurrent (forkIO)+import Control.Concurrent.MVar import qualified Control.Exception as E import Control.Monad.State.Strict import qualified Data.ByteString as B@@ -36,6 +38,10 @@ import System.Timeout import Network.TLS.Context+import Network.TLS.Context.Internal (+ incrementTLS13KeyUpdateCount,+ resetTLS13KeyUpdateCount,+ ) import Network.TLS.Extension import Network.TLS.Handshake import Network.TLS.Handshake.Common@@ -72,11 +78,39 @@ sentClientCert <- tls13stSentClientCert <$> getTLS13State ctx when (role == ClientRole && tls13 && sentClientCert) $ do rtt <- getRTT ctx- -- This 'timeout' should work.- mdat <- timeout rtt $ recvData13 ctx- case mdat of- Nothing -> return ()- Just dat -> modifyTLS13State ctx $ \st -> st{tls13stPendingRecvData = Just dat}+ -- We are only willing to wait 'rtt' for the alert, but a receive+ -- must not be abandoned once it has started. Records are read+ -- length-prefixed and the record layer keeps no receive buffer, so+ -- an aborted receive loses the bytes it has already taken off the+ -- transport and leaves the stream positioned inside a record.+ -- Every later read is then misframed, and the connection is dead+ -- with a spurious protocol error.+ --+ -- So the receive runs in its own thread and we stop waiting for it+ -- rather than interrupting it. It holds the read lock, which keeps+ -- it the only reader and makes the next receive wait for it to+ -- finish; its outcome is left in 'tls13stPendingRecv' for that+ -- receive to pick up.+ done <- newEmptyMVar+ void $ forkIO $ withReadLock ctx $ do+ r <- E.try $ recvData13 ctx+ modifyTLS13State ctx $ \st ->+ st+ { tls13stPendingRecv = case r of+ Right dat -> PendingRecvData dat+ Left err -> PendingRecvError err+ }+ putMVar done ()+ arrived <- timeout rtt $ takeMVar done+ -- Still report the authentication failure from 'handshake' itself+ -- whenever it did arrive in time.+ when (isJust arrived) $ do+ pending <- tls13stPendingRecv <$> getTLS13State ctx+ case pending of+ PendingRecvError err -> do+ modifyTLS13State ctx $ \st -> st{tls13stPendingRecv = NoPendingRecv}+ E.throwIO err+ _ -> return () rttFactor :: Int rttFactor = 3@@ -118,7 +152,7 @@ recvNST <- chk unless recvNST $ do rtt <- getRTT ctx- void $ timeout rtt $ recvHS13 ctx chk+ tryRecvHS13 rtt chk else do -- receiving Client Finished let chk = tls13stRecvCF <$> getTLS13State ctx@@ -127,8 +161,28 @@ -- no chance to measure RTT before receiving CF -- fixme: 1sec is good enough? let rtt = 1000000- void $ timeout rtt $ recvHS13 ctx chk+ tryRecvHS13 rtt chk bye_ ctx+ where+ -- Receiving these messages only improves the chances of a later session+ -- resumption, so giving up on them costs nothing important. We give up in+ -- two different situations, for two different reasons.+ --+ -- First, we need the read lock, because only one thread at a time may read+ -- the connection, but we take it only if it happens to be free. Another+ -- thread can be sitting in 'recvData' waiting for data that never arrives,+ -- or the receive that 'handshake' starts can still be running, and either+ -- holds the read lock for as long as it lasts. Waiting for the lock would+ -- therefore hang 'bye', and closing a connection that a reader is stuck on+ -- is exactly what 'bye' is for, so we skip the receive in that case.+ --+ -- Second, if we do get the lock, we wait 'rtt' for the message and then+ -- abandon the receive. Abandoning it can stop the connection part way+ -- through a record, after which nothing can be read from it again -- which+ -- is acceptable only because we are closing the connection here anyway.+ tryRecvHS13 :: Int -> IO Bool -> IO ()+ tryRecvHS13 rtt chk =+ void $ tryWithReadLock ctx $ timeout rtt $ recvHS13 ctx chk bye_ :: MonadIO m => Context -> m () bye_ ctx = liftIO $ do@@ -188,7 +242,7 @@ -- All chunks are protected with the same write lock because we don't -- want to interleave writes from other threads in the middle of our -- possibly large write.- mlen <- getPeerRecordLimit ctx -- plaintext, dont' adjust for TLS 1.3+ mlen <- getPeerRecordLimit ctx -- plaintext, don't adjust for TLS 1.3 mapM_ (mapChunks_ mlen sendP) (L.toChunks dataToSend) -- | Get data out of Data packet, and automatically renegotiate if a Handshake@@ -240,15 +294,20 @@ recvData13 :: Context -> IO ByteString recvData13 ctx = do- mdat <- tls13stPendingRecvData <$> getTLS13State ctx- case mdat of- Nothing -> do+ pending <- tls13stPendingRecv <$> getTLS13State ctx+ case pending of+ NoPendingRecv -> do pkt <- recvPacket13 ctx either (onError (terminate13 ctx)) process pkt- Just dat -> do- modifyTLS13State ctx $ \st -> st{tls13stPendingRecvData = Nothing}+ PendingRecvData dat -> do+ clearPending return dat+ PendingRecvError err -> do+ clearPending+ E.throwIO err where+ clearPending = modifyTLS13State ctx $ \st -> st{tls13stPendingRecv = NoPendingRecv}+ -- UserCanceled MUST be followed by a CloseNotify. process (Alert13 [(AlertLevel_Warning, UserCanceled)]) = return B.empty process (Alert13 [(AlertLevel_Warning, CloseNotify)]) = tryBye ctx >> setEOF ctx >> return B.empty@@ -283,7 +342,7 @@ | otherwise -> do let reason = "early data deprotect overflow" terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason- Established -> return x+ Established -> resetTLS13KeyUpdateCount ctx >> return x _ -> throwCore $ Error_Protocol "data at not-established" UnexpectedMessage process ChangeCipherSpec13 = do established <- ctxEstablished ctx@@ -345,6 +404,13 @@ -- to key update (update_requested) which we sent. if established == Established then do+ case limitKeyUpdate $ sharedLimit $ ctxShared ctx of+ Just limit | limit > 0 -> do+ count <- incrementTLS13KeyUpdateCount ctx+ when (count > limit) $ do+ let reason = "too many consecutive KeyUpdate messages"+ terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason+ _ -> return () keyUpdate ctx getRxRecordState setRxRecordState -- Write lock wraps both actions because we don't want another -- packet to be sent by another thread before the Tx state is@@ -356,10 +422,16 @@ else do let reason = "received key update before established" terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason- -- Client only- loopHandshake13 ((h@CertRequest13{}, _b) : hbs) =- postHandshakeAuthWith ctx h >> loopHandshake13 hbs- loopHandshake13 (hb@(h, _) : hbs) = do+ -- Client only. Before Established, it belongs to the handshake that+ -- 0-RTT left to the pending actions.+ loopHandshake13 (hb@(CertRequest13{}, _) : hbs) = do+ established <- ctxEstablished ctx+ if established == Established+ then postHandshakeAuthWith ctx hb >> loopHandshake13 hbs+ else loopPending hb hbs+ loopHandshake13 (hb : hbs) = loopPending hb hbs++ loopPending hb@(h, _) hbs = do rtt0 <- tls13st0RTT <$> getTLS13State ctx when rtt0 $ case h of ServerHello13 SH{..} ->
Network/TLS/Credentials.hs view
@@ -156,6 +156,9 @@ SignatureALG X509.HashSHA512 PubKeyALG_RSAPSS -> Just (TLS.HashIntrinsic, TLS.SignatureRSApssRSAeSHA512) SignatureALG_IntrinsicHash PubKeyALG_Ed25519 -> Just (TLS.HashIntrinsic, TLS.SignatureEd25519) SignatureALG_IntrinsicHash PubKeyALG_Ed448 -> Just (TLS.HashIntrinsic, TLS.SignatureEd448)+ SignatureALG_IntrinsicHash PubKeyALG_MLDSA44 -> Just TLS.MLDSA44+ SignatureALG_IntrinsicHash PubKeyALG_MLDSA65 -> Just TLS.MLDSA65+ SignatureALG_IntrinsicHash PubKeyALG_MLDSA87 -> Just TLS.MLDSA87 _ -> Nothing where convertHash sig X509.HashMD5 = Just (TLS.HashMD5, sig)
Network/TLS/Crypto.hs view
@@ -54,11 +54,12 @@ import qualified Crypto.PubKey.ECDSA as ECDSA import qualified Crypto.PubKey.Ed25519 as Ed25519 import qualified Crypto.PubKey.Ed448 as Ed448+import qualified Crypto.PubKey.MLDSA as MLDSA import qualified Crypto.PubKey.RSA as RSA import qualified Crypto.PubKey.RSA.PKCS15 as RSA import qualified Crypto.PubKey.RSA.PSS as PSS import Crypto.Random-import Data.ASN1.BinaryEncoding (BER (..), DER (..))+import Data.ASN1.BinaryEncoding (DER (..)) import Data.ASN1.Encoding import Data.ASN1.Types import Data.ByteArray (ByteArray, ByteArrayAccess, ScrubbedBytes, convert)@@ -71,6 +72,7 @@ PubKey (..), PubKeyEC (..), SerializedPoint (..),+ privkeyMLDSA_key, ) import Data.X509.EC (ecPrivKeyCurveName, ecPubKeyCurveName, unserializePoint) @@ -261,6 +263,13 @@ | ECDSAParams Hash | Ed25519Params | Ed448Params+ | -- | Pure ML-DSA with an empty context, which is what+ -- draft-ietf-tls-mldsa uses. One constructor per parameter set,+ -- since the key fixes it and a signature made under one does not+ -- verify under another.+ MLDSA44Params+ | MLDSA65Params+ | MLDSA87Params deriving (Show, Eq) -- Verify that the signature matches the given message, using the@@ -271,38 +280,21 @@ kxVerify (PubKeyRSA pk) (RSAParams alg RSApkcs1) msg sign = rsaVerifyHash alg pk msg sign kxVerify (PubKeyRSA pk) (RSAParams alg RSApss) msg sign = rsapssVerifyHash alg pk msg sign kxVerify (PubKeyDSA pk) DSAParams msg signBS =- case dsaToSignature signBS of- Just sig -> DSA.verify H.SHA1 pk sig msg+ case decodeSignatureRS signBS of+ Just (r, s) -> DSA.verify H.SHA1 pk DSA.Signature{DSA.sign_r = r, DSA.sign_s = s} msg _ -> False- where- dsaToSignature :: ByteString -> Maybe DSA.Signature- dsaToSignature b =- case decodeASN1' BER b of- Left _ -> Nothing- Right asn1 ->- case asn1 of- Start Sequence : IntVal r : IntVal s : End Sequence : _ ->- Just DSA.Signature{DSA.sign_r = r, DSA.sign_s = s}- _ ->- Nothing kxVerify (PubKeyEC key) (ECDSAParams alg) msg sigBS = fromMaybe False $ join $ withPubKeyEC key verifyProxy verifyClassic Nothing where- decodeSignatureASN1 buildRS =- case decodeASN1' BER sigBS of- Left _ -> Nothing- Right [Start Sequence, IntVal r, IntVal s, End Sequence] ->- Just (buildRS r s)- Right _ -> Nothing verifyProxy prx pubkey = do- rs <- decodeSignatureASN1 (,)+ rs <- decodeSignatureRS sigBS signature <- maybeCryptoError $ ECDSA.signatureFromIntegers prx rs verifyF <- withAlg (ECDSA.verify prx) return $ verifyF pubkey signature msg verifyClassic pubkey = do- signature <- decodeSignatureASN1 ECDSA_ECC.Signature+ signature <- uncurry ECDSA_ECC.Signature <$> decodeSignatureRS sigBS verifyF <- withAlg ECDSA_ECC.verify return $ verifyF pubkey signature msg withAlg :: (forall hash. H.HashAlgorithm hash => hash -> a) -> Maybe a@@ -322,8 +314,66 @@ case Ed448.signature sigBS of CryptoPassed sig -> Ed448.verify key msg sig _ -> False+-- A signature of the wrong length is refused by the smart constructor, and+-- one of the right length that does not verify returns False. Neither+-- throws, which is what a peer sending a damaged CertificateVerify has to+-- meet with decrypt_error rather than with a crash.+kxVerify (PubKeyMLDSA44 key) MLDSA44Params msg sigBS = mldsaVerify key msg sigBS+kxVerify (PubKeyMLDSA65 key) MLDSA65Params msg sigBS = mldsaVerify key msg sigBS+kxVerify (PubKeyMLDSA87 key) MLDSA87Params msg sigBS = mldsaVerify key msg sigBS kxVerify _ _ _ _ = False +mldsaVerify+ :: MLDSA.MLDSA p+ => MLDSA.VerificationKey p -> ByteString -> ByteString -> Bool+mldsaVerify key msg sigBS = case MLDSA.signature sigBS of+ CryptoPassed sig -> MLDSA.verify key MLDSA.emptyContext msg sig+ _ -> False++-- | Decode a DSA or ECDSA signature: a DER SEQUENCE of two INTEGERs, r+-- and s (RFC 3279 Sections 2.2.2 and 2.2.3). This is done here rather+-- than with crypton-asn1-encoding, whose 'decodeASN1'' throws on some+-- malformed input rather than returning 'Left'. Only DER is accepted:+-- definite lengths in their shortest form, non-negative INTEGERs in+-- theirs, and nothing after the SEQUENCE.+decodeSignatureRS :: ByteString -> Maybe (Integer, Integer)+decodeSignatureRS bs = do+ (0x30, body) <- whole bs+ (0x02, rbs, rest) <- tlv body+ (0x02, sbs) <- whole rest+ (,) <$> derInteger rbs <*> derInteger sbs+ where+ whole b = do+ (t, v, rest) <- tlv b+ guard $ B.null rest+ return (t, v)+ tlv b = do+ (t, b1) <- B.uncons b+ (l0, b2) <- B.uncons b1+ (len, b3) <- case l0 of+ _ | l0 < 0x80 -> Just (fromIntegral l0, b2)+ 0x81 -> do+ (l1, b3) <- B.uncons b2+ guard $ l1 >= 0x80+ Just (fromIntegral l1, b3)+ 0x82 -> do+ (l1, b3) <- B.uncons b2+ (l2, b4) <- B.uncons b3+ let n = fromIntegral l1 * 256 + fromIntegral l2 :: Int+ guard $ n >= 0x100+ Just (n, b4)+ _ -> Nothing+ guard $ B.length b3 >= len+ let (v, rest) = B.splitAt len b3+ return (t, v, rest)+ derInteger v = do+ (h, t) <- B.uncons v+ guard $ h < 0x80+ case B.uncons t of+ Just (h2, _) | h == 0 -> guard $ h2 >= 0x80+ _ -> return ()+ return $ B.foldl' (\acc w -> acc * 256 + fromIntegral w) 0 v+ -- Sign the given message using the private key. -- kxSign@@ -366,6 +416,12 @@ return $ Right $ convert $ Ed25519.sign pk pub msg kxSign (PrivKeyEd448 pk) (PubKeyEd448 pub) Ed448Params msg = return $ Right $ convert $ Ed448.sign pk pub msg+kxSign (PrivKeyMLDSA44 pk) (PubKeyMLDSA44 _) MLDSA44Params msg =+ Right . convert <$> MLDSA.sign (privkeyMLDSA_key pk) MLDSA.emptyContext msg+kxSign (PrivKeyMLDSA65 pk) (PubKeyMLDSA65 _) MLDSA65Params msg =+ Right . convert <$> MLDSA.sign (privkeyMLDSA_key pk) MLDSA.emptyContext msg+kxSign (PrivKeyMLDSA87 pk) (PubKeyMLDSA87 _) MLDSA87Params msg =+ Right . convert <$> MLDSA.sign (privkeyMLDSA_key pk) MLDSA.emptyContext msg kxSign _ _ _ _ = return (Left KxUnsupported) @@ -375,13 +431,17 @@ -> RSA.PrivateKey -> ByteString -> m (Either RSA.Error ByteString)-rsaSignHash SHA1_MD5 pk msg = RSA.signSafer noHash pk msg-rsaSignHash MD5 pk msg = RSA.signSafer (Just H.MD5) pk msg-rsaSignHash SHA1 pk msg = RSA.signSafer (Just H.SHA1) pk msg-rsaSignHash SHA224 pk msg = RSA.signSafer (Just H.SHA224) pk msg-rsaSignHash SHA256 pk msg = RSA.signSafer (Just H.SHA256) pk msg-rsaSignHash SHA384 pk msg = RSA.signSafer (Just H.SHA384) pk msg-rsaSignHash SHA512 pk msg = RSA.signSafer (Just H.SHA512) pk msg+-- SHA1_MD5 arrives already digested -- see buildVerifyData -- and what it+-- carries is MD5 ++ SHA1 with no ASN.1 around it, which is what TLS before+-- 1.2 signs. That is the DigestInfo case: nothing here hashes or encodes.+-- The rest are handed the message and hash it on the way, as signSafer did.+rsaSignHash SHA1_MD5 pk msg = RSA.signSaferDigestInfo pk msg+rsaSignHash MD5 pk msg = RSA.signSaferDigest pk (H.hashWith H.MD5 msg)+rsaSignHash SHA1 pk msg = RSA.signSaferDigest pk (H.hashWith H.SHA1 msg)+rsaSignHash SHA224 pk msg = RSA.signSaferDigest pk (H.hashWith H.SHA224 msg)+rsaSignHash SHA256 pk msg = RSA.signSaferDigest pk (H.hashWith H.SHA256 msg)+rsaSignHash SHA384 pk msg = RSA.signSaferDigest pk (H.hashWith H.SHA384 msg)+rsaSignHash SHA512 pk msg = RSA.signSaferDigest pk (H.hashWith H.SHA512 msg) rsapssSignHash :: MonadRandom m@@ -395,22 +455,19 @@ rsapssSignHash _ _ _ = error "rsapssSignHash: unsupported hash" rsaVerifyHash :: Hash -> RSA.PublicKey -> ByteString -> ByteString -> Bool-rsaVerifyHash SHA1_MD5 = RSA.verify noHash-rsaVerifyHash MD5 = RSA.verify (Just H.MD5)-rsaVerifyHash SHA1 = RSA.verify (Just H.SHA1)-rsaVerifyHash SHA224 = RSA.verify (Just H.SHA224)-rsaVerifyHash SHA256 = RSA.verify (Just H.SHA256)-rsaVerifyHash SHA384 = RSA.verify (Just H.SHA384)-rsaVerifyHash SHA512 = RSA.verify (Just H.SHA512)+rsaVerifyHash SHA1_MD5 pk msg = RSA.verifyDigestInfo pk msg+rsaVerifyHash MD5 pk msg = RSA.verifyDigest pk (H.hashWith H.MD5 msg)+rsaVerifyHash SHA1 pk msg = RSA.verifyDigest pk (H.hashWith H.SHA1 msg)+rsaVerifyHash SHA224 pk msg = RSA.verifyDigest pk (H.hashWith H.SHA224 msg)+rsaVerifyHash SHA256 pk msg = RSA.verifyDigest pk (H.hashWith H.SHA256 msg)+rsaVerifyHash SHA384 pk msg = RSA.verifyDigest pk (H.hashWith H.SHA384 msg)+rsaVerifyHash SHA512 pk msg = RSA.verifyDigest pk (H.hashWith H.SHA512 msg) rsapssVerifyHash :: Hash -> RSA.PublicKey -> ByteString -> ByteString -> Bool rsapssVerifyHash SHA256 = PSS.verify (PSS.defaultPSSParams H.SHA256) rsapssVerifyHash SHA384 = PSS.verify (PSS.defaultPSSParams H.SHA384) rsapssVerifyHash SHA512 = PSS.verify (PSS.defaultPSSParams H.SHA512) rsapssVerifyHash _ = error "rsapssVerifyHash: unsupported hash"--noHash :: Maybe H.MD5-noHash = Nothing ecdsaSignHash :: (MonadRandom m, ECDSA.EllipticCurveECDSA curve)
Network/TLS/Crypto/IES.hs view
@@ -34,8 +34,8 @@ import Crypto.PubKey.DH (PrivateNumber (..), PublicNumber (..)) import qualified Crypto.PubKey.DH as DH import Crypto.PubKey.ECIES-import Crypto.PubKey.ML_KEM (ML_KEM_1024, ML_KEM_512, ML_KEM_768)-import qualified Crypto.PubKey.ML_KEM as ML+import Crypto.PubKey.MLKEM (MLKEM1024, MLKEM512, MLKEM768)+import qualified Crypto.PubKey.MLKEM as ML import Data.ByteArray (ScrubbedBytes, convert) import qualified Data.ByteArray as BA import Data.Proxy@@ -58,12 +58,12 @@ | GroupPri_FFDHE4096 PrivateNumber | GroupPri_FFDHE6144 PrivateNumber | GroupPri_FFDHE8192 PrivateNumber- | GroupPri_MLKEM512 (ML.DecapsulationKey ML_KEM_512)- | GroupPri_MLKEM768 (ML.DecapsulationKey ML_KEM_768)- | GroupPri_MLKEM1024 (ML.DecapsulationKey ML_KEM_1024)- | GroupPri_X25519MLKEM768 (Scalar Curve_X25519, ML.DecapsulationKey ML_KEM_768)- | GroupPri_P256MLKEM768 (Scalar Curve_P256R1, ML.DecapsulationKey ML_KEM_768)- | GroupPri_P384MLKEM1024 (Scalar Curve_P384R1, ML.DecapsulationKey ML_KEM_1024)+ | GroupPri_MLKEM512 (ML.DecapsulationKey MLKEM512)+ | GroupPri_MLKEM768 (ML.DecapsulationKey MLKEM768)+ | GroupPri_MLKEM1024 (ML.DecapsulationKey MLKEM1024)+ | GroupPri_X25519MLKEM768 (Scalar Curve_X25519, ML.DecapsulationKey MLKEM768)+ | GroupPri_P256MLKEM768 (Scalar Curve_P256R1, ML.DecapsulationKey MLKEM768)+ | GroupPri_P384MLKEM1024 (Scalar Curve_P384R1, ML.DecapsulationKey MLKEM1024) deriving (Eq, Show) {- FOURMOLU_ENABLE -} @@ -79,12 +79,12 @@ | GroupPubA_FFDHE4096 PublicNumber | GroupPubA_FFDHE6144 PublicNumber | GroupPubA_FFDHE8192 PublicNumber- | GroupPubA_MLKEM512 (ML.EncapsulationKey ML_KEM_512)- | GroupPubA_MLKEM768 (ML.EncapsulationKey ML_KEM_768)- | GroupPubA_MLKEM1024 (ML.EncapsulationKey ML_KEM_1024)- | GroupPubA_X25519MLKEM768 (Point Curve_X25519, ML.EncapsulationKey ML_KEM_768)- | GroupPubA_P256MLKEM768 (Point Curve_P256R1, ML.EncapsulationKey ML_KEM_768)- | GroupPubA_P384MLKEM1024 (Point Curve_P384R1, ML.EncapsulationKey ML_KEM_1024)+ | GroupPubA_MLKEM512 (ML.EncapsulationKey MLKEM512)+ | GroupPubA_MLKEM768 (ML.EncapsulationKey MLKEM768)+ | GroupPubA_MLKEM1024 (ML.EncapsulationKey MLKEM1024)+ | GroupPubA_X25519MLKEM768 (Point Curve_X25519, ML.EncapsulationKey MLKEM768)+ | GroupPubA_P256MLKEM768 (Point Curve_P256R1, ML.EncapsulationKey MLKEM768)+ | GroupPubA_P384MLKEM1024 (Point Curve_P384R1, ML.EncapsulationKey MLKEM1024) deriving (Eq, Show) {- FOURMOLU_ENABLE -} @@ -100,12 +100,12 @@ | GroupPubB_FFDHE4096 PublicNumber | GroupPubB_FFDHE6144 PublicNumber | GroupPubB_FFDHE8192 PublicNumber- | GroupPubB_MLKEM512 (ML.Ciphertext ML_KEM_512)- | GroupPubB_MLKEM768 (ML.Ciphertext ML_KEM_768)- | GroupPubB_MLKEM1024 (ML.Ciphertext ML_KEM_1024)- | GroupPubB_X25519MLKEM768 (Point Curve_X25519, ML.Ciphertext ML_KEM_768)- | GroupPubB_P256MLKEM768 (Point Curve_P256R1, ML.Ciphertext ML_KEM_768)- | GroupPubB_P384MLKEM1024 (Point Curve_P384R1, ML.Ciphertext ML_KEM_1024)+ | GroupPubB_MLKEM512 (ML.Ciphertext MLKEM512)+ | GroupPubB_MLKEM768 (ML.Ciphertext MLKEM768)+ | GroupPubB_MLKEM1024 (ML.Ciphertext MLKEM1024)+ | GroupPubB_X25519MLKEM768 (Point Curve_X25519, ML.Ciphertext MLKEM768)+ | GroupPubB_P256MLKEM768 (Point Curve_P256R1, ML.Ciphertext MLKEM768)+ | GroupPubB_P384MLKEM1024 (Point Curve_P384R1, ML.Ciphertext MLKEM1024) deriving (Eq, Show) {- FOURMOLU_ENABLE -} @@ -126,13 +126,27 @@ x448 :: Proxy Curve_X448 x448 = Proxy -mlkem512 :: Proxy ML_KEM_512+mlkem512 :: Proxy MLKEM512 mlkem512 = Proxy -mlkem768 :: Proxy ML_KEM_768+-- crypton reports a value it will not accept as a 'CryptoFailable' and has a+-- constructor per type rather than one overloaded 'decode'. These two keep+-- the call sites below in the shape they were.+--+-- 'ML.encapsulationKey' is the stricter of the two: besides the length it+-- runs the check of FIPS 203 section 7.2, so an encapsulation key whose+-- coefficients are out of range is refused here rather than used.+decodeEK+ :: ML.MLKEM p => proxy p -> ByteString -> Maybe (ML.EncapsulationKey p)+decodeEK _ = maybeCryptoError . ML.encapsulationKey++decodeCT :: ML.MLKEM p => proxy p -> ByteString -> Maybe (ML.Ciphertext p)+decodeCT _ = maybeCryptoError . ML.ciphertext++mlkem768 :: Proxy MLKEM768 mlkem768 = Proxy -mlkem1024 :: Proxy ML_KEM_1024+mlkem1024 :: Proxy MLKEM1024 mlkem1024 = Proxy dhParamsForGroup :: Group -> Maybe DH.Params@@ -160,25 +174,25 @@ groupGenerateKeyPair FFDHE6144 = gen ffdhe6144 exp6144 GroupPri_FFDHE6144 GroupPubA_FFDHE6144 groupGenerateKeyPair FFDHE8192 = gen ffdhe8192 exp8192 GroupPri_FFDHE8192 GroupPubA_FFDHE8192 groupGenerateKeyPair MLKEM512 = do- (e, d) <- ML.generate mlkem512+ (e, d) <- ML.generateKeyPair mlkem512 return (GroupPri_MLKEM512 d, GroupPubA_MLKEM512 e) groupGenerateKeyPair MLKEM768 = do- (e, d) <- ML.generate mlkem768+ (e, d) <- ML.generateKeyPair mlkem768 return (GroupPri_MLKEM768 d, GroupPubA_MLKEM768 e) groupGenerateKeyPair MLKEM1024 = do- (e, d) <- ML.generate mlkem1024+ (e, d) <- ML.generateKeyPair mlkem1024 return (GroupPri_MLKEM1024 d, GroupPubA_MLKEM1024 e) groupGenerateKeyPair X25519MLKEM768 = do (d1, e1) <- fs' $ curveGenerateKeyPair x25519- (e2, d2) <- ML.generate mlkem768+ (e2, d2) <- ML.generateKeyPair mlkem768 return (GroupPri_X25519MLKEM768 (d1, d2), GroupPubA_X25519MLKEM768 (e1, e2)) groupGenerateKeyPair P256MLKEM768 = do (d1, e1) <- fs' $ curveGenerateKeyPair p256- (e2, d2) <- ML.generate mlkem768+ (e2, d2) <- ML.generateKeyPair mlkem768 return (GroupPri_P256MLKEM768 (d1, d2), GroupPubA_P256MLKEM768 (e1, e2)) groupGenerateKeyPair P384MLKEM1024 = do (d1, e1) <- fs' $ curveGenerateKeyPair p384- (e2, d2) <- ML.generate mlkem1024+ (e2, d2) <- ML.generateKeyPair mlkem1024 return (GroupPri_P384MLKEM1024 (d1, d2), GroupPubA_P384MLKEM1024 (e1, e2)) groupGenerateKeyPair _ = error "groupGenerateKeyPair" @@ -228,6 +242,17 @@ -> r (PrivateNumber, PublicNumber) gen' params expBits = (id &&& DH.calculatePublic params) <$> generatePriv expBits +-- 'ML.encapsulate' takes the parameter set as a proxy and reports a+-- failure, as every KEM in Crypto.KEM does; ML-KEM has none to report.+mlkemEncap+ :: (MonadRandom r, ML.MLKEM p)+ => proxy p+ -> ML.EncapsulationKey p+ -> r (Maybe (ML.Ciphertext p, GroupKey))+mlkemEncap p ek = fmap f <$> fmap maybeCryptoError (ML.encapsulate p ek)+ where+ f (ct, sec) = (ct, convert sec)+ groupEncapsulate :: MonadRandom r => GroupPublicA -> r (Maybe (GroupPublicB, GroupKey)) groupEncapsulate (GroupPubA_P256 pub) = getECDHPubShared GroupPubB_P256 p256 pub@@ -240,30 +265,45 @@ groupEncapsulate (GroupPubA_FFDHE4096 pub) = getDHPubShared ffdhe4096 exp4096 pub GroupPubB_FFDHE4096 groupEncapsulate (GroupPubA_FFDHE6144 pub) = getDHPubShared ffdhe6144 exp6144 pub GroupPubB_FFDHE6144 groupEncapsulate (GroupPubA_FFDHE8192 pub) = getDHPubShared ffdhe8192 exp8192 pub GroupPubB_FFDHE8192-groupEncapsulate (GroupPubA_MLKEM512 pub) = do- (sec, ct) <- ML.encapsulate pub- return $ Just (GroupPubB_MLKEM512 ct, convert sec)-groupEncapsulate (GroupPubA_MLKEM768 pub) = do- (sec, ct) <- ML.encapsulate pub- return $ Just (GroupPubB_MLKEM768 ct, convert sec)-groupEncapsulate (GroupPubA_MLKEM1024 pub) = do- (sec, ct) <- ML.encapsulate pub- return $ Just (GroupPubB_MLKEM1024 ct, convert sec)-groupEncapsulate (GroupPubA_X25519MLKEM768 (e1, e2)) = do- (c1, k1) <- fromJust <$> getECDHPubShared' x25519 e1- (k2, c2) <- ML.encapsulate e2- -- Sec 4.1: Specifically, the order of shares in the concatenation- -- has been reversed.- return $ Just (GroupPubB_X25519MLKEM768 (c1, c2), convert k2 <> k1)-groupEncapsulate (GroupPubA_P256MLKEM768 (e1, e2)) = do- (c1, k1) <- fromJust <$> getECDHPubShared' p256 e1- (k2, c2) <- ML.encapsulate e2- return $ Just (GroupPubB_P256MLKEM768 (c1, c2), k1 <> convert k2)-groupEncapsulate (GroupPubA_P384MLKEM1024 (e1, e2)) = do- (c1, k1) <- fromJust <$> getECDHPubShared' p384 e1- (k2, c2) <- ML.encapsulate e2- return $ Just (GroupPubB_P384MLKEM1024 (c1, c2), k1 <> convert k2)+groupEncapsulate (GroupPubA_MLKEM512 pub) =+ fmap (\(ct, k) -> (GroupPubB_MLKEM512 ct, k)) <$> mlkemEncap mlkem512 pub+groupEncapsulate (GroupPubA_MLKEM768 pub) =+ fmap (\(ct, k) -> (GroupPubB_MLKEM768 ct, k)) <$> mlkemEncap mlkem768 pub+groupEncapsulate (GroupPubA_MLKEM1024 pub) =+ fmap (\(ct, k) -> (GroupPubB_MLKEM1024 ct, k)) <$> mlkemEncap mlkem1024 pub+-- The classical part of a hybrid can fail as the group alone does: an+-- all-zero X25519 public key decodes, but the shared secret derived from+-- it is rejected. Nothing is turned into illegal_parameter by the caller.+groupEncapsulate (GroupPubA_X25519MLKEM768 (e1, e2)) =+ hybrid x25519 mlkem768 e1 e2 $ \c1 k1 c2 k2 ->+ -- Sec 4.1: Specifically, the order of shares in the concatenation+ -- has been reversed.+ (GroupPubB_X25519MLKEM768 (c1, c2), k2 <> k1)+groupEncapsulate (GroupPubA_P256MLKEM768 (e1, e2)) =+ hybrid p256 mlkem768 e1 e2 $ \c1 k1 c2 k2 ->+ (GroupPubB_P256MLKEM768 (c1, c2), k1 <> k2)+groupEncapsulate (GroupPubA_P384MLKEM1024 (e1, e2)) =+ hybrid p384 mlkem1024 e1 e2 $ \c1 k1 c2 k2 ->+ (GroupPubB_P384MLKEM1024 (c1, c2), k1 <> k2) +-- Either half can refuse: the classical one rejects a peer value that would+-- make its secret degenerate, which the caller turns into+-- illegal_parameter. ML-KEM has nothing to refuse, but says so the same+-- way, so both are read alike here.+hybrid+ :: (MonadRandom r, EllipticCurveDH curve, ML.MLKEM p)+ => Proxy curve+ -> Proxy p+ -> Point curve+ -> ML.EncapsulationKey p+ -> (Point curve -> GroupKey -> ML.Ciphertext p -> GroupKey -> (GroupPublicB, GroupKey))+ -> r (Maybe (GroupPublicB, GroupKey))+hybrid pc pk e1 e2 k = do+ mx <- getECDHPubShared' pc e1+ case mx of+ Nothing -> return Nothing+ Just (c1, k1) -> fmap (\(c2, k2) -> k c1 k1 c2 k2) <$> mlkemEncap pk e2+ dhGroupGetPubShared :: MonadRandom r => Group -> PublicNumber -> r (Maybe (PublicNumber, GroupKey)) dhGroupGetPubShared FFDHE2048 pub = getDHPubShared' ffdhe2048 exp2048 pub@@ -339,22 +379,22 @@ groupDecapsulate (GroupPubB_FFDHE6144 pub) (GroupPri_FFDHE6144 pri) = calcDHShared ffdhe6144 pub pri groupDecapsulate (GroupPubB_FFDHE8192 pub) (GroupPri_FFDHE8192 pri) = calcDHShared ffdhe8192 pub pri groupDecapsulate (GroupPubB_MLKEM512 p) (GroupPri_MLKEM512 s) =- Just $ convert $ ML.decapsulate s p+ convert <$> maybeCryptoError (ML.decapsulate mlkem512 s p) groupDecapsulate (GroupPubB_MLKEM768 p) (GroupPri_MLKEM768 s) =- Just $ convert $ ML.decapsulate s p+ convert <$> maybeCryptoError (ML.decapsulate mlkem768 s p) groupDecapsulate (GroupPubB_MLKEM1024 p) (GroupPri_MLKEM1024 s) =- Just $ convert $ ML.decapsulate s p+ convert <$> maybeCryptoError (ML.decapsulate mlkem1024 s p) groupDecapsulate (GroupPubB_X25519MLKEM768 (p1, p2)) (GroupPri_X25519MLKEM768 (s1, s2)) = do bs1 <- (unwrap <$>) . maybeCryptoError $ deriveDecrypt x25519 p1 s1- let bs2 = convert $ ML.decapsulate s2 p2+ bs2 <- convert <$> maybeCryptoError (ML.decapsulate mlkem768 s2 p2) return (bs2 <> bs1) groupDecapsulate (GroupPubB_P256MLKEM768 (p1, p2)) (GroupPri_P256MLKEM768 (s1, s2)) = do bs1 <- (unwrap <$>) . maybeCryptoError $ deriveDecrypt p256 p1 s1- let bs2 = convert $ ML.decapsulate s2 p2+ bs2 <- convert <$> maybeCryptoError (ML.decapsulate mlkem768 s2 p2) return (bs1 <> bs2) groupDecapsulate (GroupPubB_P384MLKEM1024 (p1, p2)) (GroupPri_P384MLKEM1024 (s1, s2)) = do bs1 <- (unwrap <$>) . maybeCryptoError $ deriveDecrypt p384 p1 s1- let bs2 = convert $ ML.decapsulate s2 p2+ bs2 <- convert <$> maybeCryptoError (ML.decapsulate mlkem1024 s2 p2) return (bs1 <> bs2) groupDecapsulate _ _ = Nothing @@ -376,15 +416,15 @@ groupEncodePublicA (GroupPubA_FFDHE4096 p) = enc ffdhe4096 p groupEncodePublicA (GroupPubA_FFDHE6144 p) = enc ffdhe6144 p groupEncodePublicA (GroupPubA_FFDHE8192 p) = enc ffdhe8192 p-groupEncodePublicA (GroupPubA_MLKEM512 p) = ML.encode p-groupEncodePublicA (GroupPubA_MLKEM768 p) = ML.encode p-groupEncodePublicA (GroupPubA_MLKEM1024 p) = ML.encode p+groupEncodePublicA (GroupPubA_MLKEM512 p) = BA.convert p+groupEncodePublicA (GroupPubA_MLKEM768 p) = BA.convert p+groupEncodePublicA (GroupPubA_MLKEM1024 p) = BA.convert p groupEncodePublicA (GroupPubA_X25519MLKEM768 (p1, p2)) =- ML.encode p2 <> encodePoint x25519 p1+ BA.convert p2 <> encodePoint x25519 p1 groupEncodePublicA (GroupPubA_P256MLKEM768 (p1, p2)) =- encodePoint p256 p1 <> ML.encode p2+ encodePoint p256 p1 <> BA.convert p2 groupEncodePublicA (GroupPubA_P384MLKEM1024 (p1, p2)) =- encodePoint p384 p1 <> ML.encode p2+ encodePoint p384 p1 <> BA.convert p2 groupEncodePublicB :: GroupPublicB -> ByteString groupEncodePublicB (GroupPubB_P256 p) = encodePoint p256 p@@ -421,32 +461,32 @@ groupDecodePublicA FFDHE4096 bs = Right . GroupPubA_FFDHE4096 . PublicNumber $ os2ip bs groupDecodePublicA FFDHE6144 bs = Right . GroupPubA_FFDHE6144 . PublicNumber $ os2ip bs groupDecodePublicA FFDHE8192 bs = Right . GroupPubA_FFDHE8192 . PublicNumber $ os2ip bs-groupDecodePublicA MLKEM512 bs = case ML.decode mlkem512 bs of+groupDecodePublicA MLKEM512 bs = case decodeEK mlkem512 bs of Nothing -> Left CryptoError_PointFormatInvalid Just p -> Right $ GroupPubA_MLKEM512 p-groupDecodePublicA MLKEM768 bs = case ML.decode mlkem768 bs of+groupDecodePublicA MLKEM768 bs = case decodeEK mlkem768 bs of Nothing -> Left CryptoError_PointFormatInvalid Just p -> Right $ GroupPubA_MLKEM768 p-groupDecodePublicA MLKEM1024 bs = case ML.decode mlkem1024 bs of+groupDecodePublicA MLKEM1024 bs = case decodeEK mlkem1024 bs of Nothing -> Left CryptoError_PointFormatInvalid Just p -> Right $ GroupPubA_MLKEM1024 p groupDecodePublicA X25519MLKEM768 bs = let (bs1, bs2) = BA.splitAt 1184 bs- in case ML.decode mlkem768 bs1 of+ in case decodeEK mlkem768 bs1 of Nothing -> Left CryptoError_PointFormatInvalid Just p1 -> case maybeCryptoError $ decodePoint x25519 bs2 of Nothing -> Left CryptoError_PointFormatInvalid Just p2 -> Right $ GroupPubA_X25519MLKEM768 (p2, p1) groupDecodePublicA P256MLKEM768 bs = let (bs1, bs2) = BA.splitAt 65 bs- in case ML.decode mlkem768 bs2 of+ in case decodeEK mlkem768 bs2 of Nothing -> Left CryptoError_PointFormatInvalid Just p1 -> case maybeCryptoError $ decodePoint p256 bs1 of Nothing -> Left CryptoError_PointFormatInvalid Just p2 -> Right $ GroupPubA_P256MLKEM768 (p2, p1) groupDecodePublicA P384MLKEM1024 bs = let (bs1, bs2) = BA.splitAt 97 bs- in case ML.decode mlkem1024 bs2 of+ in case decodeEK mlkem1024 bs2 of Nothing -> Left CryptoError_PointFormatInvalid Just p1 -> case maybeCryptoError $ decodePoint p384 bs1 of Nothing -> Left CryptoError_PointFormatInvalid@@ -464,32 +504,32 @@ groupDecodePublicB FFDHE4096 bs = Right . GroupPubB_FFDHE4096 . PublicNumber $ os2ip bs groupDecodePublicB FFDHE6144 bs = Right . GroupPubB_FFDHE6144 . PublicNumber $ os2ip bs groupDecodePublicB FFDHE8192 bs = Right . GroupPubB_FFDHE8192 . PublicNumber $ os2ip bs-groupDecodePublicB MLKEM512 bs = case ML.decode mlkem512 bs of+groupDecodePublicB MLKEM512 bs = case decodeCT mlkem512 bs of Nothing -> Left CryptoError_PointFormatInvalid Just p -> Right $ GroupPubB_MLKEM512 p-groupDecodePublicB MLKEM768 bs = case ML.decode mlkem768 bs of+groupDecodePublicB MLKEM768 bs = case decodeCT mlkem768 bs of Nothing -> Left CryptoError_PointFormatInvalid Just p -> Right $ GroupPubB_MLKEM768 p-groupDecodePublicB MLKEM1024 bs = case ML.decode mlkem1024 bs of+groupDecodePublicB MLKEM1024 bs = case decodeCT mlkem1024 bs of Nothing -> Left CryptoError_PointFormatInvalid Just p -> Right $ GroupPubB_MLKEM1024 p groupDecodePublicB X25519MLKEM768 bs = let (bs1, bs2) = BA.splitAt 1088 bs- in case ML.decode mlkem768 bs1 of+ in case decodeCT mlkem768 bs1 of Nothing -> Left CryptoError_PointFormatInvalid Just p1 -> case maybeCryptoError $ decodePoint x25519 bs2 of Nothing -> Left CryptoError_PointFormatInvalid Just p2 -> Right $ GroupPubB_X25519MLKEM768 (p2, p1) groupDecodePublicB P256MLKEM768 bs = let (bs1, bs2) = BA.splitAt 65 bs- in case ML.decode mlkem768 bs2 of+ in case decodeCT mlkem768 bs2 of Nothing -> Left CryptoError_PointFormatInvalid Just p1 -> case maybeCryptoError $ decodePoint p256 bs1 of Nothing -> Left CryptoError_PointFormatInvalid Just p2 -> Right $ GroupPubB_P256MLKEM768 (p2, p1) groupDecodePublicB P384MLKEM1024 bs = let (bs1, bs2) = BA.splitAt 97 bs- in case ML.decode mlkem1024 bs2 of+ in case decodeCT mlkem1024 bs2 of Nothing -> Left CryptoError_PointFormatInvalid Just p1 -> case maybeCryptoError $ decodePoint p384 bs1 of Nothing -> Left CryptoError_PointFormatInvalid
Network/TLS/Error.hs view
@@ -3,8 +3,7 @@ module Network.TLS.Error where -import Control.Exception (Exception (..))-import Data.Typeable+import qualified Control.Exception as E import Network.TLS.Imports @@ -34,7 +33,7 @@ | Error_Packet_unexpected String String | Error_Packet_Parsing String | Error_TCP_Terminate- deriving (Eq, Show, Typeable)+ deriving (Eq, Show) ---------------------------------------------------------------- @@ -61,9 +60,9 @@ -- handshake had occurred. -- Indicates that this library has been used incorrectly. MissingHandshake- deriving (Show, Eq, Typeable)+ deriving (Show, Eq) -instance Exception TLSException+instance E.Exception TLSException ----------------------------------------------------------------
Network/TLS/Extension.hs view
@@ -428,7 +428,22 @@ -- | Extension class to transform bytes to and from a high level Extension type. class Extension a where extensionID :: a -> ExtensionID++ -- | Decode an extension's body as it appears in the given message.+ --+ -- 'Nothing' covers both ways this can fail to produce a value: a body+ -- that does not parse, and a message the extension is not defined in.+ -- Both reach the peer the same way, as the decode_error alert that+ -- 'lookupAndDecode' and 'lookupAndDecodeAndDo' raise, which is what+ -- either case warrants.+ --+ -- So the last clause of an instance is @Nothing@, never @error@: the+ -- message type is chosen by this library rather than by the peer, so an+ -- unhandled one would be our own bug -- and turning our bug into an+ -- ErrorCall thrown from pure code, out through the handshake and into+ -- the application, is a worse answer than dropping the one connection. extensionDecode :: MessageType -> ByteString -> Maybe a+ extensionEncode :: a -> ByteString data MessageType@@ -438,12 +453,12 @@ | MsgTEncryptedExtensions | MsgTNewSessionTicket | MsgTCertificateRequest- deriving (Eq, Show)+ deriving (Eq, Show, Enum, Bounded) ------------------------------------------------------------ -- | Server Name extension including the name type and the associated name.--- the associated name decoding is dependant of its name type.+-- the associated name decoding is dependent of its name type. -- name type = 0 : hostname newtype ServerName = ServerName [ServerNameType] deriving (Show, Eq) @@ -466,21 +481,32 @@ where encodeNameType (ServerNameHostName hn) = putWord8 0 >> putOpaque16 (BC.pack hn) -- FIXME: should be puny code conversion encodeNameType (ServerNameOther (nt, opaque)) = putWord8 nt >> putBytes opaque- extensionDecode MsgTClientHello = decodeServerName+ extensionDecode MsgTClientHello = decodeServerNameList extensionDecode MsgTServerHello = decodeServerName extensionDecode MsgTEncryptedExtensions = decodeServerName- extensionDecode _ = error "extensionDecode: ServerName"+ extensionDecode _ = const Nothing decodeServerName :: ByteString -> Maybe ServerName decodeServerName "" = Just $ ServerName [] -- dirty hack for servers-decodeServerName bs = runGetMaybe decode bs+decodeServerName bs = decodeServerNameList bs++-- RFC 6066 Section 3: server_name_list<1..2^16-1> of+-- HostName<1..2^16-1>, which leaves no room for an empty extension, an+-- empty list, an empty host name nor trailing data.+decodeServerNameList :: ByteString -> Maybe ServerName+decodeServerNameList = runGetMaybe decode where decode = do len <- fromIntegral <$> getWord16- ServerName <$> getList len getServerName+ names <- getList len getServerName+ when (null names) $ fail "empty server_name_list"+ r <- remaining+ when (r /= 0) $ fail "trailing data in server_name"+ return $ ServerName names getServerName = do ty <- getWord8 snameParsed <- getOpaque16+ when (ty == 0 && B.null snameParsed) $ fail "empty host_name" let sname = B.copy snameParsed name = case ty of 0 -> ServerNameHostName $ BC.unpack sname -- FIXME: should be puny code conversion@@ -521,7 +547,7 @@ extensionDecode MsgTClientHello = decodeMaxFragmentLength extensionDecode MsgTServerHello = decodeMaxFragmentLength extensionDecode MsgTEncryptedExtensions = decodeMaxFragmentLength- extensionDecode _ = error "extensionDecode: MaxFragmentLength"+ extensionDecode _ = const Nothing decodeMaxFragmentLength :: ByteString -> Maybe MaxFragmentLength decodeMaxFragmentLength = runGetMaybe $ toMaxFragmentEnum <$> getWord8@@ -542,7 +568,7 @@ extensionEncode (SupportedGroups groups) = runPut $ putWords16 $ map (\(Group g) -> g) groups extensionDecode MsgTClientHello = decodeSupportedGroups extensionDecode MsgTEncryptedExtensions = decodeSupportedGroups- extensionDecode _ = error "extensionDecode: SupportedGroups"+ extensionDecode _ = const Nothing decodeSupportedGroups :: ByteString -> Maybe SupportedGroups decodeSupportedGroups =@@ -577,11 +603,14 @@ extensionEncode (EcPointFormatsSupported formats) = runPut $ putWords8 $ map fromEcPointFormat formats extensionDecode MsgTClientHello = decodeEcPointFormatsSupported extensionDecode MsgTServerHello = decodeEcPointFormatsSupported- extensionDecode _ = error "extensionDecode: EcPointFormatsSupported"+ extensionDecode _ = const Nothing decodeEcPointFormatsSupported :: ByteString -> Maybe EcPointFormatsSupported-decodeEcPointFormatsSupported =- runGetMaybe (EcPointFormatsSupported . map EcPointFormat <$> getWords8)+decodeEcPointFormatsSupported = runGetMaybe $ do+ formats <- getWords8+ -- RFC 8422 Section 5.1.2: ec_point_format_list<1..2^8-1>+ when (null formats) $ fail "empty ec_point_format_list"+ return $ EcPointFormatsSupported $ map EcPointFormat formats ------------------------------------------------------------ @@ -596,7 +625,7 @@ >> mapM_ putSignatureHashAlgorithm algs extensionDecode MsgTClientHello = decodeSignatureAlgorithms extensionDecode MsgTCertificateRequest = decodeSignatureAlgorithms- extensionDecode _ = error "extensionDecode: SignatureAlgorithms"+ extensionDecode _ = const Nothing decodeSignatureAlgorithms :: ByteString -> Maybe SignatureAlgorithms decodeSignatureAlgorithms = runGetMaybe $ do@@ -632,7 +661,7 @@ extensionEncode (HeartBeat mode) = runPut $ putWord8 $ fromHeartBeatMode mode extensionDecode MsgTClientHello = decodeHeartBeat extensionDecode MsgTServerHello = decodeHeartBeat- extensionDecode _ = error "extensionDecode: HeartBeat"+ extensionDecode _ = const Nothing decodeHeartBeat :: ByteString -> Maybe HeartBeat decodeHeartBeat = runGetMaybe $ HeartBeat . HeartBeatMode <$> getWord8@@ -651,16 +680,23 @@ extensionDecode MsgTClientHello = decodeApplicationLayerProtocolNegotiation extensionDecode MsgTServerHello = decodeApplicationLayerProtocolNegotiation extensionDecode MsgTEncryptedExtensions = decodeApplicationLayerProtocolNegotiation- extensionDecode _ = error "extensionDecode: ApplicationLayerProtocolNegotiation"+ extensionDecode _ = const Nothing decodeApplicationLayerProtocolNegotiation :: ByteString -> Maybe ApplicationLayerProtocolNegotiation decodeApplicationLayerProtocolNegotiation = runGetMaybe $ do len <- getWord16- ApplicationLayerProtocolNegotiation <$> getList (fromIntegral len) getALPN+ protos <- getList (fromIntegral len) getALPN+ -- RFC 7301 Section 3.1: protocol_name_list<2..2^16-1> of+ -- ProtocolName<1..2^8-1>, with nothing after it.+ when (null protos) $ fail "empty protocol_name_list"+ r <- remaining+ when (r /= 0) $ fail "trailing data in application_layer_protocol_negotiation"+ return $ ApplicationLayerProtocolNegotiation protos where getALPN = do alpnParsed <- getOpaque8+ when (B.null alpnParsed) $ fail "empty ProtocolName" let alpn = B.copy alpnParsed return (B.length alpn + 1, alpn) @@ -674,7 +710,7 @@ extensionEncode ExtendedMainSecret = B.empty extensionDecode MsgTClientHello "" = Just ExtendedMainSecret extensionDecode MsgTServerHello "" = Just ExtendedMainSecret- extensionDecode _ _ = error "extensionDecode: ExtendedMainSecret"+ extensionDecode _ _ = Nothing ------------------------------------------------------------ @@ -749,7 +785,7 @@ extensionEncode (SessionTicket ticket) = runPut $ putBytes ticket extensionDecode MsgTClientHello = decodeSessionTicket extensionDecode MsgTServerHello = decodeSessionTicket- extensionDecode _ = error "extensionDecode: SessionTicket"+ extensionDecode _ = const Nothing decodeSessionTicket :: ByteString -> Maybe SessionTicket decodeSessionTicket = runGetMaybe $ SessionTicket <$> (remaining >>= getBytes)@@ -792,7 +828,7 @@ fromIntegral w16 extensionDecode MsgTClientHello = decodePreSharedKeyClientHello extensionDecode MsgTServerHello = decodePreSharedKeyServerHello- extensionDecode _ = error "extensionDecode: PreShareKey"+ extensionDecode _ = const Nothing decodePreSharedKeyClientHello :: ByteString -> Maybe PreSharedKey decodePreSharedKeyClientHello = runGetMaybe $ do@@ -837,7 +873,7 @@ extensionDecode MsgTNewSessionTicket = runGetMaybe $ EarlyDataIndication . Just <$> getWord32- extensionDecode _ = error "extensionDecode: EarlyDataIndication"+ extensionDecode _ = const Nothing ------------------------------------------------------------ @@ -860,7 +896,7 @@ putBinaryVersion ver extensionDecode MsgTClientHello = decodeSupportedVersionsClientHello extensionDecode MsgTServerHello = decodeSupportedVersionsServerHello- extensionDecode _ = error "extensionDecode: SupportedVersionsServerHello"+ extensionDecode _ = const Nothing decodeSupportedVersionsClientHello :: ByteString -> Maybe SupportedVersions decodeSupportedVersionsClientHello = runGetMaybe $ do@@ -888,7 +924,7 @@ extensionID _ = EID_Cookie extensionEncode (Cookie opaque) = runPut $ putOpaque16 opaque extensionDecode MsgTServerHello = runGetMaybe (Cookie <$> getOpaque16)- extensionDecode _ = error "extensionDecode: Cookie"+ extensionDecode _ = const Nothing ------------------------------------------------------------ @@ -916,7 +952,7 @@ putWords8 $ map fromPskKexMode pkms extensionDecode MsgTClientHello = decodePskKeyExchangeModes- extensionDecode _ = error "extensionDecode: PskKeyExchangeModes"+ extensionDecode _ = const Nothing decodePskKeyExchangeModes :: ByteString -> Maybe PskKeyExchangeModes decodePskKeyExchangeModes =@@ -935,7 +971,7 @@ putDNames names extensionDecode MsgTClientHello = decodeCertificateAuthorities extensionDecode MsgTCertificateRequest = decodeCertificateAuthorities- extensionDecode _ = error "extensionDecode: CertificateAuthorities"+ extensionDecode _ = const Nothing decodeCertificateAuthorities :: ByteString -> Maybe CertificateAuthorities decodeCertificateAuthorities =@@ -949,7 +985,7 @@ extensionID _ = EID_PostHandshakeAuth extensionEncode _ = B.empty extensionDecode MsgTClientHello = runGetMaybe $ return PostHandshakeAuth- extensionDecode _ = error "extensionDecode: PostHandshakeAuth"+ extensionDecode _ = const Nothing ------------------------------------------------------------ @@ -964,7 +1000,7 @@ >> mapM_ putSignatureHashAlgorithm algs extensionDecode MsgTClientHello = decodeSignatureAlgorithmsCert extensionDecode MsgTCertificateRequest = decodeSignatureAlgorithmsCert- extensionDecode _ = error "extensionDecode: SignatureAlgorithmsCert"+ extensionDecode _ = const Nothing decodeSignatureAlgorithmsCert :: ByteString -> Maybe SignatureAlgorithmsCert decodeSignatureAlgorithmsCert = runGetMaybe $ do@@ -1021,7 +1057,7 @@ extensionDecode MsgTClientHello = decodeKeyShareClientHello extensionDecode MsgTServerHello = decodeKeyShareServerHello extensionDecode MsgTHelloRetryRequest = decodeKeyShareHRR- extensionDecode _ = error "extensionDecode: KeyShare"+ extensionDecode _ = const Nothing decodeKeyShareClientHello :: ByteString -> Maybe KeyShare decodeKeyShareClientHello = runGetMaybe $ do@@ -1059,7 +1095,7 @@ putWord8 $ fromIntegral (length ids * 2) mapM_ (putWord16 . fromExtensionID) ids extensionDecode MsgTClientHello = decodeEchOuterExtensions- extensionDecode _ = error "extensionDecode: EchOuterExtensions"+ extensionDecode _ = const Nothing decodeEchOuterExtensions :: ByteString -> Maybe EchOuterExtensions decodeEchOuterExtensions = runGetMaybe $ do@@ -1120,7 +1156,7 @@ extensionDecode MsgTClientHello = decodeECHClientHello extensionDecode MsgTEncryptedExtensions = decodeECHEncryptedExtensions extensionDecode MsgTHelloRetryRequest = decodeECHHelloRetryRequest- extensionDecode _ = error "extensionDecode: EncryptedClientHello"+ extensionDecode _ = const Nothing decodeECH :: ByteString -> Maybe EncryptedClientHello decodeECH bs =@@ -1172,4 +1208,4 @@ opaque <- getOpaque8 let (cvd, svd) = B.splitAt (B.length opaque `div` 2) opaque return $ SecureRenegotiation cvd svd- extensionDecode _ = error "extensionDecode: SecureRenegotiation"+ extensionDecode _ = const Nothing
Network/TLS/Extra/Cipher.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} module Network.TLS.Extra.Cipher ( -- * Cipher suite ciphersuite_default,@@ -70,11 +71,12 @@ ) where import Crypto.Cipher.AES-import qualified Crypto.Cipher.ChaChaPoly1305 as ChaChaPoly1305+import qualified Crypto.Cipher.AES.GCM as GCM+import qualified Crypto.Cipher.ChaCha.Poly1305 as ChaChaOne import Crypto.Cipher.Types hiding (Cipher, cipherName) import Crypto.Error-import qualified Crypto.MAC.Poly1305 as Poly1305 import Crypto.System.CPU+import Data.ByteArray (convert) import qualified Data.ByteString as B import Data.Tuple (swap) @@ -572,9 +574,22 @@ sortOptimized :: [CipherSet] -> [Cipher] sortOptimized = concatMap f where+ -- crypton 2.2 gives the AArch64 instructions their own names. Before+ -- it, the ARMv8 path set the slots Haskell read as AESNI and PCLMUL, so+ -- asking for those names answered "is AES fast here" on both+ -- architectures; from 2.2 they are x86's names, which an AArch64 machine+ -- does not have. hasAESAcceleration and hasGHASHAcceleration ask the+ -- question without naming an instruction set. Either spelling compiles+ -- against either version -- the names are pattern synonyms now -- so+ -- nothing but this guard says which one is right for which. f (SetAead gcm chacha ccm)+#if MIN_VERSION_crypton(2,2,0)+ | not hasAESAcceleration = chacha ++ gcm ++ ccm+ | not hasGHASHAcceleration = ccm ++ chacha ++ gcm+#else | AESNI `notElem` processorOptions = chacha ++ gcm ++ ccm | PCLMUL `notElem` processorOptions = ccm ++ chacha ++ gcm+#endif | otherwise = gcm ++ ccm ++ chacha f (SetOther ciphers) = ciphers @@ -619,20 +634,40 @@ in simpleDecrypt aeadIni ad d 8 ) +-- The AES-GCM and ChaCha20-Poly1305 ciphers go through the one-call+-- interfaces crypton added for this, not the general AEAD one. Two things+-- are saved on every record.+--+-- The state the key alone determines -- the AES key schedule and the table of+-- multiples of H -- was rebuilt for each record by aeadInit; newContext+-- builds it once, here, where the key is fixed.+--+-- And the general interface reaches its cipher through AEADModeImpl, whose+-- fields are @forall ba. ByteArray ba => ...@, so a dictionary is passed at+-- every call and no pragma can remove it.+--+-- Measured in C on an idle Haswell, against what this did before: 64-byte+-- record 0.169 -> 0.047 microseconds, 1400-byte 0.501 -> 0.301, 16 KiB+-- 3.285 -> 3.207. It is a fixed cost that goes, so it is most of a small+-- record and little of a full one.+aesgcm :: BulkDirection -> BulkKey -> BulkAEAD+aesgcm BulkEncrypt key =+ let ctx = noFail (GCM.newContext key)+ in \nonce d ad ->+ let sealed = GCM.encrypt ctx nonce ad d 16+ (out, tag) = B.splitAt (B.length sealed - 16) sealed+ in (out, AuthTag (convert tag))+aesgcm BulkDecrypt key =+ let ctx = noFail (GCM.newContext key)+ in \nonce d ad -> GCM.decryptWithTag ctx nonce ad d 16+ aes128gcm :: BulkDirection -> BulkKey -> BulkAEAD-aes128gcm BulkEncrypt key =- let ctx = noFail (cipherInit key) :: AES128- in ( \nonce d ad ->- let aeadIni = noFail (aeadInit AEAD_GCM ctx nonce)- in swap $ aeadSimpleEncrypt aeadIni ad d 16- )-aes128gcm BulkDecrypt key =- let ctx = noFail (cipherInit key) :: AES128- in ( \nonce d ad ->- let aeadIni = noFail (aeadInit AEAD_GCM ctx nonce)- in simpleDecrypt aeadIni ad d 16- )+aes128gcm = aesgcm +aes256gcm :: BulkDirection -> BulkKey -> BulkAEAD+aes256gcm = aesgcm++ aes256ccm :: BulkDirection -> BulkKey -> BulkAEAD aes256ccm BulkEncrypt key = let ctx = noFail (cipherInit key) :: AES256@@ -665,20 +700,6 @@ in simpleDecrypt aeadIni ad d 8 ) -aes256gcm :: BulkDirection -> BulkKey -> BulkAEAD-aes256gcm BulkEncrypt key =- let ctx = noFail (cipherInit key) :: AES256- in ( \nonce d ad ->- let aeadIni = noFail (aeadInit AEAD_GCM ctx nonce)- in swap $ aeadSimpleEncrypt aeadIni ad d 16- )-aes256gcm BulkDecrypt key =- let ctx = noFail (cipherInit key) :: AES256- in ( \nonce d ad ->- let aeadIni = noFail (aeadInit AEAD_GCM ctx nonce)- in simpleDecrypt aeadIni ad d 16- )- simpleDecrypt :: AEAD cipher -> ByteString -> ByteString -> Int -> (ByteString, AuthTag) simpleDecrypt aeadIni header input taglen = (output, tag)@@ -690,23 +711,23 @@ noFail :: CryptoFailable a -> a noFail = throwCryptoError +-- The one-call interface here too, and for the same two reasons as the AES+-- ciphers above: the step-at-a-time Crypto.Cipher.ChaChaPoly1305 is eight+-- foreign calls and the allocations between them for a message that arrived+-- whole, and the general AEAD interface passes a dictionary a call.+--+-- Measured through the Haskell interface on an Apple M4: 100 bytes 0.97 ->+-- 0.415 microseconds, 1400 bytes 2.89 -> 2.36. chacha20poly1305 :: BulkDirection -> BulkKey -> BulkAEAD-chacha20poly1305 BulkEncrypt key nonce =- let st = noFail (ChaChaPoly1305.nonce12 nonce >>= ChaChaPoly1305.initialize key)- in ( \input ad ->- let st2 = ChaChaPoly1305.finalizeAAD (ChaChaPoly1305.appendAAD ad st)- (output, st3) = ChaChaPoly1305.encrypt input st2- Poly1305.Auth tag = ChaChaPoly1305.finalize st3- in (output, AuthTag tag)- )-chacha20poly1305 BulkDecrypt key nonce =- let st = noFail (ChaChaPoly1305.nonce12 nonce >>= ChaChaPoly1305.initialize key)- in ( \input ad ->- let st2 = ChaChaPoly1305.finalizeAAD (ChaChaPoly1305.appendAAD ad st)- (output, st3) = ChaChaPoly1305.decrypt input st2- Poly1305.Auth tag = ChaChaPoly1305.finalize st3- in (output, AuthTag tag)- )+chacha20poly1305 BulkEncrypt key =+ let ctx = noFail (ChaChaOne.newContext key)+ in \nonce d ad ->+ let sealed = noFail (ChaChaOne.encrypt ctx nonce ad d 16)+ (out, tag) = B.splitAt (B.length sealed - 16) sealed+ in (out, AuthTag (convert tag))+chacha20poly1305 BulkDecrypt key =+ let ctx = noFail (ChaChaOne.newContext key)+ in \nonce d ad -> noFail (ChaChaOne.decryptWithTag ctx nonce ad d 16) ----------------------------------------------------------------
+ Network/TLS/Extra/CipherCBC.hs view
@@ -0,0 +1,201 @@+module Network.TLS.Extra.CipherCBC (+ -- * TLS 1.2 CBC ciphers with PFS and SHA2+ ciphersuite_pfs_sha2_cbc,+ ciphersuite_ecdhe_sha2_cbc,+ ciphersuite_dhe_rsa_sha2_cbc,++ -- ** Individual CBC ciphers+ cipher_DHE_RSA_AES128_SHA256,+ cipher_DHE_RSA_AES256_SHA256,+ cipher_ECDHE_RSA_AES128CBC_SHA256,+ cipher_ECDHE_RSA_AES256CBC_SHA384,+ cipher_ECDHE_ECDSA_AES128CBC_SHA256,+) where++import Crypto.Cipher.AES+import Crypto.Cipher.Types hiding (Cipher, cipherName)+import Crypto.Error+-- import Crypto.System.CPU+import qualified Data.ByteString as B++import Network.TLS.Cipher+import Network.TLS.Imports+import Network.TLS.Types hiding (IV)++----------------------------------------------------------------++-- | TLS 1.2 AES CBC ciphers with DHE or ECDHE key exchange, ECDSA or RSA+-- authentication and a SHA256 or SHA2384 MAC.+-- For legacy applications only, deprecated in HTTPS.+ciphersuite_pfs_sha2_cbc :: [Cipher]+ciphersuite_pfs_sha2_cbc =+ [ cipher_ECDHE_ECDSA_AES128CBC_SHA256+ , cipher_ECDHE_ECDSA_AES256CBC_SHA384+ , cipher_ECDHE_RSA_AES128CBC_SHA256+ , cipher_ECDHE_RSA_AES256CBC_SHA384+ , cipher_DHE_RSA_AES128_SHA256+ , cipher_DHE_RSA_AES256_SHA256+ ]++-- | TLS 1.2 AES CBC ciphers with ECDHE key exchange, ECDSA or RSA+-- authentication and a SHA256 or SHA2384 MAC.+-- For legacy applications only, deprecated in HTTPS.+ciphersuite_ecdhe_sha2_cbc :: [Cipher]+ciphersuite_ecdhe_sha2_cbc =+ [ cipher_ECDHE_ECDSA_AES128CBC_SHA256+ , cipher_ECDHE_ECDSA_AES256CBC_SHA384+ , cipher_ECDHE_RSA_AES128CBC_SHA256+ , cipher_ECDHE_RSA_AES256CBC_SHA384+ ]++-- | TLS 1.2 AES CBC ciphers with DHE key exchange, RSA authentication and a+-- SHA256 MAC.+-- For legacy applications only, deprecated in HTTPS.+ciphersuite_dhe_rsa_sha2_cbc :: [Cipher]+ciphersuite_dhe_rsa_sha2_cbc =+ [ cipher_DHE_RSA_AES256_SHA256+ , cipher_DHE_RSA_AES128_SHA256+ ]++----------------------------------------------------------------++-- | TLS 1.2 AES128 CBC, with DHE key exchange, RSA authentication and a SHA256 MAC.+-- For legacy applications only, deprecated in HTTPS.+cipher_DHE_RSA_AES128_SHA256 :: Cipher+cipher_DHE_RSA_AES128_SHA256 =+ Cipher+ { cipherID = 0x0067+ , cipherName = "DHE-RSA-AES128-SHA256"+ , cipherBulk = bulk_aes128+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_DHE_RSA+ , cipherMinVer = Just TLS12 -- RFC 5288 Sec 4+ }++-- | TLS 1.2 AES256 CBC, with DHE key exchange, RSA authentication and a SHA256 MAC.+-- For legacy applications only, deprecated in HTTPS.+cipher_DHE_RSA_AES256_SHA256 :: Cipher+cipher_DHE_RSA_AES256_SHA256 =+ cipher_DHE_RSA_AES128_SHA256+ { cipherID = 0x006B+ , cipherName = "DHE-RSA-AES256-SHA256"+ , cipherBulk = bulk_aes256+ }++-- | TLS 1.2 AES128 CBC, with ECDHE key exchange, RSA authentication and a SHA256 MAC.+-- For legacy applications only, deprecated in HTTPS.+cipher_ECDHE_RSA_AES128CBC_SHA256 :: Cipher+cipher_ECDHE_RSA_AES128CBC_SHA256 =+ Cipher+ { cipherID = 0xC027+ , cipherName = "ECDHE-RSA-AES128CBC-SHA256"+ , cipherBulk = bulk_aes128+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_ECDHE_RSA+ , cipherMinVer = Just TLS12 -- RFC 5288 Sec 4+ }++-- | TLS 1.2 AES256 CBC, with ECDHE key exchange, RSA authentication and a SHA384 MAC.+-- For legacy applications only, deprecated in HTTPS.+cipher_ECDHE_RSA_AES256CBC_SHA384 :: Cipher+cipher_ECDHE_RSA_AES256CBC_SHA384 =+ Cipher+ { cipherID = 0xC028+ , cipherName = "ECDHE-RSA-AES256CBC-SHA384"+ , cipherBulk = bulk_aes256+ , cipherHash = SHA384+ , cipherPRFHash = Just SHA384+ , cipherKeyExchange = CipherKeyExchange_ECDHE_RSA+ , cipherMinVer = Just TLS12 -- RFC 5288 Sec 4+ }++-- | TLS 1.2 AES128 CBC, with ECDHE key exchange, ECDSA authentication and a SHA256 MAC.+-- For legacy applications only, deprecated in HTTPS.+cipher_ECDHE_ECDSA_AES128CBC_SHA256 :: Cipher+cipher_ECDHE_ECDSA_AES128CBC_SHA256 =+ Cipher+ { cipherID = 0xc023+ , cipherName = "ECDHE-ECDSA-AES128CBC-SHA256"+ , cipherBulk = bulk_aes128+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_ECDHE_ECDSA+ , cipherMinVer = Just TLS12 -- RFC 5289+ }++-- | TLS 1.2 AES256 CBC, with ECDHE key exchange, ECDSA authentication and a SHA384 MAC.+-- For legacy applications only, deprecated in HTTPS.+cipher_ECDHE_ECDSA_AES256CBC_SHA384 :: Cipher+cipher_ECDHE_ECDSA_AES256CBC_SHA384 =+ Cipher+ { cipherID = 0xC024+ , cipherName = "ECDHE-ECDSA-AES256CBC-SHA384"+ , cipherBulk = bulk_aes256+ , cipherHash = SHA384+ , cipherPRFHash = Just SHA384+ , cipherKeyExchange = CipherKeyExchange_ECDHE_ECDSA+ , cipherMinVer = Just TLS12 -- RFC 5289+ }++----------------------------------------------------------------++aes128cbc :: BulkDirection -> BulkKey -> BulkBlock+aes128cbc BulkEncrypt key =+ let ctx = noFail (cipherInit key) :: AES128+ in ( \iv input ->+ let output = cbcEncrypt ctx (makeIV_ iv) input in (output, takelast 16 output)+ )+aes128cbc BulkDecrypt key =+ let ctx = noFail (cipherInit key) :: AES128+ in ( \iv input ->+ let output = cbcDecrypt ctx (makeIV_ iv) input in (output, takelast 16 input)+ )++aes256cbc :: BulkDirection -> BulkKey -> BulkBlock+aes256cbc BulkEncrypt key =+ let ctx = noFail (cipherInit key) :: AES256+ in ( \iv input ->+ let output = cbcEncrypt ctx (makeIV_ iv) input in (output, takelast 16 output)+ )+aes256cbc BulkDecrypt key =+ let ctx = noFail (cipherInit key) :: AES256+ in ( \iv input ->+ let output = cbcDecrypt ctx (makeIV_ iv) input in (output, takelast 16 input)+ )++makeIV_ :: BlockCipher a => B.ByteString -> IV a+makeIV_ = fromMaybe (error "makeIV_") . makeIV++takelast :: Int -> B.ByteString -> B.ByteString+takelast i b = B.drop (B.length b - i) b++noFail :: CryptoFailable a -> a+noFail = throwCryptoError++----------------------------------------------------------------++bulk_aes128 :: Bulk+bulk_aes128 =+ Bulk+ { bulkName = "AES128"+ , bulkKeySize = 16+ , bulkIVSize = 16+ , bulkExplicitIV = 0+ , bulkAuthTagLen = 0+ , bulkBlockSize = 16+ , bulkF = BulkBlockF aes128cbc+ }++bulk_aes256 :: Bulk+bulk_aes256 =+ Bulk+ { bulkName = "AES256"+ , bulkKeySize = 32+ , bulkIVSize = 16+ , bulkExplicitIV = 0+ , bulkAuthTagLen = 0+ , bulkBlockSize = 16+ , bulkF = BulkBlockF aes256cbc+ }
Network/TLS/Handshake/Certificate.hs view
@@ -3,13 +3,20 @@ badCertificate, rejectOnException, verifyLeafKeyUsage,+ verifyLeafKeyUsagePurpose, extractCAname, ) where -import Control.Exception (SomeException)+import qualified Control.Exception as E import Control.Monad (unless) import Control.Monad.State.Strict-import Data.X509 (ExtKeyUsage (..), ExtKeyUsageFlag, extensionGet)+import Data.X509 (+ ExtExtendedKeyUsage (..),+ ExtKeyUsage (..),+ ExtKeyUsageFlag,+ ExtKeyUsagePurpose (..),+ extensionGet,+ ) import Network.TLS.Context.Internal import Network.TLS.Struct@@ -31,7 +38,7 @@ badCertificate :: MonadIO m => String -> m a badCertificate msg = throwCore $ Error_Protocol msg BadCertificate -rejectOnException :: SomeException -> IO CertificateUsage+rejectOnException :: E.SomeException -> IO CertificateUsage rejectOnException e = return $ CertificateUsageReject $ CertificateRejectOther $ show e verifyLeafKeyUsage :: MonadIO m => [ExtKeyUsageFlag] -> CertificateChain -> m ()@@ -46,6 +53,19 @@ case extensionGet (certExtensions cert) of Nothing -> True -- unrestricted cert Just (ExtKeyUsage flags) -> any (`elem` validFlags) flags++verifyLeafKeyUsagePurpose+ :: MonadIO m => ExtKeyUsagePurpose -> CertificateChain -> m ()+verifyLeafKeyUsagePurpose _ (CertificateChain []) = return ()+verifyLeafKeyUsagePurpose validPurpose (CertificateChain (signed : _)) =+ unless verified $+ badCertificate $+ "certificate is not allowed for " ++ show validPurpose+ where+ cert = getCertificate signed+ verified = case extensionGet (certExtensions cert) of+ Nothing -> True+ Just (ExtExtendedKeyUsage purposes) -> validPurpose `elem` purposes extractCAname :: SignedCertificate -> DistinguishedName extractCAname cert = certSubjectDN $ getCertificate cert
Network/TLS/Handshake/Client.hs view
@@ -102,7 +102,7 @@ case ver of TLS13 | hrr ->- helloRetry cparams ctx mparams ver crand (grpsSupported \\ grpsSelected)+ helloRetry cparams ctx mparams ver crand grpsSupported grpsSelected | otherwise -> do recvServerSecondFlight13 cparams ctx grpsSelected sendClientSecondFlight13 cparams ctx@@ -126,8 +126,9 @@ -> Version -> ClientRandom -> [Group]+ -> [Group] -> IO ()-helloRetry cparams ctx mparams ver crand groupsSupported = do+helloRetry cparams ctx mparams ver crand groupsSupported groupsSelected = do when (null groupsSupported) $ throwCore $ Error_Protocol "no supported groups on the client side" IllegalParameter@@ -137,18 +138,22 @@ mks <- usingState_ ctx getTLS13KeyShare case mks of Just (KeyShareHRR selectedGroup)- | selectedGroup `elem` groupsSupported -> do+ -- RFC 8446 Sec 4.1.4: the selected_group MUST be in supported_groups+ -- and MUST NOT already have been offered in the initial key_share.+ | selectedGroup `elem` groupsSupported+ && selectedGroup `notElem` groupsSelected -> do usingHState ctx $ setTLS13HandshakeMode HelloRetryRequest clearTxRecordState ctx let cparams' = cparams{clientUseEarlyData = False} runPacketFlight ctx $ sendChangeCipherSpec13 ctx clientSession <- tls13stSession <$> getTLS13State ctx- let groupsSupported' = selectedGroup : filter (/= selectedGroup) groupsSupported- groupsSelected' = [selectedGroup]- grps =+ -- RFC 8446 Sec 4.1.2: the second ClientHello MUST be identical+ -- to the first except for the specific listed changes.+ -- supported_groups is NOT on that list, so it must be unchanged.+ let grps = Groups- { grpsSupported = groupsSupported'- , grpsSelected = groupsSelected'+ { grpsSupported = groupsSupported+ , grpsSelected = [selectedGroup] } handshake@@ -156,6 +161,11 @@ ctx grps (Just (crand, clientSession, ver))+ | selectedGroup `elem` groupsSelected ->+ throwCore $+ Error_Protocol+ "server selected a group already offered in key_share"+ IllegalParameter | otherwise -> throwCore $ Error_Protocol "server-selected group is not supported" IllegalParameter
Network/TLS/Handshake/Client/ClientHello.hs view
@@ -215,13 +215,16 @@ -------------------- + -- RFC 6066 Section 3: HostName is <1..2^16-1>, so no server_name+ -- is sent for an empty name. sniExt =- if clientUseServerNameIndication cparams+ if clientUseServerNameIndication cparams && not (null sni) then do- let sni = fst $ clientServerIdentification cparams usingState_ ctx $ setClientSNI sni return $ Just $ toExtensionRaw $ ServerName [ServerNameHostName sni] else return Nothing+ where+ sni = fst $ clientServerIdentification cparams -- RFC 8446 Sec 4.2.8 says: Each KeyShareEntry value MUST correspond -- to a group offered in the "supported_groups" extension and MUST@@ -262,11 +265,13 @@ Nothing -> return Nothing Just siz -> return $ Just $ toExtensionRaw $ RecordSizeLimit $ fromIntegral siz - sessionTicketExt = do+ sessionTicketExt = case clientSessions cparams of (sidOrTkt, _) : _ | isTicket sidOrTkt -> return $ Just $ toExtensionRaw $ SessionTicket sidOrTkt- _ -> return $ Just $ toExtensionRaw $ SessionTicket ""+ _+ | clientWantTicket cparams -> return $ Just $ toExtensionRaw $ SessionTicket ""+ | otherwise -> return $ Nothing earlyDataExt | rtt0 = return $ Just $ toExtensionRaw (EarlyDataIndication Nothing)@@ -506,9 +511,13 @@ step2 (sniExtI@(ExtensionRaw EID_ServerName _) : exts) = (sniExtO : os, sniExtI : is) where- sniExtO = toExtensionRaw $ ServerName [ServerNameHostName host] (os, is) = step3 exts id- step2 _ = error "step2"+ -- No server_name for an empty name: only the outer one names the+ -- public name.+ step2 exts = (sniExtO : os, is)+ where+ (os, is) = step3 exts id+ sniExtO = toExtensionRaw $ ServerName [ServerNameHostName host] step3 [] build = ([], [echOuterExt]) where echOuterExt = toExtensionRaw $ EchOuterExtensions $ build []
Network/TLS/Handshake/Client/Common.hs view
@@ -13,9 +13,10 @@ clientSessions, ) where -import Control.Exception (SomeException)+import qualified Control.Exception as E import Control.Monad.State.Strict-import Data.X509 (ExtKeyUsageFlag (..))+import qualified Data.ByteString as B+import Data.X509 (ExtKeyUsageFlag (..), ExtKeyUsagePurpose (..)) import Network.TLS.Cipher import Network.TLS.Context.Internal@@ -38,7 +39,7 @@ ---------------------------------------------------------------- -throwMiscErrorOnException :: String -> SomeException -> IO a+throwMiscErrorOnException :: String -> E.SomeException -> IO a throwMiscErrorOnException msg e = throwCore $ Error_Misc $ msg ++ ": " ++ show e @@ -124,7 +125,9 @@ -- then run certificate validation usage <- catchException (wrapCertificateChecks <$> checkCert) rejectOnException case usage of- CertificateUsageAccept -> checkLeafCertificateKeyUsage+ CertificateUsageAccept -> do+ verifyLeafKeyUsagePurpose KeyUsagePurpose_ServerAuth certs+ checkLeafCertificateKeyUsage CertificateUsageReject reason -> certificateRejected reason where shared = clientShared cparams@@ -342,14 +345,28 @@ (return ()) setAlpn where- setAlpn (ApplicationLayerProtocolNegotiation [proto]) = usingState_ ctx $ do- mprotos <- getClientALPNSuggest+ setAlpn (ApplicationLayerProtocolNegotiation [proto]) = do+ mprotos <- usingState_ ctx getClientALPNSuggest case mprotos of- Just protos -> when (proto `elem` protos) $ do- setExtensionALPN True- setNegotiatedProtocol proto- _ -> return ()- setAlpn _ = return ()+ Nothing ->+ throwCore $+ Error_Protocol+ "server sent ALPN without a client offer"+ UnsupportedExtension+ Just protos+ | not (B.null proto) && proto `elem` protos -> usingState_ ctx $ do+ setExtensionALPN True+ setNegotiatedProtocol proto+ | otherwise ->+ throwCore $+ Error_Protocol+ "server selected an ALPN protocol not offered by the client"+ IllegalParameter+ setAlpn _ =+ throwCore $+ Error_Protocol+ "server ALPN response did not contain exactly one protocol"+ IllegalParameter ----------------------------------------------------------------
Network/TLS/Handshake/Client/ServerHello.hs view
@@ -4,6 +4,7 @@ module Network.TLS.Handshake.Client.ServerHello ( receiveServerHello, processServerHello13,+ processRecordSizeLimit, ) where import Data.ByteArray (convert)@@ -138,6 +139,12 @@ ver <- usingState_ ctx getVersion + unless (cipherAllowedForVersion ver usedCipher) $+ throwCore $+ Error_Protocol+ "server selected a cipher invalid for the negotiated version"+ IllegalParameter+ when (ver == TLS12) $ setServerHelloParameters12 ctx shVersion shRandom usedCipher compressAlg @@ -173,9 +180,8 @@ then do -- Session is dummy in TLS 1.3. usingState_ ctx $ setSession shSession- processRecordSizeLimit ctx shExtensions True- enableMyRecordLimit ctx- enablePeerRecordLimit ctx+ -- RecordSizeLimit comes in EncryptedExtensions in TLS 1.3+ -- (RFC 8449 Section 4), and is processed there. let usedHash = cipherHash usedCipher transitTranscriptHashI ctx "transitI" usedHash isHRR accepted <- checkECHacceptance ctx isHRR usedHash sh@@ -266,6 +272,10 @@ (return ()) (setPeerRecordSizeLimit ctx tls13) ack <- checkPeerRecordLimit ctx+ -- RFC 8449 Section 4: a limit that is not negotiated does not+ -- bind the peer, so a server that did not send RecordSizeLimit+ -- back may send records of any size the protocol permits.+ unless ack $ setMyRecordLimit ctx Nothing -- When a client sends RecordSizeLimit, it does not know -- which TLS version the server selects. RecordLimit is -- the length of plaintext. But RecordSizeLimit also
Network/TLS/Handshake/Client/TLS12.hs view
@@ -49,13 +49,20 @@ doCertificate cparams ctx certs processCertificate ctx ClientRole certs return $ RecvStateHandshake (expectServerKeyExchange ctx)-expectCertificate _ ctx p = expectServerKeyExchange ctx p+-- Every key exchange the client implements authenticates the server with a+-- certificate; see sendClientKeyXchg.+expectCertificate _ _ p = unexpected (show p) (Just "server certificate") expectServerKeyExchange :: Context -> Handshake -> IO (RecvState IO) expectServerKeyExchange ctx (ServerKeyXchg origSkx) = do doServerKeyExchange ctx origSkx return $ RecvStateHandshake (expectCertificateRequest ctx)-expectServerKeyExchange ctx p = expectCertificateRequest ctx p+expectServerKeyExchange ctx p = do+ -- (EC)DHE takes its parameters from ServerKeyExchange.+ kx <- cipherKeyExchange <$> usingHState ctx getPendingCipher+ unless (kx == CipherKeyExchange_RSA) $+ unexpected (show p) (Just "server key exchange")+ expectCertificateRequest ctx p expectCertificateRequest :: Context -> Handshake -> IO (RecvState IO) expectCertificateRequest ctx (CertRequest cTypesSent sigAlgs dNames) = do
Network/TLS/Handshake/Client/TLS13.hs view
@@ -7,7 +7,7 @@ postHandshakeAuthClientWith, ) where -import Control.Exception (bracket)+import qualified Control.Exception as E import Control.Monad.State.Strict import qualified Data.ByteArray as BA import Data.IORef@@ -135,18 +135,28 @@ :: MonadIO m => Context -> Handshake13 -> m () expectEncryptedExtensions ctx (EncryptedExtensions13 eexts) = do liftIO $ do+ -- RFC 8449 Section 4: the server's RecordSizeLimit is in+ -- EncryptedExtensions. Until it is known, our own limit is not+ -- enforced, as the server may not have agreed to it.+ processRecordSizeLimit ctx eexts True+ enableMyRecordLimit ctx+ enablePeerRecordLimit ctx setALPN ctx MsgTEncryptedExtensions eexts modifyTLS13State ctx $ \st -> st{tls13stClientExtensions = eexts} st13 <- usingHState ctx getTLS13RTT0Status when (st13 == RTT0Sent) $ case extensionLookup EID_EarlyData eexts of Just _ -> do+ -- RFC 8446 Section 4.2.10: early data is accepted only+ -- with the first PSK.+ mode <- usingHState ctx getTLS13HandshakeMode+ unless (mode == PreSharedKey) $+ throwCore $+ Error_Protocol "early data accepted without PSK" IllegalParameter usingHState ctx $ setTLS13HandshakeMode RTT0 usingHState ctx $ setTLS13RTT0Status RTT0Accepted liftIO $ modifyTLS13State ctx $ \st -> st{tls13st0RTTAccepted = True}- Nothing -> do- usingHState ctx $ setTLS13HandshakeMode PreSharedKey- usingHState ctx $ setTLS13RTT0Status RTT0Rejected+ Nothing -> usingHState ctx $ setTLS13RTT0Status RTT0Rejected expectEncryptedExtensions _ h = unexpected (show h) (Just "encrypted extensions") ----------------------------------------------------------------@@ -222,13 +232,19 @@ :: MonadIO m => ClientParams -> Context -> CertificateChain -> RecvHandshake13M m () processCertAndVerify cparams ctx cc = do+ pubkey <- processServerCertificate cparams ctx cc+ recvHandshake13hash ctx "CertVerify" $ expectCertVerify ctx pubkey++processServerCertificate+ :: MonadIO m => ClientParams -> Context -> CertificateChain -> m PubKey+processServerCertificate cparams ctx cc = do liftIO $ usingState_ ctx $ setServerCertificateChain cc liftIO $ doCertificate cparams ctx cc let pubkey = certPubKey $ getCertificate $ getCertificateChainLeaf cc ver <- liftIO $ usingState_ ctx getVersion checkDigitalSignatureKey ver pubkey usingHState ctx $ setPublicKey pubkey- recvHandshake13hash ctx "CertVerify" $ expectCertVerify ctx pubkey+ return pubkey ---------------------------------------------------------------- @@ -377,10 +393,13 @@ ---------------------------------------------------------------- postHandshakeAuthClientWith- :: ClientParams -> Context -> Handshake13 -> IO ()-postHandshakeAuthClientWith cparams ctx (CertRequest13 certReqCtx exts) =- bracket (saveHState ctx) (restoreHState ctx) $ \_ -> do- -- updateTranscriptHash13 ctx h b+ :: ClientParams -> Context -> Handshake13R -> IO ()+postHandshakeAuthClientWith cparams ctx hb@(CertRequest13 certReqCtx exts, _) =+ E.bracket (saveHState ctx) (restoreHState ctx) $ \_ -> do+ -- RFC 8446 Section 4.4: the handshake context of+ -- post-handshake authentication is ClientHello ... client+ -- Finished + CertificateRequest.+ updateTranscriptHash13 ctx hb processCertRequest13 ctx certReqCtx exts (usedHash, _, level, applicationSecretN) <- getTxRecordState ctx unless (level == CryptApplicationSecret) $@@ -407,16 +426,42 @@ asyncServerHello13 cparams ctx groupSent chSentTime = do setPendingRecvActions ctx- [ PendingRecvActionSelfUpdate True expectServerHello- , PendingRecvAction True (expectEncryptedExtensions ctx)- , PendingRecvActionHash True expectFinishedAndSet- ]+ [PendingRecvActionSelfUpdate True expectServerHello] where expectServerHello shb@(sh, _) = do setRTT ctx chSentTime processServerHello13 cparams ctx sh updateTranscriptHash13 ctx shb -- update by myself- void $ prepareSecondFlight13 ctx groupSent+ resuming <- prepareSecondFlight13 ctx groupSent+ -- Without the PSK, the server authenticates with its certificate,+ -- as in recvServerSecondFlight13.+ setPendingRecvActions ctx $+ if resuming+ then+ [ PendingRecvAction True (expectEncryptedExtensions ctx)+ , PendingRecvActionHash True expectFinishedAndSet+ ]+ else+ [ PendingRecvAction False (expectEncryptedExtensions ctx)+ , PendingRecvAction False expectCertRequestAsync+ , PendingRecvActionHash True expectFinishedAndSet+ ]+ -- The actions below put the next one in front of Finished.+ expectCertRequestAsync (CertRequest13 token exts) = do+ processCertRequest13 ctx token exts+ pushPendingRecvAction ctx $ PendingRecvAction False expectCertAsync+ expectCertRequestAsync h = do+ usingHState ctx $ do+ setCertReqToken Nothing+ setCertReqCBdata Nothing+ expectCertAsync h+ expectCertAsync (Certificate13 _ (CertificateChain_ cc) _) = certAsync cc+ expectCertAsync (CompressedCertificate13 _ (CertificateChain_ cc) _) = certAsync cc+ expectCertAsync h = unexpected (show h) (Just "server certificate")+ certAsync cc = do+ pubkey <- processServerCertificate cparams ctx cc+ pushPendingRecvAction ctx $+ PendingRecvActionHash False (expectCertVerify ctx pubkey) expectFinishedAndSet h sf = do expectFinished cparams ctx h sf liftIO $
Network/TLS/Handshake/Common.hs view
@@ -40,7 +40,7 @@ ) where import Control.Concurrent.MVar-import Control.Exception (IOException, fromException, handle, throwIO)+import qualified Control.Exception as E import Control.Monad.State.Strict import Data.ByteArray (convert) import qualified Data.ByteString as B@@ -69,7 +69,7 @@ import Network.TLS.X509 handshakeFailed :: TLSError -> IO ()-handshakeFailed err = throwIO $ HandshakeFailed err+handshakeFailed err = E.throwIO $ HandshakeFailed err handleException :: Context -> IO () -> IO () handleException ctx f = catchException f $ \exception -> do@@ -77,12 +77,12 @@ -- If the error was an Uncontextualized TLSException, we replace the -- context with HandshakeFailed. If it's anything else, we convert -- it to a string and wrap it with Error_Misc and HandshakeFailed.- let tlserror = case fromException exception of+ let tlserror = case E.fromException exception of Just e | Uncontextualized e' <- e -> e' _ -> Error_Misc (show exception) established <- ctxEstablished ctx setEstablished ctx NotEstablished- handle ignoreIOErr $ do+ E.handle ignoreIOErr $ do tls13 <- tls13orLater ctx if tls13 then do@@ -93,7 +93,7 @@ else sendPacket12 ctx $ Alert [errorToAlert tlserror] handshakeFailed tlserror where- ignoreIOErr :: IOException -> IO ()+ ignoreIOErr :: E.IOException -> IO () ignoreIOErr _ = return () errorToAlert :: TLSError -> (AlertLevel, AlertDescription)@@ -103,6 +103,9 @@ errorToAlert (Error_Packet_Parsing msg) | "invalid version" `isInfixOf` msg = (AlertLevel_Fatal, ProtocolVersion) | "request_update" `isInfixOf` msg = (AlertLevel_Fatal, IllegalParameter)+ | "cannot be decompressed" `isInfixOf` msg = (AlertLevel_Fatal, BadCertificate)+ | "unsupported certificate compression algorithm" `isInfixOf` msg =+ (AlertLevel_Fatal, IllegalParameter) | otherwise = (AlertLevel_Fatal, DecodeError) errorToAlert _ = (AlertLevel_Fatal, InternalError)
Network/TLS/Handshake/Common13.hs view
@@ -175,18 +175,25 @@ -> Signature -> ByteString -> m Bool-checkCertVerify ctx pub hs signature hashValue- | pub `signatureCompatible13` hs = liftIO $ do- role <- usingState_ ctx getRole- let ctxStr- | role == ClientRole = serverContextString -- opposite context- | otherwise = clientContextString- target = makeTarget ctxStr hashValue- sigParams = signatureParams pub hs- checkHashSignatureValid13 hs- checkSupportedHashSignature ctx hs- verifyPublic ctx sigParams target signature- | otherwise = return False+-- RFC 8446 Section 6.2: an algorithm that may not be used -- one TLS 1.3+-- does not allow, one not offered, or one that does not fit the key -- is a+-- field that is incorrect, an illegal_parameter. Only a signature that does+-- not verify is a decrypt_error, which False leads to.+checkCertVerify ctx pub hs signature hashValue = liftIO $ do+ checkHashSignatureValid13 hs+ checkSupportedHashSignature ctx hs+ unless (pub `signatureCompatible13` hs) $+ throwCore $+ Error_Protocol+ ("signature algorithm " ++ showSignatureScheme hs ++ " does not fit the public key")+ IllegalParameter+ role <- usingState_ ctx getRole+ let ctxStr+ | role == ClientRole = serverContextString -- opposite context+ | otherwise = clientContextString+ target = makeTarget ctxStr hashValue+ sigParams = signatureParams pub hs+ verifyPublic ctx sigParams target signature makeTarget :: ByteString -> ByteString -> ByteString makeTarget contextString hashValue = runPut $ do@@ -437,7 +444,7 @@ checkHashSignatureValid13 :: HashAndSignatureAlgorithm -> IO () checkHashSignatureValid13 hs = unless (isHashSignatureValid13 hs) $- let msg = "invalid TLS13 hash and signature algorithm: " ++ show hs+ let msg = "invalid TLS13 hash and signature algorithm: " ++ showSignatureScheme hs in throwCore $ Error_Protocol msg IllegalParameter isHashSignatureValid13 :: HashAndSignatureAlgorithm -> Bool
Network/TLS/Handshake/Control.hs view
@@ -7,6 +7,7 @@ NegotiatedProtocol, ) where +import Crypto.Debug (DebugShow (..)) import Network.TLS.Cipher import Network.TLS.Imports import Network.TLS.Struct@@ -19,17 +20,40 @@ type NegotiatedProtocol = ByteString -- | Handshake information generated for traffic at 0-RTT level.+--+-- 'Show' renders the cipher and @\<secret\>@ for the key material; a trace+-- of what 'Network.TLS.QUIC.quicInstallKeys' is handed does not write the+-- traffic secrets to a log. 'Crypto.Debug.debugShow' renders them. data EarlySecretInfo = EarlySecretInfo Cipher (ClientTrafficSecret EarlySecret) deriving (Show) +instance DebugShow EarlySecretInfo where+ debugShow (EarlySecretInfo c s) =+ "EarlySecretInfo " ++ show c ++ " " ++ debugShow s+ -- | Handshake information generated for traffic at handshake level.+--+-- The secrets are not shown; see 'EarlySecretInfo'. data HandshakeSecretInfo = HandshakeSecretInfo Cipher (TrafficSecrets HandshakeSecret) deriving (Show) +instance DebugShow HandshakeSecretInfo where+ debugShow (HandshakeSecretInfo c ts) =+ "HandshakeSecretInfo " ++ show c ++ " " ++ debugShowPair ts+ -- | Handshake information generated for traffic at application level.+--+-- The secrets are not shown; see 'EarlySecretInfo'. newtype ApplicationSecretInfo = ApplicationSecretInfo (TrafficSecrets ApplicationSecret) deriving (Show)++instance DebugShow ApplicationSecretInfo where+ debugShow (ApplicationSecretInfo ts) =+ "ApplicationSecretInfo " ++ debugShowPair ts++debugShowPair :: TrafficSecrets a -> String+debugShowPair (c, s) = "(" ++ debugShow c ++ "," ++ debugShow s ++ ")" ----------------------------------------------------------------
Network/TLS/Handshake/Key.hs view
@@ -91,6 +91,9 @@ isDigitalSignatureKey (PubKeyEC _) = True isDigitalSignatureKey (PubKeyEd25519 _) = True isDigitalSignatureKey (PubKeyEd448 _) = True+isDigitalSignatureKey (PubKeyMLDSA44 _) = True+isDigitalSignatureKey (PubKeyMLDSA65 _) = True+isDigitalSignatureKey (PubKeyMLDSA87 _) = True isDigitalSignatureKey _ = False versionCompatible :: PubKey -> Version -> Bool@@ -99,6 +102,9 @@ versionCompatible (PubKeyEC _) v = v >= TLS10 versionCompatible (PubKeyEd25519 _) v = v >= TLS12 versionCompatible (PubKeyEd448 _) v = v >= TLS12+versionCompatible (PubKeyMLDSA44 _) v = v >= TLS13+versionCompatible (PubKeyMLDSA65 _) v = v >= TLS13+versionCompatible (PubKeyMLDSA87 _) v = v >= TLS13 versionCompatible _ _ = False {- FOURMOLU_ENABLE -} @@ -128,6 +134,9 @@ (PubKeyEC _, PrivKeyEC k) -> kxSupportedPrivKeyEC k (PubKeyEd25519 _, PrivKeyEd25519 _) -> True (PubKeyEd448 _, PrivKeyEd448 _) -> True+ (PubKeyMLDSA44 _, PrivKeyMLDSA44 _) -> True+ (PubKeyMLDSA65 _, PrivKeyMLDSA65 _) -> True+ (PubKeyMLDSA87 _, PrivKeyMLDSA87 _) -> True _ -> False getLocalPublicKey :: MonadIO m => Context -> m PubKey
Network/TLS/Handshake/Server.hs view
@@ -45,9 +45,9 @@ -- | Put the server context in handshake mode. -- -- Expect a client hello message as parameter.--- This is useful when the client hello has been already poped from the recv layer to inspect the packet.+-- This is useful when the client hello has been already popped from the recv layer to inspect the packet. ----- When the function returns, a new handshake has been succesfully negociated.+-- When the function returns, a new handshake has been successfully negotiated. -- On any error, a HandshakeFailed exception is raised. handshake :: ServerParams -> Context -> HandshakeR -> IO () handshake sparams ctx chb@(ClientHello ch, bs) = do@@ -66,7 +66,7 @@ SelectKeyShareHRR g -> do sendHRR ctx g r0 chI $ isJust mcrnd -- Don't reset ctxEstablished since 0-RTT data- -- would be comming, which should be ignored.+ -- would be coming, which should be ignored. handshakeServer sparams ctx SelectKeyShareFound cliKeyShare -> do unless (checkClientKeyShareKeyLength cliKeyShare) $@@ -86,4 +86,4 @@ resumeSessionData <- sendServerHello12 sparams ctx r chI recvClientSecondFlight12 sparams ctx resumeSessionData-handshake _ _ _ = throwCore $ Error_Protocol "client Hello is expected" HandshakeFailure+handshake _ _ (hs, _) = unexpected (show hs) (Just "client hello")
Network/TLS/Handshake/Server/ClientHello.hs view
@@ -57,7 +57,11 @@ (throwCore $ Error_HandshakePolicy "server: handshake denied") updateMeasure ctx incrementNbHandshakes - when (chVersion /= TLS12) $+ -- A legacy_version below TLS 1.2 is refused. One above it is not: a+ -- server negotiates the highest version it supports (RFC 5246 Appendix+ -- E.1), and with supported_versions present does not use legacy_version+ -- at all (RFC 8446 Section 4.2.1). Both are decided below.+ when (chVersion < TLS12) $ throwCore $ Error_Protocol (show chVersion ++ " is not supported") ProtocolVersion @@ -170,9 +174,20 @@ Nothing extractServerName where- extractServerName (ServerName ns) = listToMaybe (mapMaybe toHostName ns)+ extractServerName (ServerName ns) = case mapMaybe toHostName ns of+ [] -> Nothing+ [hostName]+ | all validChar hostName -> Just hostName+ | otherwise -> illegal "invalid host_name in SNI"+ _ -> illegal "multiple host_names in SNI" toHostName (ServerNameHostName hostName) = Just hostName toHostName (ServerNameOther _) = Nothing+ -- RFC 6066 Section 3: the server_name_list MUST NOT contain more+ -- than one name of the same name_type, and HostName is an ASCII+ -- DNS host name, so it has no control characters, spaces nor+ -- non-ASCII bytes.+ validChar c = c > ' ' && c < '\DEL'+ illegal msg = E.throw $ Uncontextualized $ Error_Protocol msg IllegalParameter findHighestVersionFrom :: Version -> [Version] -> Maybe Version findHighestVersionFrom clientVersion allowedVersions =
Network/TLS/Handshake/Server/ClientHello12.hs view
@@ -11,10 +11,12 @@ import Network.TLS.Crypto import Network.TLS.ErrT import Network.TLS.Extension+import Network.TLS.Handshake.Common (ticketOrSessionID12) import Network.TLS.Handshake.Server.Common import Network.TLS.Handshake.Signature import Network.TLS.Imports import Network.TLS.Parameters+import Network.TLS.Session (SessionManager (..)) import Network.TLS.State import Network.TLS.Struct import Network.TLS.Types (CipherId (..), Role (..))@@ -32,6 +34,7 @@ processClientHello12 sparams ctx ch = do let secureRenegotiation = supportedSecureRenegotiation $ serverSupported sparams when secureRenegotiation $ checkSecureRenegotiation ctx ch+ checkEcPointFormats ch serverName <- usingState_ ctx getClientSNI let hooks = serverHooks sparams extraCreds <- onServerNameIndication hooks serverName@@ -40,18 +43,58 @@ -- The shared cipherlist can become empty after filtering for compatible -- creds, check now before calling onCipherChoosing, which does not handle -- empty lists.- when (null ciphersFilteredVersion) $+ when (null ciphersFilteredVersion) $ do+ checkResumedCipherOffered ctx ch throwCore $ Error_Protocol "no cipher in common with the TLS 1.2 client" HandshakeFailure- let usedCipher = onCipherChoosing hooks TLS12 ciphersFilteredVersion+ usedCipher <- chooseCipher hooks TLS12 ciphersFilteredVersion mcred <- chooseCreds usedCipher creds signatureCreds return (usedCipher, mcred) +-- RFC 5246 Section 7.4.1.2: a client resuming a session MUST offer the+-- cipher suite of that session. validateSession reports its absence with+-- illegal_parameter once a cipher has been chosen; when none can be chosen,+-- the same is reported here before the missing common cipher is.+checkResumedCipherOffered :: Context -> ClientHello -> IO ()+checkResumedCipherOffered ctx CH{..} = do+ let mticket =+ lookupAndDecode+ EID_SessionTicket+ MsgTClientHello+ chExtensions+ Nothing+ (\(SessionTicket ticket) -> Just ticket)+ case ticketOrSessionID12 mticket chSession of+ Nothing -> return ()+ Just identity -> do+ msd <- sessionResume (sharedSessionManager $ ctxShared ctx) identity+ case msd of+ Just sd+ | sessionVersion sd <= TLS12+ , CipherId (sessionCipher sd) `notElem` chCiphers ->+ throwCore $+ Error_Protocol "new cipher is different from the old one" IllegalParameter+ _ -> return ()+ checkSecureRenegotiation :: Context -> ClientHello -> IO () checkSecureRenegotiation ctx CH{..} = do -- RFC 5746: secure renegotiation -- TLS_EMPTY_RENEGOTIATION_INFO_SCSV: {0x00, 0xFF}- when (CipherId 0xff `elem` chCiphers) $+ let hasSCSV = CipherId 0xff `elem` chCiphers+ hasExt = isJust $ extensionLookup EID_SecureRenegotiation chExtensions+ established <- ctxEstablished ctx+ secure <- usingState_ ctx getSecureRenegotiation+ -- RFC 5746 Section 3.7: when renegotiating a connection whose+ -- secure_renegotiation flag is set, ClientHello MUST NOT contain+ -- the SCSV and MUST contain the renegotiation_info extension.+ when (established == Established && secure) $ do+ when hasSCSV $+ throwCore $+ Error_Protocol "SCSV in renegotiation" HandshakeFailure+ unless hasExt $+ throwCore $+ Error_Protocol "no renegotiation_info in renegotiation" HandshakeFailure+ when hasSCSV $ usingState_ ctx $ setSecureRenegotiation True case extensionLookup EID_SecureRenegotiation chExtensions of@@ -101,7 +144,7 @@ -- Cipher selection is performed in two steps: first server credentials -- are flagged as not suitable for signature if not compatible with- -- negotiated signature parameters. Then ciphers are evalutated from+ -- negotiated signature parameters. Then ciphers are evaluated from -- the resulting credentials. supported = serverSupported sparams@@ -129,7 +172,8 @@ -- Build a list of all hash/signature algorithms in common between -- client and server.- hashAndSignatures = supportedHashSignatures supported+ -- ML-DSA is TLS 1.3 only (draft-ietf-tls-mldsa).+ hashAndSignatures = filter (not . isMLDSA) $ supportedHashSignatures supported possibleHashSigAlgs = hashAndSignaturesInCommon hashAndSignatures chExtensions -- Check that a candidate signature credential will be compatible with@@ -170,6 +214,34 @@ Error_Protocol "key exchange algorithm not implemented" HandshakeFailure ----------------------------------------------------------------++-- RFC 8422 Section 5.1.2: a client that names a curve of RFC 8422 in+-- supported_groups and sends ec_point_formats without the uncompressed+-- format is refused with illegal_parameter. An empty list is refused+-- with decode_error when decoding it.+checkEcPointFormats :: ClientHello -> IO ()+checkEcPointFormats CH{..} =+ lookupAndDecodeAndDo+ EID_EcPointFormats+ MsgTClientHello+ chExtensions+ (return ())+ $ \(EcPointFormatsSupported formats) ->+ when+ ( EcPointFormat_Uncompressed `notElem` formats+ && any (`elem` rfc8422Groups) groups+ )+ $ throwCore+ $ Error_Protocol "uncompressed point format missing" IllegalParameter+ where+ groups =+ lookupAndDecode+ EID_SupportedGroups+ MsgTClientHello+ chExtensions+ []+ (\(SupportedGroups gs) -> gs)+ rfc8422Groups = [P256, P384, P521, X25519, X448] negotiatedGroupsInCommon :: [Group] -> [ExtensionRaw] -> [Group] negotiatedGroupsInCommon serverGroups exts =
Network/TLS/Handshake/Server/ClientHello13.hs view
@@ -13,6 +13,7 @@ import Network.TLS.Crypto import Network.TLS.Extension import Network.TLS.Handshake.Common13+import Network.TLS.Handshake.Server.Common import Network.TLS.Handshake.Signature import Network.TLS.Handshake.State import Network.TLS.IO.Encode@@ -34,7 +35,8 @@ -> IO ( SelectKeyShareResult , (Cipher, Hash, Bool) -- rtt0- , (SecretPair EarlySecret, [ExtensionRaw], Bool, Bool) -- authenticated, is0RTTvalid+ , (SecretPair EarlySecret, [ExtensionRaw], Bool, Bool, Maybe ByteString)+ -- authenticated, is0RTTvalid, ticket ALPN ) processClientHello13 sparams ctx ch@CH{..} = do when@@ -48,8 +50,8 @@ when (null ciphersFilteredVersion) $ throwCore $ Error_Protocol "no cipher in common with the TLS 1.3 client" HandshakeFailure- let usedCipher = onCipherChoosing (serverHooks sparams) TLS13 ciphersFilteredVersion- usedHash = cipherHash usedCipher+ usedCipher <- chooseCipher (serverHooks sparams) TLS13 ciphersFilteredVersion+ let usedHash = cipherHash usedCipher rtt0 = lookupAndDecode EID_EarlyData@@ -126,14 +128,15 @@ -> Context -> (Cipher, Hash, Bool) -- rtt0 -> ClientHello- -> IO (SecretPair EarlySecret, [ExtensionRaw], Bool, Bool) -- authenticated, is0RTTvalid+ -> IO (SecretPair EarlySecret, [ExtensionRaw], Bool, Bool, Maybe ByteString)+ -- authenticated, is0RTTvalid, ticket ALPN pskAndEarlySecret sparams ctx (usedCipher, usedHash, rtt0) CH{..} = do- (psk, binderInfo, is0RTTvalid) <- choosePSK+ (psk, binderInfo, is0RTTvalid, ticketALPN) <- choosePSK earlyKey <- calculateEarlySecret ctx choice (Left psk) let earlySecret = pairBase earlyKey authenticated = isJust binderInfo preSharedKeyExt <- checkBinder earlySecret binderInfo- return (earlyKey, preSharedKeyExt, authenticated, is0RTTvalid)+ return (earlyKey, preSharedKeyExt, authenticated, is0RTTvalid, ticketALPN) where choice = makeCipherChoice TLS13 usedCipher @@ -142,7 +145,7 @@ EID_PreSharedKey MsgTClientHello chExtensions- (return (zero, Nothing, False))+ (return (zero, Nothing, False, Nothing)) selectPSK selectPSK (PreSharedKeyClientHello (PskIdentity identity obfAge : _) bnds@(bnd : _)) = do@@ -161,18 +164,29 @@ then sessionResumeOnlyOnce mgr identity else sessionResume mgr identity case msdata of- Just sdata -> do- let tinfo = fromJust $ sessionTicketInfo sdata- psk = sessionSecret sdata- isFresh <- checkFreshness tinfo obfAge- (isPSKvalid, is0RTTvalid) <- checkSessionEquality sdata- if isPSKvalid && isFresh- then return (psk, Just (bnd, 0 :: Int, len), is0RTTvalid)- else -- fall back to full handshake- return (zero, Nothing, False)- _ -> return (zero, Nothing, False)- else return (zero, Nothing, False)- selectPSK _ = return (zero, Nothing, False)+ -- RFC 8446 Section 4.6.1: only a TLS 1.3 session, which+ -- has its ticket information, is resumed with a PSK. A+ -- TLS 1.2 one found under the same identity falls back+ -- to a full handshake.+ Just sdata+ | sessionVersion sdata == TLS13+ , Just tinfo <- sessionTicketInfo sdata -> do+ let psk = sessionSecret sdata+ isFresh <- checkFreshness tinfo obfAge+ (isPSKvalid, is0RTTvalid) <- checkSessionEquality sdata+ if isPSKvalid && isFresh+ then+ return+ ( psk+ , Just (bnd, 0 :: Int, len)+ , is0RTTvalid+ , sessionALPN sdata+ )+ else -- fall back to full handshake+ return (zero, Nothing, False, Nothing)+ _ -> return (zero, Nothing, False, Nothing)+ else return (zero, Nothing, False, Nothing)+ selectPSK _ = return (zero, Nothing, False, Nothing) checkBinder _ Nothing = return [] checkBinder earlySecret (Just (binder, n, tlen)) = do@@ -184,9 +198,6 @@ checkSessionEquality sdata = do msni <- usingState_ ctx getClientSNI- -- ALPN should be checked.- -- But it's an extension in EE, sigh.- -- malpn <- usingState_ ctx getNegotiatedProtocol let isSameSNI = sessionClientSNI sdata == msni isSameCipher = sessionCipher sdata == cipherID usedCipher ciphers = supportedCiphers $ serverSupported sparams@@ -195,9 +206,8 @@ Nothing -> False Just c -> cipherHash c == cipherHash usedCipher isSameVersion = TLS13 == sessionVersion sdata- -- isSameALPN = sessionALPN sdata == malpn isPSKvalid = isSameKDF && isSameSNI -- fixme: SNI is not required- is0RTTvalid = isSameVersion && isSameCipher -- && isSameALPN+ is0RTTvalid = isSameVersion && isSameCipher return (isPSKvalid, is0RTTvalid) dhModes =
Network/TLS/Handshake/Server/Common.hs view
@@ -2,6 +2,7 @@ module Network.TLS.Handshake.Server.Common ( applicationProtocol,+ chooseCipher, checkValidClientCertChain, clientCertificate, credentialDigitalSignatureKey,@@ -15,8 +16,9 @@ ) where import Control.Monad.State.Strict-import Data.X509 (ExtKeyUsageFlag (..))+import Data.X509 (ExtKeyUsageFlag (..), ExtKeyUsagePurpose (..)) +import Network.TLS.Cipher import Network.TLS.Context.Internal import Network.TLS.Credentials import Network.TLS.Crypto@@ -32,6 +34,18 @@ import Network.TLS.Util (catchException) import Network.TLS.X509 +chooseCipher :: ServerHooks -> Version -> [Cipher] -> IO Cipher+chooseCipher hooks ver candidates =+ case find ((== cipherID selected) . cipherID) candidates of+ Just cipher -> return cipher+ Nothing ->+ throwCore $+ Error_Protocol+ "onCipherChoosing selected a cipher outside the candidate list"+ InternalError+ where+ selected = onCipherChoosing hooks ver candidates+ checkValidClientCertChain :: MonadIO m => Context -> String -> m CertificateChain checkValidClientCertChain ctx errmsg = do@@ -132,6 +146,11 @@ when (proto == "") $ throwCore $ Error_Protocol "no supported application protocols" NoApplicationProtocol+ unless (proto `elem` protos) $+ throwCore $+ Error_Protocol+ "ALPN callback selected a protocol not offered by the client"+ NoApplicationProtocol usingState_ ctx $ do setExtensionALPN True setNegotiatedProtocol proto@@ -151,7 +170,9 @@ (onClientCertificate (serverHooks sparams) certs) rejectOnException case usage of- CertificateUsageAccept -> verifyLeafKeyUsage [KeyUsage_digitalSignature] certs+ CertificateUsageAccept -> do+ verifyLeafKeyUsage [KeyUsage_digitalSignature] certs+ verifyLeafKeyUsagePurpose KeyUsagePurpose_ClientAuth certs CertificateUsageReject reason -> certificateRejected reason -- Remember cert chain for later use.@@ -202,4 +223,10 @@ let mysiz = fromIntegral mylim + if tls13 then 1 else 0 rsl = RecordSizeLimit mysiz return $ Just $ toExtensionRaw rsl- else return Nothing+ else do+ -- RFC 8449 Section 4: a limit that is not negotiated+ -- does not bind the peer, so a client that did not send+ -- RecordSizeLimit may send records of any size the+ -- protocol permits.+ setMyRecordLimit ctx Nothing+ return Nothing
Network/TLS/Handshake/Server/ServerHello12.hs view
@@ -93,7 +93,7 @@ | TLS12 < sessionVersion sd = return Nothing -- fixme | CipherId (sessionCipher sd) `notElem` ciphers = throwCore $- Error_Protocol "new cipher is diffrent from the old one" IllegalParameter+ Error_Protocol "new cipher is different from the old one" IllegalParameter | isJust sni && sessionClientSNI sd /= sni = do usingState_ ctx clearClientSNI return Nothing@@ -142,8 +142,8 @@ if serverWantClientCert then do let (certTypes, hashSigs) =- let as = supportedHashSignatures serverSupported- in (nub $ mapMaybe hashSigToCertType as, as)+ let as = filter (not . isMLDSA) $ supportedHashSignatures serverSupported+ in (nub $ mapMaybe (fmap certTypeOnWire . hashSigToCertType) as, as) creq = CertRequest certTypes@@ -153,8 +153,18 @@ return $ b2 . (creq :) else return b2 where+ -- RFC 8422 Section 3.1: in TLS 1.2, ecdsa_sign asks for a certificate+ -- with an ECDSA- or EdDSA-capable public key. The Ed25519 and Ed448+ -- certificate types are synthetic values with no code point.+ certTypeOnWire CertificateType_Ed25519_Sign = CertificateType_ECDSA_Sign+ certTypeOnWire CertificateType_Ed448_Sign = CertificateType_ECDSA_Sign+ certTypeOnWire t = t commonGroups = negotiatedGroupsInCommon (supportedGroups serverSupported) chExts- commonHashSigs = hashAndSignaturesInCommon (supportedHashSignatures serverSupported) chExts+ -- ML-DSA is TLS 1.3 only (draft-ietf-tls-mldsa).+ commonHashSigs =+ hashAndSignaturesInCommon+ (filter (not . isMLDSA) $ supportedHashSignatures serverSupported)+ chExts setup_DHE = do let possibleFFGroups = commonGroups `intersect` availableFFGroups (dhparams, priv, pub) <-@@ -267,9 +277,13 @@ | ems = Just $ toExtensionRaw ExtendedMainSecret | otherwise = Nothing + -- RFC 5077 Section 3.2: the extension is sent only to a client that+ -- sent it. let useTicket = sessionUseTicket $ sharedSessionManager $ serverShared sparams+ clientTicket = isJust $ extensionLookup EID_SessionTicket chExts sessionTicketExt- | not resuming && useTicket = Just $ toExtensionRaw $ SessionTicket ""+ | not resuming && useTicket && clientTicket =+ Just $ toExtensionRaw $ SessionTicket "" | otherwise = Nothing -- in TLS12, we need to check as well the certificates we are sending if they have in the extension
Network/TLS/Handshake/Server/ServerHello13.hs view
@@ -37,7 +37,8 @@ -> Context -> KeyShareEntry -> (Cipher, Hash, Bool) -- rtt0- -> (SecretPair EarlySecret, [ExtensionRaw], Bool, Bool) -- authenticated, is0RTTvalid+ -> (SecretPair EarlySecret, [ExtensionRaw], Bool, Bool, Maybe ByteString)+ -- authenticated, is0RTTvalid, ticket ALPN -> ClientHello -> Maybe ClientRandom -> IO@@ -46,7 +47,7 @@ , Bool -- authenticated , Bool -- rtt0OK )-sendServerHello13 sparams ctx clientKeyShare (usedCipher, usedHash, rtt0) (earlyKey, preSharedKeyExt, authenticated, is0RTTvalid) CH{..} mOuterClientRandom = do+sendServerHello13 sparams ctx clientKeyShare (usedCipher, usedHash, rtt0) (earlyKey, preSharedKeyExt, authenticated, is0RTTvalid, ticketALPN) CH{..} mOuterClientRandom = do let clientEarlySecret = pairClient earlyKey earlySecret = pairBase earlyKey -- parse CompressCertificate to check if it is broken here@@ -69,8 +70,15 @@ setOuterClientRandom mOuterClientRandom hrr <- usingState_ ctx getTLS13HRR alpnExt <- applicationProtocol ctx chExtensions sparams+ negotiatedALPN <- usingState_ ctx getNegotiatedProtocol setServerParameter- let rtt0OK = authenticated && not hrr && rtt0 && rtt0accept && is0RTTvalid+ let rtt0OK =+ authenticated+ && not hrr+ && rtt0+ && rtt0accept+ && is0RTTvalid+ && ticketALPN == negotiatedALPN extraCreds <- usingState_ ctx getClientSNI >>= onServerNameIndication (serverHooks sparams) let p = makeCredentialPredicate TLS13 chExtensions@@ -86,7 +94,7 @@ usingHState ctx $ setTLS13HandshakeMode RTT0 usingHState ctx $ setTLS13RTT0Status RTT0Accepted else do- usingHState ctx $ setTLS13HandshakeMode PreSharedKey+ when authenticated $ usingHState ctx $ setTLS13HandshakeMode PreSharedKey usingHState ctx $ setTLS13RTT0Status RTT0Rejected else when authenticated $ usingHState ctx $ setTLS13HandshakeMode PreSharedKey -- else : FullHandshake or HelloRetryRequest
Network/TLS/Handshake/Server/TLS12.hs view
@@ -10,6 +10,7 @@ import Network.TLS.Context.Internal import Network.TLS.Crypto+import Network.TLS.Extension import Network.TLS.Handshake.Common import Network.TLS.Handshake.Key import Network.TLS.Handshake.Server.Common@@ -37,11 +38,17 @@ Nothing -> do recvClientCCC sparams ctx mticket <- sessionEstablished ctx+ -- RFC 5077 Section 3.3: NewSessionTicket is sent only after+ -- the session_ticket extension in ServerHello, which only a+ -- client that sent it gets.+ clientTicket <-+ maybe False (isJust . extensionLookup EID_SessionTicket . chExtensions . fst)+ <$> usingHState ctx getClientHello case mticket of- Nothing -> return ()- Just ticket -> do+ Just ticket | clientTicket -> do let life = adjustLifetime $ serverTicketLifetime sparams sendPacket12 ctx $ Handshake [NewSessionTicket life ticket] []+ _ -> return () sendCCSandFinished ctx ServerRole Just _ -> do _ <- sessionEstablished ctx@@ -92,8 +99,17 @@ -- matches our request and that we support -- verifying with that certificate. - return $ RecvStateHandshake $ expectClientKeyExchange True- expectClientCertificate p = expectClientKeyExchange False p+ -- RFC 5246 Section 7.4.8: CertificateVerify follows only a+ -- certificate with signing capability, so not an empty one,+ -- which the hook may have accepted.+ let followedCertVerify = not $ isNullCertificateChain certs+ return $ RecvStateHandshake $ expectClientKeyExchange followedCertVerify+ -- RFC 5246 Section 7.4.6: a client asked for a certificate sends the+ -- message, empty if it has none.+ expectClientCertificate p = do+ certReqSent <- usingHState ctx getCertReqSent+ when certReqSent $ unexpected (show p) (Just "client certificate")+ expectClientKeyExchange False p -- cannot use RecvStateHandshake, as the next message could be a ChangeCipher, -- so we must process any packet, and in case of handshake call processHandshake manually.
Network/TLS/Handshake/Server/TLS13.hs view
@@ -9,7 +9,7 @@ KeyUpdateRequest (..), ) where -import Control.Exception+import qualified Control.Exception as E import Control.Monad.State.Strict import Data.IORef @@ -28,6 +28,7 @@ import Network.TLS.IO import Network.TLS.Imports import Network.TLS.KeySchedule+import Network.TLS.Packet13 (encodeHandshake13) import Network.TLS.Parameters import Network.TLS.Session import Network.TLS.State@@ -239,12 +240,16 @@ origCertReqCtx <- newCertReqContext ctx let certReq13 = makeCertRequest sparams ctx origCertReqCtx False _ <- withWriteLock ctx $ do- bracket (saveHState ctx) (restoreHState ctx) $ \_ -> do+ E.bracket (saveHState ctx) (restoreHState ctx) $ \_ -> do sendPacket13 ctx $ Handshake13 [certReq13] [] withReadLock ctx $ do+ baseHState <- saveHState ctx+ -- RFC 8446 Section 4.4: the handshake context of+ -- post-handshake authentication is ClientHello ... client+ -- Finished + CertificateRequest.+ updateTranscriptHash13 ctx (certReq13, [encodeHandshake13 certReq13]) (clientCert13, bClientCert13) <- getHandshake ctx ref emptyCert <- expectClientCertificate sparams ctx origCertReqCtx clientCert13- baseHState <- saveHState ctx updateTranscriptHash13 ctx (clientCert13, bClientCert13) th <- transcriptHash ctx "CH..Cert" unless emptyCert $ do@@ -274,6 +279,13 @@ Error_Protocol "post handshake authenticated" UnexpectedMessage chk [] = getHandshake ctx ref chk ((KeyUpdate13 mode, _) : hbs) = do+ case limitKeyUpdate $ sharedLimit $ ctxShared ctx of+ Just limit | limit > 0 -> do+ count <- incrementTLS13KeyUpdateCount ctx+ when (count > limit) $+ terminate ctx $+ Error_Protocol "too many consecutive KeyUpdate messages" UnexpectedMessage+ _ -> return () keyUpdate ctx getRxRecordState setRxRecordState -- Write lock wraps both actions because we don't want another -- packet to be sent by another thread before the Tx state is@@ -328,18 +340,18 @@ send = sendPacket13 ctx . Alert13 catchException (send [(level, desc)]) (\_ -> return ()) setEOF ctx- throwIO $ Terminated False reason err+ E.throwIO $ Terminated False reason err handleEx :: Context -> IO Bool -> IO Bool handleEx ctx f = catchException f $ \exception -> do -- If the error was an Uncontextualized TLSException, we replace the -- context with HandshakeFailed. If it's anything else, we convert -- it to a string and wrap it with Error_Misc and HandshakeFailed.- let tlserror = case fromException exception of+ let tlserror = case E.fromException exception of Just e | Uncontextualized e' <- e -> e' _ -> Error_Misc (show exception) sendPacket13 ctx $ Alert13 [errorToAlert tlserror]- void $ throwIO $ PostHandshake tlserror+ void $ E.throwIO $ PostHandshake tlserror return False ----------------------------------------------------------------@@ -369,7 +381,7 @@ TwoWay deriving (Eq, Show) --- | Updating appication traffic secrets for TLS 1.3.+-- | Updating application traffic secrets for TLS 1.3. -- If this API is called for TLS 1.3, 'True' is returned. -- Otherwise, 'False' is returned. updateKey :: MonadIO m => Context -> KeyUpdateRequest -> m Bool
Network/TLS/Handshake/Signature.hs view
@@ -44,9 +44,24 @@ certificateCompatible (PubKeyEC _) cTypes = CertificateType_ECDSA_Sign `elem` cTypes certificateCompatible (PubKeyEd25519 _) _ = True certificateCompatible (PubKeyEd448 _) _ = True+-- ML-DSA has no CertificateType of its own, as EdDSA does not either; what+-- a client may send is decided by signature_algorithms.+certificateCompatible (PubKeyMLDSA44 _) _ = True+certificateCompatible (PubKeyMLDSA65 _) _ = True+certificateCompatible (PubKeyMLDSA87 _) _ = True certificateCompatible _ _ = False signatureCompatible :: PubKey -> HashAndSignatureAlgorithm -> Bool+-- ML-DSA first, and with a refusal for every other kind of key. The RSA+-- equations below ignore the first byte, and ML-DSA's second byte is one+-- RSASSA-PSS also uses, so an ML-DSA scheme reaching them would be answered+-- for as though it were RSASSA-PSS.+signatureCompatible (PubKeyMLDSA44 _) MLDSA44 = True+signatureCompatible (PubKeyMLDSA65 _) MLDSA65 = True+signatureCompatible (PubKeyMLDSA87 _) MLDSA87 = True+signatureCompatible _ MLDSA44 = False+signatureCompatible _ MLDSA65 = False+signatureCompatible _ MLDSA87 = False signatureCompatible (PubKeyRSA pk) (HashSHA1, SignatureRSA) = kxCanUseRSApkcs1 pk SHA1 signatureCompatible (PubKeyRSA pk) (HashSHA256, SignatureRSA) = kxCanUseRSApkcs1 pk SHA256 signatureCompatible (PubKeyRSA pk) (HashSHA384, SignatureRSA) = kxCanUseRSApkcs1 pk SHA384@@ -60,6 +75,48 @@ signatureCompatible (PubKeyEd448 _) (_, SignatureEd448) = True signatureCompatible _ (_, _) = False +-- Whether the signature algorithm is for the type of the key, whatever+-- its other parameters.+--+-- This takes the whole scheme and not its second byte alone, because+-- ML-DSA's second byte is one RSASSA-PSS also uses: only the pair says+-- which of the two a scheme is. The ML-DSA equations come first for the+-- same reason -- an ML-DSA scheme fits no other kind of key, and one+-- ML-DSA size does not fit another's key.+keyTypeFits :: PubKey -> HashAndSignatureAlgorithm -> Bool+keyTypeFits (PubKeyMLDSA44 _) hs = hs == MLDSA44+keyTypeFits (PubKeyMLDSA65 _) hs = hs == MLDSA65+keyTypeFits (PubKeyMLDSA87 _) hs = hs == MLDSA87+keyTypeFits _ MLDSA44 = False+keyTypeFits _ MLDSA65 = False+keyTypeFits _ MLDSA87 = False+keyTypeFits (PubKeyRSA _) (_, s) =+ s+ `elem` [ SignatureRSA+ , SignatureRSApssRSAeSHA256+ , SignatureRSApssRSAeSHA384+ , SignatureRSApssRSAeSHA512+ , SignatureRSApsspssSHA256+ , SignatureRSApsspssSHA384+ , SignatureRSApsspssSHA512+ ]+keyTypeFits (PubKeyDSA _) (_, s) = s == SignatureDSA+keyTypeFits (PubKeyEC _) (_, s) = s == SignatureECDSA+keyTypeFits (PubKeyEd25519 _) (_, s) = s == SignatureEd25519+keyTypeFits (PubKeyEd448 _) (_, s) = s == SignatureEd448+keyTypeFits _ _ = False++checkKeyTypeFits :: PubKey -> HashAndSignatureAlgorithm -> IO ()+checkKeyTypeFits pubKey hs =+ unless (pubKey `keyTypeFits` hs) $+ throwCore $+ Error_Protocol+ ( "signature algorithm "+ ++ showSignatureScheme hs+ ++ " is for another type of key"+ )+ IllegalParameter+ -- Same as 'signatureCompatible' but for TLS13: for ECDSA this also checks the -- relation between hash in the HashAndSignatureAlgorithm and elliptic curve signatureCompatible13 :: PubKey -> HashAndSignatureAlgorithm -> Bool@@ -118,9 +175,24 @@ -> ByteString -> DigitallySigned -> IO Bool-checkCertificateVerify ctx usedVersion pubKey msgs digSig@(DigitallySigned hashSigAlg _)- | pubKey `signatureCompatible` hashSigAlg = doVerify- | otherwise = return False+-- An algorithm not offered in CertificateRequest (RFC 5246 Section+-- 7.4.8), or one for another type of key, is a field that is incorrect,+-- an illegal_parameter. One for the right type of key that still does+-- not fit it, an RSASSA-PSS one for an rsaEncryption key say, is a+-- signature that does not verify, a decrypt_error, which False leads to.+checkCertificateVerify ctx usedVersion pubKey msgs digSig@(DigitallySigned hashSigAlg _) = do+ -- ML-DSA is defined for TLS 1.3 only, so naming one here is a field+ -- that is incorrect whatever key the peer holds.+ when (usedVersion < TLS13 && isMLDSA hashSigAlg) $+ throwCore $+ Error_Protocol+ ("signature algorithm " ++ showSignatureScheme hashSigAlg ++ " is not for " ++ show usedVersion)+ IllegalParameter+ checkSupportedHashSignature ctx hashSigAlg+ checkKeyTypeFits pubKey hashSigAlg+ if pubKey `signatureCompatible` hashSigAlg+ then doVerify+ else return False where doVerify = prepareCertificateVerifySignatureData ctx usedVersion pubKey hashSigAlg msgs@@ -156,6 +228,9 @@ return (signatureParams pubKey hashSigAlg, msgs) signatureParams :: PubKey -> HashAndSignatureAlgorithm -> SignatureParams+-- Which of these is used is decided by the key, and signatureCompatible has+-- already refused a scheme that does not fit the key, so an ML-DSA scheme+-- cannot arrive at a clause for another kind of key. signatureParams (PubKeyRSA _) hashSigAlg = case hashSigAlg of (HashSHA512, SignatureRSA) -> RSAParams SHA512 RSApkcs1@@ -195,6 +270,9 @@ (hsh, SignatureEd448) -> error ("unimplemented Ed448 signature hash type: " ++ show hsh) (_, sigAlg) -> error ("signature algorithm is incompatible with Ed448: " ++ show sigAlg)+signatureParams (PubKeyMLDSA44 _) MLDSA44 = MLDSA44Params+signatureParams (PubKeyMLDSA65 _) MLDSA65 = MLDSA65Params+signatureParams (PubKeyMLDSA87 _) MLDSA87 = MLDSA87Params signatureParams pk _ = error ("signatureParams: " ++ pubkeyType pk ++ " is not supported") signatureCreateWithCertVerifyData@@ -207,19 +285,24 @@ DigitallySigned malg <$> signPrivate ctx role sigParam toSign signatureVerify :: Context -> DigitallySigned -> PubKey -> ByteString -> IO Bool+-- Checked as checkCertificateVerify does: the peer chose the algorithm, so+-- one that does not fit the key is its error, not ours. signatureVerify ctx digSig@(DigitallySigned hashSigAlg _) pubKey toVerifyData = do usedVersion <- usingState_ ctx getVersion- let (sigParam, toVerify) =- case (usedVersion, hashSigAlg) of- (TLS12, hs)- | pubKey `signatureCompatible` hs ->- (signatureParams pubKey hashSigAlg, toVerifyData)- | otherwise ->- error "expecting different signature algorithm"- _ ->- error- "not expecting hash and signature algorithm in a < TLS12 digitially signed structure"- signatureVerifyWithCertVerifyData ctx digSig (sigParam, toVerify)+ unless (usedVersion == TLS12) $+ throwCore $+ Error_Protocol+ ("no signature algorithm in a " ++ show usedVersion ++ " digitally-signed")+ InternalError+ checkSupportedHashSignature ctx hashSigAlg+ checkKeyTypeFits pubKey hashSigAlg+ if pubKey `signatureCompatible` hashSigAlg+ then+ signatureVerifyWithCertVerifyData+ ctx+ digSig+ (signatureParams pubKey hashSigAlg, toVerifyData)+ else return False signatureVerifyWithCertVerifyData :: Context@@ -301,5 +384,5 @@ :: Context -> HashAndSignatureAlgorithm -> IO () checkSupportedHashSignature ctx hs = unless (hs `elem` supportedHashSignatures (ctxSupported ctx)) $- let msg = "unsupported hash and signature algorithm: " ++ show hs+ let msg = "unsupported hash and signature algorithm: " ++ showSignatureScheme hs in throwCore $ Error_Protocol msg IllegalParameter
Network/TLS/Handshake/State13.hs view
@@ -17,6 +17,7 @@ clearRxRecordState, PendingRecvAction (..), setPendingRecvActions,+ pushPendingRecvAction, popPendingRecvAction, ) where @@ -140,6 +141,9 @@ setPendingRecvActions :: Context -> [PendingRecvAction] -> IO () setPendingRecvActions ctx = writeIORef (ctxPendingRecvActions ctx)++pushPendingRecvAction :: Context -> PendingRecvAction -> IO ()+pushPendingRecvAction ctx a = modifyIORef' (ctxPendingRecvActions ctx) (a :) popPendingRecvAction :: Context -> IO (Maybe PendingRecvAction) popPendingRecvAction ctx = do
Network/TLS/HashAndSignature.hs view
@@ -10,7 +10,8 @@ HashSHA256, HashSHA384, HashSHA512,- HashIntrinsic+ HashIntrinsic,+ HashMLDSA ), SignatureAlgorithm ( ..,@@ -31,8 +32,13 @@ SignatureBrainpoolP512 ), HashAndSignatureAlgorithm,+ pattern MLDSA44,+ pattern MLDSA65,+ pattern MLDSA87, supportedSignatureSchemes, signatureSchemesForTLS13,+ isMLDSA,+ showSignatureScheme, ) where import Network.TLS.Imports@@ -59,6 +65,8 @@ pattern HashSHA512 = HashAlgorithm 6 pattern HashIntrinsic :: HashAlgorithm pattern HashIntrinsic = HashAlgorithm 8+pattern HashMLDSA :: HashAlgorithm -- not a hash; see MLDSA44 below+pattern HashMLDSA = HashAlgorithm 9 instance Show HashAlgorithm where show HashNone = "None"@@ -69,6 +77,7 @@ show HashSHA384 = "SHA384" show HashSHA512 = "SHA512" show HashIntrinsic = "TLS13"+ show HashMLDSA = "MLDSA" show (HashAlgorithm x) = "Hash " ++ show x {- FOURMOLU_ENABLE -} @@ -133,11 +142,55 @@ type HashAndSignatureAlgorithm = (HashAlgorithm, SignatureAlgorithm) +-- ML-DSA, from draft-ietf-tls-mldsa: mldsa44(0x0904), mldsa65(0x0905) and+-- mldsa87(0x0906).+--+-- These are named as whole pairs rather than as a new 'SignatureAlgorithm',+-- because neither byte is what it is elsewhere: 0x09 is not a hash, and the+-- second byte repeats numbers RSASSA-PSS already uses. Only the pair+-- identifies the scheme, so only the pair is given a name, and code that+-- decides anything about ML-DSA has to match on both bytes.++-- | Name a signature scheme.+--+-- 'show' on the pair cannot do this for ML-DSA. A scheme is a 16-bit+-- number that hs-tls carries as the two bytes it is made of, and for ML-DSA+-- the size is in the second byte, whose values RSASSA-PSS also uses -- so+-- the components alone render mldsa65 as @(MLDSA,RSApssRSAeSHA384)@, which+-- names the wrong algorithm. Use this wherever a scheme is put in front of+-- a person.+showSignatureScheme :: HashAndSignatureAlgorithm -> String+showSignatureScheme MLDSA44 = "mldsa44"+showSignatureScheme MLDSA65 = "mldsa65"+showSignatureScheme MLDSA87 = "mldsa87"+showSignatureScheme hs = show hs++-- | Is this one of the ML-DSA schemes? They are TLS 1.3 only, so the+-- TLS 1.2 paths filter them out and refuse one that arrives anyway.+isMLDSA :: HashAndSignatureAlgorithm -> Bool+isMLDSA MLDSA44 = True+isMLDSA MLDSA65 = True+isMLDSA MLDSA87 = True+isMLDSA _ = False+ {- FOURMOLU_DISABLE -}+pattern MLDSA44 :: HashAndSignatureAlgorithm+pattern MLDSA44 = (HashMLDSA, SignatureAlgorithm 4)+pattern MLDSA65 :: HashAndSignatureAlgorithm+pattern MLDSA65 = (HashMLDSA, SignatureAlgorithm 5)+pattern MLDSA87 :: HashAndSignatureAlgorithm+pattern MLDSA87 = (HashMLDSA, SignatureAlgorithm 6)+{- FOURMOLU_ENABLE -}++{- FOURMOLU_DISABLE -} supportedSignatureSchemes :: [HashAndSignatureAlgorithm] supportedSignatureSchemes =+ -- ML-DSA algorithms, TLS 1.3 only+ [ MLDSA87 -- mldsa87(0x0906)+ , MLDSA65 -- mldsa65(0x0905)+ , MLDSA44 -- mldsa44(0x0904) -- EdDSA algorithms- [ (HashIntrinsic, SignatureEd448) -- ed448 (0x0808)+ , (HashIntrinsic, SignatureEd448) -- ed448 (0x0808) , (HashIntrinsic, SignatureEd25519) -- ed25519(0x0807) -- ECDSA algorithms , (HashSHA512, SignatureECDSA) -- ecdsa_secp512r1_sha512(0x0603)@@ -162,8 +215,12 @@ signatureSchemesForTLS13 :: [(HashAlgorithm, SignatureAlgorithm)] signatureSchemesForTLS13 =+ -- ML-DSA algorithms+ [ MLDSA87 -- mldsa87(0x0906)+ , MLDSA65 -- mldsa65(0x0905)+ , MLDSA44 -- mldsa44(0x0904) -- EdDSA algorithms- [ (HashIntrinsic, SignatureEd448) -- ed448 (0x0808)+ , (HashIntrinsic, SignatureEd448) -- ed448 (0x0808) , (HashIntrinsic, SignatureEd25519) -- ed25519(0x0807) -- ECDSA algorithms , (HashSHA512, SignatureECDSA) -- ecdsa_secp512r1_sha512(0x0603)
Network/TLS/IO.hs view
@@ -16,7 +16,7 @@ loadPacket13, ) where -import Control.Exception (finally, throwIO)+import qualified Control.Exception as E import Control.Monad.Reader import Control.Monad.State.Strict import qualified Data.ByteString as B@@ -102,8 +102,15 @@ Left err -> do logPacket ctx $ show err return $ Left err- Right record- | hrr && isCCS record -> loop (count + 1)+ Right record@(Record _ _ fragment)+ -- the ChangeCipherSpec after a HelloRetryRequest is skipped,+ -- but checked like any other+ | hrr && isCCS record ->+ case checkChangeCipherSpec fragment of+ Left err -> do+ logPacket ctx $ show err+ return $ Left err+ Right _ -> loop (count + 1) | otherwise -> do pktRecv <- decodePacket12 ctx record if isEmptyHandshake pktRecv@@ -181,6 +188,9 @@ return $ Right $ Handshake13 hss' bss logPacket ctx $ show pkt return pktRecv'+ Right pkt@(AppData13 _) -> do+ logPacket ctx $ show pkt+ checkNotInterleaved ctx pktRecv Right pkt -> do logPacket ctx $ show pkt return pktRecv@@ -194,6 +204,24 @@ ---------------------------------------------------------------- +-- RFC 8446 Section 5.1: handshake messages MUST NOT be interleaved with+-- other record types. Application data that arrives while a handshake+-- message is still incomplete is refused with unexpected_message rather+-- than delivered. TLS 1.3 only: RFC 5246 Section 6.2.1 lets TLS 1.2+-- interleave data of different content types.+checkNotInterleaved :: Context -> Either TLSError a -> IO (Either TLSError a)+checkNotInterleaved ctx pktRecv = do+ complete <- isRecvComplete ctx+ if complete+ then return pktRecv+ else do+ let err =+ Error_Packet_unexpected+ "application data"+ " expected: the rest of a handshake message"+ logPacket ctx $ show err+ return $ Left err+ isRecvComplete :: Context -> IO Bool isRecvComplete ctx = usingState_ ctx $ do cont12 <- gets stHandshakeRecordCont12@@ -203,9 +231,9 @@ checkValid :: Context -> IO () checkValid ctx = do established <- ctxEstablished ctx- when (established == NotEstablished) $ throwIO ConnectionNotEstablished+ when (established == NotEstablished) $ E.throwIO ConnectionNotEstablished eofed <- ctxEOF ctx- when eofed $ throwIO $ PostHandshake Error_EOF+ when eofed $ E.throwIO $ PostHandshake Error_EOF ---------------------------------------------------------------- @@ -223,7 +251,8 @@ runPacketFlight :: Context -> (forall b. Monoid b => PacketFlightM b a) -> IO a runPacketFlight ctx@Context{ctxRecordLayer = recordLayer} (PacketFlightM f) = do ref <- newIORef id- runReaderT f (recordLayer, ref) `finally` sendPendingFlight ctx recordLayer ref+ runReaderT f (recordLayer, ref)+ `E.finally` sendPendingFlight ctx recordLayer ref sendPendingFlight :: Monoid b => Context -> RecordLayer b -> IORef (Builder b) -> IO ()
Network/TLS/IO/Decode.hs view
@@ -3,6 +3,7 @@ module Network.TLS.IO.Decode ( decodePacket12, decodePacket13,+ checkChangeCipherSpec, ) where import Control.Concurrent.MVar@@ -20,6 +21,7 @@ import Network.TLS.State import Network.TLS.Struct import Network.TLS.Struct13+import Network.TLS.Types (Role (..)) import Network.TLS.Util import Network.TLS.Wire @@ -27,57 +29,76 @@ decodePacket12 _ (Record ProtocolType_AppData _ fragment) = return $ Right $ AppData $ fragmentGetBytes fragment decodePacket12 _ (Record ProtocolType_Alert _ fragment) = return (Alert `fmapEither` decodeAlerts (fragmentGetBytes fragment)) decodePacket12 ctx (Record ProtocolType_ChangeCipherSpec _ fragment) =- case decodeChangeCipherSpec $ fragmentGetBytes fragment of+ case checkChangeCipherSpec fragment of Left err -> return $ Left err Right _ -> do- switchRxEncryption ctx- return $ Right ChangeCipherSpec+ -- ChangeCipherSpec comes between complete handshake messages+ -- (RFC 5246 Section 7.1), so not before the first ClientHello+ -- of a server, which has no handshake state yet, nor in the+ -- middle of a fragmented handshake message (CVE-2004-0079),+ -- nor before the keys it switches to are computed.+ mhs <- getHState ctx+ (mCont, _) <- usingState_ ctx $ gets stHandshakeRecordCont12+ case mhs >>= hstPendingRxState of+ Just rx | isNothing mCont -> do+ modifyMVar_ (ctxRxRecordState ctx) (\_ -> return rx)+ return $ Right ChangeCipherSpec+ _ ->+ return $+ Left $+ Error_Packet_unexpected "ChangeCipherSpec" " expected: handshake" decodePacket12 ctx (Record ProtocolType_Handshake ver fragment) = do- keyxchg <-- getHState ctx >>= \hs -> return (hs >>= hstPendingCipher >>= Just . cipherKeyExchange)+ mhs <- getHState ctx+ let keyxchg = mhs >>= hstPendingCipher >>= Just . cipherKeyExchange usingState ctx $ do+ role <- getRole let currentParams = CurrentParams { cParamsVersion = ver , cParamsKeyXchgType = keyxchg }+ -- A server has no handshake state until its first ClientHello,+ -- and that is the only message it may receive then (RFC 8446+ -- Section 4). Any other is answered with unexpected_message+ -- before its body is decoded, rather than with whatever decoding+ -- the body as that type gives.+ expectClientHello = role == ServerRole && isNothing mhs+ decode ty content+ | expectClientHello && ty /= HandshakeType_ClientHello =+ Left $ Error_Packet_unexpected (show ty) " expected: client hello"+ | otherwise = decodeHandshake currentParams ty content -- get back the optional continuation, and parse as many handshake record as possible. (mCont, wirebytes) <- gets stHandshakeRecordCont12 modify' (\st -> st{stHandshakeRecordCont12 = (Nothing, [])}) (hss, bss) <-- unzip <$> parseMany currentParams mCont wirebytes (fragmentGetBytes fragment)+ unzip <$> parseMany decode mCont wirebytes (fragmentGetBytes fragment) return $ Handshake hss bss where- parseMany currentParams mCont wirebytes bs =+ parseMany decode mCont wirebytes bs = case fromMaybe decodeHandshakeRecord mCont bs of GotError err -> throwError err GotPartial cont -> do modify' (\st -> st{stHandshakeRecordCont12 = (Just cont, bs : wirebytes)}) return [] GotSuccess (ty, content) ->- case decodeHandshake currentParams ty content of+ case decode ty content of Left err -> throwError err Right h -> return [(h, reverse (bs : wirebytes))] GotSuccessRemaining (ty, content) left ->- case decodeHandshake currentParams ty content of+ case decode ty content of Left err -> throwError err Right h -> do- hbs <- parseMany currentParams Nothing [] left+ hbs <- parseMany decode Nothing [] left let len = BS.length bs - BS.length left bs' = BS.take len bs return ((h, reverse (bs' : wirebytes)) : hbs)-decodePacket12 _ _ = return $ Left (Error_Packet_Parsing "unknown protocol type")--switchRxEncryption :: Context -> IO ()-switchRxEncryption ctx =- usingHState ctx (gets hstPendingRxState) >>= \rx ->- modifyMVar_ (ctxRxRecordState ctx) (\_ -> return $ fromJust rx)+decodePacket12 _ (Record ty _ _) = return $ Left $ unknownProtocolType ty ---------------------------------------------------------------- decodePacket13 :: Context -> Record Plaintext -> IO (Either TLSError Packet13) decodePacket13 _ (Record ProtocolType_ChangeCipherSpec _ fragment) =- case decodeChangeCipherSpec $ fragmentGetBytes fragment of+ case checkChangeCipherSpec fragment of Left err -> return $ Left err Right _ -> return $ Right ChangeCipherSpec13 decodePacket13 _ (Record ProtocolType_AppData _ fragment) = return $ Right $ AppData13 $ fragmentGetBytes fragment@@ -106,4 +127,20 @@ let len = BS.length bs - BS.length left bs' = BS.take len bs return ((h, reverse (bs' : wirebytes)) : hbs)-decodePacket13 _ _ = return $ Left (Error_Packet_Parsing "unknown protocol type")+decodePacket13 _ (Record ty _ _) = return $ Left $ unknownProtocolType ty++-- RFC 8446 Section 5: a record of an unexpected type, including the inner+-- type of a TLS 1.3 record, is answered with unexpected_message.+unknownProtocolType :: ProtocolType -> TLSError+unknownProtocolType ty = Error_Packet_unexpected (show ty) " expected: TLS record type"++-- | A ChangeCipherSpec is the single byte 1. RFC 8446 Section 5 answers any+-- other value with unexpected_message, and TLS 1.2, which does not say,+-- is answered the same way: a record of two of them included.+checkChangeCipherSpec :: Fragment a -> Either TLSError ()+checkChangeCipherSpec fragment =+ case decodeChangeCipherSpec $ fragmentGetBytes fragment of+ Left _ ->+ Left $+ Error_Packet_unexpected "ChangeCipherSpec" " expected: the single byte 1"+ Right _ -> Right ()
Network/TLS/Packet.hs view
@@ -76,6 +76,7 @@ import Network.TLS.Struct import Network.TLS.Types import Network.TLS.Util.ASN1+import Network.TLS.Util.Serialization (os2ip) import Network.TLS.Wire ----------------------------------------------------------------@@ -157,12 +158,33 @@ decodeHandshakeRecord :: ByteString -> GetResult (HandshakeType, ByteString) decodeHandshakeRecord = runGet "handshake-record" $ do ty <- getHandshakeType- content <- getOpaque24+ len <- getWord24+ -- Before the bytes, not after: the length is in the first four octets, so+ -- refusing here is refusing to hold anything. Reassembly keeps every+ -- fragment until the message is whole, and the peer picks the number it+ -- announces.+ when (len > maxHandshakeSize) $+ fail $+ "handshake message of "+ ++ show len+ ++ " octets exceeds the limit of "+ ++ show maxHandshakeSize+ content <- getBytes len return (ty, content) {- FOURMOLU_DISABLE -} decodeHandshake :: CurrentParams -> HandshakeType -> ByteString -> Either TLSError Handshake+-- A ClientKeyExchange is only expected once a cipher, and with it a key+-- exchange, has been negotiated; one that comes without -- after Finished,+-- say -- is out of order rather than malformed.+decodeHandshake cp HandshakeType_ClientKeyXchg+ | isNothing (cParamsKeyXchgType cp) =+ const $+ Left $+ Error_Packet_unexpected+ (show HandshakeType_ClientKeyXchg)+ " expected: no ClientKeyExchange before a key exchange is negotiated" decodeHandshake cp ty = runGetErr ("handshake[" ++ show ty ++ "]") $ case ty of HandshakeType_HelloRequest -> decodeHelloRequest HandshakeType_ClientHello -> decodeClientHello False@@ -337,8 +359,19 @@ parseCKE CipherKeyExchange_ECDHE_RSA = parseClientECDHPublic parseCKE CipherKeyExchange_ECDHE_ECDSA = parseClientECDHPublic parseCKE _ = fail "unsupported client key exchange type"- parseClientDHPublic = CKX_DH . dhPublic <$> getInteger16- parseClientECDHPublic = CKX_ECDH <$> getOpaque8+ -- RFC 5246 Section 7.4.7.2: dh_Yc is <1..2^16-1>, so an empty one is+ -- malformed, a decode_error, before it is a public value that is not+ -- valid.+ parseClientDHPublic = do+ bs <- getOpaque16+ when (B.null bs) $ fail "empty DH public key"+ return $ CKX_DH $ dhPublic $ os2ip bs+ -- RFC 8422 Section 5.7: ecdh_Yc is <1..2^8-1>, so an empty one is+ -- malformed, a decode_error, before it is a point that does not decode.+ parseClientECDHPublic = do+ bs <- getOpaque8+ when (B.null bs) $ fail "empty ECDH public key"+ return $ CKX_ECDH bs decodeFinished :: Get Handshake decodeFinished = Finished . VerifyData <$> (remaining >>= getBytes)
Network/TLS/Packet13.hs view
@@ -116,7 +116,18 @@ decodeHandshakeRecord13 :: ByteString -> GetResult (HandshakeType, ByteString) decodeHandshakeRecord13 = runGet "handshake-record" $ do ty <- getHandshakeType- content <- getOpaque24+ len <- getWord24+ -- Before the bytes, not after: the length is in the first four octets, so+ -- refusing here is refusing to hold anything. Reassembly keeps every+ -- fragment until the message is whole, and the peer picks the number it+ -- announces.+ when (len > maxHandshakeSize) $+ fail $+ "handshake message of "+ ++ show len+ ++ " octets exceeds the limit of "+ ++ show maxHandshakeSize+ content <- getBytes len return (ty, content) {- FOURMOLU_DISABLE -}@@ -209,25 +220,39 @@ 1 -> return $ KeyUpdate13 UpdateRequested x -> fail $ "Unknown request_update: " ++ show x +-- RFC 8879 Section 4: a certificate that cannot be decompressed, or whose+-- decompressed length is not the declared one, is answered with+-- bad_certificate, and an algorithm that was not offered with+-- illegal_parameter. errorToAlert tells them apart by the messages below.+-- A message that is malformed as a whole -- an empty+-- compressed_certificate_message, which its <1..2^24-1> bound forbids, or+-- bytes beyond the declared length -- is a decode_error, and is found+-- before anything is decompressed. decodeCompressedCertificate13 :: Get Handshake13 decodeCompressedCertificate13 = do algo <- getWord16- when (algo /= 1) $ fail "comp algo is not supported" -- fixme+ when (algo /= 1) $ fail "unsupported certificate compression algorithm" -- fixme len <- getWord24 bs <- getOpaque24+ left <- remaining+ when (left /= 0) $ fail "bytes after compressed certificate" if bs == "" then fail "empty compressed certificate"- else case decompressIt bs of- Left e -> fail (show e)+ else case decompressIt len bs of+ Left e -> fail $ "certificate cannot be decompressed: " ++ show e Right bs' -> do- when (B.length bs' /= len) $ fail "plain length is wrong"+ when (B.length bs' /= len) $+ fail "certificate cannot be decompressed: wrong uncompressed_length" case runGetMaybe decodeCertificate13 bs' of Just (Certificate13 reqctx certs ess) -> return $ CompressedCertificate13 reqctx certs ess -- _ -> fail "compressed certificate cannot be parsed" _ -> fail $ "invalid compressed certificate: len = " ++ show len -decompressIt :: ByteString -> Either DecompressError ByteString-decompressIt inp = unsafePerformIO $ E.handle handler $ do- Right . BL.toStrict <$> E.evaluate (decompress (BL.fromStrict inp))+decompressIt :: Int -> ByteString -> Either DecompressError ByteString+decompressIt limit inp = unsafePerformIO $ E.handle handler $ do+ -- One extra byte distinguishes exact-length output from oversized output.+ let output = BL.take (fromIntegral limit + 1) $ decompress $ BL.fromStrict inp+ Right <$> E.evaluate (BL.toStrict output) where- handler e = return $ Left (e :: DecompressError)+ handler :: DecompressError -> IO (Either DecompressError ByteString)+ handler e = return $ Left e
Network/TLS/Parameters.hs view
@@ -65,7 +65,7 @@ -- -- Default: 'Nothing' , debugPrintSeed :: Seed -> IO ()- -- ^ Add a way to print the seed that was randomly generated. re-using the same seed+ -- ^ Add a way to print the seed that was randomly generated. reusing the same seed -- will reproduce the same randomness with 'debugSeed' -- -- Default: no printing@@ -153,6 +153,16 @@ -- specified for TLS 1.2 but only the first entry is used. -- -- Default: '[]'+ , clientWantTicket :: Bool+ -- ^ Whether to solicit TLS 1.2 session tickets (or TLS 1.3+ -- resumption PSKs) from servers. With a 'False' setting,+ -- stateless clients that never do resumption can avoid+ -- wasting server and client resources used to generate,+ -- transmit and process tickets that will never be used.+ --+ -- Default: 'True'+ --+ -- @since 2.4.1 , clientShared :: Shared -- ^ See the default value of 'Shared'. , clientHooks :: ClientHooks@@ -192,6 +202,7 @@ , clientUseServerNameIndication = True , clientWantSessionResume = Nothing , clientWantSessionResumeList = []+ , clientWantTicket = True , clientShared = def , clientHooks = def , clientSupported = def@@ -401,8 +412,9 @@ -- -- Default: @[X25519,P256,P384,X448,P521,FFDHE3072,FFDHE4096,FFDHE6144,FFDHE8192,X25519MLKEM768,P256MLKEM768,P384MLKEM1024,MLKEM768,MLKEM1024]@ , supportedGroupsTLS13 :: [[Group]]- -- ^ @[Group]@ contains @Group@s of the same level in preferred order.- -- @[Group]@ is also listed in preferred order.+ -- ^ The inside @[Group]@ is the list of @Group@ at the same level+ -- in preferred order. The inside @[Group]@s are also listed in+ -- preferred order. -- -- TLS 1.3 server: this is used as the 1st argument to -- 'onSelectKeyShare'.@@ -628,10 +640,11 @@ -- "Data.X509.Validation". This can be replaced with a custom -- validation function using different settings. --- -- The function is not expected to verify the key-usage extension- -- of the end-entity certificate, as this depends on the- -- dynamically-selected cipher and this part should not be cached.- -- Key-usage verification is performed by the library internally.+ -- The function is not expected to verify the key-usage or+ -- extended-key-usage extensions of the end-entity certificate.+ -- Key usage depends on the dynamically-selected cipher and this+ -- part should not be cached. Both checks are performed by the+ -- library internally after this function accepts the chain. -- -- Default: 'validateDefault' , onSuggestALPN :: IO (Maybe [ByteString])@@ -657,7 +670,7 @@ -- (3) rejecting unless 1 < dh_p && pub < dh_p - 1 -- (4) rejecting if dh_size < 1024 (to prevent Logjam attack) --- -- See RFC 7919 section 3.1 for recommandations.+ -- See RFC 7919 section 3.1 for recommendations. , onServerFinished :: Information -> IO () -- ^ When a handshake is done, this hook can check `Information`. , onSelectKeyShareGroups :: [Group] -> [Group]@@ -722,7 +735,7 @@ -- of the certificate. This verification is performed by the -- library internally. --- -- Default: returns the followings:+ -- Default: returns the following: -- -- @ -- CertificateUsageReject (CertificateRejectOther "no client certificates expected")@@ -738,7 +751,7 @@ -- client version and the client list of ciphers. -- -- This could be useful with old clients and as a workaround to- -- the BEAST (where RC4 is sometimes prefered with TLS < 1.1)+ -- the BEAST (where RC4 is sometimes preferred with TLS < 1.1) -- -- The client cipher list cannot be empty. --@@ -875,6 +888,14 @@ -- certificate. -- -- Default: 32+ , limitKeyUpdate :: Maybe Int+ -- ^ Maximum number of consecutive TLS 1.3 KeyUpdate messages accepted+ -- without intervening non-empty application data. This bounds the CPU+ -- work and response amplification a peer can trigger while application+ -- code is blocked inside 'recvData'. 'Nothing' and non-positive values+ -- disable the limit; they do not disable KeyUpdate processing.+ --+ -- Default: @Just 32@ } deriving (Eq, Show) @@ -884,4 +905,5 @@ Limit { limitRecordSize = Nothing , limitHandshakeFragment = 32+ , limitKeyUpdate = Just 32 }
Network/TLS/PostHandshake.hs view
@@ -26,8 +26,8 @@ -- | Handle a post-handshake authentication flight with TLS 1.3. This -- is called automatically by 'recvData', in a context where the read -- lock is already taken. Client only.-postHandshakeAuthWith :: Context -> Handshake13 -> IO ()-postHandshakeAuthWith ctx hs =+postHandshakeAuthWith :: Context -> Handshake13R -> IO ()+postHandshakeAuthWith ctx hb = withWriteLock ctx $ handleException ctx $- doPostHandshakeAuthWith_ (ctxRoleParams ctx) ctx hs+ doPostHandshakeAuthWith_ (ctxRoleParams ctx) ctx hb
Network/TLS/QUIC.hs view
@@ -116,7 +116,7 @@ { quicSend :: [(CryptLevel, ByteString)] -> IO () -- ^ Called by TLS so that QUIC sends one or more handshake fragments. The -- content transiting on this API is the plaintext of the fragments and- -- QUIC responsability is to encrypt this payload with the key material+ -- QUIC responsibility is to encrypt this payload with the key material -- given for the specified level and an appropriate encryption scheme. -- -- The size of the fragments may exceed QUIC datagram limits so QUIC may@@ -194,7 +194,7 @@ let qexts = filterQTP exts when (null qexts) $ do throwCore $- Error_Protocol "QUIC transport parameters are mssing" MissingExtension+ Error_Protocol "QUIC transport parameters are missing" MissingExtension quicNotifyExtensions callbacks ctx qexts quicInstallKeys callbacks ctx (InstallApplicationKeys appSecInfo) @@ -222,7 +222,7 @@ let qexts = filterQTP exts when (null qexts) $ do throwCore $- Error_Protocol "QUIC transport parameters are mssing" MissingExtension+ Error_Protocol "QUIC transport parameters are missing" MissingExtension quicNotifyExtensions callbacks ctx qexts quicInstallKeys callbacks ctx (InstallEarlyKeys mEarlySecInfo) quicInstallKeys callbacks ctx (InstallHandshakeKeys handSecInfo)
Network/TLS/Record/Decrypt.hs view
@@ -42,7 +42,13 @@ Left message -> throwError $ Error_Protocol message UnexpectedMessage Right (ct', d) -> return $ Record ct' ver $ fragmentPlaintext d ProtocolType_ChangeCipherSpec -> noDecryption- ProtocolType_Alert -> noDecryption+ -- A peer that fails before it has keys answers in clear, but after+ -- the handshake an alert in clear can only be forged (RFC 8446+ -- Section 5).+ ProtocolType_Alert+ | stCryptLevel st == CryptApplicationSecret ->+ throwError $ Error_Protocol "alert in clear" UnexpectedMessage+ | otherwise -> noDecryption _ -> throwError $ Error_Protocol "illegal plain text" UnexpectedMessage @@ -59,25 +65,40 @@ nonEmptyContentTypes = [ProtocolType_Handshake, ProtocolType_Alert] unknownContentType13 c = "unknown TLS 1.3 content type: " ++ show c -getCipherData :: Record a -> CipherData -> RecordM ByteString-getCipherData (Record pt ver _) cdata = do+-- | Check a decrypted record.+--+-- The first 'Bool' is what the lengths already said: 'False' when the padding+-- length the record claims cannot be one. It is carried in rather than+-- answered where it was found, so that the MAC is computed either way -- see+-- 'decryptData'.+--+-- Everything is computed before anything is decided, and the verdicts are+-- combined with '&&!', which does not short-circuit.+getCipherData :: Record a -> Bool -> CipherData -> RecordM ByteString+getCipherData (Record pt ver _) lengthValid cdata = do -- check if the MAC is valid. macValid <- case cipherDataMAC cdata of Nothing -> return True Just digest -> do let new_hdr = Header pt ver (fromIntegral $ B.length $ cipherDataContent cdata) expected_digest <- makeDigest new_hdr $ cipherDataContent cdata- return (expected_digest == digest)+ -- constEq rather than (==): (==) on ByteString is memcmp, which+ -- returns as soon as two octets differ, and how soon is a+ -- measurement of how much of the MAC was guessed correctly.+ return (expected_digest `BA.constEq` digest) -- check if the padding is filled with the correct pattern if it exists -- (before TLS10 this checks instead that the padding length is minimal) paddingValid <- case cipherDataPadding cdata of Nothing -> return True Just (pad, _blksz) -> do- let b = B.length pad - 1- return $ B.replicate (B.length pad) (fromIntegral b) == pad+ let b = fromIntegral (B.length pad - 1)+ -- Every octet, and no allocation of a pattern to compare against:+ -- B.all stops at the first wrong octet, and replicating the+ -- pattern costs time in proportion to a length the peer chose.+ return $ B.foldl' (\acc w -> acc .|. (w `xor` b)) 0 pad == 0 - unless (macValid &&! paddingValid) $+ unless (lengthValid &&! macValid &&! paddingValid) $ throwError $ Error_Protocol "bad record mac Stream/Block" BadRecordMac @@ -115,9 +136,13 @@ blockSize = bulkBlockSize bulk econtentLen = B.length econtent + -- A record too short for the cipher cannot be deprotected: RFC 5246+ -- Section 7.2.2 and RFC 8446 Section 5.2 answer it with bad_record_mac. sanityCheckError =- throwError- (Error_Packet "encrypted content too small for encryption parameters")+ throwError $+ Error_Protocol+ "encrypted content too small for encryption parameters"+ BadRecordMac decryptOf :: BulkState -> RecordM ByteString decryptOf (BulkStateBlock decryptF) = do@@ -134,11 +159,25 @@ let (content', iv') = decryptF iv econtent' modify' $ \txs -> txs{stCryptState = cst{cstIV = iv'}} - let paddinglength = fromIntegral (B.last content') + 1- let contentlen = B.length content' - paddinglength - macSize+ -- The last octet of the plaintext says how much padding there is.+ -- It may say more than the record can hold, and that already settles+ -- the record -- but answering it here, by splitting the record and+ -- failing, would answer it *without computing the MAC*. How long a+ -- record takes to reject would then say whether the padding length+ -- was plausible, which is the question the attacker is asking.+ --+ -- So carry the verdict instead and go on with a length that fits.+ -- getCipherData folds it in with the MAC, and the answer is the same+ -- BadRecordMac either way.+ let plainlen = B.length content'+ claimed = fromIntegral (B.last content') + 1+ lengthValid = claimed + macSize <= plainlen+ paddinglength = if lengthValid then claimed else 1+ contentlen = plainlen - paddinglength - macSize (content, mac, padding) <- get3i content' (contentlen, macSize, paddinglength) getCipherData record+ lengthValid CipherData { cipherDataContent = content , cipherDataMAC = Just mac@@ -155,6 +194,7 @@ modify' $ \txs -> txs{stCryptState = cst{cstKey = BulkStateStream bulkStream'}} getCipherData record+ True CipherData { cipherDataContent = content , cipherDataMAC = Just mac@@ -194,9 +234,11 @@ decryptOf BulkStateUninitialized = throwError $ Error_Protocol "decrypt state uninitialized" InternalError - -- handling of outer format can report errors with Error_Packet+ -- the outer format of a record that cannot be deprotected is reported+ -- as an integrity failure too, i.e. BadRecordMac get3o s ls =- maybe (throwError $ Error_Packet "record bad format") return $ partition3 s ls+ maybe (throwError $ Error_Protocol "record bad format" BadRecordMac) return $+ partition3 s ls get2o s (d1, d2) = get3o s (d1, d2, 0) >>= \(r1, r2, _) -> return (r1, r2) -- all format errors related to decrypted content are reported
Network/TLS/Record/Recv.hs view
@@ -50,7 +50,8 @@ -- ^ TLS context -> IO (Either TLSError (Record Plaintext)) recvRecord12 ctx =- readExactBytes ctx 5 >>= either (return . Left) (recvLengthE . decodeHeader)+ readExactBytes ctx 5+ >>= either (return . Left) (recvLengthE . (decodeHeader >=> checkType)) where recvLengthE = either (return . Left) recvLength @@ -69,7 +70,9 @@ >>= either (return . Left) (getRecord ctx header) recvRecord13 :: Context -> IO (Either TLSError (Record Plaintext))-recvRecord13 ctx = readExactBytes ctx 5 >>= either (return . Left) (recvLengthE . decodeHeader)+recvRecord13 ctx =+ readExactBytes ctx 5+ >>= either (return . Left) (recvLengthE . (decodeHeader >=> checkType)) where recvLengthE = either (return . Left) recvLength recvLength header@(Header _ _ readlen) = do@@ -90,6 +93,24 @@ maximumSizeExceeded :: TLSError maximumSizeExceeded = Error_Protocol "record exceeding maximum size" RecordOverflow++-- RFC 8446 Section 5: a record of an unexpected type is answered with+-- unexpected_message. Checked on the header, before the body is read, so+-- that what is no TLS record at all -- an SSLv2 ClientHello, say, whose first+-- byte reads as type 0x80 and whose length can ask for bytes that never+-- come -- is answered at once rather than when the peer gives up.+checkType :: Header -> Either TLSError Header+checkType header@(Header ty _ _)+ | ty `elem` known = Right header+ | otherwise =+ Left $ Error_Packet_unexpected (show ty) " expected: TLS record type"+ where+ known =+ [ ProtocolType_ChangeCipherSpec+ , ProtocolType_Alert+ , ProtocolType_Handshake+ , ProtocolType_AppData+ ] ----------------------------------------------------------------
Network/TLS/State.hs view
@@ -25,6 +25,7 @@ setVersion, setVersionIfUnset, getVersion,+ getVersionMaybe, getVersionWithDefault, setSecureRenegotiation, getSecureRenegotiation,@@ -231,6 +232,14 @@ getVersion = fromMaybe (error "internal error: version hasn't been set yet") <$> gets stVersion++-- | The negotiated version, or 'Nothing' before there is one.+--+-- 'getVersion' calls 'error' in that case, which is the right answer inside+-- the handshake -- reaching it there would be a bug -- and the wrong one for+-- anything a user of the library can call before the handshake has run.+getVersionMaybe :: TLSSt (Maybe Version)+getVersionMaybe = gets stVersion getVersionWithDefault :: Version -> TLSSt Version getVersionWithDefault defaultVer = fromMaybe defaultVer <$> gets stVersion
Network/TLS/Types.hs view
@@ -11,6 +11,7 @@ bigNumToInteger, bigNumFromInteger, defaultRecordSizeLimit,+ maxHandshakeSize, TranscriptHash (..), WireBytes, ) where@@ -58,6 +59,22 @@ -- 2^14 + 1 for TLS 1.3 defaultRecordSizeLimit :: Int defaultRecordSizeLimit = 16384++----------------------------------------------------------------++-- | The largest handshake message we will reassemble.+--+-- A handshake message carries a 24-bit length, so a peer may announce close+-- to 16MB and then feed it a record at a time. Records are bounded, but the+-- message they are reassembled into was not, and the fragments are held until+-- it is complete -- before anything has authenticated the peer.+--+-- The largest legitimate one is a Certificate message. A long chain of+-- post-quantum certificates runs to tens of kilobytes, so this leaves an+-- order of magnitude over anything real while taking two orders of magnitude+-- off what a peer can ask us to hold.+maxHandshakeSize :: Int+maxHandshakeSize = 262144 ----------------------------------------------------------------
Network/TLS/Types/Secret.hs view
@@ -1,5 +1,14 @@+-- | The secret types of the TLS key schedule.+--+-- None of these prints its key material: 'Show' renders @\<secret\>@, since+-- these values reach a QUIC implementation through+-- "Network.TLS.QUIC" and are the kind of thing a handshake trace prints+-- without meaning to. 'Crypto.Debug.debugShow' returns the hexadecimal that+-- 'Show' used to, for a debugging session that wants it. @SSLKEYLOGFILE@+-- does not go through either: it uses 'Network.TLS.Handshake.Key.LogLabel'. module Network.TLS.Types.Secret where +import Crypto.Debug (DebugShow (..)) import Data.ByteArray (convert) import Network.TLS.Imports import Network.TLS.Types.Cipher@@ -18,27 +27,39 @@ newtype BaseSecret a = BaseSecret Secret instance Show (BaseSecret a) where- show (BaseSecret bs) = showBytesHex $ convert bs+ show _ = "<secret>" +instance DebugShow (BaseSecret a) where+ debugShow (BaseSecret bs) = showBytesHex $ convert bs+ newtype AnyTrafficSecret a = AnyTrafficSecret Secret instance Show (AnyTrafficSecret a) where- show (AnyTrafficSecret bs) = showBytesHex $ convert bs+ show _ = "<secret>" +instance DebugShow (AnyTrafficSecret a) where+ debugShow (AnyTrafficSecret bs) = showBytesHex $ convert bs+ -- | A client traffic secret, typed with a parameter indicating a step in the -- TLS key schedule. newtype ClientTrafficSecret a = ClientTrafficSecret Secret instance Show (ClientTrafficSecret a) where- show (ClientTrafficSecret bs) = showBytesHex $ convert bs+ show _ = "<secret>" +instance DebugShow (ClientTrafficSecret a) where+ debugShow (ClientTrafficSecret bs) = showBytesHex $ convert bs+ -- | A server traffic secret, typed with a parameter indicating a step in the -- TLS key schedule. newtype ServerTrafficSecret a = ServerTrafficSecret Secret instance Show (ServerTrafficSecret a) where- show (ServerTrafficSecret bs) = showBytesHex $ convert bs+ show _ = "<secret>" +instance DebugShow (ServerTrafficSecret a) where+ debugShow (ServerTrafficSecret bs) = showBytesHex $ convert bs+ data SecretTriple a = SecretTriple { triBase :: BaseSecret a , triClient :: ClientTrafficSecret a@@ -59,4 +80,7 @@ newtype MainSecret = MainSecret Secret instance Show MainSecret where- show (MainSecret bs) = showBytesHex $ convert bs+ show _ = "<secret>"++instance DebugShow MainSecret where+ debugShow (MainSecret bs) = showBytesHex $ convert bs
Network/TLS/Types/Session.hs view
@@ -3,6 +3,7 @@ module Network.TLS.Types.Session where import Codec.Serialise+import Crypto.Debug (DebugShow (..)) import qualified Data.ByteString as B import GHC.Generics import Network.Socket (HostName)@@ -46,7 +47,42 @@ , sessionMaxEarlyDataSize :: Int , sessionFlags :: [SessionFlag] } -- sessionFromTicket :: Bool- deriving (Show, Eq, Generic)+ deriving (Eq, Generic)++-- | Everything but @sessionSecret@, which renders as @\<secret\>@: whoever+-- has it can resume the session. 'Crypto.Debug.debugShow' renders it.+instance Show SessionData where+ showsPrec = showsSessionData (showString "<secret>")++instance DebugShow SessionData where+ debugShow sd = showsSessionData (shows $ sessionSecret sd) 0 sd ""++-- | What the two instances above share, so that a field added to+-- 'SessionData' cannot reach one of them and not the other.+showsSessionData :: ShowS -> Int -> SessionData -> ShowS+showsSessionData secret d sd =+ showParen (d > 10) $+ showString "SessionData {sessionVersion = "+ . shows (sessionVersion sd)+ . showString ", sessionCipher = "+ . shows (sessionCipher sd)+ . showString ", sessionCompression = "+ . shows (sessionCompression sd)+ . showString ", sessionClientSNI = "+ . shows (sessionClientSNI sd)+ . showString ", sessionSecret = "+ . secret+ . showString ", sessionGroup = "+ . shows (sessionGroup sd)+ . showString ", sessionTicketInfo = "+ . shows (sessionTicketInfo sd)+ . showString ", sessionALPN = "+ . shows (sessionALPN sd)+ . showString ", sessionMaxEarlyDataSize = "+ . shows (sessionMaxEarlyDataSize sd)+ . showString ", sessionFlags = "+ . shows (sessionFlags sd)+ . showChar '}' is0RTTPossible :: SessionData -> Bool is0RTTPossible sd = sessionMaxEarlyDataSize sd > 0
Network/TLS/Util.hs view
@@ -18,7 +18,6 @@ ) where import Control.Concurrent.MVar-import Control.Exception (SomeAsyncException (..)) import qualified Control.Exception as E import Data.ByteArray (ScrubbedBytes) import qualified Data.ByteArray as BA@@ -83,7 +82,7 @@ where filterExn :: E.SomeException -> Maybe E.SomeException filterExn e = case E.fromException (E.toException e) of- Just (SomeAsyncException _) -> Nothing+ Just (E.SomeAsyncException _) -> Nothing Nothing -> Just e forEitherM :: Monad m => [a] -> (a -> m (Either l b)) -> m (Either l [b])
test/Arbitrary.hs view
@@ -7,7 +7,7 @@ import qualified Data.ByteString as B import Data.List import Data.Word-import Data.X509 (ExtKeyUsageFlag)+import Data.X509 (ExtKeyUsageFlag, privkeyMLDSAFromKey) import Network.TLS import Network.TLS.Extra.Cipher import Network.TLS.Internal@@ -220,6 +220,22 @@ , (toPubKeyEC curveName ecdsaPub, toPrivKeyEC curveName ecdsaPriv) , (PubKeyEd25519 ed25519Pub, PrivKeyEd25519 ed25519Priv) , (PubKeyEd448 ed448Pub, PrivKeyEd448 ed448Priv)+ ]++-- | One credential per ML-DSA parameter set.+arbitraryCredentialsOfEachMLDSA :: Gen [(CertificateChain, PrivKey)]+arbitraryCredentialsOfEachMLDSA = do+ (pub44, priv44) <- arbitraryMLDSA44Pair+ (pub65, priv65) <- arbitraryMLDSA65Pair+ (pub87, priv87) <- arbitraryMLDSA87Pair+ mapM+ ( \(pub, priv) -> do+ cert <- arbitraryX509WithKey (pub, priv)+ return (CertificateChain [cert], priv)+ )+ [ (PubKeyMLDSA44 pub44, PrivKeyMLDSA44 (privkeyMLDSAFromKey priv44))+ , (PubKeyMLDSA65 pub65, PrivKeyMLDSA65 (privkeyMLDSAFromKey priv65))+ , (PubKeyMLDSA87 pub87, PrivKeyMLDSA87 (privkeyMLDSAFromKey priv87)) ] arbitraryCredentialsOfEachCurve :: Gen [(CertificateChain, PrivKey)]
test/Certificate.hs view
@@ -6,6 +6,7 @@ arbitraryX509, arbitraryX509WithKey, arbitraryX509WithKeyAndUsage,+ arbitraryRSACredentialWithPurpose, arbitraryDN, simpleCertificate, simpleX509,@@ -118,6 +119,25 @@ let (signedExact, ()) = objectToSignedExact (\_ -> (B.pack sig, sigalg, ())) cert return signedExact +arbitraryRSACredentialWithPurpose+ :: ExtKeyUsagePurpose -> Gen (CertificateChain, PrivKey)+arbitraryRSACredentialWithPurpose purpose = do+ let (pubKey, privKey) = getGlobalRSAPair+ cert <- arbitraryCertificate knownKeyUsage $ PubKeyRSA pubKey+ sig <- resize 40 $ listOf1 arbitrary+ let cert' =+ cert+ { certExtensions =+ Extensions $+ Just+ [ extensionEncode True $ ExtKeyUsage knownKeyUsage+ , extensionEncode False $ ExtExtendedKeyUsage [purpose]+ ]+ }+ sigalg = getSignatureALG $ PubKeyRSA pubKey+ (signedExact, ()) = objectToSignedExact (\_ -> (B.pack sig, sigalg, ())) cert'+ return (CertificateChain [signedExact], PrivKeyRSA privKey)+ arbitraryX509 :: Gen SignedCertificate arbitraryX509 = do let (pubKey, privKey) = getGlobalRSAPair@@ -139,6 +159,9 @@ getSignatureALG (PubKeyEC _) = SignatureALG HashSHA256 PubKeyALG_EC getSignatureALG (PubKeyEd25519 _) = SignatureALG_IntrinsicHash PubKeyALG_Ed25519 getSignatureALG (PubKeyEd448 _) = SignatureALG_IntrinsicHash PubKeyALG_Ed448+getSignatureALG (PubKeyMLDSA44 _) = SignatureALG_IntrinsicHash PubKeyALG_MLDSA44+getSignatureALG (PubKeyMLDSA65 _) = SignatureALG_IntrinsicHash PubKeyALG_MLDSA65+getSignatureALG (PubKeyMLDSA87 _) = SignatureALG_IntrinsicHash PubKeyALG_MLDSA87 getSignatureALG pubKey = error $ "getSignatureALG: unsupported public key: " ++ show pubKey
test/EncodeSpec.hs view
@@ -1,8 +1,18 @@ module EncodeSpec where +import Codec.Compression.Zlib (compress)+import Control.Exception (bracket_, evaluate)+import Control.Monad (forM_, void) import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import Data.Either (isLeft)+import Data.Int (Int64)+import Data.Word (Word16)+import GHC.Conc (disableAllocationLimit, enableAllocationLimit, setAllocationCounter) import Network.TLS import Network.TLS.Internal+import Network.TLS.QUIC (errorToAlertDescription) import Test.Hspec import Test.Hspec.QuickCheck @@ -10,6 +20,36 @@ spec :: Spec spec = do+ describe "extension decoding" $ do+ prop "yields Nothing rather than throwing, for any message type" $+ \ws -> forM_ extensionDecoders $ \(name, decode) ->+ forM_ [minBound .. maxBound] $ \mt ->+ decode mt (B.pack ws) `shouldReturn` name+ describe "handshake record length" $ do+ -- A handshake message carries a 24-bit length, and the fragments are+ -- held until the message is whole. Refusing at the header means+ -- refusing to hold anything: the length arrives in the first four+ -- octets, before any of the body.+ it "refuses a length past the limit, on its header alone" $ do+ let tooBig = maxHandshakeSize + 1+ isGotError (decodeHandshakeRecord (handshakeHeader tooBig)) `shouldBe` True+ isGotError (decodeHandshakeRecord13 (handshakeHeader tooBig)) `shouldBe` True+ it "refuses the largest a 24-bit length can say" $ do+ let header = handshakeHeader 0xffffff+ isGotError (decodeHandshakeRecord header) `shouldBe` True+ isGotError (decodeHandshakeRecord13 header) `shouldBe` True+ -- Still waiting for the body rather than refusing it: at the limit+ -- the header alone is not enough to decide anything is wrong.+ it "asks for more at the limit itself" $ do+ let header = handshakeHeader maxHandshakeSize+ isGotPartial (decodeHandshakeRecord header) `shouldBe` True+ isGotPartial (decodeHandshakeRecord13 header) `shouldBe` True+ it "still decodes a message of an ordinary size" $ do+ let body = B.replicate 1000 0+ record = handshakeHeader (B.length body) `B.append` body+ gotThisMuch (B.length body) (decodeHandshakeRecord record) `shouldBe` True+ gotThisMuch (B.length body) (decodeHandshakeRecord13 record) `shouldBe` True+ describe "encoder/decoder" $ do prop "can encode/decode Header" $ \x -> do decodeHeader (encodeHeader x) `shouldBe` Right x@@ -17,7 +57,151 @@ decodeHs (encodeHandshake x) `shouldBe` Right x prop "can encode/decode Handshake13" $ \x -> do decodeHs13 (encodeHandshake13 x) `shouldBe` Right x+ it "round trips a valid TLS 1.3 compressed certificate" $ do+ let certificate =+ CompressedCertificate13+ B.empty+ (CertificateChain_ $ CertificateChain [])+ []+ decodeHs13 (encodeHandshake13 certificate) `shouldBe` Right certificate+ it "rejects decompressed output shorter than its declared size" $ do+ let plain = encodeCertificate13 B.empty (CertificateChain []) []+ compressed = BL.toStrict $ compress $ BL.fromStrict plain+ encoded = runPut $ do+ putWord16 1+ putWord24 (B.length plain + 1)+ putOpaque24 compressed+ decodeHandshake13 HandshakeType_CompressedCertificate encoded+ `shouldSatisfy` isLeft+ -- RFC 8879 Section 4: a CompressedCertificate that cannot be+ -- decompressed, or whose decompressed length is not the one+ -- declared, is answered with bad_certificate; one with an algorithm+ -- that was not offered breaks no decoding rule but a field value,+ -- and is answered with illegal_parameter. One malformed as a whole+ -- stays a decode_error.+ it "answers a decompressed length mismatch with bad_certificate" $ do+ let plain = encodeCertificate13 B.empty (CertificateChain []) []+ compressed = BL.toStrict $ compress $ BL.fromStrict plain+ compressedCertificateAlert 1 (B.length plain + 1) compressed+ `shouldBe` Just BadCertificate+ it "answers data that is not zlib with bad_certificate" $+ compressedCertificateAlert 1 16 (B.replicate 16 0xff)+ `shouldBe` Just BadCertificate+ it "answers an empty compressed certificate with decode_error" $+ compressedCertificateAlert 1 16 B.empty+ `shouldBe` Just DecodeError+ it "answers bytes after a compressed certificate with decode_error" $ do+ let plain = encodeCertificate13 B.empty (CertificateChain []) []+ compressed = BL.toStrict $ compress $ BL.fromStrict plain+ either (Just . errorToAlertDescription) (const Nothing)+ ( decodeHandshake13 HandshakeType_CompressedCertificate $+ runPut $ do+ putWord16 1+ putWord24 (B.length plain)+ putOpaque24 (B.drop 2 compressed)+ putBytes (B.take 2 compressed)+ )+ `shouldBe` Just DecodeError+ it "answers an unsupported compression algorithm with illegal_parameter" $ do+ let plain = encodeCertificate13 B.empty (CertificateChain []) []+ compressed = BL.toStrict $ compress $ BL.fromStrict plain+ compressedCertificateAlert 2 (B.length plain) compressed+ `shouldBe` Just IllegalParameter+ -- A ClientKeyExchange is only expected once a cipher, and with it a+ -- key exchange, has been negotiated -- not, say, after Finished.+ -- One that comes without is out of order: unexpected_message.+ it "answers a ClientKeyExchange before a key exchange with unexpected_message" $+ either (Just . errorToAlertDescription) (const Nothing)+ ( decodeHandshake+ CurrentParams{cParamsVersion = TLS12, cParamsKeyXchgType = Nothing}+ HandshakeType_ClientKeyXchg+ (B.replicate 130 1)+ )+ `shouldBe` Just UnexpectedMessage+ -- RFC 7301 Section 3.1: protocol_name_list<2..2^16-1> of+ -- ProtocolName<1..2^8-1>.+ it "refuses a malformed application_layer_protocol_negotiation" $+ forM_+ [ B.empty -- empty extension+ , B.pack [0, 0] -- empty list+ , B.pack [0, 1, 0] -- empty ProtocolName+ , B.pack [0, 2, 1, 104, 2, 104, 50] -- trailing data+ ]+ $ \bs ->+ ( extensionDecode MsgTClientHello bs+ :: Maybe ApplicationLayerProtocolNegotiation+ )+ `shouldBe` Nothing+ it "decodes an application_layer_protocol_negotiation" $+ ( extensionDecode MsgTClientHello (B.pack [0, 3, 2, 104, 50])+ :: Maybe ApplicationLayerProtocolNegotiation+ )+ `shouldBe` Just (ApplicationLayerProtocolNegotiation [B.pack [104, 50]])+ -- RFC 8422 Section 5.7: ecdh_Yc is <1..2^8-1>, so an empty one is+ -- malformed -- a decode_error -- rather than a point that does not+ -- decode, which is an illegal_parameter.+ it "answers an empty ECDH public key with decode_error" $+ either (Just . errorToAlertDescription) (const Nothing)+ ( decodeHandshake+ CurrentParams+ { cParamsVersion = TLS12+ , cParamsKeyXchgType = Just CipherKeyExchange_ECDHE_RSA+ }+ HandshakeType_ClientKeyXchg+ (B.singleton 0)+ )+ `shouldBe` Just DecodeError+ -- RFC 5246 Section 7.4.7.2: dh_Yc is <1..2^16-1>, so an empty one is+ -- malformed -- a decode_error -- rather than a public value that is+ -- not valid, which is an illegal_parameter.+ -- RFC 6066 Section 3: server_name_list<1..2^16-1> of+ -- HostName<1..2^16-1>.+ it "refuses a malformed server_name in ClientHello" $+ forM_+ [ B.empty -- empty extension+ , B.pack [0, 0] -- empty list+ , B.pack [0, 3, 0, 0, 0] -- empty host_name+ , B.pack [0, 4, 0, 0, 1, 101, 120] -- trailing data+ ]+ $ \bs ->+ (extensionDecode MsgTClientHello bs :: Maybe ServerName)+ `shouldBe` Nothing+ it "decodes a server_name in ClientHello" $+ (extensionDecode MsgTClientHello (B.pack [0, 4, 0, 0, 1, 101]) :: Maybe ServerName)+ `shouldBe` Just (ServerName [ServerNameHostName "e"])+ it "answers an empty DH public key with decode_error" $+ either (Just . errorToAlertDescription) (const Nothing)+ ( decodeHandshake+ CurrentParams+ { cParamsVersion = TLS12+ , cParamsKeyXchgType = Just CipherKeyExchange_DHE_RSA+ }+ HandshakeType_ClientKeyXchg+ (B.pack [0, 0])+ )+ `shouldBe` Just DecodeError+ it "bounds TLS 1.3 certificate decompression by the declared size" $ do+ let compressed = BL.toStrict $ compress $ BL.replicate (32 * 1024 * 1024) 0+ encoded = runPut $ do+ putWord16 1+ putWord24 1+ putOpaque24 compressed+ _ <- evaluate $ B.length encoded+ decoded <-+ withinAllocationLimit (8 * 1024 * 1024) $+ evaluate $+ decodeHandshake13 HandshakeType_CompressedCertificate encoded+ decoded `shouldSatisfy` isLeft +compressedCertificateAlert :: Word16 -> Int -> ByteString -> Maybe AlertDescription+compressedCertificateAlert algo len compressed =+ either (Just . errorToAlertDescription) (const Nothing) $+ decodeHandshake13 HandshakeType_CompressedCertificate $+ runPut $ do+ putWord16 algo+ putWord24 len+ putOpaque24 compressed+ decodeHs :: ByteString -> Either TLSError Handshake decodeHs b = verifyResult (decodeHandshake cp) $ decodeHandshakeRecord b where@@ -30,6 +214,28 @@ decodeHs13 :: ByteString -> Either TLSError Handshake13 decodeHs13 b = verifyResult decodeHandshake13 $ decodeHandshakeRecord13 b +-- | A handshake record header: a type octet then a 24-bit length.+handshakeHeader :: Int -> ByteString+handshakeHeader len =+ B.pack+ [ 1 -- ClientHello+ , fromIntegral (len `div` 65536)+ , fromIntegral ((len `div` 256) `mod` 256)+ , fromIntegral (len `mod` 256)+ ]++isGotError :: GetResult a -> Bool+isGotError (GotError _) = True+isGotError _ = False++isGotPartial :: GetResult a -> Bool+isGotPartial (GotPartial _) = True+isGotPartial _ = False++gotThisMuch :: Int -> GetResult (a, ByteString) -> Bool+gotThisMuch n (GotSuccess (_, content)) = B.length content == n+gotThisMuch _ _ = False+ verifyResult :: (f -> r -> a) -> GetResult (f, r) -> a verifyResult fn result = case result of@@ -37,3 +243,48 @@ GotError e -> error ("got error: " ++ show e) GotSuccessRemaining _ _ -> error "got remaining byte left" GotSuccess (ty, content) -> fn ty content++withinAllocationLimit :: Int64 -> IO a -> IO a+withinAllocationLimit limit =+ bracket_+ (setAllocationCounter limit >> enableAllocationLimit)+ disableAllocationLimit++-- | Every 'Extension' instance, each wrapped so that the decoded value is+-- forced inside IO. A partial 'extensionDecode' therefore surfaces as a+-- thrown exception the test can see, rather than as a thunk nobody looks at.+--+-- The name is threaded through as the return value only so that a failure+-- report says which instance it was.+type Decoder a = MessageType -> ByteString -> Maybe a++extensionDecoders :: [(String, MessageType -> ByteString -> IO String)]+extensionDecoders =+ [+ entry "ServerName" (extensionDecode :: Decoder ServerName),+ entry "MaxFragmentLength" (extensionDecode :: Decoder MaxFragmentLength),+ entry "SecureRenegotiation" (extensionDecode :: Decoder SecureRenegotiation),+ entry "ApplicationLayerProtocolNegotiation" (extensionDecode :: Decoder ApplicationLayerProtocolNegotiation),+ entry "ExtendedMainSecret" (extensionDecode :: Decoder ExtendedMainSecret),+ entry "CompressCertificate" (extensionDecode :: Decoder CompressCertificate),+ entry "SupportedGroups" (extensionDecode :: Decoder SupportedGroups),+ entry "EcPointFormatsSupported" (extensionDecode :: Decoder EcPointFormatsSupported),+ entry "RecordSizeLimit" (extensionDecode :: Decoder RecordSizeLimit),+ entry "SessionTicket" (extensionDecode :: Decoder SessionTicket),+ entry "HeartBeat" (extensionDecode :: Decoder HeartBeat),+ entry "SignatureAlgorithms" (extensionDecode :: Decoder SignatureAlgorithms),+ entry "SignatureAlgorithmsCert" (extensionDecode :: Decoder SignatureAlgorithmsCert),+ entry "SupportedVersions" (extensionDecode :: Decoder SupportedVersions),+ entry "KeyShare" (extensionDecode :: Decoder KeyShare),+ entry "PostHandshakeAuth" (extensionDecode :: Decoder PostHandshakeAuth),+ entry "PskKeyExchangeModes" (extensionDecode :: Decoder PskKeyExchangeModes),+ entry "PreSharedKey" (extensionDecode :: Decoder PreSharedKey),+ entry "EarlyDataIndication" (extensionDecode :: Decoder EarlyDataIndication),+ entry "Cookie" (extensionDecode :: Decoder Cookie),+ entry "CertificateAuthorities" (extensionDecode :: Decoder CertificateAuthorities),+ entry "EchOuterExtensions" (extensionDecode :: Decoder EchOuterExtensions),+ entry "EncryptedClientHello" (extensionDecode :: Decoder EncryptedClientHello)+ ]+ where+ entry name decode = (name, \mt bs -> name <$ evaluate (length (show (decode mt bs))))+
test/HandshakeSpec.hs view
@@ -2,1041 +2,2837 @@ module HandshakeSpec where -import Control.Monad-import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as L-import Data.IORef-import Data.List-import Data.Maybe-import Data.X509 (ExtKeyUsageFlag (..))-import Network.TLS-import Network.TLS.Extra.Cipher-import Network.TLS.Internal-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.QuickCheck--import API-import Arbitrary-import PipeChan-import Run-import Session--spec :: Spec-spec = do- describe "pipe" $ do- it "can setup a channel" pipe_work- describe "handshake" $ do- prop "can run TLS 1.2" handshake_simple- prop "can run TLS 1.3" handshake13_simple- prop "can update key for TLS 1.3" handshake_update_key- prop "can prevent downgrade attack" handshake13_downgrade- prop "can negotiate hash and signature" handshake_hashsignatures- prop "can negotiate cipher suite" handshake_ciphersuites- prop "can negotiate group" handshake_groups- prop "can negotiate elliptic curve" handshake_ec- prop "can fallback for certificate with cipher" handshake_cert_fallback_cipher- prop- "can fallback for certificate with hash and signature"- handshake_cert_fallback_hs- prop "can handle server key usage" handshake_server_key_usage- prop "can handle client key usage" handshake_client_key_usage- prop "can authenticate client" handshake_client_auth- prop "can receive client authentication failure" handshake_client_auth_fail- prop "can handle extended main secret" handshake_ems- prop "can resume with extended main secret" handshake_resumption_ems- prop "can handle ALPN" handshake_alpn- prop "can handle SNI" handshake_sni- prop "can re-negotiate with TLS 1.2" handshake12_renegotiation- prop "can resume session with TLS 1.2" handshake12_session_resumption- prop "can resume session ticket with TLS 1.2" handshake12_session_ticket- prop "can handshake with TLS 1.3 Full" handshake13_full- prop "can handshake with TLS 1.3 HRR" handshake13_hrr- prop "can handshake with TLS 1.3 PSK" handshake13_psk- prop "can handshake with TLS 1.3 PSK ticket" handshake13_psk_ticket- prop "can handshake with TLS 1.3 PSK -> HRR" handshake13_psk_fallback- prop "can handshake with TLS 1.3 0RTT" handshake13_0rtt- prop "can handshake with TLS 1.3 0RTT -> PSK" handshake13_0rtt_fallback- prop "can handshake with TLS 1.3 EE" handshake13_ee_groups- prop "can handshake with TLS 1.3 EC groups" handshake13_ec- prop "can handshake with TLS 1.3 FFDHE groups" handshake13_ffdhe- prop "can handshake with TLS 1.3 Post-handshake auth" post_handshake_auth------------------------------------------------------------------pipe_work :: IO ()-pipe_work = do- pipe <- newPipe- _ <- runPipe pipe-- let bSize = 16- n <- generate (choose (1, 32))-- let d1 = B.replicate (bSize * n) 40- let d2 = B.replicate (bSize * n) 45-- d1' <- writePipeC pipe d1 >> readPipeS pipe (B.length d1)- d1' `shouldBe` d1-- d2' <- writePipeS pipe d2 >> readPipeC pipe (B.length d2)- d2' `shouldBe` d2------------------------------------------------------------------handshake_simple :: (ClientParams, ServerParams) -> IO ()-handshake_simple = runTLSSimple------------------------------------------------------------------newtype CSP13 = CSP13 (ClientParams, ServerParams) deriving (Show)--instance Arbitrary CSP13 where- arbitrary = CSP13 <$> arbitraryPairParams13--handshake13_simple :: CSP13 -> IO ()-handshake13_simple (CSP13 params) = runTLSSimple13 params hs- where- cgrps = supportedGroups $ clientSupported $ fst params- sgrps = supportedGroups $ serverSupported $ snd params- hs = if unsafeHead cgrps `elem` sgrps then FullHandshake else HelloRetryRequest------------------------------------------------------------------handshake13_downgrade :: (ClientParams, ServerParams) -> IO ()-handshake13_downgrade (cparam, sparam) = do- versionForced <-- generate $ elements (supportedVersions $ clientSupported cparam)- let debug' = (serverDebug sparam){debugVersionForced = Just versionForced}- sparam' = sparam{serverDebug = debug'}- params = (cparam, sparam')- downgraded =- (isVersionEnabled TLS13 params && versionForced < TLS13)- || (isVersionEnabled TLS12 params && versionForced < TLS12)- if downgraded- then runTLSFailure params handshake handshake- else runTLSSimple params--handshake_update_key :: (ClientParams, ServerParams) -> IO ()-handshake_update_key = runTLSSimpleKeyUpdate------------------------------------------------------------------handshake_hashsignatures- :: ([HashAndSignatureAlgorithm], [HashAndSignatureAlgorithm]) -> IO ()-handshake_hashsignatures (clientHashSigs, serverHashSigs) = do- tls13 <- generate arbitrary- let version = if tls13 then TLS13 else TLS12- ciphers =- [ cipher_ECDHE_RSA_WITH_AES_256_GCM_SHA384- , cipher_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384- , cipher13_AES_128_GCM_SHA256- ]- (clientParam, serverParam) <-- generate $- arbitraryPairParamsWithVersionsAndCiphers- ([version], [version])- (ciphers, ciphers)- let clientParam' =- clientParam- { clientSupported =- (clientSupported clientParam)- { supportedHashSignatures = clientHashSigs- }- }- serverParam' =- serverParam- { serverSupported =- (serverSupported serverParam)- { supportedHashSignatures = serverHashSigs- }- }- commonHashSigs = clientHashSigs `intersect` serverHashSigs- shouldFail- | tls13 = all incompatibleWithDefaultCurve commonHashSigs- | otherwise = null commonHashSigs- if shouldFail- then runTLSFailure (clientParam', serverParam') handshake handshake- else runTLSSimple (clientParam', serverParam')- where- incompatibleWithDefaultCurve (h, SignatureECDSA) = h /= HashSHA256- incompatibleWithDefaultCurve _ = False--handshake_ciphersuites :: ([Cipher], [Cipher]) -> IO ()-handshake_ciphersuites (clientCiphers, serverCiphers) = do- tls13 <- generate arbitrary- let version = if tls13 then TLS13 else TLS12- (clientParam, serverParam) <-- generate $- arbitraryPairParamsWithVersionsAndCiphers- ([version], [version])- (clientCiphers, serverCiphers)- let adequate = cipherAllowedForVersion version- shouldSucceed = any adequate (clientCiphers `intersect` serverCiphers)- if shouldSucceed- then runTLSSimple (clientParam, serverParam)- else runTLSFailure (clientParam, serverParam) handshake handshake------------------------------------------------------------------handshake_groups :: GGP -> IO ()-handshake_groups (GGP clientGroups serverGroups) = do- tls13 <- generate arbitrary- let versions = if tls13 then [TLS13] else [TLS12]- ciphers = ciphersuite_strong- (clientParam, serverParam) <-- generate $- arbitraryPairParamsWithVersionsAndCiphers- (versions, versions)- (ciphers, ciphers)- denyCustom <- generate arbitrary- let groupUsage =- if denyCustom- then GroupUsageUnsupported "custom group denied"- else GroupUsageValid- clientParam' =- clientParam- { clientSupported =- (clientSupported clientParam)- { supportedGroups = clientGroups- }- , clientHooks =- (clientHooks clientParam)- { onCustomFFDHEGroup = \_ _ -> return groupUsage- }- }- serverParam' =- serverParam- { serverSupported =- (serverSupported serverParam)- { supportedGroups = serverGroups- , supportedGroupsTLS13 = [serverGroups]- }- }- commonGroups = clientGroups `intersect` serverGroups- shouldFail = null commonGroups- p minfo = isNothing (minfo >>= infoSupportedGroup) == null commonGroups- if shouldFail- then runTLSFailure (clientParam', serverParam') handshake handshake- else runTLSPredicate (clientParam', serverParam') p------------------------------------------------------------------newtype SG = SG [Group] deriving (Show)--instance Arbitrary SG where- arbitrary = SG <$> shuffle sigGroups- where- sigGroups = [P256, P521]--handshake_ec :: SG -> IO ()-handshake_ec (SG sigGroups) = do- let versions = [TLS12]- ciphers =- [ cipher_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384- ]- hashSignatures =- [ (HashSHA256, SignatureECDSA)- ]- (clientParam, serverParam) <-- generate $- arbitraryPairParamsWithVersionsAndCiphers- (versions, versions)- (ciphers, ciphers)- clientGroups <- generate $ shuffle sigGroups- clientHashSignatures <- generate $ sublistOf hashSignatures- serverHashSignatures <- generate $ sublistOf hashSignatures- credentials <- generate arbitraryCredentialsOfEachCurve- let clientParam' =- clientParam- { clientSupported =- (clientSupported clientParam)- { supportedGroups = clientGroups- , supportedHashSignatures = clientHashSignatures- }- }- serverParam' =- serverParam- { serverSupported =- (serverSupported serverParam)- { supportedGroups = sigGroups- , supportedGroupsTLS13 = [sigGroups]- , supportedHashSignatures = serverHashSignatures- }- , serverShared =- (serverShared serverParam)- { sharedCredentials = Credentials credentials- }- }- sigAlgs = map snd (clientHashSignatures `intersect` serverHashSignatures)- ecdsaDenied = SignatureECDSA `notElem` sigAlgs- if ecdsaDenied- then runTLSFailure (clientParam', serverParam') handshake handshake- else runTLSSimple (clientParam', serverParam')---- Tests ability to use or ignore client "signature_algorithms" extension when--- choosing a server certificate. Here peers allow DHE_RSA_AES128_SHA1 but--- the server RSA certificate has a SHA-1 signature that the client does not--- support. Server may choose the DSA certificate only when cipher--- DHE_DSA_AES128_SHA1 is allowed. Otherwise it must fallback to the RSA--- certificate.--data OC = OC [Cipher] [Cipher] deriving (Show)--instance Arbitrary OC where- arbitrary = OC <$> sublistOf otherCiphers <*> sublistOf otherCiphers- where- otherCiphers =- [ cipher_ECDHE_RSA_WITH_AES_256_GCM_SHA384- , cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256- ]--handshake_cert_fallback_cipher :: OC -> IO ()-handshake_cert_fallback_cipher (OC clientCiphers serverCiphers) = do- let clientVersions = [TLS12]- serverVersions = [TLS12]- commonCiphers = [cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256]- hashSignatures = [(HashSHA256, SignatureRSA), (HashSHA1, SignatureDSA)]- chainRef <- newIORef Nothing- (clientParam, serverParam) <-- generate $- arbitraryPairParamsWithVersionsAndCiphers- (clientVersions, serverVersions)- (clientCiphers ++ commonCiphers, serverCiphers ++ commonCiphers)- let clientParam' =- clientParam- { clientSupported =- (clientSupported clientParam)- { supportedHashSignatures = hashSignatures- }- , clientHooks =- (clientHooks clientParam)- { onServerCertificate = \_ _ _ chain ->- writeIORef chainRef (Just chain) >> return []- }- }- runTLSSimple (clientParam', serverParam)- serverChain <- readIORef chainRef- isLeafRSA serverChain `shouldBe` True---- Same as above but testing with supportedHashSignatures directly instead of--- ciphers, and thus allowing TLS13. Peers accept RSA with SHA-256 but the--- server RSA certificate has a SHA-1 signature. When Ed25519 is allowed by--- both client and server, the Ed25519 certificate is selected. Otherwise the--- server fallbacks to RSA.------ Note: SHA-1 is supposed to be disallowed in X.509 signatures with TLS13--- unless client advertises explicit support. Currently this is not enforced by--- the library, which is useful to test this scenario. SHA-1 could be replaced--- by another algorithm.--data OHS = OHS [HashAndSignatureAlgorithm] [HashAndSignatureAlgorithm]- deriving (Show)--instance Arbitrary OHS where- arbitrary = OHS <$> sublistOf otherHS <*> sublistOf otherHS- where- otherHS = [(HashIntrinsic, SignatureEd25519)]--handshake_cert_fallback_hs :: OHS -> IO ()-handshake_cert_fallback_hs (OHS clientHS serverHS) = do- tls13 <- generate arbitrary- let versions = if tls13 then [TLS13] else [TLS12]- ciphers =- [ cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256- , cipher_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256- , cipher13_AES_128_GCM_SHA256- ]- commonHS =- [ (HashSHA256, SignatureRSA)- , (HashIntrinsic, SignatureRSApssRSAeSHA256)- ]- chainRef <- newIORef Nothing- (clientParam, serverParam) <-- generate $- arbitraryPairParamsWithVersionsAndCiphers- (versions, versions)- (ciphers, ciphers)- let clientParam' =- clientParam- { clientSupported =- (clientSupported clientParam)- { supportedHashSignatures = commonHS ++ clientHS- }- , clientHooks =- (clientHooks clientParam)- { onServerCertificate = \_ _ _ chain ->- writeIORef chainRef (Just chain) >> return []- }- }- serverParam' =- serverParam- { serverSupported =- (serverSupported serverParam)- { supportedHashSignatures = commonHS ++ serverHS- }- }- eddsaDisallowed =- (HashIntrinsic, SignatureEd25519) `notElem` clientHS- || (HashIntrinsic, SignatureEd25519) `notElem` serverHS- runTLSSimple (clientParam', serverParam')- serverChain <- readIORef chainRef- isLeafRSA serverChain `shouldBe` eddsaDisallowed------------------------------------------------------------------handshake_server_key_usage :: [ExtKeyUsageFlag] -> IO ()-handshake_server_key_usage usageFlags = do- tls13 <- generate arbitrary- let versions = if tls13 then [TLS13] else [TLS12]- ciphers = ciphersuite_all- (clientParam, serverParam) <-- generate $- arbitraryPairParamsWithVersionsAndCiphers- (versions, versions)- (ciphers, ciphers)- cred <- generate $ arbitraryRSACredentialWithUsage usageFlags- let serverParam' =- serverParam- { serverShared =- (serverShared serverParam)- { sharedCredentials = Credentials [cred]- }- }- shouldSucceed = KeyUsage_digitalSignature `elem` usageFlags- if shouldSucceed- then runTLSSimple (clientParam, serverParam')- else runTLSFailure (clientParam, serverParam') handshake handshake--handshake_client_key_usage :: [ExtKeyUsageFlag] -> IO ()-handshake_client_key_usage usageFlags = do- (clientParam, serverParam) <- generate arbitrary- cred <- generate $ arbitraryRSACredentialWithUsage usageFlags- let clientParam' =- clientParam- { clientHooks =- (clientHooks clientParam)- { onCertificateRequest = \_ -> return $ Just cred- }- }- serverParam' =- serverParam- { serverWantClientCert = True- , serverHooks =- (serverHooks serverParam)- { onClientCertificate = \_ -> return CertificateUsageAccept- }- }- shouldSucceed = KeyUsage_digitalSignature `elem` usageFlags- if shouldSucceed- then runTLSSimple (clientParam', serverParam')- else runTLSFailure (clientParam', serverParam') handshake handshake------------------------------------------------------------------handshake_client_auth :: (ClientParams, ServerParams) -> IO ()-handshake_client_auth (clientParam, serverParam) = do- let clientVersions = supportedVersions $ clientSupported clientParam- serverVersions = supportedVersions $ serverSupported serverParam- version = maximum (clientVersions `intersect` serverVersions)- cred <- generate (arbitraryClientCredential version)- let clientParam' =- clientParam- { clientHooks =- (clientHooks clientParam)- { onCertificateRequest = \_ -> return $ Just cred- }- }- serverParam' =- serverParam- { serverWantClientCert = True- , serverHooks =- (serverHooks serverParam)- { onClientCertificate = validateChain cred- }- }- runTLSSimple (clientParam', serverParam')- where- validateChain cred chain- | chain == fst cred = return CertificateUsageAccept- | otherwise = return (CertificateUsageReject CertificateRejectUnknownCA)--handshake_client_auth_fail :: (ClientParams, ServerParams) -> IO ()-handshake_client_auth_fail (clientParam, serverParam) = do- let clientVersions = supportedVersions $ clientSupported clientParam- serverVersions = supportedVersions $ serverSupported serverParam- version = maximum (clientVersions `intersect` serverVersions)- cred <- generate (arbitraryClientCredential version)- let clientParam' =- clientParam- { clientHooks =- (clientHooks clientParam)- { onCertificateRequest = \_ -> return $ Just cred- }- }- serverParam' =- serverParam- { serverWantClientCert = True- , serverHooks =- (serverHooks serverParam)- { onClientCertificate = validateChain cred- }- }- runTLSFailure (clientParam', serverParam') handshake handshake- where- validateChain _ _ = return (CertificateUsageReject CertificateRejectUnknownCA)------------------------------------------------------------------handshake_ems :: (EMSMode, EMSMode) -> IO ()-handshake_ems (cems, sems) = do- params <- generate arbitrary- let params' = setEMSMode (cems, sems) params- version = getConnectVersion params'- emsVersion = version >= TLS10 && version <= TLS12- use = cems /= NoEMS && sems /= NoEMS- require = cems == RequireEMS || sems == RequireEMS- p info = infoExtendedMainSecret info == (emsVersion && use)- if emsVersion && require && not use- then runTLSFailure params' handshake handshake- else runTLSPredicate params' (maybe False p)--newtype CompatEMS = CompatEMS (EMSMode, EMSMode) deriving (Show)--instance Arbitrary CompatEMS where- arbitrary = CompatEMS <$> (arbitrary `suchThat` compatible)- where- compatible (NoEMS, RequireEMS) = False- compatible (RequireEMS, NoEMS) = False- compatible _ = True--handshake_resumption_ems :: (CompatEMS, CompatEMS) -> IO ()-handshake_resumption_ems (CompatEMS ems, CompatEMS ems2) = do- sessionRefs <- twoSessionRefs- let sessionManagers = twoSessionManagers sessionRefs-- plainParams <- generate arbitrary- let params =- setEMSMode ems $- setPairParamsSessionManagers sessionManagers plainParams-- runTLSSimple params-- -- and resume- sessionParams <- readClientSessionRef sessionRefs- expectJust "session param should be Just" sessionParams- let params2 =- setEMSMode ems2 $- setPairParamsSessionResuming (fromJust sessionParams) params-- let version = getConnectVersion params2- emsVersion = version >= TLS10 && version <= TLS12-- if emsVersion && use ems && not (use ems2)- then runTLSFailure params2 handshake handshake- else do- runTLSSimple params2- mSessionParams2 <- readClientSessionRef sessionRefs- let sameSession = sessionParams == mSessionParams2- sameUse = use ems == use ems2- when emsVersion (sameSession `shouldBe` sameUse)- where- use (NoEMS, _) = False- use (_, NoEMS) = False- use _ = True------------------------------------------------------------------handshake_alpn :: (ClientParams, ServerParams) -> IO ()-handshake_alpn (clientParam, serverParam) = do- let clientParam' =- clientParam- { clientHooks =- (clientHooks clientParam)- { onSuggestALPN = return $ Just ["h2", "http/1.1"]- }- }- serverParam' =- serverParam- { serverHooks =- (serverHooks serverParam)- { onALPNClientSuggest = Just alpn- }- }- params' = (clientParam', serverParam')- runTLSSuccess params' hsClient hsServer- where- hsClient ctx = do- handshake ctx- proto <- getNegotiatedProtocol ctx- proto `shouldBe` Just "h2"- hsServer ctx = do- handshake ctx- proto <- getNegotiatedProtocol ctx- proto `shouldBe` Just "h2"- alpn xs- | "h2" `elem` xs = return "h2"- | otherwise = return "http/1.1"--handshake_sni :: (ClientParams, ServerParams) -> IO ()-handshake_sni (clientParam, serverParam) = do- ref <- newIORef Nothing- let clientParam' =- clientParam- { clientServerIdentification = (serverName, "")- }- serverParam' =- serverParam- { serverHooks =- (serverHooks serverParam)- { onServerNameIndication = onSNI ref- }- }- params' = (clientParam', serverParam')- runTLSSuccess params' hsClient hsServer- receivedName <- readIORef ref- receivedName `shouldBe` Just (Just serverName)- where- hsClient ctx = do- handshake ctx- msni <- getClientSNI ctx- expectMaybe "C: SNI should be Just" serverName msni- hsServer ctx = do- handshake ctx- msni <- getClientSNI ctx- expectMaybe "S: SNI should be Just" serverName msni- onSNI ref name = do- mx <- readIORef ref- mx `shouldBe` Nothing- writeIORef ref (Just name)- return (Credentials [])- serverName = "haskell.org"------------------------------------------------------------------newtype CSP12 = CSP12 (ClientParams, ServerParams) deriving (Show)--instance Arbitrary CSP12 where- arbitrary = CSP12 <$> arbitraryPairParams12--handshake12_renegotiation :: CSP12 -> IO ()-handshake12_renegotiation (CSP12 (cparams, sparams)) = do- renegDisabled <- generate arbitrary- let sparams' =- sparams- { serverSupported =- (serverSupported sparams)- { supportedClientInitiatedRenegotiation = not renegDisabled- }- }- if renegDisabled- then runTLSFailure (cparams, sparams') hsClient hsServer- else runTLSSimple (cparams, sparams')- where- hsClient ctx = handshake ctx >> handshake ctx- -- recvData receives the alert from the second handshake- hsServer ctx = handshake ctx >> void (recvData ctx)--handshake12_session_resumption :: CSP12 -> IO ()-handshake12_session_resumption (CSP12 plainParams) = do- sessionRefs <- twoSessionRefs- let sessionManagers = twoSessionManagers sessionRefs-- let params = setPairParamsSessionManagers sessionManagers plainParams-- runTLSSimple params-- -- and resume- sessionParams <- readClientSessionRef sessionRefs- expectJust "session param should be Just" sessionParams- let params2 = setPairParamsSessionResuming (fromJust sessionParams) params-- runTLSPredicate params2 (maybe False infoTLS12Resumption)--handshake12_session_ticket :: CSP12 -> IO ()-handshake12_session_ticket (CSP12 plainParams) = do- sessionRefs <- twoSessionRefs- let sessionManagers0 = twoSessionManagers sessionRefs- sessionManagers = (fst sessionManagers0, oneSessionTicket)-- let params = setPairParamsSessionManagers sessionManagers plainParams-- runTLSSimple params-- -- and resume- sessionParams <- readClientSessionRef sessionRefs- expectJust "session param should be Just" sessionParams- let params2 = setPairParamsSessionResuming (fromJust sessionParams) params-- runTLSPredicate params2 (maybe False infoTLS12Resumption)------------------------------------------------------------------handshake13_full :: CSP13 -> IO ()-handshake13_full (CSP13 (cli, srv)) = do- let cliSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [X25519]- }- svrSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [X25519]- , supportedGroupsTLS13 = [[X25519]]- }- params =- ( cli{clientSupported = cliSupported}- , srv{serverSupported = svrSupported}- )- runTLSSimple13 params FullHandshake--handshake13_hrr :: CSP13 -> IO ()-handshake13_hrr (CSP13 (cli, srv)) = do- let cliSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [P256, X25519]- }- svrSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [X25519]- , supportedGroupsTLS13 = [[X25519]]- }- params =- ( cli{clientSupported = cliSupported}- , srv{serverSupported = svrSupported}- )- runTLSSimple13 params HelloRetryRequest--handshake13_psk :: CSP13 -> IO ()-handshake13_psk (CSP13 (cli, srv)) = do- let cliSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [P256, X25519]- }- svrSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [X25519]- , supportedGroupsTLS13 = [[X25519]]- }- params0 =- ( cli{clientSupported = cliSupported}- , srv{serverSupported = svrSupported}- )-- sessionRefs <- twoSessionRefs- let sessionManagers = twoSessionManagers sessionRefs-- let params = setPairParamsSessionManagers sessionManagers params0-- runTLSSimple13 params HelloRetryRequest-- -- and resume- sessionParams <- readClientSessionRef sessionRefs- expectJust "session param should be Just" sessionParams- let params2 = setPairParamsSessionResuming (fromJust sessionParams) params-- runTLSSimple13 params2 PreSharedKey--handshake13_psk_ticket :: CSP13 -> IO ()-handshake13_psk_ticket (CSP13 (cli, srv)) = do- let cliSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [P256, X25519]- }- svrSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [X25519]- , supportedGroupsTLS13 = [[X25519]]- }- params0 =- ( cli{clientSupported = cliSupported}- , srv{serverSupported = svrSupported}- )-- sessionRefs <- twoSessionRefs- let sessionManagers0 = twoSessionManagers sessionRefs- sessionManagers = (fst sessionManagers0, oneSessionTicket)-- let params = setPairParamsSessionManagers sessionManagers params0-- runTLSSimple13 params HelloRetryRequest-- -- and resume- sessionParams <- readClientSessionRef sessionRefs- expectJust "session param should be Just" sessionParams- let params2 = setPairParamsSessionResuming (fromJust sessionParams) params-- runTLSSimple13 params2 PreSharedKey--handshake13_psk_fallback :: CSP13 -> IO ()-handshake13_psk_fallback (CSP13 (cli, srv)) = do- let cliSupported =- defaultSupported- { supportedCiphers =- [ cipher13_AES_128_GCM_SHA256- , cipher13_AES_128_CCM_SHA256- ]- , supportedGroups = [P256, X25519]- }- svrSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [X25519]- , supportedGroupsTLS13 = [[X25519]]- }- params0 =- ( cli{clientSupported = cliSupported}- , srv{serverSupported = svrSupported}- )-- sessionRefs <- twoSessionRefs- let sessionManagers = twoSessionManagers sessionRefs-- let params = setPairParamsSessionManagers sessionManagers params0-- runTLSSimple13 params HelloRetryRequest-- -- resumption fails because GCM cipher is not supported anymore, full- -- handshake is not possible because X25519 has been removed, so we are- -- back with P256 after hello retry- sessionParams <- readClientSessionRef sessionRefs- expectJust "session param should be Just" sessionParams- let (cli2, srv2) = setPairParamsSessionResuming (fromJust sessionParams) params- srv2' =- srv2{serverSupported = svrSupported'}- svrSupported' =- defaultSupported- { supportedCiphers = [cipher13_AES_128_CCM_SHA256]- , supportedGroups = [P256]- , supportedGroupsTLS13 = [[P256]]- }-- runTLSSimple13 (cli2, srv2') HelloRetryRequest--handshake13_0rtt :: CSP13 -> IO ()-handshake13_0rtt (CSP13 (cli, srv)) = do- let cliSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [P256, X25519]- }- svrSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [X25519]- , supportedGroupsTLS13 = [[X25519]]- }- cliHooks =- defaultClientHooks- { onSuggestALPN = return $ Just ["h2"]- }- svrHooks =- defaultServerHooks- { onALPNClientSuggest = Just (return . unsafeHead)- }- params0 =- ( cli- { clientSupported = cliSupported- , clientHooks = cliHooks- }- , srv- { serverSupported = svrSupported- , serverHooks = svrHooks- , serverEarlyDataSize = 2048- }- )-- sessionRefs <- twoSessionRefs- let sessionManagers = twoSessionManagers sessionRefs-- let params = setPairParamsSessionManagers sessionManagers params0-- runTLSSimple13 params HelloRetryRequest- runTLS0rtt params sessionRefs- runTLS0rtt params sessionRefs- where- runTLS0rtt params sessionRefs = do- -- and resume- sessionParams <- readClientSessionRef sessionRefs- expectJust "session param should be Just" sessionParams- clearClientSessionRef sessionRefs- earlyData <- B.pack <$> generate (someWords8 256)- let (pc, ps) = setPairParamsSessionResuming (fromJust sessionParams) params- params2 = (pc{clientUseEarlyData = True}, ps)-- runTLS0RTT params2 RTT0 earlyData--handshake13_0rtt_fallback :: CSP13 -> IO ()-handshake13_0rtt_fallback (CSP13 (cli, srv)) = do- group0 <- generate $ elements [P256, X25519]- let cliSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [P256, X25519]- }- svrSupported =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [group0]- , supportedGroupsTLS13 = [[group0]]- }- params =- ( cli{clientSupported = cliSupported}- , srv- { serverSupported = svrSupported- , serverEarlyDataSize = 1024- }- )-- sessionRefs <- twoSessionRefs- let sessionManagers = twoSessionManagers sessionRefs-- let params0 = setPairParamsSessionManagers sessionManagers params-- let mode = if group0 == P256 then FullHandshake else HelloRetryRequest- runTLSSimple13 params0 mode-- -- and resume- mSessionParams <- readClientSessionRef sessionRefs- case mSessionParams of- Nothing -> expectationFailure "session params: Just is expected"- Just sessionParams -> do- earlyData <- B.pack <$> generate (someWords8 256)- group1 <- generate $ elements [P256, X25519]- let (pc, ps) = setPairParamsSessionResuming sessionParams params0- svrSupported1 =- defaultSupported- { supportedCiphers = [cipher13_AES_128_GCM_SHA256]- , supportedGroups = [group1]- , supportedGroupsTLS13 = [[group1]]- }- params1 =- ( pc{clientUseEarlyData = True}- , ps- { serverEarlyDataSize = 0- , serverSupported = svrSupported1- }- )- -- C: [P256, X25519]- -- S: [group0]- -- C: [P256, X25519]- -- S: [group1]- if group0 == group1- -- 0-RTT is not allowed, so fallback to PreSharedKey- then runTLS0RTT params1 PreSharedKey earlyData- -- HRR but not allowed for 0-RTT- else runTLSFailure params1 (tlsClient earlyData) tlsServer- where- tlsClient earlyData ctx = do- handshake ctx- sendData ctx $ L.fromStrict earlyData- _ <- recvData ctx- bye ctx- tlsServer ctx = do- handshake ctx- _ <- recvData ctx- bye ctx--handshake13_ee_groups :: CSP13 -> IO ()-handshake13_ee_groups (CSP13 (cli, srv)) = do- let -- The client prefers P256- cliSupported = (clientSupported cli){supportedGroups = [P256, X25519]}- -- The server prefers X25519- svrSupported =- (serverSupported srv)- { supportedGroups = [X25519, P256]- , supportedGroupsTLS13 = [[X25519, P256]]- }- params =- ( cli{clientSupported = cliSupported}- , srv{serverSupported = svrSupported}- )- (_, serverMessages) <- runTLSCapture13 params- -- The server should tell X25519 in supported_groups in EE to clinet- let isSupportedGroups (ExtensionRaw eid _) = eid == EID_SupportedGroups- eeMessagesHaveExt =- [ any isSupportedGroups exts- | EncryptedExtensions13 exts <- serverMessages- ]- eeMessagesHaveExt `shouldBe` [True]--handshake13_ec :: CSP13 -> IO ()-handshake13_ec (CSP13 (cli, srv)) = do- EC cgrps <- generate arbitrary- EC sgrps <- generate arbitrary- let cliSupported = (clientSupported cli){supportedGroups = cgrps}- svrSupported =- (serverSupported srv)- { supportedGroups = sgrps- , supportedGroupsTLS13 = [sgrps]- }- params =- ( cli{clientSupported = cliSupported}- , srv{serverSupported = svrSupported}- )- runTLSSimple13 params FullHandshake--handshake13_ffdhe :: CSP13 -> IO ()-handshake13_ffdhe (CSP13 (cli, srv)) = do- FFDHE cgrps <- generate arbitrary- FFDHE sgrps <- generate arbitrary- let cliSupported = (clientSupported cli){supportedGroups = cgrps}- svrSupported =- (serverSupported srv)- { supportedGroups = sgrps- , supportedGroupsTLS13 = [sgrps]- }- params =- ( cli{clientSupported = cliSupported}- , srv{serverSupported = svrSupported}- )- runTLSSimple13 params FullHandshake--post_handshake_auth :: CSP13 -> IO ()-post_handshake_auth (CSP13 (clientParam, serverParam)) = do- cred <- generate (arbitraryClientCredential TLS13)- let clientParam' =- clientParam- { clientHooks =- (clientHooks clientParam)- { onCertificateRequest = \_ -> return $ Just cred- }- }- serverParam' =- serverParam- { serverHooks =- (serverHooks serverParam)- { onClientCertificate = validateChain cred- }- }- if isCredentialDSA cred- then runTLSFailure (clientParam', serverParam') hsClient hsServer- else runTLSSuccess (clientParam', serverParam') hsClient hsServer- where- validateChain cred chain- | chain == fst cred = return CertificateUsageAccept- | otherwise = return (CertificateUsageReject CertificateRejectUnknownCA)- hsClient ctx = do- handshake ctx- sendData ctx "request 1"- recvDataAssert ctx "response 1"- sendData ctx "request 2"- recvDataAssert ctx "response 2"- hsServer ctx = do- handshake ctx- recvDataAssert ctx "request 1"- _ <- requestCertificate ctx -- single request- sendData ctx "response 1"- recvDataAssert ctx "request 2"- _ <- requestCertificate ctx- _ <- requestCertificate ctx -- two simultaneously- sendData ctx "response 2"+import Control.Concurrent (threadDelay)+import Control.Concurrent.Async (concurrently_)+import qualified Control.Exception as E+import Control.Monad+import Crypto.Cipher.AES (AES128)+import Crypto.Cipher.Types (AEADMode (..), AuthTag (..), aeadInit, aeadSimpleEncrypt, cipherInit)+import Crypto.Error (throwCryptoError)+import Data.Bits (shiftR, xor)+import qualified Data.ByteArray as BA+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import Data.IORef+import Data.List+import Data.Maybe+import Data.Word (Word16, Word64, Word8)+import Data.X509 (ExtKeyUsageFlag (..), ExtKeyUsagePurpose (..))+import Network.TLS+import Network.TLS.Extra.Cipher+import Network.TLS.Extra.CipherCBC+import Network.TLS.Internal+import Network.TLS.QUIC (hkdfExpandLabel)+import Test.Hspec+import Test.Hspec.QuickCheck+import System.Timeout (timeout)+import Test.QuickCheck++import API+import Arbitrary+import Certificate (arbitraryRSACredentialWithPurpose)+import PipeChan+import Run+import Session++spec :: Spec+spec = do+ describe "pipe" $ do+ it "can setup a channel" pipe_work+ describe "channel binding" $ do+ prop "is unavailable before the handshake" binding_before_handshake+ describe "handshake" $ do+ prop "can run TLS 1.2" handshake_simple+ prop "can run TLS 1.3" handshake13_simple+ prop "can update key for TLS 1.3" handshake_update_key+ it+ "rejects more than 32 consecutive TLS 1.3 KeyUpdates"+ handshake_key_update_flood+ it+ "can disable the consecutive TLS 1.3 KeyUpdate limit"+ handshake_key_update_unlimited+ it+ "does not disable TLS 1.3 KeyUpdates with non-positive limits"+ handshake_key_update_non_positive+ prop "can prevent downgrade attack" handshake13_downgrade+ it "negotiates TLS 1.2 with a ClientHello naming a higher version" $+ handshake_high_legacy_version TLS12 (Version 0x0309)+ it "ignores legacy_version when supported_versions is present" $+ handshake_high_legacy_version TLS13 TLS13+ it "keeps to the TLS 1.3 server's record size limit" $+ record_size_limit_negotiated TLS13 False+ it "keeps to the TLS 1.3 client's record size limit" $+ record_size_limit_negotiated TLS13 True+ it "keeps to the TLS 1.2 server's record size limit" $+ record_size_limit_negotiated TLS12 False+ it "keeps to the TLS 1.2 client's record size limit" $+ record_size_limit_negotiated TLS12 True+ it "ignores the client's record size limit the TLS 1.3 server does not take" $+ record_size_limit_unnegotiated TLS13 True+ it "ignores the server's record size limit the TLS 1.3 client did not offer" $+ record_size_limit_unnegotiated TLS13 False+ it "ignores the client's record size limit the TLS 1.2 server does not take" $+ record_size_limit_unnegotiated TLS12 True+ it "ignores the server's record size limit the TLS 1.2 client did not offer" $+ record_size_limit_unnegotiated TLS12 False+ it "rejects ec_point_formats without uncompressed" $+ handshake12_ec_point_formats+ (B.pack [1, 1])+ rejectedAsIllegalParameter+ it "rejects an empty ec_point_formats" $+ handshake12_ec_point_formats (B.pack [0]) rejectedAsDecodeError+ prop "can negotiate hash and signature" handshake_hashsignatures+ prop "can negotiate cipher suite" handshake_ciphersuites+ it "rejects a cipher outside the server callback candidates" $+ handshake_rejects_server_cipher_callback_escape+ it "rejects a TLS 1.2-only cipher selected for TLS 1.3" $+ handshake_rejects_legacy_cipher_in_tls13+ prop "can negotiate group" handshake_groups+ prop "can negotiate elliptic curve" handshake_ec+ prop "can fallback for certificate with cipher" handshake_cert_fallback_cipher+ prop+ "can fallback for certificate with hash and signature"+ handshake_cert_fallback_hs+ prop "can handle server key usage" handshake_server_key_usage+ it "accepts a TLS 1.2 server certificate permitting server auth" $+ handshake_server_key_purpose TLS12 KeyUsagePurpose_ServerAuth True+ it "accepts a TLS 1.3 server certificate permitting server auth" $+ handshake_server_key_purpose TLS13 KeyUsagePurpose_ServerAuth True+ it "rejects a TLS 1.2 server certificate restricted to client auth" $+ handshake_server_key_purpose TLS12 KeyUsagePurpose_ClientAuth False+ it "rejects a TLS 1.3 server certificate restricted to client auth" $+ handshake_server_key_purpose TLS13 KeyUsagePurpose_ClientAuth False+ prop "can handle client key usage" handshake_client_key_usage+ prop "can authenticate client" handshake_client_auth+ it "rejects a TLS 1.3 CertificateVerify algorithm unfit for the key" $+ handshake13_client_cert_verify_unfit_sigalg+ it "rejects a primitive SEQUENCE as an ECDSA signature" $+ handshake13_client_cert_verify_malformed_ecdsa $ \sig ->+ B.cons (B.head sig `xor` 0x20) (B.tail sig)+ it "rejects a BIT STRING in an ECDSA signature" $+ handshake13_client_cert_verify_malformed_ecdsa $ \_ ->+ B.pack [0x30, 0x06, 0x02, 0x01, 0x01, 0x03, 0x01, 0x09]+ it "rejects a TLS 1.2 CertificateVerify algorithm for another key type" $+ handshake12_client_cert_verify_sigalg+ (HashSHA256, SignatureECDSA)+ rejectedAsIllegalParameter+ it "rejects a TLS 1.2 CertificateVerify that does not fit the RSA key" $+ handshake12_client_cert_verify_sigalg+ (HashIntrinsic, SignatureRSApsspssSHA256)+ rejectedAsDecryptError+ it "rejects a TLS 1.2 ServerKeyExchange algorithm for another key type" $+ handshake12_server_key_xchg_sigalg+ (HashSHA256, SignatureECDSA)+ rejectedAsIllegalParameter+ it "rejects a TLS 1.2 ServerKeyExchange that does not fit the RSA key" $+ handshake12_server_key_xchg_sigalg+ (HashIntrinsic, SignatureRSApsspssSHA256)+ rejectedAsDecryptError+ prop "can receive client authentication failure" handshake_client_auth_fail+ it "accepts an empty TLS 1.2 client certificate when the hook does" $+ handshake_client_auth_empty TLS12+ it "accepts an empty TLS 1.3 client certificate when the hook does" $+ handshake_client_auth_empty TLS13+ it "rejects a TLS 1.2 client omitting the requested certificate" $+ handshake12_client_cert_omitted+ it "rejects a TLS 1.2 server omitting Certificate" $+ handshake12_server_message_omitted 11+ it "rejects a TLS 1.2 server omitting ServerKeyExchange for ECDHE" $+ handshake12_server_message_omitted 12+ it "requests only defined certificate types in TLS 1.2" $+ handshake12_cert_request_types+ prop "can handle extended main secret" handshake_ems+ prop "can resume with extended main secret" handshake_resumption_ems+ prop "can handle ALPN" handshake_alpn+ it "rejects an unoffered ALPN selection from the server hook" $+ handshake_alpn_rejects_unoffered_server_selection+ it "rejects an unoffered ALPN selection received by the client" $+ handshake_alpn_rejects_unoffered_client_selection+ prop "can handle SNI" handshake_sni+ it "sends no SNI for an empty server name" handshake_sni_empty+ it "rejects multiple host_names in SNI" $+ handshake_sni_illegal ["example.com", "example.org"]+ it "rejects a host_name with a control character in SNI" $+ handshake_sni_illegal ["example\0.com"]+ it "rejects a non-ASCII host_name in SNI" $+ handshake_sni_illegal ["ex\xc4\x85mple.com"]+ prop "can handshake with TLS 1.2 CBC" handshake_cbc+ prop "can re-negotiate with TLS 1.2" handshake12_renegotiation+ it "rejects SCSV in a secure renegotiation" $+ handshake12_renegotiation_tampered $ \ch ->+ ch{chCiphers = chCiphers ch ++ [CipherId 0xff]}+ it "rejects a secure renegotiation without renegotiation_info" $+ handshake12_renegotiation_tampered $ \ch ->+ ch+ { chExtensions =+ filter+ (\(ExtensionRaw eid _) -> eid /= EID_SecureRenegotiation)+ (chExtensions ch)+ }+ prop "can resume session with TLS 1.2" handshake12_session_resumption+ prop+ "rejects resuming a TLS 1.2 session without its cipher"+ handshake12_session_resumption_cipher_missing+ prop "can resume session ticket with TLS 1.2" handshake12_session_ticket+ it "sends no session ticket to a TLS 1.2 client that did not ask" $+ handshake12_session_ticket_unoffered+ prop "can handshake with TLS 1.3 Full" handshake13_full+ prop "can handshake with TLS 1.3 HRR" handshake13_hrr+ prop "can handshake with TLS 1.3 PSK" handshake13_psk+ it "does not resume a TLS 1.2 session with a TLS 1.3 PSK" $+ handshake13_psk_tls12_session+ prop "can handshake with TLS 1.3 PSK ticket" handshake13_psk_ticket+ prop "can handshake with TLS 1.3 PSK -> HRR" handshake13_psk_fallback+ prop "can handshake with TLS 1.3 0RTT" handshake13_0rtt+ it "rejects TLS 1.3 early data when ALPN changes" $+ handshake13_0rtt_alpn+ prop "can handshake with TLS 1.3 0RTT -> PSK" handshake13_0rtt_fallback+ it "authenticates a server rejecting the PSK of 0RTT" $+ handshake13_0rtt_psk_rejected False+ it "authenticates a server rejecting the PSK of 0RTT, asking a cert" $+ handshake13_0rtt_psk_rejected True+ prop "can handshake with TLS 1.3 EE" handshake13_ee_groups+ prop "can handshake with TLS 1.3 EC groups" handshake13_ec+ prop "can handshake with TLS 1.3 FFDHE groups" handshake13_ffdhe+ mapM_+ ( \grp ->+ prop ("can handshake with " ++ show grp) $+ handshake13_kem grp+ )+ [ MLKEM512+ , MLKEM768+ , MLKEM1024+ , X25519MLKEM768+ , P256MLKEM768+ , P384MLKEM1024+ ]+ mapM_+ ( \(n, nm) -> do+ prop ("can handshake with an " ++ nm ++ " server certificate") $+ handshake13_mldsa n+ prop ("can handshake with an " ++ nm ++ " client certificate") $+ handshake13_mldsa_client n+ )+ [(0, "ML-DSA-44"), (1, "ML-DSA-65"), (2, "ML-DSA-87")]+ it "rejects a CertificateVerify whose scheme is not the key's" $+ handshake13_mldsa_wrong_scheme+ it "rejects an X25519MLKEM768 key share with a zero X25519 part" $+ handshake13_x25519mlkem768_zero_x25519+ prop "can handshake with TLS 1.3 Post-handshake auth" post_handshake_auth+ it+ "keeps record alignment when a slow record follows client auth"+ handshake13_client_auth_slow_record+ it "rejects a too short TLS 1.2 AEAD record with bad_record_mac" $+ short_record_bad_record_mac TLS12 cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ it "rejects a too short TLS 1.2 CBC record with bad_record_mac" $+ short_record_bad_record_mac TLS12 cipher_ECDHE_RSA_AES128CBC_SHA256+ it "rejects a too short TLS 1.3 record with bad_record_mac" $+ short_record_bad_record_mac TLS13 cipher13_AES_128_GCM_SHA256+ it "rejects a TLS 1.3 alert in clear after the handshake" $+ plaintext_alert13_rejected+ it "rejects a TLS 1.2 ChangeCipherSpec before the keys" $+ early_ccs12_rejected+ it "rejects a ServerHello as the first client message" $+ server_first_message_unexpected 2+ it "rejects a Finished as the first client message" $+ server_first_message_unexpected 20+ it "rejects an unknown handshake type as the first client message" $+ server_first_message_unexpected 254+ it "rejects a ChangeCipherSpec before the first ClientHello" $+ server_ccs_interleaved 0+ it "rejects a ChangeCipherSpec inside a fragmented ClientHello" $+ server_ccs_interleaved 2+ it "rejects an SSLv2-style record header at once" $+ server_first_record_type_unexpected 0x80 0x3fff+ it "rejects an unknown record type" $+ server_first_record_type_unexpected 24 0+ it "rejects application data inside a TLS 1.3 handshake message" $+ handshake13_interleaved_app_data+ it "rejects a two-byte TLS 1.2 ChangeCipherSpec" $+ malformed_ccs_unexpected+ TLS12+ cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ [P256]+ [P256]+ it "rejects a two-byte TLS 1.3 ChangeCipherSpec" $+ malformed_ccs_unexpected TLS13 cipher13_AES_128_GCM_SHA256 [X25519] [X25519]+ it "rejects a two-byte TLS 1.3 ChangeCipherSpec after HelloRetryRequest" $+ malformed_ccs_unexpected+ TLS13+ cipher13_AES_128_GCM_SHA256+ [P256, X25519]+ [X25519]++--------------------------------------------------------------++-- | Both channel bindings already answer with 'Maybe', and a caller may+-- reasonably ask for one on a context whose handshake has not run -- or has+-- failed. The answer is that there is no binding, not a crash.+binding_before_handshake :: (ClientParams, ServerParams) -> IO ()+binding_before_handshake params = withPairContext params $ \(cCtx, sCtx) ->+ forM_ [cCtx, sCtx] $ \ctx -> do+ getTLSUnique ctx `shouldReturn` Nothing+ getTLSExporter ctx `shouldReturn` Nothing++pipe_work :: IO ()+pipe_work = do+ pipe <- newPipe+ _ <- runPipe pipe++ let bSize = 16+ n <- generate (choose (1, 32))++ let d1 = B.replicate (bSize * n) 40+ let d2 = B.replicate (bSize * n) 45++ d1' <- writePipeC pipe d1 >> readPipeS pipe (B.length d1)+ d1' `shouldBe` d1++ d2' <- writePipeS pipe d2 >> readPipeC pipe (B.length d2)+ d2' `shouldBe` d2++--------------------------------------------------------------++handshake_simple :: (ClientParams, ServerParams) -> IO ()+handshake_simple = runTLSSimple++--------------------------------------------------------------++newtype CSP13 = CSP13 (ClientParams, ServerParams) deriving (Show)++instance Arbitrary CSP13 where+ arbitrary = CSP13 <$> arbitraryPairParams13++handshake13_simple :: CSP13 -> IO ()+handshake13_simple (CSP13 params) = runTLSSimple13 params hs+ where+ cgrps = supportedGroups $ clientSupported $ fst params+ sgrps = supportedGroups $ serverSupported $ snd params+ hs = if unsafeHead cgrps `elem` sgrps then FullHandshake else HelloRetryRequest++handshake_rejects_server_cipher_callback_escape :: IO ()+handshake_rejects_server_cipher_callback_escape = do+ (clientParam, serverParam) <- generate arbitraryPairParams13+ let params = cipherSelectionParams clientParam serverParam selectLegacy+ withPairContextWith (id, id) params $ \(cctx, sctx) ->+ concurrently_+ (handshake sctx `shouldThrow` serverRejectedCipherEscape)+ (handshake cctx `shouldThrow` anyTLSException)+ where+ selectLegacy _ _ = cipher_ECDHE_RSA_AES128CBC_SHA256++handshake_rejects_legacy_cipher_in_tls13 :: IO ()+handshake_rejects_legacy_cipher_in_tls13 = do+ (clientParam, serverParam) <- generate arbitraryPairParams13+ let params = cipherSelectionParams clientParam serverParam defaultSelection+ withPairContextWith (id, id) params $ \(cctx, sctx) -> do+ contextHookSetHandshakeRecv cctx tamperCipher+ concurrently_+ (handshake sctx `shouldThrow` anyTLSException)+ (handshake cctx `shouldThrow` clientRejectedLegacyCipher)+ where+ defaultSelection _ = unsafeHead+ tamperCipher (ServerHello sh) =+ pure $+ ServerHello+ sh+ { shCipher =+ CipherId $ cipherID cipher_ECDHE_RSA_AES128CBC_SHA256+ }+ tamperCipher hs = pure hs++cipherSelectionParams+ :: ClientParams+ -> ServerParams+ -> (Version -> [Cipher] -> Cipher)+ -> (ClientParams, ServerParams)+cipherSelectionParams clientParam serverParam select =+ ( clientParam{clientSupported = supported}+ , serverParam+ { serverSupported = supported+ , serverHooks =+ (serverHooks serverParam)+ { onCipherChoosing = select+ }+ }+ )+ where+ supported =+ defaultSupported+ { supportedVersions = [TLS13]+ , supportedCiphers =+ [ cipher13_AES_128_GCM_SHA256+ , cipher_ECDHE_RSA_AES128CBC_SHA256+ ]+ }++serverRejectedCipherEscape :: TLSException -> Bool+serverRejectedCipherEscape (HandshakeFailed (Error_Protocol msg alert)) =+ msg == "onCipherChoosing selected a cipher outside the candidate list"+ && alert == InternalError+serverRejectedCipherEscape _ = False++clientRejectedLegacyCipher :: TLSException -> Bool+clientRejectedLegacyCipher (HandshakeFailed (Error_Protocol msg alert)) =+ msg == "server selected a cipher invalid for the negotiated version"+ && alert == IllegalParameter+clientRejectedLegacyCipher _ = False++anyTLSException :: TLSException -> Bool+anyTLSException = const True++handshake_key_update_flood :: IO ()+handshake_key_update_flood = do+ params <- generate arbitraryPairParams13+ withPairContextWith (id, id) params $ \(cctx, sctx) ->+ concurrently_+ ( do+ handshake sctx+ recvData sctx `shouldReturn` "after 32 key updates"+ recvData sctx `shouldThrow` excessiveKeyUpdate+ )+ ( do+ handshake cctx+ replicateM_ 32 $ void $ updateKey cctx OneWay+ sendData cctx "after 32 key updates"+ replicateM_ 33 $ void $ updateKey cctx OneWay+ sendData cctx "after 33 key updates"+ )+ where+ excessiveKeyUpdate+ (Terminated _ _ (Error_Misc "too many consecutive KeyUpdate messages")) = True+ excessiveKeyUpdate _ = False++handshake_key_update_unlimited :: IO ()+handshake_key_update_unlimited = do+ (cparams, sparams0) <- generate arbitraryPairParams13+ let shared0 = serverShared sparams0+ limits = (sharedLimit shared0){limitKeyUpdate = Nothing}+ sparams = sparams0{serverShared = shared0{sharedLimit = limits}}+ withPairContextWith (id, id) (cparams, sparams) $ \(cctx, sctx) ->+ concurrently_+ ( do+ handshake sctx+ recvData sctx `shouldReturn` "after 33 key updates"+ )+ ( do+ handshake cctx+ replicateM_ 33 $ void $ updateKey cctx OneWay+ sendData cctx "after 33 key updates"+ )++handshake_key_update_non_positive :: IO ()+handshake_key_update_non_positive =+ forM_ [0, -1] $ \limit -> do+ (cparams, sparams0) <- generate arbitraryPairParams13+ let shared0 = serverShared sparams0+ limits = (sharedLimit shared0){limitKeyUpdate = Just limit}+ sparams = sparams0{serverShared = shared0{sharedLimit = limits}}+ withPairContextWith (id, id) (cparams, sparams) $ \(cctx, sctx) ->+ concurrently_+ ( do+ handshake sctx+ recvData sctx `shouldReturn` "after key update"+ )+ ( do+ handshake cctx+ void $ updateKey cctx OneWay+ sendData cctx "after key update"+ )++--------------------------------------------------------------++handshake_cbc :: IO ()+handshake_cbc = do+ clientCiphers <- generate $ cipherGen >>= shuffle+ serverCiphers <- generate $ cipherGen >>= shuffle+ clientGroups <- generate $ groupGen >>= shuffle+ serverGroups <- generate $ groupGen >>= shuffle+ (clientParam, serverParam) <- generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([TLS12], [TLS12])+ (clientCiphers, serverCiphers)+ let clientParam' = clientParam {+ clientSupported = (clientSupported clientParam)+ { supportedGroups = clientGroups } }+ serverParam' = serverParam {+ serverSupported = (serverSupported serverParam)+ { supportedGroups = serverGroups } }+ let ciphers = clientCiphers `intersect` serverCiphers+ groups = clientGroups `intersect` serverGroups+ in if compat ciphers groups+ then runTLSSimple (clientParam', serverParam')+ else runTLSFailure (clientParam', serverParam') handshake handshake+ where+ groupGen :: Gen [Group]+ groupGen = sublistOf grps `suchThat` (not . null)+ where+ grps = [X25519, P256, P384, FFDHE2048, FFDHE3072, FFDHE4096]++ cipherGen :: Gen [Cipher]+ cipherGen = sublistOf ciphersuite_pfs_sha2_cbc `suchThat` (not . null)++ compat :: [Cipher] -> [Group] -> Bool+ compat [] _ = False+ compat _ [] = False+ compat ciphers groups =+ let mustdh = all (== CipherKeyExchange_DHE_RSA) $ map cipherKeyExchange ciphers+ mustec = all (/= CipherKeyExchange_DHE_RSA) $ map cipherKeyExchange ciphers+ havedh = any (`elem` [FFDHE2048, FFDHE3072, FFDHE4096]) groups+ haveec = any (`elem` [X25519, P256, P384]) groups+ in ((not mustdh || havedh) && (not mustec || haveec))++--------------------------------------------------------------++handshake13_downgrade :: (ClientParams, ServerParams) -> IO ()+handshake13_downgrade (cparam, sparam) = do+ versionForced <-+ generate $ elements (supportedVersions $ clientSupported cparam)+ let debug' = (serverDebug sparam){debugVersionForced = Just versionForced}+ sparam' = sparam{serverDebug = debug'}+ params = (cparam, sparam')+ downgraded =+ (isVersionEnabled TLS13 params && versionForced < TLS13)+ || (isVersionEnabled TLS12 params && versionForced < TLS12)+ if downgraded+ then runTLSFailure params handshake handshake+ else runTLSSimple params++handshake_update_key :: (ClientParams, ServerParams) -> IO ()+handshake_update_key = runTLSSimpleKeyUpdate++--------------------------------------------------------------++handshake_hashsignatures+ :: ([HashAndSignatureAlgorithm], [HashAndSignatureAlgorithm]) -> IO ()+handshake_hashsignatures (clientHashSigs, serverHashSigs) = do+ tls13 <- generate arbitrary+ let version = if tls13 then TLS13 else TLS12+ ciphers =+ [ cipher_ECDHE_RSA_WITH_AES_256_GCM_SHA384+ , cipher_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384+ , cipher13_AES_128_GCM_SHA256+ ]+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([version], [version])+ (ciphers, ciphers)+ let clientParam' =+ clientParam+ { clientSupported =+ (clientSupported clientParam)+ { supportedHashSignatures = clientHashSigs+ }+ }+ serverParam' =+ serverParam+ { serverSupported =+ (serverSupported serverParam)+ { supportedHashSignatures = serverHashSigs+ }+ }+ commonHashSigs = clientHashSigs `intersect` serverHashSigs+ shouldFail+ | tls13 = all incompatibleWithDefaultCurve commonHashSigs+ | otherwise = null commonHashSigs+ if shouldFail+ then runTLSFailure (clientParam', serverParam') handshake handshake+ else runTLSSimple (clientParam', serverParam')+ where+ incompatibleWithDefaultCurve (h, SignatureECDSA) = h /= HashSHA256+ incompatibleWithDefaultCurve _ = False++handshake_ciphersuites :: ([Cipher], [Cipher]) -> IO ()+handshake_ciphersuites (clientCiphers, serverCiphers) = do+ tls13 <- generate arbitrary+ let version = if tls13 then TLS13 else TLS12+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([version], [version])+ (clientCiphers, serverCiphers)+ let adequate = cipherAllowedForVersion version+ shouldSucceed = any adequate (clientCiphers `intersect` serverCiphers)+ if shouldSucceed+ then runTLSSimple (clientParam, serverParam)+ else runTLSFailure (clientParam, serverParam) handshake handshake++--------------------------------------------------------------++handshake_groups :: GGP -> IO ()+handshake_groups (GGP clientGroups serverGroups) = do+ tls13 <- generate arbitrary+ let versions = if tls13 then [TLS13] else [TLS12]+ ciphers = ciphersuite_strong+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ (versions, versions)+ (ciphers, ciphers)+ denyCustom <- generate arbitrary+ let groupUsage =+ if denyCustom+ then GroupUsageUnsupported "custom group denied"+ else GroupUsageValid+ clientParam' =+ clientParam+ { clientSupported =+ (clientSupported clientParam)+ { supportedGroups = clientGroups+ }+ , clientHooks =+ (clientHooks clientParam)+ { onCustomFFDHEGroup = \_ _ -> return groupUsage+ }+ }+ serverParam' =+ serverParam+ { serverSupported =+ (serverSupported serverParam)+ { supportedGroups = serverGroups+ , supportedGroupsTLS13 = [serverGroups]+ }+ }+ commonGroups = clientGroups `intersect` serverGroups+ shouldFail = null commonGroups+ p minfo = isNothing (minfo >>= infoSupportedGroup) == null commonGroups+ if shouldFail+ then runTLSFailure (clientParam', serverParam') handshake handshake+ else runTLSPredicate (clientParam', serverParam') p++--------------------------------------------------------------++newtype SG = SG [Group] deriving (Show)++instance Arbitrary SG where+ arbitrary = SG <$> shuffle sigGroups+ where+ sigGroups = [P256, P521]++handshake_ec :: SG -> IO ()+handshake_ec (SG sigGroups) = do+ let versions = [TLS12]+ ciphers =+ [ cipher_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384+ ]+ hashSignatures =+ [ (HashSHA256, SignatureECDSA)+ ]+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ (versions, versions)+ (ciphers, ciphers)+ clientGroups <- generate $ shuffle sigGroups+ clientHashSignatures <- generate $ sublistOf hashSignatures+ serverHashSignatures <- generate $ sublistOf hashSignatures+ credentials <- generate arbitraryCredentialsOfEachCurve+ let clientParam' =+ clientParam+ { clientSupported =+ (clientSupported clientParam)+ { supportedGroups = clientGroups+ , supportedHashSignatures = clientHashSignatures+ }+ }+ serverParam' =+ serverParam+ { serverSupported =+ (serverSupported serverParam)+ { supportedGroups = sigGroups+ , supportedGroupsTLS13 = [sigGroups]+ , supportedHashSignatures = serverHashSignatures+ }+ , serverShared =+ (serverShared serverParam)+ { sharedCredentials = Credentials credentials+ }+ }+ sigAlgs = map snd (clientHashSignatures `intersect` serverHashSignatures)+ ecdsaDenied = SignatureECDSA `notElem` sigAlgs+ if ecdsaDenied+ then runTLSFailure (clientParam', serverParam') handshake handshake+ else runTLSSimple (clientParam', serverParam')++-- Tests ability to use or ignore client "signature_algorithms" extension when+-- choosing a server certificate. Here peers allow DHE_RSA_AES128_SHA1 but+-- the server RSA certificate has a SHA-1 signature that the client does not+-- support. Server may choose the DSA certificate only when cipher+-- DHE_DSA_AES128_SHA1 is allowed. Otherwise it must fallback to the RSA+-- certificate.++data OC = OC [Cipher] [Cipher] deriving (Show)++instance Arbitrary OC where+ arbitrary = OC <$> sublistOf otherCiphers <*> sublistOf otherCiphers+ where+ otherCiphers =+ [ cipher_ECDHE_RSA_WITH_AES_256_GCM_SHA384+ , cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ ]++handshake_cert_fallback_cipher :: OC -> IO ()+handshake_cert_fallback_cipher (OC clientCiphers serverCiphers) = do+ let clientVersions = [TLS12]+ serverVersions = [TLS12]+ commonCiphers = [cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256]+ hashSignatures = [(HashSHA256, SignatureRSA), (HashSHA1, SignatureDSA)]+ chainRef <- newIORef Nothing+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ (clientVersions, serverVersions)+ (clientCiphers ++ commonCiphers, serverCiphers ++ commonCiphers)+ let clientParam' =+ clientParam+ { clientSupported =+ (clientSupported clientParam)+ { supportedHashSignatures = hashSignatures+ }+ , clientHooks =+ (clientHooks clientParam)+ { onServerCertificate = \_ _ _ chain ->+ writeIORef chainRef (Just chain) >> return []+ }+ }+ runTLSSimple (clientParam', serverParam)+ serverChain <- readIORef chainRef+ isLeafRSA serverChain `shouldBe` True++-- Same as above but testing with supportedHashSignatures directly instead of+-- ciphers, and thus allowing TLS13. Peers accept RSA with SHA-256 but the+-- server RSA certificate has a SHA-1 signature. When Ed25519 is allowed by+-- both client and server, the Ed25519 certificate is selected. Otherwise the+-- server fallbacks to RSA.+--+-- Note: SHA-1 is supposed to be disallowed in X.509 signatures with TLS13+-- unless client advertises explicit support. Currently this is not enforced by+-- the library, which is useful to test this scenario. SHA-1 could be replaced+-- by another algorithm.++data OHS = OHS [HashAndSignatureAlgorithm] [HashAndSignatureAlgorithm]+ deriving (Show)++instance Arbitrary OHS where+ arbitrary = OHS <$> sublistOf otherHS <*> sublistOf otherHS+ where+ otherHS = [(HashIntrinsic, SignatureEd25519)]++handshake_cert_fallback_hs :: OHS -> IO ()+handshake_cert_fallback_hs (OHS clientHS serverHS) = do+ tls13 <- generate arbitrary+ let versions = if tls13 then [TLS13] else [TLS12]+ ciphers =+ [ cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ , cipher_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256+ , cipher13_AES_128_GCM_SHA256+ ]+ commonHS =+ [ (HashSHA256, SignatureRSA)+ , (HashIntrinsic, SignatureRSApssRSAeSHA256)+ ]+ chainRef <- newIORef Nothing+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ (versions, versions)+ (ciphers, ciphers)+ let clientParam' =+ clientParam+ { clientSupported =+ (clientSupported clientParam)+ { supportedHashSignatures = commonHS ++ clientHS+ }+ , clientHooks =+ (clientHooks clientParam)+ { onServerCertificate = \_ _ _ chain ->+ writeIORef chainRef (Just chain) >> return []+ }+ }+ serverParam' =+ serverParam+ { serverSupported =+ (serverSupported serverParam)+ { supportedHashSignatures = commonHS ++ serverHS+ }+ }+ eddsaDisallowed =+ (HashIntrinsic, SignatureEd25519) `notElem` clientHS+ || (HashIntrinsic, SignatureEd25519) `notElem` serverHS+ runTLSSimple (clientParam', serverParam')+ serverChain <- readIORef chainRef+ isLeafRSA serverChain `shouldBe` eddsaDisallowed++--------------------------------------------------------------++handshake_server_key_usage :: [ExtKeyUsageFlag] -> IO ()+handshake_server_key_usage usageFlags = do+ tls13 <- generate arbitrary+ let versions = if tls13 then [TLS13] else [TLS12]+ ciphers = ciphersuite_all+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ (versions, versions)+ (ciphers, ciphers)+ cred <- generate $ arbitraryRSACredentialWithUsage usageFlags+ let serverParam' =+ serverParam+ { serverShared =+ (serverShared serverParam)+ { sharedCredentials = Credentials [cred]+ }+ }+ shouldSucceed = KeyUsage_digitalSignature `elem` usageFlags+ if shouldSucceed+ then runTLSSimple (clientParam, serverParam')+ else runTLSFailure (clientParam, serverParam') handshake handshake++handshake_server_key_purpose :: Version -> ExtKeyUsagePurpose -> Bool -> IO ()+handshake_server_key_purpose version purpose shouldSucceed = do+ let cipher+ | version == TLS13 = cipher13_AES_128_GCM_SHA256+ | otherwise = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([version], [version])+ ([cipher], [cipher])+ cred <- generate $ arbitraryRSACredentialWithPurpose purpose+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onServerCertificate = \_ _ _ _ -> return []+ }+ }+ serverParam' =+ serverParam+ { serverShared =+ (serverShared serverParam)+ { sharedCredentials = Credentials [cred]+ }+ }+ if shouldSucceed+ then runTLSSimple (clientParam', serverParam')+ else runTLSFailure (clientParam', serverParam') handshake handshake++handshake_client_key_usage :: [ExtKeyUsageFlag] -> IO ()+handshake_client_key_usage usageFlags = do+ (clientParam, serverParam) <- generate arbitrary+ cred <- generate $ arbitraryRSACredentialWithUsage usageFlags+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return $ Just cred+ }+ }+ serverParam' =+ serverParam+ { serverWantClientCert = True+ , serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = \_ -> return CertificateUsageAccept+ }+ }+ shouldSucceed = KeyUsage_digitalSignature `elem` usageFlags+ if shouldSucceed+ then runTLSSimple (clientParam', serverParam')+ else runTLSFailure (clientParam', serverParam') handshake handshake++--------------------------------------------------------------++handshake_client_auth :: (ClientParams, ServerParams) -> IO ()+handshake_client_auth (clientParam, serverParam) = do+ let clientVersions = supportedVersions $ clientSupported clientParam+ serverVersions = supportedVersions $ serverSupported serverParam+ version = maximum (clientVersions `intersect` serverVersions)+ cred <- generate (arbitraryClientCredential version)+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return $ Just cred+ }+ }+ serverParam' =+ serverParam+ { serverWantClientCert = True+ , serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = validateChain cred+ }+ }+ runTLSSimple (clientParam', serverParam')+ where+ validateChain cred chain+ | chain == fst cred = return CertificateUsageAccept+ | otherwise = return (CertificateUsageReject CertificateRejectUnknownCA)++-- A client without a certificate answers CertificateRequest with an empty+-- Certificate and, in TLS 1.2, sends no CertificateVerify. A server whose+-- hook accepts that must go on to ChangeCipherSpec rather than wait for a+-- CertificateVerify.+handshake_client_auth_empty :: Version -> IO ()+handshake_client_auth_empty version = do+ let cipher+ | version == TLS13 = cipher13_AES_128_GCM_SHA256+ | otherwise = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([version], [version])+ ([cipher], [cipher])+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return Nothing+ }+ }+ serverParam' =+ serverParam+ { serverWantClientCert = True+ , serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = acceptEmpty+ }+ }+ runTLSSimple (clientParam', serverParam')+ where+ acceptEmpty chain+ | isNullCertificateChain chain = return CertificateUsageAccept+ | otherwise = return (CertificateUsageReject CertificateRejectUnknownCA)++-- RFC 5246 Section 7.4.6: a client asked for a certificate sends the+-- message even when it has none. Here it is cut out on the wire; the+-- Finished no longer matches, but the server must stop before that.+handshake12_client_cert_omitted :: IO ()+handshake12_client_cert_omitted = do+ let cipher = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([TLS12], [TLS12])+ ([cipher], [cipher])+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return Nothing+ }+ }+ serverParam' =+ serverParam+ { serverWantClientCert = True+ , serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = \_ -> return CertificateUsageAccept+ }+ }+ ccsSent <- newIORef False+ let params = (clientParam', serverParam')+ withPairContextWith (dropHandshake12 11 ccsSent, id) params $+ \(cctx, sctx) ->+ concurrently_+ (handshake sctx `shouldThrow` rejectedAsUnexpected)+ (void (E.try (handshake cctx) :: IO (Either E.SomeException ())))++-- A TLS 1.2 client needs the server's Certificate for every key exchange+-- it implements, and ServerKeyExchange for (EC)DHE. One of them is cut+-- out on the wire, and the client must say so rather than fail later.+handshake12_server_message_omitted :: Word8 -> IO ()+handshake12_server_message_omitted typ = do+ let cipher = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([TLS12], [TLS12])+ ([cipher], [cipher])+ ccsSent <- newIORef False+ withPairContextWith (id, dropHandshake12 typ ccsSent) (clientParam, serverParam) $+ \(cctx, sctx) ->+ concurrently_+ (void (E.try (handshake sctx) :: IO (Either E.SomeException ())))+ (handshake cctx `shouldThrow` rejectedAsUnexpected)++-- | Cut the handshake messages of a type out of what a TLS 1.2 peer sends+-- in clear, that is, before its ChangeCipherSpec.+dropHandshake12 :: Word8 -> IORef Bool -> Backend -> Backend+dropHandshake12 typ ccsSent be =+ be+ { backendSend = \bs -> do+ done <- readIORef ccsSent+ when (B.take 1 bs == B.singleton 20) $ writeIORef ccsSent True+ backendSend be $ if done then bs else stripRecords bs+ }+ where+ stripRecords bs+ | B.length bs < 5 = bs+ | otherwise =+ let len = fromIntegral (B.index bs 3) * 256 + fromIntegral (B.index bs 4)+ (body, rest) = B.splitAt len $ B.drop 5 bs+ body'+ | B.head bs == 22 = stripMessages body+ | otherwise = body+ len' = B.length body'+ hdr = B.take 3 bs <> B.pack [fromIntegral (len' `div` 256), fromIntegral len']+ in (if B.null body' then "" else hdr <> body') <> stripRecords rest+ stripMessages m+ | B.length m < 4 = m+ | otherwise =+ let len =+ fromIntegral (B.index m 1) * 65536+ + fromIntegral (B.index m 2) * 256+ + fromIntegral (B.index m 3)+ (msg, rest) = B.splitAt (4 + len) m+ in (if B.head m == typ then "" else msg) <> stripMessages rest++-- In TLS 1.2 a client certificate with an Ed25519 or Ed448 key is asked+-- for with ecdsa_sign (RFC 8422 Section 3.1). The Ed25519 and Ed448+-- certificate types of this library are synthetic and have no code point,+-- so they must not reach the wire.+handshake12_cert_request_types :: IO ()+handshake12_cert_request_types = do+ let cipher = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([TLS12], [TLS12])+ ([cipher], [cipher])+ ref <- newIORef Nothing+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return Nothing+ }+ }+ serverParam' =+ serverParam+ { serverWantClientCert = True+ , serverSupported =+ (serverSupported serverParam)+ { supportedHashSignatures =+ supportedHashSignatures defaultSupported+ }+ , serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = \_ -> return CertificateUsageAccept+ }+ }+ record hs@(CertRequest certTypes _ _) = writeIORef ref (Just certTypes) >> return hs+ record hs = return hs+ withPairContextWith (id, id) (clientParam', serverParam') $ \(cctx, sctx) -> do+ contextHookSetHandshakeRecv cctx record+ concurrently_ (handshake sctx) (handshake cctx)+ mtypes <- readIORef ref+ case mtypes of+ Nothing -> expectationFailure "no CertificateRequest received"+ Just certTypes -> do+ certTypes `shouldSatisfy` all (`elem` defined)+ certTypes `shouldContain` [CertificateType_ECDSA_Sign]+ where+ defined =+ [ CertificateType_RSA_Sign+ , CertificateType_DSA_Sign+ , CertificateType_ECDSA_Sign+ ]++-- RFC 5246 Section 7.4.8: a TLS 1.2 CertificateVerify algorithm for+-- another type of key than the certificate's has a field that is+-- incorrect, an illegal_parameter, even when onUnverifiedClientCert would+-- accept a signature that does not verify. One for an RSA key that still+-- does not fit it, RSASSA-PSS for an rsaEncryption key, is a signature+-- that does not verify, a decrypt_error. The client signs with an+-- rsaEncryption key and the server reads the given algorithm.+handshake12_client_cert_verify_sigalg+ :: HashAndSignatureAlgorithm -> Selector TLSException -> IO ()+handshake12_client_cert_verify_sigalg alg rejected = do+ let cipher = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([TLS12], [TLS12])+ ([cipher], [cipher])+ cred <- generate $ arbitraryRSACredentialWithPurpose KeyUsagePurpose_ClientAuth+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return $ Just cred+ }+ }+ acceptUnverified = alg == (HashSHA256, SignatureECDSA)+ serverParam' =+ serverParam+ { serverWantClientCert = True+ , serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = \_ -> return CertificateUsageAccept+ , onUnverifiedClientCert = return acceptUnverified+ }+ }+ unfit (CertVerify (DigitallySigned _ sig)) =+ pure $ CertVerify (DigitallySigned alg sig)+ unfit hs = pure hs+ r <- timeout 10000000 $+ withPairContextWith (id, id) (clientParam', serverParam') $ \(cctx, sctx) -> do+ contextHookSetHandshakeRecv sctx unfit+ concurrently_+ (handshake sctx `shouldThrow` rejected)+ (void (E.try (handshake cctx) :: IO (Either TLSException ())))+ r `shouldSatisfy` isJust++-- The same for the signature of ServerKeyExchange, which the client used to+-- answer with an ErrorCall and internal_error.+handshake12_server_key_xchg_sigalg+ :: HashAndSignatureAlgorithm -> Selector TLSException -> IO ()+handshake12_server_key_xchg_sigalg alg rejected = do+ let cipher = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([TLS12], [TLS12])+ ([cipher], [cipher])+ let (HashAlgorithm h, SignatureAlgorithm sa) = alg+ unfit (ServerKeyXchg (SKX_ECDHE_RSA params (DigitallySigned _ sig))) =+ pure $ ServerKeyXchg (SKX_ECDHE_RSA params (DigitallySigned alg sig))+ -- Arriving with ServerHello, it is decoded before the cipher is+ -- known: curve type and group, the point, then the algorithm.+ unfit (ServerKeyXchg (SKX_Unparsed bs)) =+ let off = 4 + fromIntegral (B.index bs 3)+ in pure $+ ServerKeyXchg $+ SKX_Unparsed $+ B.take off bs <> B.pack [h, sa] <> B.drop (off + 2) bs+ unfit hs = pure hs+ r <- timeout 10000000 $+ withPairContextWith (id, id) (clientParam, serverParam) $ \(cctx, sctx) -> do+ contextHookSetHandshakeRecv cctx unfit+ concurrently_+ (void (E.try (handshake sctx) :: IO (Either TLSException ())))+ (handshake cctx `shouldThrow` rejected)+ r `shouldSatisfy` isJust++-- A DER ECDSA signature that does not decode is a signature that does not+-- verify, a decrypt_error. The client's TLS 1.3 CertificateVerify+-- signature is replaced on its way to the server with one that the ASN.1+-- library used to throw on, which ended the handshake with internal_error.+handshake13_client_cert_verify_malformed_ecdsa+ :: (B.ByteString -> B.ByteString) -> IO ()+handshake13_client_cert_verify_malformed_ecdsa corrupt = do+ let cipher = cipher13_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([TLS13], [TLS13])+ ([cipher], [cipher])+ creds <- generate arbitraryCredentialsOfEachType+ let cred = head [c | c@(_, PrivKeyEC _) <- creds]+ clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return $ Just cred+ }+ }+ serverParam' =+ serverParam+ { serverWantClientCert = True+ , serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = \_ -> return CertificateUsageAccept+ }+ }+ malform (CertVerify13 (DigitallySigned alg sig)) =+ pure $ CertVerify13 (DigitallySigned alg (corrupt sig))+ malform hs = pure hs+ r <- timeout 10000000 $+ withPairContextWith (id, id) (clientParam', serverParam') $ \(cctx, sctx) -> do+ contextHookSetHandshake13Recv sctx malform+ concurrently_+ ((handshake sctx >> recvData sctx) `shouldThrow` rejectedAsDecryptError)+ ( void+ (E.try (handshake cctx >> recvData cctx) :: IO (Either TLSException B.ByteString))+ )+ r `shouldSatisfy` isJust++rejectedAsDecryptError :: TLSException -> Bool+rejectedAsDecryptError (HandshakeFailed (Error_Protocol _ DecryptError)) = True+rejectedAsDecryptError _ = False++-- RFC 8446 Section 6.2: a CertificateVerify whose algorithm may not be used+-- with the certificate's key has a field that is incorrect, which is an+-- illegal_parameter; decrypt_error is for a signature that does not verify.+-- The client signs with RSA-PSS for an rsaEncryption key and the server+-- reads the algorithm as rsa_pss_pss_sha256, which needs an RSASSA-PSS key.+handshake13_client_cert_verify_unfit_sigalg :: IO ()+handshake13_client_cert_verify_unfit_sigalg = do+ let cipher = cipher13_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([TLS13], [TLS13])+ ([cipher], [cipher])+ cred <- generate $ arbitraryRSACredentialWithPurpose KeyUsagePurpose_ClientAuth+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return $ Just cred+ }+ }+ serverParam' =+ serverParam+ { serverWantClientCert = True+ , serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = \_ -> return CertificateUsageAccept+ }+ }+ unfit (CertVerify13 (DigitallySigned _ sig)) =+ pure $+ CertVerify13 (DigitallySigned (HashIntrinsic, SignatureRSApsspssSHA256) sig)+ unfit hs = pure hs+ r <- timeout 10000000 $+ withPairContextWith (id, id) (clientParam', serverParam') $ \(cctx, sctx) -> do+ contextHookSetHandshake13Recv sctx unfit+ concurrently_+ ((handshake sctx >> recvData sctx) `shouldThrow` rejectedAsIllegalParameter)+ ( void+ (E.try (handshake cctx >> recvData cctx) :: IO (Either TLSException B.ByteString))+ )+ r `shouldSatisfy` isJust++rejectedAsIllegalParameter :: TLSException -> Bool+rejectedAsIllegalParameter (HandshakeFailed (Error_Protocol _ IllegalParameter)) = True+rejectedAsIllegalParameter (Terminated _ _ (Error_Protocol _ IllegalParameter)) = True+rejectedAsIllegalParameter _ = False++-- A server that receives a ClientHello whose legacy_version is higher than+-- its own negotiates the highest version it supports (RFC 5246 Appendix+-- E.1); with supported_versions present it does not use legacy_version at+-- all (RFC 8446 Section 4.2.1). The ClientHello's legacy_version is raised+-- on its way to the server, and the handshake must still reach the version+-- both sides support.+handshake_high_legacy_version :: Version -> Version -> IO ()+handshake_high_legacy_version version legacy = do+ let cipher+ | version == TLS13 = cipher13_AES_128_GCM_SHA256+ | otherwise = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([version], [version])+ ([cipher], [cipher])+ let raise (ClientHello ch) = pure $ ClientHello ch{chVersion = legacy}+ raise hs = pure hs+ withPairContextWith (id, id) (clientParam, serverParam) $ \(cctx, sctx) -> do+ contextHookSetHandshakeRecv sctx raise+ concurrently_ (handshake sctx) (handshake cctx)+ info <- contextGetInformation sctx+ (infoVersion <$> info) `shouldBe` Just version++-- RFC 8422 Section 5.1.2: ec_point_format_list is <1..2^8-1>, and a+-- client naming one of its curves in supported_groups must offer the+-- uncompressed format. The ClientHello's ec_point_formats is replaced+-- on its way to the server, which must refuse it.+handshake12_ec_point_formats+ :: B.ByteString -> Selector TLSException -> IO ()+handshake12_ec_point_formats formats rejected = do+ CSP12 (cparams, sparams) <- generate arbitrary+ let cparams' =+ cparams+ { clientSupported =+ (clientSupported cparams){supportedGroups = [X25519]}+ }+ sparams' =+ sparams+ { serverSupported =+ (serverSupported sparams){supportedGroups = [X25519]}+ }+ replace (ClientHello ch) =+ pure $+ ClientHello+ ch+ { chExtensions =+ filter+ (\(ExtensionRaw eid _) -> eid /= EID_EcPointFormats)+ (chExtensions ch)+ ++ [ExtensionRaw EID_EcPointFormats formats]+ }+ replace hs = pure hs+ r <- timeout 10000000 $+ withPairContextWith (id, id) (cparams', sparams') $ \(cctx, sctx) -> do+ contextHookSetHandshakeRecv sctx replace+ concurrently_+ (handshake sctx `shouldThrow` rejected)+ (void (E.try (handshake cctx) :: IO (Either TLSException ())))+ r `shouldSatisfy` isJust++rejectedAsDecodeError :: TLSException -> Bool+rejectedAsDecodeError (HandshakeFailed (Error_Protocol _ DecodeError)) = True+rejectedAsDecodeError _ = False++-- RFC 8449 Section 4: a record size limit binds the peer only when the+-- extension is negotiated. Only one side sets limitRecordSize, so the+-- extension is not negotiated, and a record of 2^14 octets from the other+-- side must be received rather than refused with record_overflow.+record_size_limit_unnegotiated :: Version -> Bool -> IO ()+record_size_limit_unnegotiated version clientLimited = do+ let cipher+ | version == TLS13 = cipher13_AES_128_GCM_SHA256+ | otherwise = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([version], [version])+ ([cipher], [cipher])+ let limited shared = shared{sharedLimit = (sharedLimit shared){limitRecordSize = Just 1024}}+ clientParam'+ | clientLimited = clientParam{clientShared = limited $ clientShared clientParam}+ | otherwise = clientParam+ serverParam'+ | clientLimited = serverParam+ | otherwise = serverParam{serverShared = limited $ serverShared serverParam}+ payload = B.replicate 16384 0x61+ send ctx = handshake ctx >> sendData ctx (L.fromStrict payload)+ received <- newIORef B.empty+ let recv ctx = do+ handshake ctx+ let loop acc+ | B.length acc >= B.length payload = writeIORef received acc+ | otherwise = recvData ctx >>= \bs -> loop (acc <> bs)+ loop B.empty+ r <- timeout 10000000 $+ withPairContextWith (id, id) (clientParam', serverParam') $ \(cctx, sctx) ->+ if clientLimited+ then concurrently_ (send sctx) (recv cctx)+ else concurrently_ (recv sctx) (send cctx)+ r `shouldSatisfy` isJust+ readIORef received `shouldReturn` payload++-- RFC 8449 Section 4: with record_size_limit negotiated, each side keeps+-- to the other's limit. Both sides set limitRecordSize, which a TLS 1.3+-- server returns in EncryptedExtensions, and 2^14 octets sent by one side+-- must reach the other in records within its limit.+record_size_limit_negotiated :: Version -> Bool -> IO ()+record_size_limit_negotiated version serverSends = do+ let cipher+ | version == TLS13 = cipher13_AES_128_GCM_SHA256+ | otherwise = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([version], [version])+ ([cipher], [cipher])+ let limited shared = shared{sharedLimit = (sharedLimit shared){limitRecordSize = Just 1024}}+ clientParam' = clientParam{clientShared = limited $ clientShared clientParam}+ serverParam' = serverParam{serverShared = limited $ serverShared serverParam}+ payload = B.replicate 16384 0x61+ send ctx = handshake ctx >> sendData ctx (L.fromStrict payload)+ received <- newIORef B.empty+ let recv ctx = do+ handshake ctx+ let loop acc+ | B.length acc >= B.length payload = writeIORef received acc+ | otherwise = recvData ctx >>= \bs -> loop (acc <> bs)+ loop B.empty+ r <- timeout 10000000 $+ withPairContextWith (id, id) (clientParam', serverParam') $ \(cctx, sctx) ->+ if serverSends+ then concurrently_ (send sctx) (recv cctx)+ else concurrently_ (recv sctx) (send cctx)+ r `shouldSatisfy` isJust+ readIORef received `shouldReturn` payload++handshake_client_auth_fail :: (ClientParams, ServerParams) -> IO ()+handshake_client_auth_fail (clientParam, serverParam) = do+ let clientVersions = supportedVersions $ clientSupported clientParam+ serverVersions = supportedVersions $ serverSupported serverParam+ version = maximum (clientVersions `intersect` serverVersions)+ cred <- generate (arbitraryClientCredential version)+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return $ Just cred+ }+ }+ serverParam' =+ serverParam+ { serverWantClientCert = True+ , serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = validateChain cred+ }+ }+ runTLSFailure (clientParam', serverParam') handshake handshake+ where+ validateChain _ _ = return (CertificateUsageReject CertificateRejectUnknownCA)++--------------------------------------------------------------++handshake_ems :: (EMSMode, EMSMode) -> IO ()+handshake_ems (cems, sems) = do+ params <- generate arbitrary+ let params' = setEMSMode (cems, sems) params+ version = getConnectVersion params'+ emsVersion = version >= TLS10 && version <= TLS12+ use = cems /= NoEMS && sems /= NoEMS+ require = cems == RequireEMS || sems == RequireEMS+ p info = infoExtendedMainSecret info == (emsVersion && use)+ if emsVersion && require && not use+ then runTLSFailure params' handshake handshake+ else runTLSPredicate params' (maybe False p)++newtype CompatEMS = CompatEMS (EMSMode, EMSMode) deriving (Show)++instance Arbitrary CompatEMS where+ arbitrary = CompatEMS <$> (arbitrary `suchThat` compatible)+ where+ compatible (NoEMS, RequireEMS) = False+ compatible (RequireEMS, NoEMS) = False+ compatible _ = True++handshake_resumption_ems :: (CompatEMS, CompatEMS) -> IO ()+handshake_resumption_ems (CompatEMS ems, CompatEMS ems2) = do+ sessionRefs <- twoSessionRefs+ let sessionManagers = twoSessionManagers sessionRefs++ plainParams <- generate arbitrary+ let params =+ setEMSMode ems $+ setPairParamsSessionManagers sessionManagers plainParams++ runTLSSimple params++ -- and resume+ sessionParams <- readClientSessionRef sessionRefs+ expectJust "session param should be Just" sessionParams+ let params2 =+ setEMSMode ems2 $+ setPairParamsSessionResuming (fromJust sessionParams) params++ let version = getConnectVersion params2+ emsVersion = version >= TLS10 && version <= TLS12++ if emsVersion && use ems && not (use ems2)+ then runTLSFailure params2 handshake handshake+ else do+ runTLSSimple params2+ mSessionParams2 <- readClientSessionRef sessionRefs+ let sameSession = sessionParams == mSessionParams2+ sameUse = use ems == use ems2+ when emsVersion (sameSession `shouldBe` sameUse)+ where+ use (NoEMS, _) = False+ use (_, NoEMS) = False+ use _ = True++--------------------------------------------------------------++handshake_alpn :: (ClientParams, ServerParams) -> IO ()+handshake_alpn (clientParam, serverParam) = do+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onSuggestALPN = return $ Just ["h2", "http/1.1"]+ }+ }+ serverParam' =+ serverParam+ { serverHooks =+ (serverHooks serverParam)+ { onALPNClientSuggest = Just alpn+ }+ }+ params' = (clientParam', serverParam')+ runTLSSuccess params' hsClient hsServer+ where+ hsClient ctx = do+ handshake ctx+ proto <- getNegotiatedProtocol ctx+ proto `shouldBe` Just "h2"+ hsServer ctx = do+ handshake ctx+ proto <- getNegotiatedProtocol ctx+ proto `shouldBe` Just "h2"+ alpn xs+ | "h2" `elem` xs = return "h2"+ | otherwise = return "http/1.1"++handshake_alpn_rejects_unoffered_server_selection :: IO ()+handshake_alpn_rejects_unoffered_server_selection = do+ (clientParam, serverParam) <- generate arbitraryPairParams13+ let params = alpnParams clientParam serverParam (const $ pure "h2")+ withPairContextWith (id, id) params $ \(cctx, sctx) ->+ concurrently_+ (handshake sctx `shouldThrow` serverRejectedUnofferedALPN)+ (handshake cctx `shouldThrow` anyTLSException)++handshake_alpn_rejects_unoffered_client_selection :: IO ()+handshake_alpn_rejects_unoffered_client_selection = do+ (clientParam, serverParam) <- generate arbitraryPairParams13+ let params = alpnParams clientParam serverParam (pure . unsafeHead)+ withPairContextWith (id, id) params $ \(cctx, sctx) -> do+ contextHookSetHandshake13Recv cctx tamperALPN+ concurrently_+ (handshake sctx `shouldThrow` anyTLSException)+ (handshake cctx `shouldThrow` clientRejectedUnofferedALPN)+ where+ tamperALPN (EncryptedExtensions13 exts) =+ pure $ EncryptedExtensions13 $ map replaceALPN exts+ tamperALPN hs = pure hs+ replaceALPN ext@(ExtensionRaw eid _)+ | eid == EID_ApplicationLayerProtocolNegotiation =+ toExtensionRaw $ ApplicationLayerProtocolNegotiation ["h2"]+ | otherwise = ext++alpnParams+ :: ClientParams+ -> ServerParams+ -> ([B.ByteString] -> IO B.ByteString)+ -> (ClientParams, ServerParams)+alpnParams clientParam serverParam select =+ ( clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onSuggestALPN = pure $ Just ["http/1.1"]+ }+ }+ , serverParam+ { serverHooks =+ (serverHooks serverParam)+ { onALPNClientSuggest = Just select+ }+ }+ )++serverRejectedUnofferedALPN :: TLSException -> Bool+serverRejectedUnofferedALPN (HandshakeFailed (Error_Protocol msg alert)) =+ msg == "ALPN callback selected a protocol not offered by the client"+ && alert == NoApplicationProtocol+serverRejectedUnofferedALPN _ = False++clientRejectedUnofferedALPN :: TLSException -> Bool+clientRejectedUnofferedALPN (HandshakeFailed (Error_Protocol msg alert)) =+ msg == "server selected an ALPN protocol not offered by the client"+ && alert == IllegalParameter+clientRejectedUnofferedALPN _ = False++-- RFC 6066 Section 3: HostName is <1..2^16-1>, so a client with an+-- empty server name sends no server_name, and the server sees none.+handshake_sni_empty :: IO ()+handshake_sni_empty = do+ CSP12 (clientParam, serverParam) <- generate arbitrary+ let clientParam' = clientParam{clientServerIdentification = ("", "")}+ runTLSSuccess (clientParam', serverParam) hs hs+ where+ hs ctx = do+ handshake ctx+ msni <- getClientSNI ctx+ msni `shouldBe` Nothing++-- RFC 6066 Section 3: the server_name_list MUST NOT contain more than one+-- name of the same name_type, and HostName is an ASCII DNS host name. The+-- ClientHello's server_name is replaced on its way to the server, which+-- must refuse it with illegal_parameter.+handshake_sni_illegal :: [B.ByteString] -> IO ()+handshake_sni_illegal names = do+ CSP12 (clientParam, serverParam) <- generate arbitrary+ let entry name =+ B.concat [B.pack [0, len `shiftR` 8, len], name]+ where+ len = fromIntegral $ B.length name+ list = B.concat $ map entry names+ listLen = fromIntegral $ B.length list+ sni = B.concat [B.pack [listLen `shiftR` 8, listLen], list]+ replace (ClientHello ch) =+ pure $+ ClientHello+ ch+ { chExtensions =+ ExtensionRaw EID_ServerName sni+ : filter+ (\(ExtensionRaw eid _) -> eid /= EID_ServerName)+ (chExtensions ch)+ }+ replace hs = pure hs+ r <- timeout 10000000 $+ withPairContextWith (id, id) (clientParam, serverParam) $ \(cctx, sctx) -> do+ contextHookSetHandshakeRecv sctx replace+ concurrently_+ (handshake sctx `shouldThrow` rejectedAsIllegalParameter)+ (void (E.try (handshake cctx) :: IO (Either TLSException ())))+ r `shouldSatisfy` isJust++handshake_sni :: (ClientParams, ServerParams) -> IO ()+handshake_sni (clientParam, serverParam) = do+ ref <- newIORef Nothing+ let clientParam' =+ clientParam+ { clientServerIdentification = (serverName, "")+ }+ serverParam' =+ serverParam+ { serverHooks =+ (serverHooks serverParam)+ { onServerNameIndication = onSNI ref+ }+ }+ params' = (clientParam', serverParam')+ runTLSSuccess params' hsClient hsServer+ receivedName <- readIORef ref+ receivedName `shouldBe` Just (Just serverName)+ where+ hsClient ctx = do+ handshake ctx+ msni <- getClientSNI ctx+ expectMaybe "C: SNI should be Just" serverName msni+ hsServer ctx = do+ handshake ctx+ msni <- getClientSNI ctx+ expectMaybe "S: SNI should be Just" serverName msni+ onSNI ref name = do+ mx <- readIORef ref+ mx `shouldBe` Nothing+ writeIORef ref (Just name)+ return (Credentials [])+ serverName = "haskell.org"++--------------------------------------------------------------++newtype CSP12 = CSP12 (ClientParams, ServerParams) deriving (Show)++instance Arbitrary CSP12 where+ arbitrary = CSP12 <$> arbitraryPairParams12++handshake12_renegotiation :: CSP12 -> IO ()+handshake12_renegotiation (CSP12 (cparams, sparams)) = do+ renegDisabled <- generate arbitrary+ let sparams' =+ sparams+ { serverSupported =+ (serverSupported sparams)+ { supportedClientInitiatedRenegotiation = not renegDisabled+ }+ }+ if renegDisabled+ then runTLSFailure (cparams, sparams') hsClient hsServer+ else runTLSSimple (cparams, sparams')+ where+ hsClient ctx = handshake ctx >> handshake ctx+ -- recvData receives the alert from the second handshake+ hsServer ctx = handshake ctx >> void (recvData ctx)++-- RFC 5746 Section 3.7: when a connection with secure renegotiation is+-- renegotiated, ClientHello must not contain the SCSV and must contain+-- the renegotiation_info extension. The second ClientHello is tampered+-- with on its way to the server, which must abort with handshake_failure.+handshake12_renegotiation_tampered :: (ClientHello -> ClientHello) -> IO ()+handshake12_renegotiation_tampered tamper = do+ CSP12 (cparams, sparams) <- generate arbitrary+ let cparams' =+ cparams+ { clientSupported =+ (clientSupported cparams)+ { supportedSecureRenegotiation = True+ }+ }+ sparams' =+ sparams+ { serverSupported =+ (serverSupported sparams)+ { supportedSecureRenegotiation = True+ , supportedClientInitiatedRenegotiation = True+ }+ }+ count <- newIORef (0 :: Int)+ let tamperSecond (ClientHello ch) = do+ n <- atomicModifyIORef' count $ \i -> (i + 1, i)+ pure $ ClientHello $ if n == 0 then ch else tamper ch+ tamperSecond hs = pure hs+ r <- timeout 10000000 $+ withPairContextWith (id, id) (cparams', sparams') $ \(cctx, sctx) -> do+ contextHookSetHandshakeRecv sctx tamperSecond+ concurrently_ (handshake sctx) (handshake cctx)+ concurrently_+ (recvData sctx `shouldThrow` rejectedAsHandshakeFailure)+ ( void+ (E.try (handshake cctx >> recvData cctx) :: IO (Either TLSException B.ByteString))+ )+ r `shouldSatisfy` isJust++rejectedAsHandshakeFailure :: TLSException -> Bool+rejectedAsHandshakeFailure (HandshakeFailed (Error_Protocol _ HandshakeFailure)) = True+rejectedAsHandshakeFailure (Terminated _ _ (Error_Protocol _ HandshakeFailure)) = True+rejectedAsHandshakeFailure _ = False++handshake12_session_resumption :: CSP12 -> IO ()+handshake12_session_resumption (CSP12 plainParams) = do+ sessionRefs <- twoSessionRefs+ let sessionManagers = twoSessionManagers sessionRefs++ let params = setPairParamsSessionManagers sessionManagers plainParams++ runTLSSimple params++ -- and resume+ sessionParams <- readClientSessionRef sessionRefs+ expectJust "session param should be Just" sessionParams+ let params2 = setPairParamsSessionResuming (fromJust sessionParams) params++ runTLSPredicate params2 (maybe False infoTLS12Resumption)++-- RFC 5246 Section 7.4.1.2: a client resuming a session MUST offer its+-- cipher suite. When it asks to resume one and offers none the server can+-- use, that is what is reported -- illegal_parameter, as when a cipher could+-- be chosen -- rather than the missing common cipher.+handshake12_session_resumption_cipher_missing :: CSP12 -> IO ()+handshake12_session_resumption_cipher_missing (CSP12 plainParams) = do+ sessionRefs <- twoSessionRefs+ let sessionManagers = twoSessionManagers sessionRefs+ params = setPairParamsSessionManagers sessionManagers plainParams+ runTLSSimple params+ sessionParams <- readClientSessionRef sessionRefs+ expectJust "session param should be Just" sessionParams+ let params2 = setPairParamsSessionResuming (fromJust sessionParams) params+ -- TLS_RSA_WITH_NULL_MD5, which the server does not support+ nullCiphers (ClientHello ch) = pure $ ClientHello ch{chCiphers = [CipherId 0x0001]}+ nullCiphers hs = pure hs+ withPairContextWith (id, id) params2 $ \(cctx, sctx) -> do+ contextHookSetHandshakeRecv sctx nullCiphers+ concurrently_+ (handshake sctx `shouldThrow` serverRejectedMissingCipher)+ (handshake cctx `shouldThrow` anyTLSException)++serverRejectedMissingCipher :: TLSException -> Bool+serverRejectedMissingCipher (HandshakeFailed (Error_Protocol _ IllegalParameter)) = True+serverRejectedMissingCipher _ = False++-- RFC 5077 Sections 3.2 and 3.3: a server sends the session_ticket+-- extension and NewSessionTicket only to a client that sent the+-- extension. The client's session_ticket is removed on its way to a+-- server whose session manager uses tickets, and the client must then+-- receive neither.+handshake12_session_ticket_unoffered :: IO ()+handshake12_session_ticket_unoffered = do+ CSP12 (cparams, sparams) <- generate arbitrary+ let sparams' =+ sparams+ { serverShared =+ (serverShared sparams){sharedSessionManager = oneSessionTicket}+ }+ unoffer (ClientHello ch) =+ pure $+ ClientHello+ ch+ { chExtensions =+ filter+ (\(ExtensionRaw eid _) -> eid /= EID_SessionTicket)+ (chExtensions ch)+ }+ unoffer hs = pure hs+ received <- newIORef []+ let record hs = modifyIORef received (hs :) >> pure hs+ withPairContextWith (id, id) (cparams, sparams') $ \(cctx, sctx) -> do+ contextHookSetHandshakeRecv sctx unoffer+ contextHookSetHandshakeRecv cctx record+ concurrently_ (handshake sctx) (handshake cctx)+ hss <- readIORef received+ let ticketExt (ServerHello sh) =+ any (\(ExtensionRaw eid _) -> eid == EID_SessionTicket) (shExtensions sh)+ ticketExt _ = False+ newTicket NewSessionTicket{} = True+ newTicket _ = False+ filter (\h -> ticketExt h || newTicket h) hss `shouldBe` []++handshake12_session_ticket :: CSP12 -> IO ()+handshake12_session_ticket (CSP12 plainParams) = do+ sessionRefs <- twoSessionRefs+ let sessionManagers0 = twoSessionManagers sessionRefs+ sessionManagers = (fst sessionManagers0, oneSessionTicket)++ let params = setPairParamsSessionManagers sessionManagers plainParams++ runTLSSimple params++ -- and resume+ sessionParams <- readClientSessionRef sessionRefs+ expectJust "session param should be Just" sessionParams+ let params2 = setPairParamsSessionResuming (fromJust sessionParams) params++ runTLSPredicate params2 (maybe False infoTLS12Resumption)++--------------------------------------------------------------++handshake13_full :: CSP13 -> IO ()+handshake13_full (CSP13 (cli, srv)) = do+ let cliSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ }+ svrSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ params =+ ( cli{clientSupported = cliSupported}+ , srv{serverSupported = svrSupported}+ )+ runTLSSimple13 params FullHandshake++-- A handshake that succeeds, for each group whose key exchange is a KEM.+--+-- Until this was added the suite had one ML-KEM test and it was a negative+-- one: a zero X25519 part, rejected before any shared secret was computed.+-- Nothing ran encapsulation and decapsulation to the end and checked the two+-- sides reached the same key, which is the one thing a key exchange has to+-- do. An implementation that returned the ciphertext where the secret+-- belongs would have passed everything else here.+handshake13_kem :: Group -> CSP13 -> IO ()+handshake13_kem grp (CSP13 (cli, srv)) = do+ let cliSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [grp]+ }+ svrSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [grp]+ , supportedGroupsTLS13 = [[grp]]+ }+ params =+ ( cli{clientSupported = cliSupported}+ , srv{serverSupported = svrSupported}+ )+ runTLSSimple13 params FullHandshake++-- A handshake with an ML-DSA server certificate, and one with an ML-DSA+-- client certificate, for each parameter set.+handshake13_mldsa :: Int -> CSP13 -> IO ()+handshake13_mldsa n (CSP13 (cli, srv)) = do+ creds <- generate arbitraryCredentialsOfEachMLDSA+ let cred = creds !! n+ -- one group on both sides, so the mode is a full handshake and not+ -- a retry, which is what this is asserting about+ cliSupported =+ (clientSupported cli)+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ }+ svrSupported =+ (serverSupported srv)+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ params =+ ( cli{clientSupported = cliSupported}+ , srv+ { serverSupported = svrSupported+ , serverShared =+ (serverShared srv){sharedCredentials = Credentials [cred]}+ }+ )+ runTLSSimple13 params FullHandshake++handshake13_mldsa_client :: Int -> CSP13 -> IO ()+handshake13_mldsa_client n (CSP13 (clientParam, serverParam)) = do+ creds <- generate arbitraryCredentialsOfEachMLDSA+ let cred = creds !! n+ clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return $ Just cred+ }+ }+ serverParam' =+ serverParam+ { serverWantClientCert = True+ , serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = \chain ->+ if chain == fst cred+ then return CertificateUsageAccept+ else return (CertificateUsageReject CertificateRejectUnknownCA)+ }+ , serverSupported =+ (serverSupported serverParam)+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ }+ clientParam'' =+ clientParam'+ { clientSupported =+ (clientSupported clientParam)+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ }+ }+ runTLSSimple13 (clientParam'', serverParam') FullHandshake++-- A CertificateVerify naming a scheme the key cannot have made.+--+-- The second byte of an ML-DSA scheme is one RSASSA-PSS also uses, so code+-- that looks at it alone answers for the wrong algorithm; this is here to+-- catch that, and to pin the alert, which has to be illegal_parameter and+-- not decrypt_error -- the field is wrong, rather than the signature.+handshake13_mldsa_wrong_scheme :: IO ()+handshake13_mldsa_wrong_scheme = do+ CSP13 (cli, srv) <- generate arbitrary+ creds <- generate arbitraryCredentialsOfEachMLDSA+ let cred = head creds -- ML-DSA-44+ cliSupported =+ (clientSupported cli)+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ }+ svrSupported =+ (serverSupported srv)+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ params =+ ( cli{clientSupported = cliSupported}+ , srv+ { serverSupported = svrSupported+ , serverShared =+ (serverShared srv){sharedCredentials = Credentials [cred]}+ }+ )+ withPairContextWith (id, id) params $ \(cctx, sctx) -> do+ contextHookSetHandshake13Recv cctx relabel+ concurrently_+ (handshake cctx `shouldThrow` clientRejectedTheScheme)+ (handshake sctx `shouldThrow` anyTLSException)+ where+ -- The signature is the one ML-DSA-44 made; only the scheme is changed,+ -- to another ML-DSA size, which the key cannot have signed under.+ relabel (CertVerify13 (DigitallySigned _ sig)) =+ pure $ CertVerify13 $ DigitallySigned MLDSA65 sig+ relabel hs = pure hs++-- The reason as well as the alert. Matching the alert alone would also be+-- satisfied by an illegal_parameter raised for some other reason, which is+-- not what this is asking about.+clientRejectedTheScheme :: TLSException -> Bool+clientRejectedTheScheme (HandshakeFailed (Error_Protocol msg alert)) =+ alert == IllegalParameter && "does not fit the public key" `isInfixOf` msg+clientRejectedTheScheme _ = False++handshake13_hrr :: CSP13 -> IO ()+handshake13_hrr (CSP13 (cli, srv)) = do+ let cliSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [P256, X25519]+ }+ svrSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ params =+ ( cli{clientSupported = cliSupported}+ , srv{serverSupported = svrSupported}+ )+ runTLSSimple13 params HelloRetryRequest++handshake13_psk :: CSP13 -> IO ()+handshake13_psk (CSP13 (cli, srv)) = do+ let cliSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [P256, X25519]+ }+ svrSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ params0 =+ ( cli{clientSupported = cliSupported}+ , srv{serverSupported = svrSupported}+ )++ sessionRefs <- twoSessionRefs+ let sessionManagers = twoSessionManagers sessionRefs++ let params = setPairParamsSessionManagers sessionManagers params0++ runTLSSimple13 params HelloRetryRequest++ -- and resume+ sessionParams <- readClientSessionRef sessionRefs+ expectJust "session param should be Just" sessionParams+ let params2 = setPairParamsSessionResuming (fromJust sessionParams) params++ runTLSSimple13 params2 PreSharedKey++-- RFC 8446 Section 4.6.1: a TLS 1.3 PSK resumes only a TLS 1.3 session.+-- The server's session manager answers the client's PSK identity with a+-- TLS 1.2 session, which has no ticket information; the server must+-- fall back to a full handshake rather than fail.+handshake13_psk_tls12_session :: IO ()+handshake13_psk_tls12_session = do+ CSP12 params12 <- generate arbitrary+ refs12 <- twoSessionRefs+ runTLSSimple $ setPairParamsSessionManagers (twoSessionManagers refs12) params12+ Just (_, sdata12) <- readIORef (snd refs12)+ sessionVersion sdata12 `shouldBe` TLS12++ CSP13 (cli, srv) <- generate arbitrary+ let cliSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [P256, X25519]+ }+ svrSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ params0 =+ ( cli{clientSupported = cliSupported}+ , srv{serverSupported = svrSupported}+ )+ refs13 <- twoSessionRefs+ let params = setPairParamsSessionManagers (twoSessionManagers refs13) params0+ runTLSSimple13 params HelloRetryRequest+ Just sessionParams <- readClientSessionRef refs13++ let tls12Manager =+ noSessionManager+ { sessionResume = \_ -> return $ Just sdata12+ , sessionResumeOnlyOnce = \_ -> return $ Just sdata12+ }+ params2 =+ setPairParamsSessionResuming sessionParams $+ setPairParamsSessionManagers (fst (twoSessionManagers refs13), tls12Manager) params0+ -- The key share is for the group of the earlier session, so no+ -- HelloRetryRequest, and the PSK is not used.+ runTLSSimple13 params2 FullHandshake++handshake13_psk_ticket :: CSP13 -> IO ()+handshake13_psk_ticket (CSP13 (cli, srv)) = do+ let cliSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [P256, X25519]+ }+ svrSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ params0 =+ ( cli{clientSupported = cliSupported}+ , srv{serverSupported = svrSupported}+ )++ sessionRefs <- twoSessionRefs+ let sessionManagers0 = twoSessionManagers sessionRefs+ sessionManagers = (fst sessionManagers0, oneSessionTicket)++ let params = setPairParamsSessionManagers sessionManagers params0++ runTLSSimple13 params HelloRetryRequest++ -- and resume+ sessionParams <- readClientSessionRef sessionRefs+ expectJust "session param should be Just" sessionParams+ let params2 = setPairParamsSessionResuming (fromJust sessionParams) params++ runTLSSimple13 params2 PreSharedKey++handshake13_psk_fallback :: CSP13 -> IO ()+handshake13_psk_fallback (CSP13 (cli, srv)) = do+ let cliSupported =+ defaultSupported+ { supportedCiphers =+ [ cipher13_AES_128_GCM_SHA256+ , cipher13_AES_128_CCM_SHA256+ ]+ , supportedGroups = [P256, X25519]+ }+ svrSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ params0 =+ ( cli{clientSupported = cliSupported}+ , srv{serverSupported = svrSupported}+ )++ sessionRefs <- twoSessionRefs+ let sessionManagers = twoSessionManagers sessionRefs++ let params = setPairParamsSessionManagers sessionManagers params0++ runTLSSimple13 params HelloRetryRequest++ -- resumption fails because GCM cipher is not supported anymore, full+ -- handshake is not possible because X25519 has been removed, so we are+ -- back with P256 after hello retry+ sessionParams <- readClientSessionRef sessionRefs+ expectJust "session param should be Just" sessionParams+ let (cli2, srv2) = setPairParamsSessionResuming (fromJust sessionParams) params+ srv2' =+ srv2{serverSupported = svrSupported'}+ svrSupported' =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_CCM_SHA256]+ , supportedGroups = [P256]+ , supportedGroupsTLS13 = [[P256]]+ }++ runTLSSimple13 (cli2, srv2') HelloRetryRequest++handshake13_0rtt :: CSP13 -> IO ()+handshake13_0rtt (CSP13 (cli, srv)) = do+ let cliSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [P256, X25519]+ }+ svrSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ cliHooks =+ defaultClientHooks+ { onSuggestALPN = return $ Just ["h2"]+ }+ svrHooks =+ defaultServerHooks+ { onALPNClientSuggest = Just (return . unsafeHead)+ }+ params0 =+ ( cli+ { clientSupported = cliSupported+ , clientHooks = cliHooks+ }+ , srv+ { serverSupported = svrSupported+ , serverHooks = svrHooks+ , serverEarlyDataSize = 2048+ }+ )++ sessionRefs <- twoSessionRefs+ let sessionManagers = twoSessionManagers sessionRefs++ let params = setPairParamsSessionManagers sessionManagers params0++ runTLSSimple13 params HelloRetryRequest+ runTLS0rtt params sessionRefs+ runTLS0rtt params sessionRefs+ where+ runTLS0rtt params sessionRefs = do+ -- and resume+ sessionParams <- readClientSessionRef sessionRefs+ expectJust "session param should be Just" sessionParams+ clearClientSessionRef sessionRefs+ earlyData <- B.pack <$> generate (someWords8 256)+ let (pc, ps) = setPairParamsSessionResuming (fromJust sessionParams) params+ params2 = (pc{clientUseEarlyData = True}, ps)++ runTLS0RTT params2 RTT0 earlyData++handshake13_0rtt_alpn :: IO ()+handshake13_0rtt_alpn = do+ (cli, srv) <- generate arbitraryPairParams13+ let cliSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ }+ svrSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ cliHooks =+ defaultClientHooks+ { onSuggestALPN = return $ Just ["h2"]+ }+ svrHooks =+ defaultServerHooks+ { onALPNClientSuggest = Just (return . unsafeHead)+ }+ params0 =+ ( cli+ { clientSupported = cliSupported+ , clientHooks = cliHooks+ }+ , srv+ { serverSupported = svrSupported+ , serverHooks = svrHooks+ , serverEarlyDataSize = 2048+ }+ )+ sessionRefs <- twoSessionRefs+ let params =+ setPairParamsSessionManagers+ (twoSessionManagers sessionRefs)+ params0+ runTLSSimple13 params FullHandshake++ sessionParams <- readClientSessionRef sessionRefs+ expectJust "session param should be Just" sessionParams+ sessionALPN (snd $ fromJust sessionParams) `shouldBe` Just "h2"+ let (pc, ps) = setPairParamsSessionResuming (fromJust sessionParams) params+ pc' =+ pc+ { clientUseEarlyData = True+ , clientHooks =+ (clientHooks pc)+ { onSuggestALPN = return $ Just ["http/1.1"]+ }+ }+ ps' =+ ps+ { serverHooks =+ (serverHooks ps)+ { onALPNClientSuggest = Just (return . unsafeHead)+ }+ }+ runTLS0RTT (pc', ps') PreSharedKey "GET /admin HTTP/1.1\r\n\r\n"++handshake13_0rtt_fallback :: CSP13 -> IO ()+handshake13_0rtt_fallback (CSP13 (cli, srv)) = do+ group0 <- generate $ elements [P256, X25519]+ let cliSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [P256, X25519]+ }+ svrSupported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [group0]+ , supportedGroupsTLS13 = [[group0]]+ }+ params =+ ( cli{clientSupported = cliSupported}+ , srv+ { serverSupported = svrSupported+ , serverEarlyDataSize = 1024+ }+ )++ sessionRefs <- twoSessionRefs+ let sessionManagers = twoSessionManagers sessionRefs++ let params0 = setPairParamsSessionManagers sessionManagers params++ let mode = if group0 == P256 then FullHandshake else HelloRetryRequest+ runTLSSimple13 params0 mode++ -- and resume+ mSessionParams <- readClientSessionRef sessionRefs+ case mSessionParams of+ Nothing -> expectationFailure "session params: Just is expected"+ Just sessionParams -> do+ earlyData <- B.pack <$> generate (someWords8 256)+ group1 <- generate $ elements [P256, X25519]+ let (pc, ps) = setPairParamsSessionResuming sessionParams params0+ svrSupported1 =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [group1]+ , supportedGroupsTLS13 = [[group1]]+ }+ params1 =+ ( pc{clientUseEarlyData = True}+ , ps+ { serverEarlyDataSize = 0+ , serverSupported = svrSupported1+ }+ )+ -- C: [P256, X25519]+ -- S: [group0]+ -- C: [P256, X25519]+ -- S: [group1]+ if group0 == group1+ -- 0-RTT is not allowed, so fallback to PreSharedKey+ then runTLS0RTT params1 PreSharedKey earlyData+ -- HRR but not allowed for 0-RTT+ else runTLSFailure params1 (tlsClient earlyData) tlsServer+ where+ tlsClient earlyData ctx = do+ handshake ctx+ sendData ctx $ L.fromStrict earlyData+ _ <- recvData ctx+ bye ctx+ tlsServer ctx = do+ handshake ctx+ _ <- recvData ctx+ bye ctx++-- The server has forgotten the session, so it answers 0-RTT with a full+-- handshake, and the client must check its certificate.+handshake13_0rtt_psk_rejected :: Bool -> IO ()+handshake13_0rtt_psk_rejected wantCert = do+ (cli, srv) <- generate arbitraryPairParams13+ let supported =+ defaultSupported+ { supportedCiphers = [cipher13_AES_128_GCM_SHA256]+ , supportedGroups = [X25519]+ , supportedGroupsTLS13 = [[X25519]]+ }+ params0 =+ ( cli{clientSupported = supported}+ , srv{serverSupported = supported, serverEarlyDataSize = 2048}+ )+ sessionRefs <- twoSessionRefs+ let params = setPairParamsSessionManagers (twoSessionManagers sessionRefs) params0+ runTLSSimple13 params FullHandshake++ sessionParams <- readClientSessionRef sessionRefs+ expectJust "session param should be Just" sessionParams+ writeIORef (snd sessionRefs) Nothing+ earlyData <- B.pack <$> generate (someWords8 256)+ let (pc, ps) = setPairParamsSessionResuming (fromJust sessionParams) params+ ps' =+ ps+ { serverWantClientCert = wantCert+ , serverHooks =+ (serverHooks ps)+ { onClientCertificate = \_ -> return CertificateUsageAccept+ }+ }+ runTLS0RTT (pc{clientUseEarlyData = True}, ps') FullHandshake earlyData++handshake13_ee_groups :: CSP13 -> IO ()+handshake13_ee_groups (CSP13 (cli, srv)) = do+ let -- The client prefers P256+ cliSupported = (clientSupported cli){supportedGroups = [P256, X25519]}+ -- The server prefers X25519+ svrSupported =+ (serverSupported srv)+ { supportedGroups = [X25519, P256]+ , supportedGroupsTLS13 = [[X25519, P256]]+ }+ params =+ ( cli{clientSupported = cliSupported}+ , srv{serverSupported = svrSupported}+ )+ (_, serverMessages) <- runTLSCapture13 params+ -- The server should tell X25519 in supported_groups in EE to client+ let isSupportedGroups (ExtensionRaw eid _) = eid == EID_SupportedGroups+ eeMessagesHaveExt =+ [ any isSupportedGroups exts+ | EncryptedExtensions13 exts <- serverMessages+ ]+ eeMessagesHaveExt `shouldBe` [True]++handshake13_ec :: CSP13 -> IO ()+handshake13_ec (CSP13 (cli, srv)) = do+ EC cgrps <- generate arbitrary+ EC sgrps <- generate arbitrary+ let cliSupported = (clientSupported cli){supportedGroups = cgrps}+ svrSupported =+ (serverSupported srv)+ { supportedGroups = sgrps+ , supportedGroupsTLS13 = [sgrps]+ }+ params =+ ( cli{clientSupported = cliSupported}+ , srv{serverSupported = svrSupported}+ )+ runTLSSimple13 params FullHandshake++handshake13_ffdhe :: CSP13 -> IO ()+handshake13_ffdhe (CSP13 (cli, srv)) = do+ FFDHE cgrps <- generate arbitrary+ FFDHE sgrps <- generate arbitrary+ let cliSupported = (clientSupported cli){supportedGroups = cgrps}+ svrSupported =+ (serverSupported srv)+ { supportedGroups = sgrps+ , supportedGroupsTLS13 = [sgrps]+ }+ params =+ ( cli{clientSupported = cliSupported}+ , srv{serverSupported = svrSupported}+ )+ runTLSSimple13 params FullHandshake++-- An all-zero X25519 public key decodes, but the shared secret computed+-- from it is all zero and is rejected. The server must answer with+-- illegal_parameter, as it does for X25519 alone, rather than crash.+handshake13_x25519mlkem768_zero_x25519 :: IO ()+handshake13_x25519mlkem768_zero_x25519 = do+ CSP13 (cli, srv) <- generate arbitrary+ let cliSupported =+ (clientSupported cli){supportedGroups = [X25519MLKEM768]}+ svrSupported =+ (serverSupported srv)+ { supportedGroups = [X25519MLKEM768]+ , supportedGroupsTLS13 = [[X25519MLKEM768]]+ }+ params =+ ( cli{clientSupported = cliSupported}+ , srv{serverSupported = svrSupported}+ )+ withPairContextWith (id, id) params $ \(cctx, sctx) -> do+ contextHookSetHandshakeRecv sctx zeroX25519+ concurrently_+ (handshake sctx `shouldThrow` serverRejectedZeroX25519)+ (handshake cctx `shouldThrow` anyTLSException)+ where+ zeroX25519 (ClientHello ch) =+ pure $ ClientHello ch{chExtensions = map zeroKeyShare $ chExtensions ch}+ zeroX25519 hs = pure hs+ zeroKeyShare ext@(ExtensionRaw eid bs)+ | eid == EID_KeyShare+ , Just (KeyShareClientHello kses) <- extensionDecode MsgTClientHello bs =+ toExtensionRaw $ KeyShareClientHello $ map zeroEntry kses+ | otherwise = ext+ -- The ML-KEM-768 encapsulation key (1184 bytes) is followed by the+ -- X25519 public key (32 bytes).+ zeroEntry (KeyShareEntry grp key)+ | grp == X25519MLKEM768 =+ KeyShareEntry grp $ B.take 1184 key <> B.replicate 32 0+ zeroEntry kse = kse++serverRejectedZeroX25519 :: TLSException -> Bool+serverRejectedZeroX25519 (HandshakeFailed (Error_Protocol msg alert)) =+ msg == "invalid client X25519MLKEM768 public key"+ && alert == IllegalParameter+serverRejectedZeroX25519 _ = False++post_handshake_auth :: CSP13 -> IO ()+post_handshake_auth (CSP13 (clientParam, serverParam)) = do+ cred <- generate (arbitraryClientCredential TLS13)+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return $ Just cred+ }+ }+ serverParam' =+ serverParam+ { serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = validateChain cred+ }+ }+ if isCredentialDSA cred+ then runTLSFailure (clientParam', serverParam') hsClient hsServer+ else runTLSSuccess (clientParam', serverParam') hsClient hsServer+ where+ validateChain cred chain+ | chain == fst cred = return CertificateUsageAccept+ | otherwise = return (CertificateUsageReject CertificateRejectUnknownCA)+ hsClient ctx = do+ handshake ctx+ sendData ctx "request 1"+ recvDataAssert ctx "response 1"+ sendData ctx "request 2"+ recvDataAssert ctx "response 2"+ hsServer ctx = do+ handshake ctx+ recvDataAssert ctx "request 1"+ _ <- requestCertificate ctx -- single request+ sendData ctx "response 1"+ recvDataAssert ctx "request 2"+ _ <- requestCertificate ctx+ _ <- requestCertificate ctx -- two simultaneously+ sendData ctx "response 2"++-- | After sending a Certificate message, a TLS 1.3 client peeks for a+-- client-authentication alert with a deadline of a few RTTs. That peek must+-- not abandon a record it has already started reading: the record layer has no+-- receive buffer, so the bytes consumed for the record header would be lost and+-- the caller's next 'recvData' would decode part of a record body as a header.+--+-- Here the server's first write is one full-size record whose body is made to+-- arrive late, so the peek does hit its deadline with the header already+-- consumed. Before the fix this failed with+-- @Error_Protocol "record exceeding maximum size" RecordOverflow@.+handshake13_client_auth_slow_record :: IO ()+handshake13_client_auth_slow_record = do+ (clientParam, serverParam) <- generate arbitraryPairParams13+ cred <- generate (arbitraryClientCredential TLS13)+ let clientParam' =+ clientParam+ { clientHooks =+ (clientHooks clientParam)+ { onCertificateRequest = \_ -> return $ Just cred+ }+ }+ serverParam' =+ serverParam+ { serverWantClientCert = True+ , serverHooks =+ (serverHooks serverParam)+ { onClientCertificate = \_ -> return CertificateUsageAccept+ }+ }+ payload = B.replicate 16384 65+ withPairContextWith (delayBigReads, id) (clientParam', serverParam') $+ \(cCtx, sCtx) ->+ concurrently_+ ( do+ handshake sCtx+ sendData sCtx $ L.fromStrict payload+ )+ ( do+ handshake cCtx+ recvData cCtx `shouldReturn` payload+ )+ where+ -- Only the body of the big record is held back; every handshake record is+ -- far smaller than this threshold and so arrives immediately.+ delayBigReads be =+ be+ { backendRecv = \n -> do+ when (n > 4096) $ threadDelay 300000+ backendRecv be n+ }++-- A protected record too short for the cipher cannot be deprotected, which+-- RFC 5246 Section 7.2.2 and RFC 8446 Section 5.2 answer with+-- bad_record_mac. The client's first application data record is cut down+-- to a single byte of ciphertext on its way to the server.+short_record_bad_record_mac :: Version -> Cipher -> IO ()+short_record_bad_record_mac version cipher = do+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([version], [version])+ ([cipher], [cipher])+ armed <- newIORef False+ let truncateRecord be =+ be+ { backendSend = \bs -> do+ cut <- readIORef armed+ if cut && B.length bs > 6 && B.head bs == 23+ then do+ writeIORef armed False+ backendSend be $ B.take 3 bs <> B.pack [0, 1] <> B.take 1 (B.drop 5 bs)+ else backendSend be bs+ }+ withPairContextWith (truncateRecord, id) (clientParam, serverParam) $+ \(cctx, sctx) ->+ concurrently_+ ( do+ handshake sctx+ recvData sctx `shouldThrow` serverRejectedShortRecord+ )+ ( do+ handshake cctx+ writeIORef armed True+ sendData cctx "hello"+ void (E.try (recvData cctx) :: IO (Either E.SomeException B.ByteString))+ )++-- RFC 8446 Section 5: once keys are in use, only ChangeCipherSpec may come+-- in clear. An injected close_notify must not read as an orderly close.+plaintext_alert13_rejected :: IO ()+plaintext_alert13_rejected = do+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([TLS13], [TLS13])+ ([cipher13_AES_128_GCM_SHA256], [cipher13_AES_128_GCM_SHA256])+ armed <- newIORef False+ let injectAlert be =+ be+ { backendSend = \bs -> do+ inject <- readIORef armed+ if inject && B.take 1 bs == B.singleton 23+ then do+ writeIORef armed False+ backendSend be $ B.pack [21, 3, 3, 0, 2, 1, 0]+ else backendSend be bs+ }+ withPairContextWith (injectAlert, id) (clientParam, serverParam) $+ \(cctx, sctx) ->+ concurrently_+ ( do+ handshake sctx+ recvData sctx `shouldThrow` rejectedPlaintextAlert+ )+ ( do+ handshake cctx+ writeIORef armed True+ sendData cctx "hello"+ void (E.try (recvData cctx) :: IO (Either E.SomeException B.ByteString))+ )++rejectedPlaintextAlert :: TLSException -> Bool+rejectedPlaintextAlert (Terminated _ _ (Error_Protocol _ UnexpectedMessage)) = True+rejectedPlaintextAlert _ = False++-- A ChangeCipherSpec ahead of the client's second flight, before the server+-- has the keys to switch to. The handshake would refuse it anyway; this+-- keeps the record layer from switching to keys that do not exist.+early_ccs12_rejected :: IO ()+early_ccs12_rejected = do+ let cipher = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([TLS12], [TLS12])+ ([cipher], [cipher])+ sent <- newIORef (0 :: Int)+ let injectCCS be =+ be+ { backendSend = \bs -> do+ n <- readIORef sent+ writeIORef sent (n + 1)+ -- The first send is ClientHello.+ when (n == 1) $ backendSend be $ B.pack [20, 3, 3, 0, 1, 1]+ backendSend be bs+ }+ withPairContextWith (injectCCS, id) (clientParam, serverParam) $+ \(cctx, sctx) ->+ concurrently_+ (handshake sctx `shouldThrow` rejectedAsUnexpected)+ (void (E.try (handshake cctx) :: IO (Either E.SomeException ())))++serverRejectedShortRecord :: TLSException -> Bool+serverRejectedShortRecord (Terminated _ _ (Error_Protocol _ BadRecordMac)) = True+serverRejectedShortRecord _ = False++-- The first handshake message a server receives must be a ClientHello; any+-- other is out of order and answered with unexpected_message (RFC 8446+-- Section 4), whatever its body would decode to. The handshake type of the+-- client's first record is replaced on its way to the server.+server_first_message_unexpected :: Word8 -> IO ()+server_first_message_unexpected ty = do+ (clientParam, serverParam) <- generate arbitrary+ armed <- newIORef True+ let retype be =+ be+ { backendSend = \bs -> do+ first <- atomicModifyIORef' armed (\a -> (False, a))+ if first && B.length bs > 5 && B.head bs == 22+ then backendSend be $ B.take 5 bs <> B.singleton ty <> B.drop 6 bs+ else backendSend be bs+ }+ withPairContextWith (retype, id) (clientParam, serverParam) $ \(cctx, sctx) ->+ concurrently_+ (handshake sctx `shouldThrow` serverRejectedUnexpectedFirst)+ (handshake cctx `shouldThrow` anyTLSException)++-- ChangeCipherSpec comes between complete handshake messages (RFC 5246+-- Section 7.1), and a server has no handshake state before its first+-- ClientHello (CVE-2004-0079). The client's first record is split after+-- the given number of bytes of its body, and a ChangeCipherSpec record is+-- sent in between; with 0 it is sent before the whole ClientHello.+server_ccs_interleaved :: Int -> IO ()+server_ccs_interleaved n = do+ (clientParam, serverParam) <- generate arbitrary+ armed <- newIORef True+ let interleave be =+ be+ { backendSend = \bs -> do+ first <- atomicModifyIORef' armed (\a -> (False, a))+ if first && B.length bs > 5 + n && B.head bs == 22+ then do+ let (hdr, body) = B.splitAt 5 bs+ (body1, body2) = B.splitAt n body+ record b = B.take 3 hdr <> encodeWord16 (fromIntegral $ B.length b) <> b+ ccs = B.pack [20, 3, 3, 0, 1, 1]+ if n == 0+ then backendSend be $ ccs <> bs+ else backendSend be $ record body1 <> ccs <> record body2+ else backendSend be bs+ }+ withPairContextWith (interleave, id) (clientParam, serverParam) $ \(cctx, sctx) ->+ concurrently_+ (handshake sctx `shouldThrow` serverRejectedUnexpectedFirst)+ (handshake cctx `shouldThrow` anyTLSException)++-- An unknown record type is answered with unexpected_message (RFC 8446+-- Section 5). The type of the client's first record is replaced; with a+-- length larger than what follows, as an SSLv2 ClientHello reads when taken+-- for a TLS record header, the server must answer from the header alone+-- rather than wait for a body that never comes. A length of 0 keeps the+-- original one.+server_first_record_type_unexpected :: Word8 -> Word16 -> IO ()+server_first_record_type_unexpected ty len = do+ (clientParam, serverParam) <- generate arbitrary+ armed <- newIORef True+ let retype be =+ be+ { backendSend = \bs -> do+ first <- atomicModifyIORef' armed (\a -> (False, a))+ if first && B.length bs > 5 && B.head bs == 22+ then do+ let lenBytes+ | len == 0 = B.take 2 (B.drop 3 bs)+ | otherwise =+ B.pack [fromIntegral (len `div` 256), fromIntegral (len `mod` 256)]+ backendSend be $+ B.singleton ty <> B.take 2 (B.drop 1 bs) <> lenBytes <> B.drop 5 bs+ else backendSend be bs+ }+ r <- timeout 5000000 $+ withPairContextWith (retype, id) (clientParam, serverParam) $ \(cctx, sctx) ->+ concurrently_+ (handshake sctx `shouldThrow` serverRejectedUnexpectedFirst)+ (handshake cctx `shouldThrow` anyTLSException)+ r `shouldSatisfy` isJust++-- RFC 8446 Section 5.1: handshake messages MUST NOT be interleaved with+-- other record types. After the handshake the client sends, protected under+-- its application traffic secret, a record holding only the first two bytes+-- of a KeyUpdate and then a record of application data. The server must not+-- deliver the data and must answer with unexpected_message.+handshake13_interleaved_app_data :: IO ()+handshake13_interleaved_app_data = do+ let cipher = cipher13_AES_128_GCM_SHA256+ (clientParam, serverParam) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([TLS13], [TLS13])+ ([cipher], [cipher])+ secretRef <- newIORef Nothing+ sendRef <- newIORef (\_ -> return ())+ let logKey line = case words line of+ ["CLIENT_TRAFFIC_SECRET_0", _, h] -> writeIORef secretRef (Just h)+ _ -> return ()+ clientParam' =+ clientParam+ { clientDebug = (clientDebug clientParam){debugKeyLogger = logKey}+ }+ -- remember the raw sender so that records can be written by hand+ capture be =+ be+ { backendSend = \bs -> do+ writeIORef sendRef (backendSend be)+ backendSend be bs+ }+ withPairContextWith (capture, id) (clientParam', serverParam) $ \(cctx, sctx) ->+ concurrently_+ ( do+ handshake sctx+ r <- E.try (recvData sctx) :: IO (Either TLSException B.ByteString)+ case r of+ Right d -> expectationFailure $ "server delivered " ++ show d+ Left _ -> return ()+ )+ ( do+ handshake cctx+ Just h <- readIORef secretRef+ send <- readIORef sendRef+ let secret = BA.convert (unhex h) :: BA.ScrubbedBytes+ key = hkdfExpandLabel SHA256 secret "key" "" 16 :: B.ByteString+ iv = hkdfExpandLabel SHA256 secret "iv" "" 12 :: B.ByteString+ -- the first two bytes of KeyUpdate(update_not_requested)+ partialKeyUpdate = B.pack [24, 0]+ send $ protect13 key iv 0 22 partialKeyUpdate+ send $ protect13 key iv 1 23 "hello"+ recvData cctx `shouldThrow` peerSentUnexpected+ )+ where+ unhex [] = B.empty+ unhex (a : b : rest) = B.cons (read ['0', 'x', a, b]) (unhex rest)+ unhex _ = error "unhex"++-- A ChangeCipherSpec is the single byte 1; any other value is answered with+-- unexpected_message (RFC 8446 Section 5), and so is one carrying two of+-- them. The client's ChangeCipherSpec record is made two bytes long. The+-- groups are fixed so that a TLS 1.3 handshake goes through a+-- HelloRetryRequest -- after which the client sends its ChangeCipherSpec+-- before the second ClientHello -- or not, as the test asks.+malformed_ccs_unexpected :: Version -> Cipher -> [Group] -> [Group] -> IO ()+malformed_ccs_unexpected version cipher cgroups sgroups = do+ (clientParam0, serverParam0) <-+ generate $+ arbitraryPairParamsWithVersionsAndCiphers+ ([version], [version])+ ([cipher], [cipher])+ let clientParam =+ clientParam0+ { clientSupported = (clientSupported clientParam0){supportedGroups = cgroups}+ }+ serverParam =+ serverParam0+ { serverSupported =+ (serverSupported serverParam0)+ { supportedGroups = sgroups+ , supportedGroupsTLS13 = [sgroups]+ }+ }+ seen <- newIORef False+ let ccs = B.pack [20, 3, 3, 0, 1, 1]+ doubled = B.pack [20, 3, 3, 0, 2, 1, 1]+ double be =+ be+ { backendSend = \bs ->+ if ccs `B.isPrefixOf` bs+ then do+ writeIORef seen True+ backendSend be $ doubled <> B.drop 6 bs+ else backendSend be bs+ }+ -- a TLS 1.3 server takes the client's ChangeCipherSpec and Finished in+ -- its first recvData, a TLS 1.2 one in handshake+ r <- timeout 10000000 $+ withPairContextWith (double, id) (clientParam, serverParam) $ \(cctx, sctx) ->+ concurrently_+ ((handshake sctx >> recvData sctx) `shouldThrow` rejectedAsUnexpected)+ ( void+ (E.try (handshake cctx >> recvData cctx) :: IO (Either TLSException B.ByteString))+ )+ r `shouldSatisfy` isJust+ readIORef seen `shouldReturn` True++rejectedAsUnexpected :: TLSException -> Bool+rejectedAsUnexpected (HandshakeFailed (Error_Packet_unexpected _ _)) = True+rejectedAsUnexpected (Terminated _ _ (Error_Packet_unexpected _ _)) = True+rejectedAsUnexpected _ = False++-- An AES-128-GCM TLS 1.3 record: content and inner type, protected with the+-- record header as additional data and the sequence number in the nonce.+protect13 :: B.ByteString -> B.ByteString -> Word64 -> Word8 -> B.ByteString -> B.ByteString+protect13 key iv sqn innerType content = hdr <> ct <> BA.convert tag+ where+ len = B.length content + 1 + 16+ hdr = B.pack [23, 3, 3, fromIntegral (len `div` 256), fromIntegral (len `mod` 256)]+ sqnBytes = B.pack [fromIntegral (sqn `shiftR` (8 * i)) | i <- [7, 6 .. 0]]+ nonce = B.pack $ B.zipWith xor iv (B.replicate 4 0 <> sqnBytes)+ aes = throwCryptoError (cipherInit key) :: AES128+ aead = throwCryptoError (aeadInit AEAD_GCM aes nonce)+ (AuthTag tag, ct) = aeadSimpleEncrypt aead hdr (content <> B.singleton innerType) 16++peerSentUnexpected :: TLSException -> Bool+peerSentUnexpected (Terminated True _ (Error_Protocol _ UnexpectedMessage)) = True+peerSentUnexpected _ = False++serverRejectedUnexpectedFirst :: TLSException -> Bool+serverRejectedUnexpectedFirst (HandshakeFailed (Error_Packet_unexpected _ _)) = True+serverRejectedUnexpectedFirst _ = False expectJust :: String -> Maybe a -> Expectation expectJust tag mx = case mx of
test/PubKey.hs view
@@ -4,6 +4,9 @@ arbitraryECDSAPair, arbitraryEd25519Pair, arbitraryEd448Pair,+ arbitraryMLDSA44Pair,+ arbitraryMLDSA65Pair,+ arbitraryMLDSA87Pair, globalRSAPair, getGlobalRSAPair, knownECCurves,@@ -20,8 +23,10 @@ import qualified Crypto.PubKey.ECC.Types as ECC import qualified Crypto.PubKey.Ed25519 as Ed25519 import qualified Crypto.PubKey.Ed448 as Ed448+import qualified Crypto.PubKey.MLDSA as MLDSA import qualified Crypto.PubKey.RSA as RSA import Crypto.Random+import Data.Proxy (Proxy (..)) import qualified Data.ByteString as B import System.IO.Unsafe import Test.QuickCheck@@ -111,6 +116,27 @@ bytes <- vectorOf 32 arbitrary let priv = fromCryptoPassed $ Ed25519.secretKey (B.pack bytes) return (Ed25519.toPublic priv, priv)++-- ML-DSA, from a seed drawn here, so a run is reproducible from its seed+-- the way the other pairs are.+arbitraryMLDSAPair+ :: MLDSA.MLDSA p+ => proxy p -> Gen (MLDSA.VerificationKey p, MLDSA.SigningKey p)+arbitraryMLDSAPair p = do+ bytes <- vectorOf MLDSA.seedSize arbitrary+ return $ fromCryptoPassed $ MLDSA.keyPairFromSeed p (B.pack bytes)++arbitraryMLDSA44Pair+ :: Gen (MLDSA.VerificationKey MLDSA.MLDSA44, MLDSA.SigningKey MLDSA.MLDSA44)+arbitraryMLDSA44Pair = arbitraryMLDSAPair (Proxy :: Proxy MLDSA.MLDSA44)++arbitraryMLDSA65Pair+ :: Gen (MLDSA.VerificationKey MLDSA.MLDSA65, MLDSA.SigningKey MLDSA.MLDSA65)+arbitraryMLDSA65Pair = arbitraryMLDSAPair (Proxy :: Proxy MLDSA.MLDSA65)++arbitraryMLDSA87Pair+ :: Gen (MLDSA.VerificationKey MLDSA.MLDSA87, MLDSA.SigningKey MLDSA.MLDSA87)+arbitraryMLDSA87Pair = arbitraryMLDSAPair (Proxy :: Proxy MLDSA.MLDSA87) arbitraryEd448Pair :: Gen (Ed448.PublicKey, Ed448.SecretKey) arbitraryEd448Pair = do
test/Run.hs view
@@ -16,6 +16,9 @@ runTLSFailure, expectMaybe, newPairContext,+ newPairContextWith,+ withPairContext,+ withPairContextWith, withDataPipe, byeBye, ) where@@ -156,6 +159,16 @@ handshake ctx sendData ctx $ L.fromStrict earlyData _ <- recvData ctx+ -- One more exchange, and this one the client starts. Our Finished is+ -- not sent by 'handshake' here: 0-RTT defers it, and the receive loop+ -- above is what puts it on the wire. The server emits the+ -- NewSessionTicket when it reads that Finished, which is after it sent+ -- the echo -- so reading the echo is not enough to have seen the+ -- ticket, and neither is a byte the server sends straight after it.+ -- The server cannot answer this without having read past the Finished+ -- first, and records arrive in order.+ sendData ctx "x"+ recvDataAssert ctx "x" bye ctx mmode <- (>>= infoTLS13HandshakeMode) <$> contextGetInformation ctx expectMaybe "C: mode should be Just" mode mmode@@ -165,6 +178,8 @@ chunks <- replicateM (length ls) $ recvData ctx (map B.length chunks, B.concat chunks) `shouldBe` (ls, earlyData) sendData ctx $ L.fromStrict earlyData+ recvDataAssert ctx "x"+ sendData ctx "x" bye ctx mmode <- (>>= infoTLS13HandshakeMode) <$> contextGetInformation ctx expectMaybe "S: mode should be Just" mode mmode@@ -187,6 +202,16 @@ handshake ctx sendData ctx $ L.fromStrict earlyData _ <- recvData ctx+ -- One more exchange, and this one the client starts. Our Finished is+ -- not sent by 'handshake' here: 0-RTT defers it, and the receive loop+ -- above is what puts it on the wire. The server emits the+ -- NewSessionTicket when it reads that Finished, which is after it sent+ -- the echo -- so reading the echo is not enough to have seen the+ -- ticket, and neither is a byte the server sends straight after it.+ -- The server cannot answer this without having read past the Finished+ -- first, and records arrive in order.+ sendData ctx "x"+ recvDataAssert ctx "x" bye ctx minfo <- contextGetInformation ctx let mmode = minfo >>= infoTLS13HandshakeMode@@ -199,6 +224,8 @@ chunks <- replicateM (length ls) $ recvData ctx (map B.length chunks, B.concat chunks) `shouldBe` (ls, earlyData) sendData ctx $ L.fromStrict earlyData+ recvDataAssert ctx "x"+ sendData ctx "x" bye ctx mmode <- (>>= infoTLS13HandshakeMode) <$> contextGetInformation ctx expectMaybe "S: mode should be Just" mode mmode@@ -295,11 +322,15 @@ hsClient ctx d <- readChan queue sendData ctx (L.fromChunks [d])+ -- The server writes after any TLS 1.3 NewSessionTicket, so waiting for+ -- this byte ensures the client session manager received the ticket.+ recvDataAssert ctx "x" checkCtxFinished ctx bye ctx tlsServer ctx queue = do hsServer ctx d <- recvData ctx+ sendData ctx "x" writeChan queue [d] checkCtxFinished ctx bye ctx@@ -326,16 +357,32 @@ withPairContext :: (ClientParams, ServerParams) -> ((Context, Context) -> IO ()) -> IO ()-withPairContext params body =+withPairContext = withPairContextWith (id, id)++withPairContextWith+ :: (Backend -> Backend, Backend -> Backend)+ -> (ClientParams, ServerParams)+ -> ((Context, Context) -> IO ())+ -> IO ()+withPairContextWith wrapBackends params body = E.bracket- (newPairContext params)+ (newPairContextWith wrapBackends params) (\((t1, t2), _) -> killThread t1 >> killThread t2) (\(_, ctxs) -> body ctxs) newPairContext :: (ClientParams, ServerParams) -> IO ((ThreadId, ThreadId), (Context, Context))-newPairContext (cParams, sParams) = do+newPairContext = newPairContextWith (id, id)++-- | 'newPairContext' with a hook on each side's 'Backend' -- client first, as+-- with the parameters -- so that a test can control how bytes arrive (delay+-- them, split them). Pass 'id' for a side to leave it alone.+newPairContextWith+ :: (Backend -> Backend, Backend -> Backend)+ -> (ClientParams, ServerParams)+ -> IO ((ThreadId, ThreadId), (Context, Context))+newPairContextWith (wrapCBackend, wrapSBackend) (cParams, sParams) = do pipe <- newPipe tids <- runPipe pipe let noFlush = return ()@@ -343,8 +390,8 @@ let cBackend = Backend noFlush noClose (writePipeC pipe) (readPipeC pipe) let sBackend = Backend noFlush noClose (writePipeS pipe) (readPipeS pipe)- cCtx' <- contextNew cBackend cParams- sCtx' <- contextNew sBackend sParams+ cCtx' <- contextNew (wrapCBackend cBackend) cParams+ sCtx' <- contextNew (wrapSBackend sBackend) sParams contextHookSetLogging cCtx' (logging "client: ") contextHookSetLogging sCtx' (logging "server: ")
+ test/SecretSpec.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE OverloadedStrings #-}++module SecretSpec where++import Crypto.Debug (debugShow)+import qualified Data.ByteArray as BA+import qualified Data.ByteString as B+import Data.List (isInfixOf)+import Network.TLS (Version (TLS13))+import Network.TLS.Extra.Cipher (ciphersuite_default)+import Network.TLS.Internal (SessionData (..))+import Network.TLS.QUIC+import Test.Hspec++-- | The traffic secrets reach a QUIC implementation through+-- 'quicInstallKeys', so a trace of what it is handed must not write them to a+-- log. 'debugShow' is how a debugging session asks for them on purpose.+spec :: Spec+spec = do+ describe "Show of the QUIC secret types" $ do+ it "does not print an early traffic secret" $+ check $+ EarlySecretInfo cipher clientSecret+ it "does not print the handshake traffic secrets" $+ check $+ HandshakeSecretInfo cipher (clientSecret, serverSecret)+ it "does not print the application traffic secrets" $+ check $+ ApplicationSecretInfo (clientSecret, serverSecret)+ describe "Show of a resumable session" $+ it "does not print the session secret" $ do+ let shown = show sessionData+ show (B.replicate 32 0xa5) `isInfixOf` shown `shouldBe` False+ "<secret>" `isInfixOf` shown `shouldBe` True+ show (B.replicate 32 0xa5)+ `isInfixOf` debugShow sessionData+ `shouldBe` True+ where+ sessionData =+ SessionData+ { sessionVersion = TLS13+ , sessionCipher = 0x1301+ , sessionCompression = 0+ , sessionClientSNI = Just "example.com"+ , sessionSecret = B.replicate 32 0xa5+ , sessionGroup = Nothing+ , sessionTicketInfo = Nothing+ , sessionALPN = Nothing+ , sessionMaxEarlyDataSize = 0+ , sessionFlags = []+ }+ cipher = case ciphersuite_default of+ c : _ -> c+ [] -> error "ciphersuite_default is empty"+ clientSecret = ClientTrafficSecret $ BA.convert $ B.replicate 32 0xa5+ serverSecret = ServerTrafficSecret $ BA.convert $ B.replicate 32 0x5a+ hexOf w = concat $ replicate 32 w+ check x = do+ let shown = show x+ hexOf "a5" `isInfixOf` shown `shouldBe` False+ hexOf "5a" `isInfixOf` shown `shouldBe` False+ "<secret>" `isInfixOf` shown `shouldBe` True+ hexOf "a5" `isInfixOf` debugShow x `shouldBe` True
tls.cabal view
@@ -1,6 +1,6 @@-cabal-version: >=1.10+cabal-version: 2.0 name: tls-version: 2.3.1+version: 2.4.12 license: BSD3 license-file: LICENSE copyright: Vincent Hanquez <vincent@snarc.org>@@ -15,6 +15,8 @@ build-type: Simple extra-source-files: test/*.hs++extra-doc-files: CHANGELOG.md source-repository head@@ -34,6 +36,7 @@ Network.TLS.Internal Network.TLS.Extra Network.TLS.Extra.Cipher+ Network.TLS.Extra.CipherCBC Network.TLS.Extra.FFDHE Network.TLS.QUIC @@ -121,23 +124,22 @@ base16-bytestring, bytestring >=0.10 && <0.13, cereal >=0.5.3 && <0.6,- crypton >=1.1.2 && <1.2,+ crypton >=2.1.9 && <2.3, crypton-asn1-encoding >= 0.10.0 && < 0.11, crypton-asn1-types >= 0.4.1 && < 0.5,- crypton-x509 >=1.9 && <1.10,- crypton-x509-store >=1.9 && <1.10,- crypton-x509-validation >=1.9 && <1.10,+ crypton-x509 >=1.10 && <1.11,+ crypton-x509-store >=1.10 && <1.11,+ crypton-x509-validation >=1.10 && <1.11, data-default, ech-config,- hpke >=0.1.0 && <0.2,- mlkem >= 0.2.0 && <0.3,+ hpke >=0.3.0 && <0.4, mtl >=2.2 && <2.4, network >=3.1, ram >=0.22.0 && <0.23, random >=1.2 && <1.4, serialise >=0.2 && <0.3, transformers >=0.5 && <0.7,- unix-time >=0.4.11 && <0.5,+ unix-time >=0.4.11 && <0.6, zlib >=0.7 && <0.8 executable tls-server@@ -189,7 +191,7 @@ crypton-x509-system, ech-config, network,- network-run >=0.5,+ network-run >=0.6.0 && < 0.7, tls if flag(devel)@@ -213,6 +215,7 @@ PipeChan PubKey Run+ SecretSpec Session ThreadSpec @@ -234,46 +237,5 @@ ram, serialise, time-hourglass,- tls--benchmark tls-bench- type: exitcode-stdio-1.0- main-is: Benchmarks.hs- hs-source-dirs: Benchmarks test- other-modules:- API- Arbitrary- Certificate- CiphersSpec- ECHSpec- EncodeSpec- HandshakeSpec- PipeChan- PubKey- Run- Session- ThreadSpec-- default-language: Haskell2010- ghc-options: -Wall- build-depends:- base >=4.9 && <5,- QuickCheck,- async,- base64-bytestring,- bytestring,- containers,- crypton,- crypton-asn1-types,- crypton-x509,- crypton-x509-store,- crypton-x509-validation,- data-default,- ech-config,- hspec,- network,- network-run,- serialise,- tasty-bench,- time-hourglass,- tls+ tls,+ zlib
util/Common.hs view
@@ -111,7 +111,7 @@ minfo <- contextGetInformation ctx case minfo of Nothing -> do- putStrLn "Erro: information cannot be obtained"+ putStrLn "Error: information cannot be obtained" exitFailure Just info -> return info
util/Server.hs view
@@ -28,15 +28,18 @@ where body = "<html><<body>Hello world!</body></html>" +-- An HTTP request is answered with HTML, anything else is echoed.+-- tlsfuzzer's test-lengths.py sends "A..A\n", or just "\n", and expects+-- it back. server :: Context -> Bool -> IO () server ctx showRequest = do bs <- recvData ctx case C8.uncons bs of Nothing -> return ()- Just ('A', _) -> do+ Just ('G', _) -> handleHTML ctx showRequest bs+ Just _ -> do sendData ctx $ CL8.fromStrict bs echo ctx- Just _ -> handleHTML ctx showRequest bs echo :: Context -> IO () echo ctx = loop
util/tls-server.hs view
@@ -4,12 +4,16 @@ module Main where +import qualified Control.Exception as E import Data.IORef import qualified Data.Map.Strict as M import Data.X509.CertificateStore import Network.Run.TCP import Network.TLS import Network.TLS.ECH.Config+import Network.TLS.Extra.Cipher+import Network.TLS.Extra.CipherCBC+import Network.TLS.Extra.FFDHE import Network.TLS.Internal import System.Console.GetOpt import System.Environment (getArgs)@@ -27,12 +31,14 @@ , optShow :: Bool , optKeyLogFile :: Maybe FilePath , optTrustedAnchor :: Maybe FilePath- , optGroups :: [Group]+ , optGroups :: Maybe [Group] , optCertFile :: FilePath , optKeyFile :: FilePath , optECHConfigFile :: Maybe FilePath , optECHKeyFile :: Maybe FilePath , optTraceKey :: Bool+ , optUseWeakCiphers :: Bool+ , optServerName :: Maybe HostName } deriving (Show) @@ -44,13 +50,14 @@ , optShow = False , optKeyLogFile = Nothing , optTrustedAnchor = Nothing- , -- excluding FFDHE8192 for retry- optGroups = FFDHE8192 `delete` supportedGroups defaultSupported+ , optGroups = Nothing , optCertFile = "servercert.pem" , optKeyFile = "serverkey.pem" , optECHConfigFile = Nothing , optECHKeyFile = Nothing , optTraceKey = False+ , optUseWeakCiphers = False+ , optServerName = Nothing } options :: [OptDescr (Options -> Options)]@@ -78,7 +85,7 @@ , Option ['g'] ["groups"]- (ReqArg (\gs o -> o{optGroups = readGroups gs}) "<groups>")+ (ReqArg (\gs o -> o{optGroups = Just $ readGroups gs}) "<groups>") "groups for key exchange" , Option ['c']@@ -110,6 +117,16 @@ ["trace-key"] (NoArg (\o -> o{optTraceKey = True})) "Trace transcript hash"+ , Option+ []+ ["use-weak-ciphers"]+ (NoArg (\o -> o{optUseWeakCiphers = True}))+ "accept deprecated ciphers and relax checks (for tlsfuzzer)"+ , Option+ []+ ["server-name"]+ (ReqArg (\n o -> o{optServerName = Just n}) "<name>")+ "refuse other names in SNI with unrecognized_name" ] usage :: String@@ -135,7 +152,16 @@ (host, port) <- case ips of [h, p] -> return (h, p) _ -> showUsageAndExit "cannot recognize <addr> and <port>\n"- when (null optGroups) $ do+ let groups = fromMaybe defaultGroups optGroups+ defaultGroups+ | optUseWeakCiphers = supportedGroups defaultSupported+ -- excluding FFDHE8192 for retry+ | otherwise = FFDHE8192 `delete` supportedGroups defaultSupported+ -- A TLS 1.3 server picks a group from supportedGroupsTLS13, so the+ -- groups given with -g go there too. Without -g, the default+ -- preference stands.+ groupsTLS13 = maybe (supportedGroupsTLS13 defaultSupported) (: []) optGroups+ when (null groups) $ do putStrLn "Error: unsupported groups" exitFailure smgr <- newSessionManager@@ -169,7 +195,9 @@ let sparams = getServerParams creds- optGroups+ optUseWeakCiphers+ groups+ groupsTLS13 smgr keyLog optClientAuth@@ -177,6 +205,7 @@ ech printError traceKey+ optServerName ctx <- contextNew sock sparams when optDebugLog $ contextHookSetLogging@@ -196,7 +225,9 @@ getServerParams :: Credentials+ -> Bool -> [Group]+ -> [[Group]] -> SessionManager -> (String -> IO ()) -> Bool@@ -204,8 +235,9 @@ -> ([(Word8, ByteString)], ECHConfigList) -> (String -> IO ()) -> (String -> IO ())+ -> Maybe HostName -> ServerParams-getServerParams creds groups sm keyLog clientAuth mstore (ekey, ecnf) printError traceKey =+getServerParams creds weak groups groupsTLS13 sm keyLog clientAuth mstore (ekey, ecnf) printError traceKey mname = defaultParamsServer { serverSupported = supported , serverShared = shared@@ -214,6 +246,7 @@ , serverEarlyDataSize = 2048 , serverWantClientCert = clientAuth , serverECHKey = ekey+ , serverDHEParams = if weak then Just ffdhe2048 else Nothing } where shared =@@ -231,15 +264,27 @@ } supported = defaultSupported- { supportedGroups = groups+ { supportedCiphers = ciphers+ , supportedGroups = groups+ , supportedGroupsTLS13 = groupsTLS13+ , supportedExtendedMainSecret =+ if weak then AllowEMS else supportedExtendedMainSecret defaultSupported+ , supportedClientInitiatedRenegotiation =+ weak || supportedClientInitiatedRenegotiation defaultSupported }+ ciphers+ | weak = ciphersuite_default ++ ciphersForFuzzer+ | otherwise = ciphersuite_default hooks = defaultServerHooks- { onALPNClientSuggest = Just chooseALPN+ { onALPNClientSuggest = Just $ chooseALPN weak , onClientCertificate = case mstore of Nothing -> onClientCertificate defaultServerHooks- Just _ ->- validateClientCertificate (sharedCAStore shared) (sharedValidationCache shared)+ Just _+ | weak -> acceptEmptyCertificate+ | otherwise ->+ validateClientCertificate (sharedCAStore shared) (sharedValidationCache shared)+ , onServerNameIndication = checkServerName mname } debug = defaultDebugParams@@ -247,12 +292,122 @@ , debugError = printError , debugTraceKey = traceKey }+ acceptEmptyCertificate cc+ | isNullCertificateChain cc = return CertificateUsageAccept+ | otherwise =+ validateClientCertificate+ (sharedCAStore shared)+ (sharedValidationCache shared)+ cc -chooseALPN :: [ByteString] -> IO ByteString-chooseALPN protos- | "http/1.1" `elem` protos = return "http/1.1"- | otherwise = return ""+----------------------------------------------------------------+-- Deprecated ciphers, accepted only with --use-weak-ciphers.+-- tlsfuzzer uses them in its TLS 1.2 tests. +ciphersForFuzzer :: [Cipher]+ciphersForFuzzer =+ [ cipher_ECDHE_RSA_WITH_AES_128_CBC_SHA+ , cipher_DHE_RSA_WITH_AES_128_CBC_SHA+ , cipher_RSA_WITH_AES_256_CBC_SHA+ , cipher_RSA_WITH_AES_128_CBC_SHA+ , cipher_RSA_WITH_AES_128_CBC_SHA256+ , cipher_DHE_RSA_WITH_AES_128_GCM_SHA256+ , cipher_RSA_WITH_AES_128_GCM_SHA256+ , cipher_RSA_WITH_AES_256_GCM_SHA384+ , cipher_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256+ , cipher13_AES_128_CCM_8_SHA256+ ]+ ++ ciphersuite_pfs_sha2_cbc++-- CBC with HMAC-SHA1, derived from the SHA-2 ones in CipherCBC.+cipher_RSA_WITH_AES_128_CBC_SHA :: Cipher+cipher_RSA_WITH_AES_128_CBC_SHA =+ cipher_DHE_RSA_AES128_SHA256+ { cipherID = 0x002F+ , cipherName = "TLS_RSA_WITH_AES_128_CBC_SHA"+ , cipherHash = SHA1+ , cipherPRFHash = Nothing+ , cipherKeyExchange = CipherKeyExchange_RSA+ , cipherMinVer = Just SSL3+ }++cipher_RSA_WITH_AES_256_CBC_SHA :: Cipher+cipher_RSA_WITH_AES_256_CBC_SHA =+ cipher_DHE_RSA_AES256_SHA256+ { cipherID = 0x0035+ , cipherName = "TLS_RSA_WITH_AES_256_CBC_SHA"+ , cipherHash = SHA1+ , cipherPRFHash = Nothing+ , cipherKeyExchange = CipherKeyExchange_RSA+ , cipherMinVer = Just SSL3+ }++-- tlsfuzzer's test-atypical-padding.py and test-lengths.py use this one+-- for an HMAC-SHA256 record.+cipher_RSA_WITH_AES_128_CBC_SHA256 :: Cipher+cipher_RSA_WITH_AES_128_CBC_SHA256 =+ cipher_DHE_RSA_AES128_SHA256+ { cipherID = 0x003C+ , cipherName = "TLS_RSA_WITH_AES_128_CBC_SHA256"+ , cipherKeyExchange = CipherKeyExchange_RSA+ }++cipher_DHE_RSA_WITH_AES_128_CBC_SHA :: Cipher+cipher_DHE_RSA_WITH_AES_128_CBC_SHA =+ cipher_RSA_WITH_AES_128_CBC_SHA+ { cipherID = 0x0033+ , cipherName = "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"+ , cipherKeyExchange = CipherKeyExchange_DHE_RSA+ , cipherMinVer = Nothing+ }++cipher_ECDHE_RSA_WITH_AES_128_CBC_SHA :: Cipher+cipher_ECDHE_RSA_WITH_AES_128_CBC_SHA =+ cipher_RSA_WITH_AES_128_CBC_SHA+ { cipherID = 0xC013+ , cipherName = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"+ , cipherKeyExchange = CipherKeyExchange_ECDHE_RSA+ , cipherMinVer = Just TLS10+ }++-- AES-GCM with RSA key exchange, derived from the DHE ones.+cipher_RSA_WITH_AES_128_GCM_SHA256 :: Cipher+cipher_RSA_WITH_AES_128_GCM_SHA256 =+ cipher_DHE_RSA_WITH_AES_128_GCM_SHA256+ { cipherID = 0x009C+ , cipherName = "TLS_RSA_WITH_AES_128_GCM_SHA256"+ , cipherKeyExchange = CipherKeyExchange_RSA+ }++cipher_RSA_WITH_AES_256_GCM_SHA384 :: Cipher+cipher_RSA_WITH_AES_256_GCM_SHA384 =+ cipher_DHE_RSA_WITH_AES_256_GCM_SHA384+ { cipherID = 0x009D+ , cipherName = "TLS_RSA_WITH_AES_256_GCM_SHA384"+ , cipherKeyExchange = CipherKeyExchange_RSA+ }++-- Only HTTP/1.1 is spoken. With --use-weak-ciphers, the names+-- tlsfuzzer's test-alpn-negotiation.py switches to on renegotiation and+-- resumption are accepted too, in the client's order.+chooseALPN :: Bool -> [ByteString] -> IO ByteString+chooseALPN weak protos = return $ fromMaybe "" $ find (`elem` known) protos+ where+ known+ | weak = ["http/1.1", "h2", "http/2"]+ | otherwise = ["http/1.1"]++-- RFC 6066 Section 3: a server that does not recognize the name may+-- abort with a fatal unrecognized_name, a warning one being NOT+-- RECOMMENDED.+checkServerName :: Maybe HostName -> Maybe HostName -> IO Credentials+checkServerName (Just name) (Just sni)+ | sni /= name =+ E.throwIO $+ Uncontextualized $+ Error_Protocol ("unrecognized name: " ++ sni) UnrecognizedName+checkServerName _ _ = return mempty+ newSessionManager :: IO SessionManager newSessionManager = do ref <- newIORef M.empty@@ -262,9 +417,11 @@ M.lookup key <$> readIORef ref , sessionResumeOnlyOnce = \key -> do M.lookup key <$> readIORef ref- , sessionEstablish = \key val -> do- atomicModifyIORef' ref $ \m -> (M.insert key val m, Nothing)+ , -- The session ID doubles as the ticket, so the table+ -- serves resumption by either.+ sessionEstablish = \key val -> do+ atomicModifyIORef' ref $ \m -> (M.insert key val m, Just key) , sessionInvalidate = \key -> do atomicModifyIORef' ref $ \m -> (M.delete key m, ())- , sessionUseTicket = False+ , sessionUseTicket = True }