File: Common.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 (363 lines) | stat: -rw-r--r-- 15,473 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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}

module Network.TLS.Handshake.Client.Common (
    throwMiscErrorOnException,
    doServerKeyExchange,
    doCertificate,
    getLocalHashSigAlg,
    clientChain,
    sigAlgsToCertTypes,
    setALPN,
    contextSync,
    clientSessions,
) where

import Control.Exception (SomeException)
import Control.Monad.State.Strict
import Data.X509 (ExtKeyUsageFlag (..))

import Network.TLS.Cipher
import Network.TLS.Context.Internal
import Network.TLS.Credentials
import Network.TLS.Crypto
import Network.TLS.Extension
import Network.TLS.Handshake.Certificate
import Network.TLS.Handshake.Common
import Network.TLS.Handshake.Control
import Network.TLS.Handshake.Key
import Network.TLS.Handshake.Signature
import Network.TLS.Handshake.State
import Network.TLS.Imports
import Network.TLS.Packet hiding (getExtensions)
import Network.TLS.Parameters
import Network.TLS.State
import Network.TLS.Struct
import Network.TLS.Util (catchException)
import Network.TLS.X509

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

throwMiscErrorOnException :: String -> SomeException -> IO a
throwMiscErrorOnException msg e =
    throwCore $ Error_Misc $ msg ++ ": " ++ show e

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

doServerKeyExchange :: Context -> ServerKeyXchgAlgorithmData -> IO ()
doServerKeyExchange ctx origSkx = do
    cipher <- usingHState ctx getPendingCipher
    processWithCipher cipher origSkx
  where
    processWithCipher cipher skx =
        case (cipherKeyExchange cipher, skx) of
            (CipherKeyExchange_DHE_RSA, SKX_DHE_RSA dhparams signature) ->
                doDHESignature dhparams signature KX_RSA
            (CipherKeyExchange_DHE_DSA, SKX_DHE_DSA dhparams signature) ->
                doDHESignature dhparams signature KX_DSA
            (CipherKeyExchange_ECDHE_RSA, SKX_ECDHE_RSA ecdhparams signature) ->
                doECDHESignature ecdhparams signature KX_RSA
            (CipherKeyExchange_ECDHE_ECDSA, SKX_ECDHE_ECDSA ecdhparams signature) ->
                doECDHESignature ecdhparams signature KX_ECDSA
            (cke, SKX_Unparsed bytes) -> do
                ver <- usingState_ ctx getVersion
                case decodeReallyServerKeyXchgAlgorithmData ver cke bytes of
                    Left _ ->
                        throwCore $
                            Error_Protocol
                                ("unknown server key exchange received, expecting: " ++ show cke)
                                HandshakeFailure
                    Right realSkx -> processWithCipher cipher realSkx
            -- we need to resolve the result. and recall processWithCipher ..
            (c, _) ->
                throwCore $
                    Error_Protocol
                        ("unknown server key exchange received, expecting: " ++ show c)
                        HandshakeFailure
    doDHESignature dhparams signature kxsAlg = do
        -- FF group selected by the server is verified when generating CKX
        publicKey <- getSignaturePublicKey kxsAlg
        verified <- digitallySignDHParamsVerify ctx dhparams publicKey signature
        unless verified $
            decryptError
                ("bad " ++ pubkeyType publicKey ++ " signature for dhparams " ++ show dhparams)
        usingHState ctx $ setServerDHParams dhparams

    doECDHESignature ecdhparams signature kxsAlg = do
        -- EC group selected by the server is verified when generating CKX
        publicKey <- getSignaturePublicKey kxsAlg
        verified <- digitallySignECDHParamsVerify ctx ecdhparams publicKey signature
        unless verified $
            decryptError ("bad " ++ pubkeyType publicKey ++ " signature for ecdhparams")
        usingHState ctx $ setServerECDHParams ecdhparams

    getSignaturePublicKey kxsAlg = do
        publicKey <- usingHState ctx getRemotePublicKey
        unless (isKeyExchangeSignatureKey kxsAlg publicKey) $
            throwCore $
                Error_Protocol
                    ("server public key algorithm is incompatible with " ++ show kxsAlg)
                    HandshakeFailure
        ver <- usingState_ ctx getVersion
        unless (publicKey `versionCompatible` ver) $
            throwCore $
                Error_Protocol
                    (show ver ++ " has no support for " ++ pubkeyType publicKey)
                    IllegalParameter
        let groups = supportedGroups (ctxSupported ctx)
        unless (satisfiesEcPredicate (`elem` groups) publicKey) $
            throwCore $
                Error_Protocol
                    "server public key has unsupported elliptic curve"
                    IllegalParameter
        return publicKey

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

