tls-2.4.3: test/ECHSpec.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module ECHSpec (spec) where
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Lazy as L
import Data.Maybe
import Network.TLS
import Network.TLS.ECH.Config
import Network.TLS.Extra.Cipher
import Network.TLS.Internal
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
import Arbitrary
import Run
import Session
spec :: Spec
spec = do
describe "ECH" $ do
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 EC groups" handshake13_ec
prop "can handshake with TLS 1.3 FFDHE groups" handshake13_ffdhe
describe "ECH greasing" $ do
prop "sends greasing ECH" handshake13_greasing
prop "sends greasing ECH HRR" handshake13_greasing_hrr
--------------------------------------------------------------
newtype CSP13 = CSP13 (ClientParams, ServerParams) deriving (Show)
instance Arbitrary CSP13 where
arbitrary = CSP13 <$> arbitraryPairParams13
--------------------------------------------------------------
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 =
setParams
( cli{clientSupported = cliSupported}
, srv{serverSupported = svrSupported}
)
runTLSSimple13ECH 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 =
setParams
( cli{clientSupported = cliSupported}
, srv{serverSupported = svrSupported}
)
runTLSSimple13ECH 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 =
setParams
( cli{clientSupported = cliSupported}
, srv{serverSupported = svrSupported}
)
sessionRefs <- twoSessionRefs
let sessionManagers = twoSessionManagers sessionRefs
let params = setPairParamsSessionManagers sessionManagers params0
runTLSSimple13ECH params HelloRetryRequest
-- and resume
sessionParams <- readClientSessionRef sessionRefs
expectJust "session param should be Just" sessionParams
let params2 = setPairParamsSessionResuming (fromJust sessionParams) params
runTLSSimple13ECH 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 =
setParams
( cli{clientSupported = cliSupported}
, srv{serverSupported = svrSupported}
)
sessionRefs <- twoSessionRefs
let sessionManagers0 = twoSessionManagers sessionRefs
sessionManagers = (fst sessionManagers0, oneSessionTicket)
let params = setPairParamsSessionManagers sessionManagers params0
runTLSSimple13ECH params HelloRetryRequest
-- and resume
sessionParams <- readClientSessionRef sessionRefs
expectJust "session param should be Just" sessionParams
let params2 = setPairParamsSessionResuming (fromJust sessionParams) params
runTLSSimple13ECH 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 =
setParams
( cli{clientSupported = cliSupported}
, srv{serverSupported = svrSupported}
)
sessionRefs <- twoSessionRefs
let sessionManagers = twoSessionManagers sessionRefs
let params = setPairParamsSessionManagers sessionManagers params0
runTLSSimple13ECH 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]]
}
runTLSSimple13ECH (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 =
setParams
( cli
{ clientSupported = cliSupported
, clientHooks = cliHooks
}
, srv
{ serverSupported = svrSupported
, serverHooks = svrHooks
, serverEarlyDataSize = 2048
}
)
sessionRefs <- twoSessionRefs
let sessionManagers = twoSessionManagers sessionRefs
let params = setPairParamsSessionManagers sessionManagers params0
runTLSSimple13ECH 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)
runTLS0RTTech 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 =
setParams
( 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
runTLSSimple13ECH 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 runTLS0RTTech 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_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 =
setParams
( cli{clientSupported = cliSupported}
, srv{serverSupported = svrSupported}
)
runTLSSimple13ECH 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 =
setParams
( cli{clientSupported = cliSupported}
, srv{serverSupported = svrSupported}
)
runTLSSimple13ECH params FullHandshake
handshake13_greasing :: CSP13 -> IO ()
handshake13_greasing (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
, clientUseECH = True
, clientShared = (clientShared cli){sharedECHConfigList = echConfList}
}
, srv{serverSupported = svrSupported}
)
(clientMessages, _) <- runTLSCaptureFail params
let isGreasing (ExtensionRaw eid _) = eid == EID_EncryptedClientHello
eeMessagesHaveExt =
[ any isGreasing chExtensions
| ClientHello CH{..} <- clientMessages
]
eeMessagesHaveExt `shouldBe` [True]
handshake13_greasing_hrr :: CSP13 -> IO ()
handshake13_greasing_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
, clientUseECH = True
, clientShared = (clientShared cli){sharedECHConfigList = echConfList}
}
, srv{serverSupported = svrSupported}
)
(clientMessages, _) <- runTLSCaptureFail params
let isGreasing (ExtensionRaw eid _) = eid == EID_EncryptedClientHello
eeMessagesHaveExt =
[ any isGreasing chExtensions
| ClientHello CH{..} <- clientMessages
]
eeMessagesHaveExt `shouldBe` [True, True]
expectJust :: String -> Maybe a -> Expectation
expectJust tag mx = case mx of
Nothing -> expectationFailure tag
Just _ -> return ()
setParams :: (ClientParams, ServerParams) -> (ClientParams, ServerParams)
setParams (cli, srv) = (cli', srv')
where
cli' =
cli
{ clientUseECH = True
, clientShared = (clientShared cli){sharedECHConfigList = echConfList}
}
srv' =
srv
{ serverECHKey = echKey
, serverShared = (serverShared srv){sharedECHConfigList = echConfList}
}
echKey :: [(ConfigId, ByteString)]
echKey = [(0, B64.decodeLenient "GAl/YqzDDnssODe5t+2xlQsbSv26kNlfJ0D+nZbK62I=")]
echConfList :: ECHConfigList
echConfList =
fromJust $
decodeECHConfigList $
B64.decodeLenient
"AEP+DQA/AAAgACDGNVZWrmqQfzAuYGJNa8+OEc6zaUfzd0ltyJQ2y1U2AwAEAAEAAQAQcHVibGljLWxvY2FsaG9zdAAA"