File: TLS12.hs

package info (click to toggle)
haskell-tls 2.1.8-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,056 kB
  • sloc: haskell: 15,695; makefile: 3
file content (286 lines) | stat: -rw-r--r-- 11,625 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Network.TLS.Handshake.Client.TLS12 (
    recvServerFirstFlight12,
    sendClientSecondFlight12,
    recvServerSecondFlight12,
) where

import Control.Monad.State.Strict
import qualified Data.ByteString as B

import Network.TLS.Cipher
import Network.TLS.Context.Internal
import Network.TLS.Crypto
import Network.TLS.Handshake.Client.Common
import Network.TLS.Handshake.Common
import Network.TLS.Handshake.Key
import Network.TLS.Handshake.Signature
import Network.TLS.Handshake.State
import Network.TLS.IO
import Network.TLS.Imports
import Network.TLS.Packet hiding (getExtensions, getSession)
import Network.TLS.Parameters
import Network.TLS.Session
import Network.TLS.State
import Network.TLS.Struct
import Network.TLS.Types
import Network.TLS.Util (catchException)
import Network.TLS.Wire
import Network.TLS.X509 hiding (Certificate)

----------------------------------------------------------------

recvServerFirstFlight12 :: ClientParams -> Context -> [Handshake] -> IO ()
recvServerFirstFlight12 cparams ctx hs = do
    resuming <- usingState_ ctx getTLS12SessionResuming
    if resuming
        then recvNSTandCCSandFinished ctx
        else do
            let st = RecvStateHandshake (expectCertificate cparams ctx)
            runRecvStateHS ctx st hs

expectCertificate :: ClientParams -> Context -> Handshake -> IO (RecvState IO)
expectCertificate cparams ctx (Certificate (TLSCertificateChain certs)) = do
    usingState_ ctx $ setServerCertificateChain certs
    doCertificate cparams ctx certs
    processCertificate ctx ClientRole certs
    return $ RecvStateHandshake (expectServerKeyExchange ctx)
expectCertificate _ ctx p = expectServerKeyExchange ctx p

expectServerKeyExchange :: Context -> Handshake -> IO (RecvState IO)
expectServerKeyExchange ctx (ServerKeyXchg origSkx) = do
    doServerKeyExchange ctx origSkx
    return $ RecvStateHandshake (expectCertificateRequest ctx)
expectServerKeyExchange ctx p = expectCertificateRequest ctx p

expectCertificateRequest :: Context -> Handshake -> IO (RecvState IO)
expectCertificateRequest ctx (CertRequest cTypesSent sigAlgs dNames) = do
    let cTypes = filter (<= lastSupportedCertificateType) cTypesSent
    usingHState ctx $ setCertReqCBdata $ Just (cTypes, Just sigAlgs, dNames)
    return $ RecvStateHandshake (expectServerHelloDone ctx)
expectCertificateRequest ctx p = do
    usingHState ctx $ setCertReqCBdata Nothing
    expectServerHelloDone ctx p

expectServerHelloDone :: Context -> Handshake -> IO (RecvState m)
expectServerHelloDone _ ServerHelloDone = return RecvStateDone
expectServerHelloDone _ p = unexpected (show p) (Just "server hello data")

----------------------------------------------------------------

sendClientSecondFlight12 :: ClientParams -> Context -> IO ()
sendClientSecondFlight12 cparams ctx = do
    sessionResuming <- usingState_ ctx getTLS12SessionResuming
    if sessionResuming
        then sendCCSandFinished ctx ClientRole
        else do
            sendClientCCC cparams ctx
            sendCCSandFinished ctx ClientRole

recvServerSecondFlight12 :: ClientParams -> Context -> IO ()
recvServerSecondFlight12 cparams ctx = do
    sessionResuming <- usingState_ ctx getTLS12SessionResuming
    unless sessionResuming $ recvNSTandCCSandFinished ctx
    mticket <- usingState_ ctx getTLS12SessionTicket
    session <- usingState_ ctx getSession
    let midentity = ticketOrSessionID12 mticket session
    case midentity of
        Nothing -> return ()
        Just identity -> do
            sessionData <- getSessionData ctx
            void $
                sessionEstablish
                    (sharedSessionManager $ ctxShared ctx)
                    identity
                    (fromJust sessionData)
    handshakeDone12 ctx
    liftIO $ do
        minfo <- contextGetInformation ctx
        case minfo of
            Nothing -> return ()
            Just info -> onServerFinished (clientHooks cparams) info