doCertificate :: ClientParams -> Context -> CertificateChain -> IO ()
doCertificate cparams ctx certs = do
    when (isNullCertificateChain certs) $
        throwCore $
            Error_Protocol "server certificate missing" DecodeError
    -- run certificate recv hook
    ctxWithHooks ctx (`hookRecvCertificates` certs)
    -- then run certificate validation
    usage <- catchException (wrapCertificateChecks <$> checkCert) rejectOnException
    case usage of
        CertificateUsageAccept -> checkLeafCertificateKeyUsage
        CertificateUsageReject reason -> certificateRejected reason
  where
    shared = clientShared cparams
    checkCert =
        onServerCertificate
            (clientHooks cparams)
            (sharedCAStore shared)
            (sharedValidationCache shared)
            (clientServerIdentification cparams)
            certs
    -- also verify that the certificate optional key usage is compatible
    -- with the intended key-exchange.  This check is not delegated to
    -- x509-validation 'checkLeafKeyUsage' because it depends on negotiated
    -- cipher, which is not available from onServerCertificate parameters.
    -- Additionally, with only one shared ValidationCache, x509-validation
    -- would cache validation result based on a key usage and reuse it with
    -- another key usage.
    checkLeafCertificateKeyUsage = do
        cipher <- usingHState ctx getPendingCipher
        case requiredCertKeyUsage cipher of
            [] -> return ()
            flags -> verifyLeafKeyUsage flags certs

-- Unless result is empty, server certificate must be allowed for at least one
-- of the returned values.  Constraints for RSA-based key exchange are relaxed
-- to avoid rejecting certificates having incomplete extension.
requiredCertKeyUsage :: Cipher -> [ExtKeyUsageFlag]
requiredCertKeyUsage cipher =
    case cipherKeyExchange cipher of
        CipherKeyExchange_RSA -> rsaCompatibility
        CipherKeyExchange_DH_Anon -> [] -- unrestricted
        CipherKeyExchange_DHE_RSA -> rsaCompatibility
        CipherKeyExchange_ECDHE_RSA -> rsaCompatibility
        CipherKeyExchange_DHE_DSA -> [KeyUsage_digitalSignature]
        CipherKeyExchange_DH_DSA -> [KeyUsage_keyAgreement]
        CipherKeyExchange_DH_RSA -> rsaCompatibility
        CipherKeyExchange_ECDH_ECDSA -> [KeyUsage_keyAgreement]
        CipherKeyExchange_ECDH_RSA -> rsaCompatibility
        CipherKeyExchange_ECDHE_ECDSA -> [KeyUsage_digitalSignature]
        CipherKeyExchange_TLS13 -> [KeyUsage_digitalSignature]
  where
    rsaCompatibility =
        [ KeyUsage_digitalSignature
        , KeyUsage_keyEncipherment
        , KeyUsage_keyAgreement
        ]

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

-- | Return the supported 'CertificateType' values that are
-- compatible with at least one supported signature algorithm.
supportedCtypes
    :: [HashAndSignatureAlgorithm]
    -> [CertificateType]
supportedCtypes hashAlgs =
    nub $ foldr ctfilter [] hashAlgs
  where
    ctfilter x acc = case hashSigToCertType x of
        Just cType
            | cType <= lastSupportedCertificateType ->
                cType : acc
        _ -> acc

clientSupportedCtypes
    :: Context
    -> [CertificateType]
clientSupportedCtypes ctx =
    supportedCtypes $ supportedHashSignatures $ ctxSupported ctx

sigAlgsToCertTypes
    :: Context
    -> [HashAndSignatureAlgorithm]
    -> [CertificateType]
sigAlgsToCertTypes ctx hashSigs =
    filter (`elem` supportedCtypes hashSigs) $ clientSupportedCtypes ctx

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

-- | When the server requests a client certificate, we try to
-- obtain a suitable certificate chain and private key via the
-- callback in the client parameters.  It is OK for the callback
-- to return an empty chain, in many cases the client certificate
-- is optional.  If the client wishes to abort the handshake for
-- lack of a suitable certificate, it can throw an exception in
-- the callback.
--
-- The return value is 'Nothing' when no @CertificateRequest@ was
-- received and no @Certificate@ message needs to be sent. An empty
-- chain means that an empty @Certificate@ message needs to be sent
-- to the server, naturally without a @CertificateVerify@.  A non-empty
-- 'CertificateChain' is the chain to send to the server along with
-- a corresponding 'CertificateVerify'.
--
-- With TLS < 1.2 the server's @CertificateRequest@ does not carry
-- a signature algorithm list.  It has a list of supported public
-- key signing algorithms in the @certificate_types@ field.  The
-- hash is implicit.  It is 'SHA1' for DSA and 'SHA1_MD5' for RSA.
--
-- With TLS == 1.2 the server's @CertificateRequest@ always has a
-- @supported_signature_algorithms@ list, as a fixed component of
-- the structure.  This list is (wrongly) overloaded to also limit
-- X.509 signatures in the client's certificate chain.  The BCP
-- strategy is to find a compatible chain if possible, but else
-- ignore the constraint, and let the server verify the chain as it
-- sees fit.  The @supported_signature_algorithms@ field is only
-- obligatory with respect to signatures on TLS messages, in this
-- case the @CertificateVerify@ message.  The @certificate_types@
-- field is still included.
--
-- With TLS 1.3 the server's @CertificateRequest@ has a mandatory
-- @signature_algorithms@ extension, the @signature_algorithms_cert@
-- extension, which is optional, carries a list of algorithms the
-- server promises to support in verifying the certificate chain.
-- As with TLS 1.2, the client's makes a /best-effort/ to deliver
-- a compatible certificate chain where all the CA signatures are
-- known to be supported, but it should not abort the connection
-- just because the chain might not work out, just send the best
-- chain you have and let the server worry about the rest.  The
-- supported public key algorithms are now inferred from the
-- @signature_algorithms@ extension and @certificate_types@ is
-- gone.
--
-- With TLS 1.3, we synthesize and store a @certificate_types@
-- field at the time that the server's @CertificateRequest@
-- message is received.  This is then present across all the
-- protocol versions, and can be used to determine whether
-- a @CertificateRequest@ was received or not.
--
-- If @signature_algorithms@ is 'Nothing', then we're doing
-- TLS 1.0 or 1.1.  The @signature_algorithms_cert@ extension
-- is optional in TLS 1.3, and so the application callback
-- will not be able to distinguish between TLS 1.[01] and
-- TLS 1.3 with no certificate algorithm hints, but this
-- just simplifies the chain selection process, all CA
-- signatures are OK.
clientChain :: ClientParams -> Context -> IO (Maybe CertificateChain)
clientChain cparams ctx =
    usingHState ctx getCertReqCBdata >>= \case
        Nothing -> return Nothing
        Just cbdata -> do
            let callback = onCertificateRequest $ clientHooks cparams
            chain <-
                liftIO $
                    callback cbdata
                        `catchException` throwMiscErrorOnException "certificate request callback failed"
            case chain of
                Nothing ->
                    return $ Just $ CertificateChain []
                Just (CertificateChain [], _) ->
                    return $ Just $ CertificateChain []
                Just cred@(cc, _) ->
                    do
                        let (cTypes, _, _) = cbdata
                        storePrivInfoClient ctx cTypes cred
                        return $ Just cc

-- | Store the keypair and check that it is compatible with the current protocol
-- version and a list of 'CertificateType' values.
storePrivInfoClient
    :: Context
    -> [CertificateType]
    -> Credential
    -> IO ()
storePrivInfoClient ctx cTypes (cc, privkey) = do
    pubkey <- storePrivInfo ctx cc privkey
    unless (certificateCompatible pubkey cTypes) $
        throwCore $
            Error_Protocol
                (pubkeyType pubkey ++ " credential does not match allowed certificate types")
                InternalError
    ver <- usingState_ ctx getVersion
    unless (pubkey `versionCompatible` ver) $
        throwCore $
            Error_Protocol
                (pubkeyType pubkey ++ " credential is not supported at version " ++ show ver)
                InternalError

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

-- | Return a most preferred 'HandAndSignatureAlgorithm' that is compatible with
-- the local key and server's signature algorithms (both already saved).  Must
-- only be called for TLS versions 1.2 and up, with compatibility function
-- 'signatureCompatible' or 'signatureCompatible13' based on version.
--
-- The values in the server's @signature_algorithms@ extension are
-- in descending order of preference.  However here the algorithms
-- are selected by client preference in @cHashSigs@.
getLocalHashSigAlg
    :: Context
    -> (PubKey -> HashAndSignatureAlgorithm -> Bool)
    -> [HashAndSignatureAlgorithm]
    -> PubKey
    -> IO HashAndSignatureAlgorithm
getLocalHashSigAlg ctx isCompatible cHashSigs pubKey = do
    -- Must be present with TLS 1.2 and up.
    (Just (_, Just hashSigs, _)) <- usingHState ctx getCertReqCBdata
    let want =
            (&&)
                <$> isCompatible pubKey
                <*> flip elem hashSigs
    case find want cHashSigs of
        Just best -> return best
        Nothing -> throwCore $ Error_Protocol (keyerr pubKey) HandshakeFailure
  where
    keyerr k = "no " ++ pubkeyType k ++ " hash algorithm in common with the server"

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

setALPN :: Context -> MessageType -> [ExtensionRaw] -> IO ()
setALPN ctx msgt exts =
    lookupAndDecodeAndDo
        EID_ApplicationLayerProtocolNegotiation
        msgt
        exts
        (return ())
        setAlpn
  where
    setAlpn (ApplicationLayerProtocolNegotiation [proto]) = usingState_ ctx $ do
        mprotos <- getClientALPNSuggest
        case mprotos of
            Just protos -> when (proto `elem` protos) $ do
                setExtensionALPN True
                setNegotiatedProtocol proto
            _ -> return ()
    setAlpn _ = return ()

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

contextSync :: Context -> ClientState -> IO ()
contextSync ctx ctl = case ctxHandshakeSync ctx of
    HandshakeSync sync _ -> sync ctx ctl

clientSessions :: ClientParams -> [(SessionID, SessionData)]
clientSessions ClientParams{..} = case clientWantSessionResume of
    Nothing -> clientWantSessionResumeList
    Just ent -> clientWantSessionResumeList ++ [ent]