recvNSTandCCSandFinished :: Context -> IO ()
recvNSTandCCSandFinished ctx = do
    st <- isJust <$> usingState_ ctx getTLS12SessionTicket
    if st
        then runRecvState ctx $ RecvStateHandshake expectNewSessionTicket
        else do runRecvState ctx $ RecvStatePacket expectChangeCipher
  where
    expectNewSessionTicket (NewSessionTicket _ ticket) = do
        usingState_ ctx $ setTLS12SessionTicket ticket
        return $ RecvStatePacket expectChangeCipher
    expectNewSessionTicket p = unexpected (show p) (Just "Handshake Finished")

    expectChangeCipher ChangeCipherSpec = do
        enableMyRecordLimit ctx
        return $ RecvStateHandshake $ expectFinished ctx
    expectChangeCipher p = unexpected (show p) (Just "change cipher")

----------------------------------------------------------------

-- | TLS 1.2 and below.  Send the client handshake messages that
-- follow the @ServerHello@, etc. except for @CCS@ and @Finished@.
--
-- XXX: Is any buffering done here to combined these messages into
-- a single TCP packet?  Otherwise we're prone to Nagle delays, or
-- in any case needlessly generate multiple small packets, where
-- a single larger packet will do.  The TLS 1.3 code path seems
-- to separating record generation and transmission and sending
-- multiple records in a single packet.
--
--       -> [certificate]
--       -> client key exchange
--       -> [cert verify]
sendClientCCC :: ClientParams -> Context -> IO ()
sendClientCCC cparams ctx = do
    sendCertificate cparams ctx
    sendClientKeyXchg cparams ctx
    sendCertificateVerify ctx

----------------------------------------------------------------

sendCertificate :: ClientParams -> Context -> IO ()
sendCertificate cparams ctx = do
    usingHState ctx $ setClientCertSent False
    clientChain cparams ctx >>= \case
        Nothing -> return ()
        Just cc@(CertificateChain certs) -> do
            unless (null certs) $
                usingHState ctx $
                    setClientCertSent True
            sendPacket12 ctx $ Handshake [Certificate (TLSCertificateChain cc)]

----------------------------------------------------------------

sendClientKeyXchg :: ClientParams -> Context -> IO ()
sendClientKeyXchg cparams ctx = do
    cipher <- usingHState ctx getPendingCipher
    (ckx, setMainSec) <- case cipherKeyExchange cipher of
        CipherKeyExchange_RSA -> getCKX_RSA ctx
        CipherKeyExchange_DHE_RSA -> getCKX_DHE cparams ctx
        CipherKeyExchange_DHE_DSA -> getCKX_DHE cparams ctx
        CipherKeyExchange_ECDHE_RSA -> getCKX_ECDHE ctx
        CipherKeyExchange_ECDHE_ECDSA -> getCKX_ECDHE ctx
        _ ->
            throwCore $
                Error_Protocol "client key exchange unsupported type" HandshakeFailure
    sendPacket12 ctx $ Handshake [ClientKeyXchg ckx]
    mainSecret <- usingHState ctx setMainSec
    logKey ctx (MainSecret mainSecret)

--------------------------------

getCKX_RSA
    :: Context -> IO (ClientKeyXchgAlgorithmData, HandshakeM ByteString)
getCKX_RSA ctx = do
    clientVersion <- usingHState ctx $ gets hstClientVersion
    (xver, prerand) <- usingState_ ctx $ (,) <$> getVersion <*> genRandom 46

    let preMain = encodePreMainSecret clientVersion prerand
        setMainSec = setMainSecretFromPre xver ClientRole preMain
    encryptedPreMain <- do
        -- SSL3 implementation generally forget this length field since it's redundant,
        -- however TLS10 make it clear that the length field need to be present.
        e <- encryptRSA ctx preMain
        let extra = encodeWord16 $ fromIntegral $ B.length e
        return $ extra `B.append` e
    return (CKX_RSA encryptedPreMain, setMainSec)

--------------------------------

getCKX_DHE
    :: ClientParams
    -> Context
    -> IO (ClientKeyXchgAlgorithmData, HandshakeM ByteString)
getCKX_DHE cparams ctx = do
    xver <- usingState_ ctx getVersion
    serverParams <- usingHState ctx getServerDHParams

    let params = serverDHParamsToParams serverParams
        ffGroup = findFiniteFieldGroup params
        srvpub = serverDHParamsToPublic serverParams

    unless (maybe False (isSupportedGroup ctx) ffGroup) $ do
        groupUsage <-
            onCustomFFDHEGroup (clientHooks cparams) params srvpub
                `catchException` throwMiscErrorOnException "custom group callback failed"
        case groupUsage of
            GroupUsageInsecure ->
                throwCore $
                    Error_Protocol "FFDHE group is not secure enough" InsufficientSecurity
            GroupUsageUnsupported reason ->
                throwCore $
                    Error_Protocol ("unsupported FFDHE group: " ++ reason) HandshakeFailure
            GroupUsageInvalidPublic -> throwCore $ Error_Protocol "invalid server public key" IllegalParameter
            GroupUsageValid -> return ()

    -- When grp is known but not in the supported list we use it
    -- anyway.  This provides additional validation and a more
    -- efficient implementation.
    (clientDHPub, preMain) <-
        case ffGroup of
            Nothing -> do
                (clientDHPriv, clientDHPub) <- generateDHE ctx params
                let preMain = dhGetShared params clientDHPriv srvpub
                return (clientDHPub, preMain)
            Just grp -> do
                usingHState ctx $ setSupportedGroup grp
                dhePair <- generateFFDHEShared ctx grp srvpub
                case dhePair of
                    Nothing ->
                        throwCore $
                            Error_Protocol ("invalid server " ++ show grp ++ " public key") IllegalParameter
                    Just pair -> return pair

    let setMainSec = setMainSecretFromPre xver ClientRole preMain
    return (CKX_DH clientDHPub, setMainSec)

--------------------------------

getCKX_ECDHE
    :: Context -> IO (ClientKeyXchgAlgorithmData, HandshakeM ByteString)
getCKX_ECDHE ctx = do
    ServerECDHParams grp srvpub <- usingHState ctx getServerECDHParams
    checkSupportedGroup ctx grp
    usingHState ctx $ setSupportedGroup grp
    ecdhePair <- generateECDHEShared ctx srvpub
    case ecdhePair of
        Nothing ->
            throwCore $
                Error_Protocol ("invalid server " ++ show grp ++ " public key") IllegalParameter
        Just (clipub, preMain) -> do
            xver <- usingState_ ctx getVersion
            let setMainSec = setMainSecretFromPre xver ClientRole preMain
            return (CKX_ECDH $ encodeGroupPublic clipub, setMainSec)

----------------------------------------------------------------

-- In order to send a proper certificate verify message,
-- we have to do the following:
--
-- 1. Determine which signing algorithm(s) the server supports
--    (we currently only support RSA).
-- 2. Get the current handshake hash from the handshake state.
-- 3. Sign the handshake hash
-- 4. Send it to the server.
--
sendCertificateVerify :: Context -> IO ()
sendCertificateVerify ctx = do
    ver <- usingState_ ctx getVersion

    -- Only send a certificate verify message when we
    -- have sent a non-empty list of certificates.
    --
    certSent <- usingHState ctx getClientCertSent
    when certSent $ do
        pubKey <- getLocalPublicKey ctx
        mhashSig <-
            let cHashSigs = supportedHashSignatures $ ctxSupported ctx
             in getLocalHashSigAlg ctx signatureCompatible cHashSigs pubKey
        -- Fetch all handshake messages up to now.
        msgs <- usingHState ctx $ B.concat <$> getHandshakeMessages
        sigDig <- createCertificateVerify ctx ver pubKey mhashSig msgs
        sendPacket12 ctx $ Handshake [CertVerify sigDig]