tls 1.2.8 → 2.4.3
raw patch · 121 files changed
Files
- Benchmarks/Benchmarks.hs +168/−45
- CHANGELOG.md +569/−0
- LICENSE +1/−1
- Network/TLS.hs +350/−101
- Network/TLS/Backend.hs +45/−32
- Network/TLS/Cap.hs +0/−19
- Network/TLS/Cipher.hs +66/−101
- Network/TLS/Compression.hs +21/−27
- Network/TLS/Context.hs +260/−166
- Network/TLS/Context/Internal.hs +450/−142
- Network/TLS/Core.hs +476/−87
- Network/TLS/Credentials.hs +150/−70
- Network/TLS/Crypto.hs +454/−119
- Network/TLS/Crypto/DH.hs +54/−31
- Network/TLS/Crypto/IES.hs +525/−0
- Network/TLS/Crypto/Types.hs +143/−0
- Network/TLS/ErrT.hs +15/−0
- Network/TLS/Error.hs +198/−0
- Network/TLS/Extension.hs +1175/−133
- Network/TLS/Extension.hs-boot +22/−0
- Network/TLS/Extra.hs +6/−11
- Network/TLS/Extra/Cipher.hs +808/−432
- Network/TLS/Extra/CipherCBC.hs +201/−0
- Network/TLS/Extra/FFDHE.hs +72/−0
- Network/TLS/Handshake.hs +27/−31
- Network/TLS/Handshake/Certificate.hs +57/−17
- Network/TLS/Handshake/Client.hs +159/−337
- Network/TLS/Handshake/Client/ClientHello.hs +610/−0
- Network/TLS/Handshake/Client/Common.hs +363/−0
- Network/TLS/Handshake/Client/ServerHello.hs +306/−0
- Network/TLS/Handshake/Client/TLS12.hs +288/−0
- Network/TLS/Handshake/Client/TLS13.hs +425/−0
- Network/TLS/Handshake/Common.hs +405/−90
- Network/TLS/Handshake/Common13.hs +693/−0
- Network/TLS/Handshake/Control.hs +43/−0
- Network/TLS/Handshake/Key.hs +157/−37
- Network/TLS/Handshake/Process.hs +0/−109
- Network/TLS/Handshake/Random.hs +73/−0
- Network/TLS/Handshake/Server.hs +71/−352
- Network/TLS/Handshake/Server/ClientHello.hs +305/−0
- Network/TLS/Handshake/Server/ClientHello12.hs +235/−0
- Network/TLS/Handshake/Server/ClientHello13.hs +212/−0
- Network/TLS/Handshake/Server/Common.hs +207/−0
- Network/TLS/Handshake/Server/ServerHello12.hs +325/−0
- Network/TLS/Handshake/Server/ServerHello13.hs +367/−0
- Network/TLS/Handshake/Server/TLS12.hs +216/−0
- Network/TLS/Handshake/Server/TLS13.hs +387/−0
- Network/TLS/Handshake/Signature.hs +292/−96
- Network/TLS/Handshake/State.hs +501/−194
- Network/TLS/Handshake/State13.hs +150/−0
- Network/TLS/Handshake/TranscriptHash.hs +131/−0
- Network/TLS/HashAndSignature.hs +181/−0
- Network/TLS/Hooks.hs +34/−33
- Network/TLS/IO.hs +225/−107
- Network/TLS/IO/Decode.hs +109/−0
- Network/TLS/IO/Encode.hs +148/−0
- Network/TLS/Imports.hs +34/−0
- Network/TLS/Internal.hs +33/−20
- Network/TLS/KeySchedule.hs +66/−0
- Network/TLS/MAC.hs +69/−63
- Network/TLS/Measurement.hs +25/−27
- Network/TLS/Packet.hs +495/−428
- Network/TLS/Packet13.hs +233/−0
- Network/TLS/Parameters.hs +899/−243
- Network/TLS/PostHandshake.hs +33/−0
- Network/TLS/QUIC.hs +265/−0
- Network/TLS/RNG.hs +22/−9
- Network/TLS/Receiving.hs +0/−76
- Network/TLS/Record.hs +32/−37
- Network/TLS/Record/Decrypt.hs +207/−0
- Network/TLS/Record/Disengage.hs +0/−114
- Network/TLS/Record/Encrypt.hs +131/−0
- Network/TLS/Record/Engage.hs +0/−84
- Network/TLS/Record/Layer.hs +64/−0
- Network/TLS/Record/Recv.hs +112/−0
- Network/TLS/Record/Send.hs +61/−0
- Network/TLS/Record/State.hs +129/−87
- Network/TLS/Record/Types.hs +50/−59
- Network/TLS/Sending.hs +0/−90
- Network/TLS/Session.hs +28/−23
- Network/TLS/State.hs +309/−152
- Network/TLS/Struct.hs +441/−418
- Network/TLS/Struct13.hs +81/−0
- Network/TLS/Types.hs +61/−43
- Network/TLS/Types/Cipher.hs +130/−0
- Network/TLS/Types/Secret.hs +62/−0
- Network/TLS/Types/Session.hs +73/−0
- Network/TLS/Types/Version.hs +40/−0
- Network/TLS/Util.hs +103/−63
- Network/TLS/Util/ASN1.hs +21/−26
- Network/TLS/Util/Serialization.hs +6/−5
- Network/TLS/Wire.hs +115/−87
- Network/TLS/X509.hs +66/−43
- Setup.hs +1/−0
- Tests/Certificate.hs +0/−86
- Tests/Ciphers.hs +0/−38
- Tests/Connection.hs +0/−166
- Tests/Marshalling.hs +0/−111
- Tests/PipeChan.hs +0/−56
- Tests/PubKey.hs +0/−71
- Tests/Tests.hs +0/−182
- test/API.hs +22/−0
- test/Arbitrary.hs +452/−0
- test/Certificate.hs +161/−0
- test/CiphersSpec.hs +76/−0
- test/ECHSpec.hs +446/−0
- test/EncodeSpec.hs +39/−0
- test/HandshakeSpec.hs +1088/−0
- test/PipeChan.hs +85/−0
- test/PubKey.hs +123/−0
- test/Run.hs +394/−0
- test/Session.hs +92/−0
- test/Spec.hs +1/−0
- test/ThreadSpec.hs +46/−0
- tls.cabal +275/−149
- util/Client.hs +61/−0
- util/Common.hs +133/−0
- util/Imports.hs +17/−0
- util/Server.hs +92/−0
- util/tls-client.hs +465/−0
- util/tls-server.hs +270/−0
Benchmarks/Benchmarks.hs view
@@ -1,64 +1,187 @@ {-# LANGUAGE BangPatterns #-}+ module Main where -import Connection import Certificate-import PubKey-import Criterion.Main import Control.Concurrent.Chan-import Network.TLS+import Data.Default (def)+import Data.IORef import Data.X509 import Data.X509.Validation-import Data.Default.Class+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 -recvDataNonNull ctx = recvData ctx >>= \l -> if B.null l then recvDataNonNull ctx else return 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)+ 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- }- }+ , clientShared =+ def+ { sharedValidationCache =+ ValidationCache+ { cacheAdd = \_ _ _ -> return ()+ , cacheQuery = \_ _ _ -> return ValidationCachePass+ }+ } }- supported = def { supportedCiphers = [cipher]- , supportedVersions = [connectVer]- }- (pubKey, privKey) = getGlobalRSAPair+ supported =+ def+ { supportedCiphers = [cipher]+ , supportedVersions = [connectVer]+ , supportedGroups = [X25519, FFDHE2048]+ }+ (pubKey, privKey) = getGlobalRSAPair -runTLSPipe params tlsServer tlsClient d name = bench name $ do- (startQueue, resultQueue) <- establishDataPipe params tlsServer tlsClient- writeChan startQueue d- readChan resultQueue+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 -bench1 params !d name = runTLSPipe params tlsServer tlsClient d name- where tlsServer ctx queue = do- handshake ctx- d <- recvDataNonNull ctx- writeChan queue d- return ()- tlsClient queue ctx = do- handshake ctx- d <- readChan queue- sendData ctx (L.fromChunks [d])- bye ctx- return ()+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 -main = defaultMain- [ bgroup "connection"- -- not sure the number actually make sense for anything. improve ..- [ bench1 (getParams SSL3 blockCipher) (B.replicate 256 0) "SSL3-256 bytes"- , bench1 (getParams TLS10 blockCipher) (B.replicate 256 0) "TLS10-256 bytes"- , bench1 (getParams TLS11 blockCipher) (B.replicate 256 0) "TLS11-256 bytes"- , bench1 (getParams TLS12 blockCipher) (B.replicate 256 0) "TLS12-256 bytes"+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,3 +1,572 @@+# Change log for "tls"++## 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)+* Expose get certificate chain function+ [#520](https://github.com/haskell-tls/hs-tls/pull/520)++## Version 2.3.0++* Using "ram" instead of "memory".++## Version 2.2.2++* A new architecture to calculate receiver's transcript hash with wire+ format.+ [#515](https://github.com/haskell-tls/hs-tls/pull/515)+* Enabling compressed certificate on the client side again.++## Version 2.2.1++* Disabling compressed certificate on the client side.+ [#514](https://github.com/haskell-tls/hs-tls/issues/514)++## Version 2.2.0++* Using crypton-asn1-* and time-hourglass.+ [#512](https://github.com/haskell-tls/hs-tls/pull/512)+* Major version up due to re-exports.++## Version 2.1.14++* Supporting P384 and P521 curves.+ [#511](https://github.com/haskell-tls/hs-tls/pull/511)+* Fixing some bugs of `tls-client`.++## Version 2.1.13++* Don't contain early_data if serverEarlyDataSize is 0.+ [#510](https://github.com/haskell-tls/hs-tls/pull/510)++## Version 2.1.12++* Restore benchmarks.+ [#509](https://github.com/haskell-tls/hs-tls/pull/509)+* Supporting random 1.2.+ [#508](https://github.com/haskell-tls/hs-tls/pull/508)+* Add --trusted-anchor cli option to tls-client.+ [#505](https://github.com/haskell-tls/hs-tls/pull/505)++## Version 2.1.11++* Removing OVERLAPS instances.++## Version 2.1.10++* Supporting the SSLKEYLOGFILE environment variable.+ [#499](https://github.com/haskell-tls/hs-tls/pull/499)++## Version 2.1.9++* Providing ECH(Encrypted Client Hello). See `sharedECHConfigList`,+ `clientUseECH` and `serverECHKey`. Note that the `ech-gen` command,+ `loadECHConfigList` and `loadECHSecretKeys` are provided by the+ `ech-config` package.++## Version 2.1.8++* Moving `Limit` to `Shared` to maintain backward compatibility+ of `TLSParams` class.+* Deprecating 2.1.7.++## Version 2.1.7++* Introducing `Limit` parameter.+* Implementing "Record Size Limit Extension for TLS" (RFC8449).+ Set `limitRecordSize` use it.+* Implementing "TLS Certificate Compression" (RFC 8879).+ This feature is automatically used if the peer supports it.+* More tests with `tlsfuzzer` especially for client authentication+ and 0-RTT.+* 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 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.+* `tls-client -d` and `tls-server -d` pretty-prints `Handshake`.++## Version 2.1.5++* Removing the dependency on the async package.+* Restore a few DHE_RSA ciphers.+ [#493](https://github.com/haskell-tls/hs-tls/pull/493)++## Version 2.1.4++* Exporting defaultValidationCache.++## Version 2.1.3++* Remove `data-default` version constraint.+ [#492](https://github.com/haskell-tls/hs-tls/pull/492)+* Exporting default variables.+ [#448](https://github.com/haskell-tls/hs-tls/pull/488)++## Version 2.1.2++* Using data-default instead of data-default-class.++## Version 2.1.1++* `bye` directly calls `timeout recvHS13`, not spawning a thread for+ `timeout recvHS13`. So, `bye` can receive an exception if thrown.++## Version 2.1.0++* Breaking change: stop exporting constructors to maintain future+ compatibilities. Field names are still exported, and values can be updated+ with them using record syntax. Use `def` and `noSessionManager` as initial+ values.+* `onServerFinished` is added to `ClientHooks`.+* `clientWantSessionResumeList` is added to `ClientParams` to support+ multiple tickets for TLS 1.3.++## Version 2.0.6++* Setting `supportedCiphers` in `defaultSupported` to `ciphersuite_default`.+ So, users don't have to override this value anymore by exporting+ `Network.TLS.Extra.Cipher`.+ [#471](https://github.com/haskell-tls/hs-tls/pull/471)+* `ciphersuite_default` is the same as `ciphersuite_strong`.+ So, the duplicated definition is removed.+* Add missing modules for util/tls-client and util/tls-server.++## Version 2.0.5++* Fixing handshake13_0rtt_fallback+* Client checks if the group of PSK is contained in Supported_Groups.+* HRR is not allowed for 0-RTT.++## Version 2.0.4++* More fix for 0-RTT when application data is available while receiving CF.+* New util/tls-client and util/tls-server.++## Version 2.0.3++* Fixing a bug where `timeout` in `bye` does not work.+* util/client -> util/tls-client+* util/server -> util/tls-server++## Version 2.0.2++* Client checks sessionMaxEarlyDataSize to decide 0-RTT+* Client checks the resumption cipher properly.++## Version 2.0.1++* Fix a leak of pending data to be sent.++## Version 2.0.0++* `tls` now only supports TLS 1.2 and TLS 1.3 with safe cipher suites.+* Security: BREAKING CHANGE: TLS 1.0 and TLS 1.1 are removed.+* Security: BREAKING CHANGE: all CBC cipher suite are removed.+* Security: BREAKING CHANGE: RC4 and 3DES are removed.+* Security: BREAKING CHANGE: DSS(digital signature standard) is removed.+* Security: BREAKING CHANGE: TLS 1.2 servers require+ EMS(extended main secret) by default.+ `supportedExtendedMasterSec` is renamed to+ `supportedExtendedMainSecret`.+* BREAKING CHANGE: the package is now complied with `Strict` and `StrictData`.+* BREAKING CHANGE: Many data structures are re-defined with+ `PatternSynonyms` for extensibility.+* BREAKING CHANGE: the structure of `SessionManager` is changed+ to support session tickets.+* API: BREAKING CHANGE: `sendData` can send early data (0-RTT).+ `clientEarlyData` is removed.+ To send early data via `sendData`, set `clientUseEarlyData` to `True`.+ [#466](https://github.com/haskell-tls/hs-tls/issues/466)+* API: `handshake` can receive an alert of client authentication failure+ for TLS 1.3.+ [#463](https://github.com/haskell-tls/hs-tls/pull/463)+* API: `bye` can receive NewSessionTicket for TLS 1.3.+* Channel binding: `getFinished` and `getPeerFinished` are deprecated.+ Use `getTLSUnique` instead.+ [#462](https://github.com/haskell-tls/hs-tls/pull/462)+* Channel binding: `getTLSExporter` and `getTLSServerEndPoint` are provided.+ [#462](https://github.com/haskell-tls/hs-tls/pull/462)+* Refactoring: the monolithic `handshake` is divided to follow+ the diagram of TLS 1.2 and 1.3 for readability.+* Refactoring: test cases are refactored for maintenability+ and readablity. `hspec` is used instead of `tasty`.+* Code format: `fourmolu` is used as an official formatter.+* Catching up RFC8446bis-09.+ [#467](https://github.com/haskell-tls/hs-tls/issues/467)++## Version 1.9.0++* BREAKING CHANGE: The type of the `Error_Protocol` constructor of `TLSError` has changed.+ The "warning" case has been split off into a new `Error_Protocol_Warning` constructor.+ [#460](https://github.com/haskell-tls/hs-tls/pull/460)++## Version 1.8.0++* BREAKING CHANGE: Remove `Exception` instance for `TLSError`.+ The library now throws `TLSException` only.+ If you need to change your code, please refer to+ [this example](https://github.com/snoyberg/http-client/commit/73d1a4eb451c089878ba95e96371d0b18287ffb8) first.+ [#457](https://github.com/haskell-tls/hs-tls/pull/457)++## Version 1.7.1++* NOP on UserCanceled event+ [#454](https://github.com/haskell-tls/hs-tls/pull/454)++## Version 1.7.0++* Major version up because "crypton" is used instead of "cryptonite"++## Version 1.6.0++- Major version up because of disabling SSL3+- Some fixes against tlsfuzzer++## Version 1.5.8++- Require mtl-2.2.1 or newer+ [#448](https://github.com/haskell-tls/hs-tls/pull/448)++## Version 1.5.7++- New APIs: getFinished and getPeerFinished+ [#445](https://github.com/vincenthz/hs-tls/pull/445)++## Version 1.5.6++- Dynamically setting enctypted extensions+ [#444](https://github.com/vincenthz/hs-tls/pull/444)++## Version 1.5.5++- QUIC support+ [#419](https://github.com/vincenthz/hs-tls/pull/419)+ [#427](https://github.com/vincenthz/hs-tls/pull/427)+ [#428](https://github.com/vincenthz/hs-tls/pull/428)+ [#430](https://github.com/vincenthz/hs-tls/pull/430)+ [#433](https://github.com/vincenthz/hs-tls/pull/433)+ [#441](https://github.com/vincenthz/hs-tls/pull/441)+- Server ECDSA for P-256+ [#436](https://github.com/vincenthz/hs-tls/pull/436)+- Sort ciphersuites based on hardware-acceleration support+ [#439](https://github.com/vincenthz/hs-tls/pull/439)+- Sending no_application_protocol+ [#440](https://github.com/vincenthz/hs-tls/pull/440)+- Internal improvements+ [#426](https://github.com/vincenthz/hs-tls/pull/426)+ [#431](https://github.com/vincenthz/hs-tls/pull/431)++## Version 1.5.4++- Restore interoperability with early Java 6+ [#422](https://github.com/vincenthz/hs-tls/pull/422)+- Test cleanups for timeout and async usage+ [#416](https://github.com/vincenthz/hs-tls/pull/416)++## Version 1.5.3++- Additional verification regarding EC signatures+ [#412](https://github.com/vincenthz/hs-tls/pull/412)+- Fixing ALPN+ [#411](https://github.com/vincenthz/hs-tls/pull/411)+- Check SSLv3 padding length+ [#410](https://github.com/vincenthz/hs-tls/pull/410)+- Exposing getClientCertificateChain+ [#407](https://github.com/vincenthz/hs-tls/pull/407)+- Extended Master Secret+ [#406](https://github.com/vincenthz/hs-tls/pull/406)+- Brushing up the documentation+ [#404](https://github.com/vincenthz/hs-tls/pull/404)+ [#408](https://github.com/vincenthz/hs-tls/pull/408)+- Improving tests+ [#403](https://github.com/vincenthz/hs-tls/pull/403)+- Avoid calling onServerNameIndication twice with HRR+ [#402](https://github.com/vincenthz/hs-tls/pull/402)+- Enable X448 and FFDHE groups+ [#401](https://github.com/vincenthz/hs-tls/pull/401)+- Refactoring+ [#400](https://github.com/vincenthz/hs-tls/pull/400)+ [#399](https://github.com/vincenthz/hs-tls/pull/399)++## Version 1.5.2++- Enabled TLS 1.3 by default [#398](https://github.com/vincenthz/hs-tls/pull/398)+- Avoid handshake failure with small RSA keys [#394](https://github.com/vincenthz/hs-tls/pull/394)++NOTES:++- Starting with tls-1.5.0, the parameter `supportedVersions` contains values+ ordered by decreasing preference, so typically the higher versions first.+ This departs from code samples previously available. For maximum+ interoperability, users overriding the default value should verify and adapt+ their code.++## Version 1.5.1++- Post-handshake authentication [#363](https://github.com/vincenthz/hs-tls/pull/363)+- Middlebox compatibility [#386](https://github.com/vincenthz/hs-tls/pull/386)+- Verification and configuration of session-ticket lifetime [#373](https://github.com/vincenthz/hs-tls/pull/373)+- Fixing memory leak [#366](https://github.com/vincenthz/hs-tls/pull/366)+- Don't send 0-RTT data when ticket is expired [#370](https://github.com/vincenthz/hs-tls/pull/370)+- Handshake packet fragmentation [#371](https://github.com/vincenthz/hs-tls/pull/371)+- Fix SSLv2 deprecated header [#383](https://github.com/vincenthz/hs-tls/pull/383)+- Other improvements to TLS 1.3 and RFC conformance [#368](https://github.com/vincenthz/hs-tls/pull/368) [#372](https://github.com/vincenthz/hs-tls/pull/372) [#375](https://github.com/vincenthz/hs-tls/pull/375) [#376](https://github.com/vincenthz/hs-tls/pull/376) [#377](https://github.com/vincenthz/hs-tls/pull/377) [#378](https://github.com/vincenthz/hs-tls/pull/378) [#380](https://github.com/vincenthz/hs-tls/pull/380) [#382](https://github.com/vincenthz/hs-tls/pull/382) [#385](https://github.com/vincenthz/hs-tls/pull/385) [#387](https://github.com/vincenthz/hs-tls/pull/387) [#388](https://github.com/vincenthz/hs-tls/pull/388)++## Version 1.5.0++- Add and enable AES CCM ciphers [#271](https://github.com/vincenthz/hs-tls/pull/271) [#287](https://github.com/vincenthz/hs-tls/pull/287)+- Verify certificate key usage [#274](https://github.com/vincenthz/hs-tls/pull/274) [#301](https://github.com/vincenthz/hs-tls/pull/301)+- TLS 1.3 support [#278](https://github.com/vincenthz/hs-tls/pull/278) [#279](https://github.com/vincenthz/hs-tls/pull/279) [#280](https://github.com/vincenthz/hs-tls/pull/280) [#283](https://github.com/vincenthz/hs-tls/pull/283) [#298](https://github.com/vincenthz/hs-tls/pull/298) [#331](https://github.com/vincenthz/hs-tls/pull/331) [#290](https://github.com/vincenthz/hs-tls/pull/290) [#314](https://github.com/vincenthz/hs-tls/pull/314)+- Enable RSASSA-PSS [#280](https://github.com/vincenthz/hs-tls/pull/280) [#353](https://github.com/vincenthz/hs-tls/pull/353)+- Add and enable ChaCha20-Poly1305 ciphers [#287](https://github.com/vincenthz/hs-tls/pull/287) [#340](https://github.com/vincenthz/hs-tls/pull/340)+- Certificate selection with extension "signature_algorithms_cert" [#302](https://github.com/vincenthz/hs-tls/pull/302)+- Preventing Logjam attack [#300](https://github.com/vincenthz/hs-tls/pull/300)+- Downgrade protection [#308](https://github.com/vincenthz/hs-tls/pull/308)+- Support for EdDSA certificates [#328](https://github.com/vincenthz/hs-tls/pull/328) [#353](https://github.com/vincenthz/hs-tls/pull/353)+- Key logging [#317](https://github.com/vincenthz/hs-tls/pull/317)+- Thread safety for writes [#329](https://github.com/vincenthz/hs-tls/pull/329)+- Verify signature schemes and (EC)DHE groups received [#337](https://github.com/vincenthz/hs-tls/pull/337) [#338](https://github.com/vincenthz/hs-tls/pull/338)+- Throw BadRecordMac when the decrypted record has invalid format [#347](https://github.com/vincenthz/hs-tls/pull/347)+- Improve documentation format [#341](https://github.com/vincenthz/hs-tls/pull/341) [#343](https://github.com/vincenthz/hs-tls/pull/343)+- Fix recvClientData with single Handshake packet [#352](https://github.com/vincenthz/hs-tls/pull/352)+- Decrease memory footprint of SessionData values [#354](https://github.com/vincenthz/hs-tls/pull/354)++FEATURES:++- TLS version 1.3 is available with most features but is not enabled by default.+ One notable omission is post-handshake authentication. Scenarios where+ servers previously used renegotiation to conditionally request a certificate+ are not possible yet when `TLS13` is negotiated. Users may enable the version+ in `supportedVersions` only when sure post-handshake authentication is not+ required.++API CHANGES:++- `SessionManager` implementations need to provide a `sessionResumeOnlyOnce`+ 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`+ values must deal with the new fields.+- New configuration parameters and constructors are added for TLS version 1.3+ but the API change should be backward compatible for most use-cases.+- Function `cipherExchangeNeedMoreData` has been removed.++## Version 1.4.1++- Enable X25519 in default parameters [#265](https://github.com/vincenthz/hs-tls/pull/265)+- Checking EOF in bye [#262](https://github.com/vincenthz/hs-tls/pull/262)+- Improving validation in DH key exchange [#256](https://github.com/vincenthz/hs-tls/pull/256)+- Handle TCP reset during handshake [#251](https://github.com/vincenthz/hs-tls/pull/251)+- Accepting hlint suggestions.++## Version 1.4.0++- Wrap renegotiation failures with HandshakeFailed [#237](https://github.com/vincenthz/hs-tls/pull/237)+- Improve selection of server certificate and use "signature_algorithms" extension [#236](https://github.com/vincenthz/hs-tls/pull/236)+- Change Bytes to ByteString and deprecate the Bytes type alias [#230](https://github.com/vincenthz/hs-tls/pull/230)+- Session compression and SNI [#223](https://github.com/vincenthz/hs-tls/pull/223)+- Deprecating ciphersuite_medium. Putting WARNING to ciphersuite_all since this includes RC4 [#153](https://github.com/vincenthz/hs-tls/pull/153) [#222](https://github.com/vincenthz/hs-tls/pull/222)+- Removing NPN [#214](https://github.com/vincenthz/hs-tls/pull/214)+- Supporting RSAPSS defined in TLS 1.3 [#207](https://github.com/vincenthz/hs-tls/pull/207)+- Supporting X25519 and X448 in the IES style. [#205](https://github.com/vincenthz/hs-tls/pull/205)+- Strip leading zeros in DHE premaster secret [#201](https://github.com/vincenthz/hs-tls/pull/201)++FEATURES:++- RSASSA-PSS signatures can be enabled with `supportedHashSignatures`. This+ uses assignments from TLS 1.3, for example `(HashIntrinsic, SignatureRSApssSHA256)`.+- Diffie-Hellman with elliptic curves X25519 and X448: This can be enabled with+ `supportedGroups`, which also gives control over curve preference.+- ECDH with curve P-256 now uses optimized C implementation from package `cryptonite`.++API CHANGES:++- Cipher list `ciphersuite_medium` is now deprecated, users are advised to use+ `ciphersuite_default` or `ciphersuite_strong`. List `ciphersuite_all` is kept+ for compatibility with old servers but this is discouraged and generates a+ warning (this includes RC4 ciphers, see [#153](https://github.com/vincenthz/hs-tls/pull/153)+ for reference).+- Support for NPN (Next Protocol Negotiation) has been removed. The replacement+ is ALPN (Application-Layer Protocol Negotiation).+- Data type `SessionData` now contains fields for compression algorithm and+ client SNI. A `SessionManager` implementation that serializes/deserializes+ `SessionData` values must deal with the new fields.+- Module `Network.TLS` exports a type alias named `Bytes` which is now deprecated.+ The replacement is to use strict `ByteString` directly.++## Version 1.3.11++- Using reliable versions of dependent libraries.++## Version 1.3.10++- Selecting a cipher based on "signature_algorithms" [#193](https://github.com/vincenthz/hs-tls/pull/193)+- Respecting the "signature_algorithms" extension [#137](https://github.com/vincenthz/hs-tls/pull/137)+- Fix RSA signature in CertificateVerify with TLS < 1.2 [#189](https://github.com/vincenthz/hs-tls/pull/189)+- Fix ECDSA with TLS 1.0 / TLS 1.1 [#187](https://github.com/vincenthz/hs-tls/pull/187)+- Sending an empty server name from a server if necessary. [#175](https://github.com/vincenthz/hs-tls/pull/175)+- `Network.TLS.Extra` provides Finite Field Diffie-Hellman Ephemeral Parameters in RFC 7919 [#174](https://github.com/vincenthz/hs-tls/pull/174)+- Restore ability to renegotiate[#164](https://github.com/vincenthz/hs-tls/pull/164)++## Version 1.3.9++- Drop support for old GHC.+- Enable sha384 ciphers and provide `ciphersuite_default` as default set of ciphers for common needs [#168](https://github.com/vincenthz/hs-tls/pull/168)+- SNI late checks [#147](https://github.com/vincenthz/hs-tls/pull/147)+- Expose the HasBackend(..) class fully, so that developers can use TLS over their own channels [#149](https://github.com/vincenthz/hs-tls/pull/149)++## Version 1.3.8++- Fix older GHC builds++## Version 1.3.7++- Disable SHA384 based cipher, as they don't work properly yet.++## Version 1.3.6++- Add new ciphers+- Improve some debugging and outputs++## Version 1.3.5++- Fix a bug with ECDHE based cipher where serialization+- Debugging: Add a way to print random seed and a way to side-load a seed for replayability+- Improve tests++## Version 1.3.4++- Fix tests on 32 bits `time_t` machines (time not within bound)+- VirtualHost: Add a way to load credentials related to the hostname used by the client (Julian Beaumont)+- VirtualHost: Expose an API to query which hostname the client has contacted (Julian Beaumont)+- Add a way to disable empty packet that are use for security when+ using old versions + old CBC based cipher (Anton Dessiatov)++## Version 1.3.3++- Add support for Hans (Haskell Network Stack) (Adam Wick)+- Add support for ECDSA signature+- Add support for ECDSA-ECDHE Cipher+- Improve parsing of ECC related structure++## Version 1.3.2++- Add cipher suites for forward secrecy on more clients (Aaron Friel)+- Maintain more handshake information to be queried by protocol (Adam Wick)+- handle SCSV on client and server side (Kazu Yamamoto)+- Cleanup renegotiation logic (Kazu Yamamoto)+- Various testing improvements with the openssl test parts+- Cleanup AEAD handling for future support of other ciphers++## Version 1.3.1++- Repair DHE RSA handling on the cipher by creating signature properly++## Version 1.3.0++- modernize the crypto stack by using cryptonite.++## Version 1.2.18++- add more tests (network, local)+- cleanup cipher / bulk code, certificate verify / creation, and digitall signed handling+- fix handling of DHE ciphers with MS SSL stack that serialize leading zero.++## Version 1.2.17++- Fix an issue of type of key / hash that prevented connection with SChannel.++## Version 1.2.16++- Fix an issue with stream cipher not correctly calculating the internal state,+ resulting systematically in bad record mac failure during handshake++## Version 1.2.15++- support chain certificate in credentials++## Version 1.2.14++- adding ALPN extension+- adding support for AEAD, and particularly AES128-GCM+- Adding support for ECDH+- Do not support SSL3 by default for security reason.+- add EnumSafe8 and 16 for specific sized Enum instance that are safer+- export signatureAndHash parser/encoder+- add a "known" list of extensions+- add SignatureAlgorithms extension+- add Heartbeat extension+- add support for EC curves and point format extensions+- add preliminary SessionTicket extension+- Debug: Add the ability to choose arbitrary cipher in the client hello.++## Version 1.2.13++- Fix compilation with old mtl version++## Version 1.2.12++- Propagate asynchronous exception++## Version 1.2.11++- use hourglass instead of time+- use tasty instead of test-framework+- add travis file+- remove old de-optimisation flag as the bytestring bug is old now and it conflict with cabal check++## Version 1.2.10++- Update x509 dependencies++## Version 1.2.9++- Export TLSParams and HasBackend type names+- Added FlexibleContexts flag required by ghc-7.9+- debug: add support for specifying the timeout length in milliseconds.+- debug: add support for 3DES in simple client++## Version 1.2.8++- add support for 3DES-EDE-CBC-SHA1 (cipher 0xa)++## Version 1.2.7++- repair retrieve certificate validation, and improve fingerprints+- remove groom from dependency+- make RecordM an instance of Applicative+- Fixes the Error_EOF partial pattern match error in exception handling+ ## Version 1.2.6 (23 Mar 2014) - Fixed socket backend endless loop when the server does not close connection
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2010-2012 Vincent Hanquez <vincent@snarc.org>+Copyright (c) 2010-2015 Vincent Hanquez <vincent@snarc.org> All rights reserved.
Network/TLS.hs view
@@ -1,128 +1,377 @@ -- |--- Module : Network.TLS--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown+-- Native Haskell TLS protocol implementation for servers and+-- clients. ---module Network.TLS- (- -- * Context configuration- ClientParams(..)- , ServerParams(..)- , ClientHooks(..)- , ServerHooks(..)- , Supported(..)- , Shared(..)- , Hooks(..)- , Logging(..)- , Measurement(..)- , CertificateUsage(..)- , CertificateRejectReason(..)- , defaultParamsClient- , MaxFragmentEnum(..)- , HashAndSignatureAlgorithm- , HashAlgorithm(..)- , SignatureAlgorithm(..)- , CertificateType(..)-- -- * raw types- , ProtocolType(..)- , Header(..)+-- This provides a high-level implementation of a sensitive security+-- protocol, eliminating a common set of security issues through the+-- use of the advanced type system, high level constructions and+-- common Haskell features.+--+-- Currently implement the TLS1.2 and TLS 1.3+-- protocol, and support RSA and Ephemeral (Elliptic curve and+-- regular) Diffie Hellman key exchanges, and many extensions.+--+-- The typical usage is:+--+-- > socket <- ...+-- > ctx <- contextNew socket <params>+-- > handshake ctx+-- > ... (using recvData and sendData)+-- > bye+module Network.TLS (+ -- * Basic APIs+ Context,+ contextNew,+ handshake,+ sendData,+ recvData,+ bye, - -- * Session- , SessionID- , SessionData(..)- , SessionManager(..)- , noSessionManager+ -- * Exceptions+ -- $exceptions -- * Backend abstraction- , Backend(..)+ HasBackend (..),+ Backend (..), - -- * Context object- , Context- , ctxConnection+ -- * Parameters - -- * Creating a context- , contextNew- , contextNewOnHandle- , contextNewOnSocket- , contextFlush- , contextClose- , contextHookSetHandshakeRecv- , contextHookSetCertificateRecv- , contextHookSetLogging- , contextModifyHooks+ -- intentionally hide the internal methods even haddock warns.+ TLSParams, - -- * Information gathering- , Information(..)- , contextGetInformation+ -- ** Client parameters+ ClientParams,+ defaultParamsClient,+ clientServerIdentification,+ clientUseServerNameIndication,+ clientWantSessionResume,+ clientWantSessionResumeList,+ clientWantTicket,+ clientShared,+ clientHooks,+ clientSupported,+ clientDebug,+ clientUseEarlyData,+ clientUseECH, - -- * Credentials- , Credentials(..)- , Credential- , credentialLoadX509- , credentialLoadX509FromMemory+ -- ** Server parameters+ ServerParams,+ defaultParamsServer,+ serverWantClientCert,+ serverCACertificates,+ serverDHEParams,+ serverHooks,+ serverShared,+ serverSupported,+ serverDebug,+ serverEarlyDataSize,+ serverTicketLifetime,+ serverECHKey, - -- * Initialisation and Termination of context- , bye- , handshake+ -- ** Shared+ Shared,+ defaultShared,+ sharedCredentials,+ sharedSessionManager,+ sharedCAStore,+ sharedValidationCache,+ sharedHelloExtensions,+ sharedECHConfigList,+ sharedLimit, - -- * Next Protocol Negotiation- , getNegotiatedProtocol+ -- ** Client hooks+ ClientHooks,+ defaultClientHooks,+ OnCertificateRequest,+ onCertificateRequest,+ OnServerCertificate,+ onServerCertificate,+ onSuggestALPN,+ onCustomFFDHEGroup,+ onServerFinished,+ onSelectKeyShareGroups, - -- * High level API- , sendData- , recvData- , recvData'+ -- ** Server hooks+ ServerHooks,+ defaultServerHooks,+ onClientCertificate,+ validateClientCertificate,+ onUnverifiedClientCert,+ onCipherChoosing,+ onServerNameIndication,+ onNewHandshake,+ onALPNClientSuggest,+ onEncryptedExtensionsCreating,+ onSelectKeyShare,+ Measurement,+ nbHandshakes,+ bytesReceived,+ bytesSent, - -- * Crypto Key- , PubKey(..)- , PrivKey(..)+ -- ** Supported+ Supported,+ defaultSupported,+ supportedVersions,+ supportedCiphers,+ supportedCompressions,+ supportedHashSignatures,+ supportedSecureRenegotiation,+ supportedClientInitiatedRenegotiation,+ supportedExtendedMainSecret,+ supportedSession,+ supportedFallbackScsv,+ supportedEmptyPacket,+ supportedGroups,+ supportedGroupsTLS13, - -- * Compressions & Predefined compressions- , module Network.TLS.Compression+ -- ** Debug parameters+ DebugParams,+ defaultDebugParams,+ debugSeed,+ debugPrintSeed,+ debugVersionForced,+ debugKeyLogger,+ defaultKeyLogger,+ debugError,+ debugTraceKey, - -- * Ciphers & Predefined ciphers- , module Network.TLS.Cipher+ -- ** Limit parameters+ Limit,+ defaultLimit,+ limitHandshakeFragment,+ limitRecordSize, - -- * Versions- , Version(..)+ -- * Shared parameters - -- * Errors- , TLSError(..)- , KxError(..)- , AlertDescription(..)+ -- ** Credentials+ Credentials (..),+ Credential,+ credentialLoadX509,+ credentialLoadX509FromMemory,+ credentialLoadX509Chain,+ credentialLoadX509ChainFromMemory, - -- * Exceptions- , TLSException(..)+ -- ** Session manager+ SessionManager,+ noSessionManager,+ sessionResume,+ sessionResumeOnlyOnce,+ sessionEstablish,+ sessionInvalidate,+ sessionUseTicket,+ SessionID,+ SessionIDorTicket,+ Ticket, - -- * X509 Validation- , ValidationChecks(..)- , ValidationHooks(..)+ -- ** Session data+ SessionData,+ sessionVersion,+ sessionCipher,+ sessionCompression,+ sessionClientSNI,+ sessionSecret,+ sessionGroup,+ sessionTicketInfo,+ sessionALPN,+ sessionMaxEarlyDataSize,+ sessionFlags,+ SessionFlag (..),+ TLS13TicketInfo,+ is0RTTPossible, - -- * X509 Validation Cache- , ValidationCache(..)- , ValidationCacheResult(..)- , exceptionValidationCache- ) where+ -- ** Validation Cache+ ValidationCache (..),+ defaultValidationCache,+ ValidationCacheQueryCallback,+ ValidationCacheAddCallback,+ ValidationCacheResult (..),+ exceptionValidationCache, -import Network.TLS.Backend (Backend(..))-import Network.TLS.Struct ( TLSError(..), TLSException(..)- , HashAndSignatureAlgorithm, HashAlgorithm(..), SignatureAlgorithm(..)- , Header(..), ProtocolType(..), CertificateType(..)- , AlertDescription(..))-import Network.TLS.Crypto (KxError(..))+ -- * Types++ -- ** For 'Supported'+ Version (..),+ Compression (..),+ nullCompression,+ HashAndSignatureAlgorithm,+ supportedSignatureSchemes,+ HashAlgorithm (..),+ SignatureAlgorithm (..),+ Group (..),+ supportedNamedGroups,+ EMSMode (..),++ -- ** For parameters and hooks+ DHParams,+ DHPublic,+ GroupUsage (..),+ CertificateUsage (..),+ CertificateRejectReason (..),+ CertificateType (..),+ CertificateChain (..),+ HostName,+ MaxFragmentEnum (..),++ -- * Advanced APIs++ -- ** Backend+ ctxBackend,+ contextFlush,+ contextClose,++ -- ** Information gathering+ Information,+ contextGetInformation,+ infoVersion,+ infoCipher,+ infoCompression,+ infoMainSecret,+ infoExtendedMainSecret,+ infoClientRandom,+ infoServerRandom,+ infoSupportedGroup,+ infoTLS12Resumption,+ infoTLS13HandshakeMode,+ infoIsEarlyDataAccepted,+ infoIsECHAccepted,+ ClientRandom,+ ServerRandom,+ unClientRandom,+ unServerRandom,+ HandshakeMode13 (..),+ getClientCertificateChain,+ getServerCertificateChain,++ -- ** Negotiated+ getNegotiatedProtocol,+ getClientSNI,++ -- ** Post-handshake actions+ updateKey,+ KeyUpdateRequest (..),+ requestCertificate,+ getTLSUnique,+ getTLSExporter,+ getTLSServerEndPoint,+ getFinished,+ getPeerFinished,++ -- ** Modifying hooks in context+ Hooks,+ defaultHooks,+ hookRecvHandshake,+ hookRecvHandshake13,+ hookRecvCertificates,+ hookLogging,+ contextModifyHooks,+ Handshake,+ contextHookSetHandshakeRecv,+ Handshake13,+ contextHookSetHandshake13Recv,+ contextHookSetCertificateRecv,+ Logging,+ defaultLogging,+ loggingPacketSent,+ loggingPacketRecv,+ loggingIOSent,+ loggingIORecv,+ Header (..),+ ProtocolType (..),+ contextHookSetLogging,++ -- * Errors and exceptions++ -- ** Errors+ TLSError (..),+ KxError (..),+ AlertDescription (..),++ -- ** Exceptions+ TLSException (..),++ -- * Raw types++ -- ** Compressions class+ CompressionC (..),+ CompressionID,++ -- ** Crypto Key+ PubKey (..),+ PrivKey (..),++ -- ** Ciphers & Predefined ciphers+ module Network.TLS.Cipher,++ -- * Deprecated+ recvData',+ Bytes,+ ValidationChecks (..),+ ValidationHooks (..),+ clientUseMaxFragmentLength,+) where++import Data.X509 (PrivKey (..), PubKey (..))+import Data.X509.Validation hiding (HostName, defaultHooks)++import Network.TLS.Backend (Backend (..), HasBackend (..)) import Network.TLS.Cipher+import Network.TLS.Compression (+ Compression (..),+ CompressionC (..),+ nullCompression,+ )+import Network.TLS.Context+import Network.TLS.Core+import Network.TLS.Credentials+import Network.TLS.Crypto (+ DHParams,+ DHPublic,+ KxError (..),+ supportedNamedGroups,+ )+import Network.TLS.Extension+import Network.TLS.Handshake.State (HandshakeMode13 (..)) import Network.TLS.Hooks+import Network.TLS.Imports import Network.TLS.Measurement-import Network.TLS.Credentials-import Network.TLS.Compression (CompressionC(..), Compression(..), nullCompression)-import Network.TLS.Context import Network.TLS.Parameters-import Network.TLS.Core import Network.TLS.Session-import Network.TLS.X509+import qualified Network.TLS.State as S+import Network.TLS.Struct (+ AlertDescription (..),+ CertificateType (..),+ ClientRandom (..),+ Handshake,+ HashAlgorithm (..),+ HashAndSignatureAlgorithm,+ Header (..),+ ProtocolType (..),+ ServerRandom (..),+ SignatureAlgorithm (..),+ TLSError (..),+ TLSException (..),+ supportedSignatureSchemes,+ )+import Network.TLS.Struct13 (Handshake13) import Network.TLS.Types-import Data.X509 (PubKey(..), PrivKey(..))-import Data.X509.Validation+import Network.TLS.X509++{-# DEPRECATED Bytes "Use Data.ByteString.Bytestring instead of Bytes." #-}+type Bytes = ByteString++-- | Getting certificates from a client, if any.+-- Note that the certificates are not sent by a client+-- on resumption even if client authentication is required.+-- So, this API would be replaced by the one which can treat+-- both cases of full-negotiation and resumption.+getClientCertificateChain :: Context -> IO (Maybe CertificateChain)+getClientCertificateChain ctx = usingState_ ctx S.getClientCertificateChain++getServerCertificateChain :: Context -> IO (Maybe CertificateChain)+getServerCertificateChain ctx = usingState_ ctx S.getServerCertificateChain++-- $exceptions+-- Since 1.8.0, this library only throws exceptions of type 'TLSException'.+-- In the common case where the chosen backend is socket, 'IOException'+-- may be thrown as well. This happens because the backend for sockets,+-- opaque to most modules in the @tls@ library, throws those exceptions.
Network/TLS/Backend.hs view
@@ -1,11 +1,4 @@--- |--- Module : Network.TLS.Backend--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ A Backend represents a unified way to do IO on different+-- | A Backend represents a unified way to do IO on different -- types without burdening our calling API with multiple -- ways to initialize a new context. --@@ -14,25 +7,28 @@ -- * a way to write data -- * a way to close the stream -- * a way to flush the stream----module Network.TLS.Backend- ( HasBackend(..)- , Backend(..)- ) where+module Network.TLS.Backend (+ HasBackend (..),+ Backend (..),+) where -import Control.Monad-import Network.Socket (Socket, sClose)-import qualified Network.Socket.ByteString as Socket-import Data.ByteString (ByteString) import qualified Data.ByteString as B-import System.IO (Handle, hSetBuffering, BufferMode(..), hFlush, hClose)+import qualified Network.Socket as Network+import qualified Network.Socket.ByteString as Network+import System.IO (BufferMode (..), Handle, hClose, hFlush, hSetBuffering) +import Network.TLS.Imports+ -- | Connection IO backend data Backend = Backend- { backendFlush :: IO () -- ^ Flush the connection sending buffer, if any.- , backendClose :: IO () -- ^ Close the connection.- , backendSend :: ByteString -> IO () -- ^ Send a bytestring through the connection.- , backendRecv :: Int -> IO ByteString -- ^ Receive specified number of bytes from the connection.+ { backendFlush :: IO ()+ -- ^ Flush the connection sending buffer, if any.+ , backendClose :: IO ()+ -- ^ Close the connection.+ , backendSend :: ByteString -> IO ()+ -- ^ Send a bytestring through the connection.+ , backendRecv :: Int -> IO ByteString+ -- ^ Receive specified number of bytes from the connection. } class HasBackend a where@@ -43,17 +39,34 @@ initializeBackend _ = return () getBackend = id -instance HasBackend Socket where+safeRecv :: Network.Socket -> Int -> IO ByteString+safeRecv = Network.recv++instance HasBackend Network.Socket where initializeBackend _ = return ()- getBackend sock = Backend (return ()) (sClose sock) (Socket.sendAll sock) recvAll- where recvAll n = B.concat `fmap` loop n- where loop 0 = return []- loop left = do- r <- Socket.recv sock left- if B.null r- then return []- else liftM (r:) (loop (left - B.length r))+ getBackend sock =+ Backend+ { backendFlush = return ()+ , backendClose = Network.close sock+ , backendSend = Network.sendAll sock+ , backendRecv = recvAll+ }+ where+ recvAll n = B.concat <$> loop n+ where+ loop 0 = return []+ loop left = do+ r <- safeRecv sock left+ if B.null r+ then return []+ else (r :) <$> loop (left - B.length r) instance HasBackend Handle where initializeBackend handle = hSetBuffering handle NoBuffering- getBackend handle = Backend (hFlush handle) (hClose handle) (B.hPut handle) (B.hGet handle)+ getBackend handle =+ Backend+ { backendFlush = hFlush handle+ , backendClose = hClose handle+ , backendSend = B.hPut handle+ , backendRecv = B.hGet handle+ }
− Network/TLS/Cap.hs
@@ -1,19 +0,0 @@--- |--- Module : Network.TLS.Cap--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown-----module Network.TLS.Cap- ( hasHelloExtensions- , hasExplicitBlockIV- ) where--import Network.TLS.Struct--hasHelloExtensions, hasExplicitBlockIV :: Version -> Bool--hasHelloExtensions ver = ver >= SSL3-hasExplicitBlockIV ver = ver >= TLS11
Network/TLS/Cipher.hs view
@@ -1,119 +1,84 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE ExistentialQuantification #-}--- |--- Module : Network.TLS.Cipher--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Cipher- ( BulkFunctions(..)- , CipherKeyExchangeType(..)- , Bulk(..)- , Hash(..)- , Cipher(..)- , CipherID- , Key- , IV- , cipherKeyBlockSize- , cipherAllowedForVersion- , cipherExchangeNeedMoreData- ) where--import Network.TLS.Types (CipherID)-import Network.TLS.Struct (Version(..))--import qualified Data.ByteString as B---- FIXME convert to newtype-type Key = B.ByteString-type IV = B.ByteString--data BulkFunctions =- BulkBlockF (Key -> IV -> B.ByteString -> B.ByteString)- (Key -> IV -> B.ByteString -> B.ByteString)- | BulkStreamF (Key -> IV)- (IV -> B.ByteString -> (B.ByteString, IV))- (IV -> B.ByteString -> (B.ByteString, IV))+{-# OPTIONS_HADDOCK hide #-} -data CipherKeyExchangeType =- CipherKeyExchange_RSA- | CipherKeyExchange_DH_Anon- | CipherKeyExchange_DHE_RSA- | CipherKeyExchange_ECDHE_RSA- | CipherKeyExchange_DHE_DSS- | CipherKeyExchange_DH_DSS- | CipherKeyExchange_DH_RSA- | CipherKeyExchange_ECDH_ECDSA- | CipherKeyExchange_ECDH_RSA- | CipherKeyExchange_ECDHE_ECDSA- deriving (Show,Eq)+module Network.TLS.Cipher (+ CipherKeyExchangeType (..),+ Bulk (..),+ BulkFunctions (..),+ BulkDirection (..),+ BulkState (..),+ BulkStream (..),+ BulkBlock,+ BulkAEAD,+ bulkInit,+ Hash (..),+ Cipher (..),+ CipherID,+ cipherKeyBlockSize,+ BulkKey,+ BulkIV,+ BulkNonce,+ BulkAdditionalData,+ cipherAllowedForVersion,+ hasMAC,+ hasRecordIV,+ elemCipher,+ intersectCiphers,+ findCipher,+) where -data Bulk = Bulk- { bulkName :: String- , bulkKeySize :: Int- , bulkIVSize :: Int- , bulkBlockSize :: Int- , bulkF :: BulkFunctions- }+import Network.TLS.Crypto (Hash (..), hashDigestSize)+import Network.TLS.Imports+import Network.TLS.Types -instance Show Bulk where- show bulk = bulkName bulk-instance Eq Bulk where- b1 == b2 = and [ bulkName b1 == bulkName b2- , bulkKeySize b1 == bulkKeySize b2- , bulkIVSize b1 == bulkIVSize b2- , bulkBlockSize b1 == bulkBlockSize b2- ]+data BulkState+ = BulkStateStream BulkStream+ | BulkStateBlock BulkBlock+ | BulkStateAEAD BulkAEAD+ | BulkStateUninitialized -data Hash = Hash- { hashName :: String- , hashSize :: Int- , hashF :: B.ByteString -> B.ByteString- }+instance Show BulkState where+ show (BulkStateStream _) = "BulkStateStream"+ show (BulkStateBlock _) = "BulkStateBlock"+ show (BulkStateAEAD _) = "BulkStateAEAD"+ show BulkStateUninitialized = "BulkStateUninitialized" -instance Show Hash where- show hash = hashName hash-instance Eq Hash where- h1 == h2 = hashName h1 == hashName h2 && hashSize h1 == hashSize h2+bulkInit :: Bulk -> BulkDirection -> BulkKey -> BulkState+bulkInit bulk direction key =+ case bulkF bulk of+ BulkBlockF ini -> BulkStateBlock (ini direction key)+ BulkStreamF ini -> BulkStateStream (ini direction key)+ BulkAeadF ini -> BulkStateAEAD (ini direction key) --- | Cipher algorithm-data Cipher = Cipher- { cipherID :: CipherID- , cipherName :: String- , cipherHash :: Hash- , cipherBulk :: Bulk- , cipherKeyExchange :: CipherKeyExchangeType- , cipherMinVer :: Maybe Version- }+hasMAC, hasRecordIV :: BulkFunctions -> Bool+hasMAC (BulkBlockF _) = True+hasMAC (BulkStreamF _) = True+hasMAC (BulkAeadF _) = False+hasRecordIV = hasMAC cipherKeyBlockSize :: Cipher -> Int-cipherKeyBlockSize cipher = 2 * (hashSize (cipherHash cipher) + bulkIVSize bulk + bulkKeySize bulk)- where bulk = cipherBulk cipher+cipherKeyBlockSize cipher = 2 * (hashDigestSize (cipherHash cipher) + bulkIVSize bulk + bulkKeySize bulk)+ where+ bulk = cipherBulk cipher -- | Check if a specific 'Cipher' is allowed to be used -- with the version specified cipherAllowedForVersion :: Version -> Cipher -> Bool cipherAllowedForVersion ver cipher = case cipherMinVer cipher of- Nothing -> True- Just cVer -> cVer <= ver+ Nothing -> ver < TLS13+ Just cVer -> cVer <= ver && (ver < TLS13 || cVer >= TLS13) -instance Show Cipher where- show c = cipherName c+eqCipher :: CipherID -> Cipher -> Bool+eqCipher cid c = cipherID c == cid -instance Eq Cipher where- (==) c1 c2 = cipherID c1 == cipherID c2+elemCipher :: [CipherId] -> Cipher -> Bool+elemCipher cids c = cid `elem` cids+ where+ cid = CipherId $ cipherID c -cipherExchangeNeedMoreData :: CipherKeyExchangeType -> Bool-cipherExchangeNeedMoreData CipherKeyExchange_RSA = False-cipherExchangeNeedMoreData CipherKeyExchange_DH_Anon = True-cipherExchangeNeedMoreData CipherKeyExchange_DHE_RSA = True-cipherExchangeNeedMoreData CipherKeyExchange_ECDHE_RSA = True-cipherExchangeNeedMoreData CipherKeyExchange_DHE_DSS = True-cipherExchangeNeedMoreData CipherKeyExchange_DH_DSS = False-cipherExchangeNeedMoreData CipherKeyExchange_DH_RSA = False-cipherExchangeNeedMoreData CipherKeyExchange_ECDH_ECDSA = True-cipherExchangeNeedMoreData CipherKeyExchange_ECDH_RSA = True-cipherExchangeNeedMoreData CipherKeyExchange_ECDHE_ECDSA = True+intersectCiphers :: [CipherId] -> [Cipher] -> [Cipher]+intersectCiphers peerCiphers myCiphers = filter (elemCipher peerCiphers) myCiphers++findCipher :: CipherID -> [Cipher] -> Maybe Cipher+findCipher cid = find $ eqCipher cid
Network/TLS/Compression.hs view
@@ -1,41 +1,35 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE ExistentialQuantification #-}--- |--- Module : Network.TLS.Compression--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Compression- ( CompressionC(..)- , Compression(..)- , CompressionID- , nullCompression- , NullCompression+{-# OPTIONS_HADDOCK hide #-} +module Network.TLS.Compression (+ CompressionC (..),+ Compression (..),+ CompressionID,+ nullCompression,+ NullCompression,+ -- * member redefined for the class abstraction- , compressionID- , compressionDeflate- , compressionInflate+ compressionID,+ compressionDeflate,+ compressionInflate, -- * helper- , compressionIntersectID- ) where+ compressionIntersectID,+) where -import Data.Word-import Network.TLS.Types (CompressionID)-import Data.ByteString (ByteString) import Control.Arrow (first) +import Network.TLS.Imports+import Network.TLS.Types (CompressionID)+ -- | supported compression algorithms need to be part of this class class CompressionC a where- compressionCID :: a -> CompressionID+ compressionCID :: a -> CompressionID compressionCDeflate :: a -> ByteString -> (a, ByteString) compressionCInflate :: a -> ByteString -> (a, ByteString) -- | every compression need to be wrapped in this, to fit in structure-data Compression = forall a . CompressionC a => Compression a+data Compression = forall a. CompressionC a => Compression a -- | return the associated ID for this algorithm compressionID :: Compression -> CompressionID@@ -57,16 +51,16 @@ (==) 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 -> elem (compressionID c) ids) l+compressionIntersectID l ids = filter (\c -> compressionID c `elem` ids) l -- | This is the default compression which is a NOOP. data NullCompression = NullCompression instance CompressionC NullCompression where- compressionCID _ = 0+ compressionCID _ = 0 compressionCDeflate s b = (s, b) compressionCInflate s b = (s, b)
Network/TLS/Context.hs view
@@ -1,217 +1,311 @@--- |--- Module : Network.TLS.Context--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Context- (+{-# LANGUAGE OverloadedStrings #-}++module Network.TLS.Context ( -- * Context configuration- TLSParams+ TLSParams, -- * Context object and accessor- , Context(..)- , Hooks(..)- , ctxEOF- , ctxHasSSLv2ClientHello- , ctxDisableSSLv2ClientHello- , ctxEstablished- , withLog- , ctxWithHooks- , contextModifyHooks- , setEOF- , setEstablished- , contextFlush- , contextClose- , contextSend- , contextRecv- , updateMeasure- , withMeasure- , withReadLock- , withWriteLock- , withStateLock- , withRWLock+ Context (..),+ Hooks (..),+ Established (..),+ RecordLayer (..),+ ctxEOF,+ ctxEstablished,+ withLog,+ ctxWithHooks,+ contextModifyHooks,+ setEOF,+ setEstablished,+ contextFlush,+ contextClose,+ contextSend,+ contextRecv,+ updateMeasure,+ withMeasure,+ withReadLock,+ withWriteLock,+ withStateLock,+ withRWLock, -- * information- , Information(..)- , contextGetInformation+ Information (..),+ contextGetInformation, -- * New contexts- , contextNew- -- * Deprecated new contexts methods- , contextNewOnHandle- , contextNewOnSocket+ contextNew, -- * Context hooks- , contextHookSetHandshakeRecv- , contextHookSetCertificateRecv- , contextHookSetLogging+ contextHookSetHandshakeRecv,+ contextHookSetHandshake13Recv,+ contextHookSetCertificateRecv,+ contextHookSetLogging, -- * Using context states- , throwCore- , usingState- , usingState_- , runTxState- , runRxState- , usingHState- , getHState- , getStateRNG- ) where+ throwCore,+ usingState,+ usingState_,+ runTxRecordState,+ runRxRecordState,+ usingHState,+ getHState,+ getStateRNG,+ tls13orLater,+ getTLSUnique,+ getTLSExporter,+ getTLSServerEndPoint,+ getFinished,+ getPeerFinished,+ TLS13State (..),+ getTLS13State,+ modifyTLS13State,+ setMyRecordLimit,+ enableMyRecordLimit,+ getMyRecordLimit,+ checkMyRecordLimit,+ setPeerRecordLimit,+ enablePeerRecordLimit,+ getPeerRecordLimit,+ checkPeerRecordLimit,+ newRecordLimitRef,+) where +import Control.Concurrent.MVar+import Control.Monad.State.Strict+import Data.IORef+ import Network.TLS.Backend+import Network.TLS.Cipher import Network.TLS.Context.Internal-import Network.TLS.Struct-import Network.TLS.Cipher (Cipher(..), CipherKeyExchangeType(..))-import Network.TLS.Credentials-import Network.TLS.State+import Network.TLS.Crypto+import Network.TLS.Handshake (+ handshakeClient,+ handshakeClientWith,+ handshakeServer,+ handshakeServerWith,+ )+import Network.TLS.Handshake.State13 import Network.TLS.Hooks-import Network.TLS.Record.State-import Network.TLS.Parameters+import Network.TLS.Imports+import Network.TLS.KeySchedule import Network.TLS.Measurement-import Network.TLS.Types (Role(..))-import Network.TLS.Handshake (handshakeClient, handshakeClientWith, handshakeServer, handshakeServerWith)+import Network.TLS.Packet+import Network.TLS.Parameters+import Network.TLS.PostHandshake (+ postHandshakeAuthClientWith,+ requestCertificateServer,+ )+import Network.TLS.RNG+import Network.TLS.Record.Recv+import Network.TLS.Record.Send+import Network.TLS.Record.State+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Types (+ Role (..),+ TranscriptHash (..),+ defaultRecordSizeLimit,+ ) import Network.TLS.X509-import Data.Maybe (isJust) -import Crypto.Random--import Control.Concurrent.MVar-import Control.Monad.State-import Data.IORef---- deprecated imports-import Network.Socket (Socket)-import System.IO (Handle)- class TLSParams a where getTLSCommonParams :: a -> CommonParams- getTLSRole :: a -> Role- getCiphers :: a -> [Cipher]- doHandshake :: a -> Context -> IO ()- doHandshakeWith :: a -> Context -> Handshake -> IO ()+ getTLSRole :: a -> Role+ doHandshake :: a -> Context -> IO ()+ doHandshakeWith :: a -> Context -> HandshakeR -> IO ()+ doRequestCertificate :: a -> Context -> IO Bool+ doPostHandshakeAuthWith :: a -> Context -> Handshake13 -> IO () instance TLSParams ClientParams where- getTLSCommonParams cparams = ( clientSupported cparams- , clientShared cparams- )+ getTLSCommonParams cparams =+ ( clientSupported cparams+ , clientShared cparams+ , clientDebug cparams+ ) getTLSRole _ = ClientRole- getCiphers cparams = supportedCiphers $ clientSupported cparams doHandshake = handshakeClient doHandshakeWith = handshakeClientWith+ doRequestCertificate _ _ = return False+ doPostHandshakeAuthWith = postHandshakeAuthClientWith instance TLSParams ServerParams where- getTLSCommonParams sparams = ( serverSupported sparams- , serverShared sparams- )+ getTLSCommonParams sparams =+ ( serverSupported sparams+ , serverShared sparams+ , serverDebug sparams+ ) getTLSRole _ = ServerRole- -- on the server we filter our allowed ciphers here according- -- to the credentials and DHE parameters loaded- getCiphers sparams = filter authorizedCKE (supportedCiphers $ serverSupported sparams)- where authorizedCKE cipher =- case cipherKeyExchange cipher of- CipherKeyExchange_RSA -> canEncryptRSA- CipherKeyExchange_DH_Anon -> canDHE- CipherKeyExchange_DHE_RSA -> canSignRSA && canDHE- CipherKeyExchange_DHE_DSS -> canSignDSS && canDHE- -- unimplemented: non ephemeral DH- CipherKeyExchange_DH_DSS -> False- CipherKeyExchange_DH_RSA -> False- -- unimplemented: EC- CipherKeyExchange_ECDHE_RSA -> False- CipherKeyExchange_ECDH_ECDSA -> False- CipherKeyExchange_ECDH_RSA -> False- CipherKeyExchange_ECDHE_ECDSA -> False-- canDHE = isJust $ serverDHEParams sparams- canSignDSS = SignatureDSS `elem` signingAlgs- canSignRSA = SignatureRSA `elem` signingAlgs- canEncryptRSA = isJust $ credentialsFindForDecrypting creds- signingAlgs = credentialsListSigningAlgorithms creds- creds = sharedCredentials $ serverShared sparams doHandshake = handshakeServer doHandshakeWith = handshakeServerWith+ doRequestCertificate = requestCertificateServer+ doPostHandshakeAuthWith = \_ _ _ -> return () -- | create a new context using the backend and parameters specified.-contextNew :: (MonadIO m, CPRG rng, HasBackend backend, TLSParams params)- => backend -- ^ Backend abstraction with specific method to interact with the connection type.- -> params -- ^ Parameters of the context.- -> rng -- ^ Random number generator associated with this context.- -> m Context-contextNew backend params rng = liftIO $ do+contextNew+ :: (MonadIO m, HasBackend backend, TLSParams params)+ => backend+ -- ^ Backend abstraction with specific method to interact with the connection type.+ -> params+ -- ^ Parameters of the context.+ -> m Context+contextNew backend params = liftIO $ do initializeBackend backend - let role = getTLSRole params- st = newTLSState rng role- (supported, shared) = getTLSCommonParams params- ciphers = getCiphers params+ let (supported, shared, debug) = getTLSCommonParams params - when (null ciphers) $ error "no ciphers available with those parameters"+ seed <- case debugSeed debug of+ Nothing -> do+ seed <- seedNew+ debugPrintSeed debug seed+ return seed+ Just determ -> return determ+ let rng = newStateRNG seed - stvar <- newMVar st- eof <- newIORef False- established <- newIORef False+ let role = getTLSRole params+ st = newTLSState rng role++ tlsstate <- newMVar st+ eof <- newIORef False+ established <- newIORef NotEstablished stats <- newIORef newMeasurement- -- we enable the reception of SSLv2 ClientHello message only in the- -- server context, where we might be dealing with an old/compat client.- sslv2Compat <- newIORef (role == ServerRole) needEmptyPacket <- newIORef False hooks <- newIORef defaultHooks- tx <- newMVar newRecordState- rx <- newMVar newRecordState- hs <- newMVar Nothing- lockWrite <- newMVar ()- lockRead <- newMVar ()- lockState <- newMVar ()+ tx <- newMVar newRecordState+ rx <- newMVar newRecordState+ hs <- newMVar Nothing+ recvActionsRef <- newIORef []+ sendActionRef <- newIORef Nothing+ locks <- Locks <$> newMVar () <*> newMVar () <*> newMVar ()+ st13ref <- newIORef defaultTLS13State+ mylimref <- newRecordLimitRef $ Just defaultRecordSizeLimit+ peerlimref <- newRecordLimitRef $ Just defaultRecordSizeLimit+ hpkeref <- newIORef Nothing+ let roleParams =+ RoleParams+ { doHandshake_ = doHandshake params+ , doHandshakeWith_ = doHandshakeWith params+ , doRequestCertificate_ = doRequestCertificate params+ , doPostHandshakeAuthWith_ = doPostHandshakeAuthWith params+ } - return $ Context- { ctxConnection = getBackend backend- , ctxShared = shared- , ctxSupported = supported- , ctxCiphers = ciphers- , ctxState = stvar- , ctxTxState = tx- , ctxRxState = rx- , ctxHandshake = hs- , ctxDoHandshake = doHandshake params- , ctxDoHandshakeWith = doHandshakeWith params- , ctxMeasurement = stats- , ctxEOF_ = eof- , ctxEstablished_ = established- , ctxSSLv2ClientHello = sslv2Compat- , ctxNeedEmptyPacket = needEmptyPacket- , ctxHooks = hooks- , ctxLockWrite = lockWrite- , ctxLockRead = lockRead- , ctxLockState = lockState- }+ let ctx =+ Context+ { ctxBackend = getBackend backend+ , ctxShared = shared+ , ctxSupported = supported+ , ctxDebug = debug+ , ctxTLSState = tlsstate+ , ctxMyRecordLimit = mylimref+ , ctxPeerRecordLimit = peerlimref+ , ctxTxRecordState = tx+ , ctxRxRecordState = rx+ , ctxHandshakeState = hs+ , ctxRoleParams = roleParams+ , ctxMeasurement = stats+ , ctxEOF_ = eof+ , ctxEstablished_ = established+ , ctxNeedEmptyPacket = needEmptyPacket+ , ctxHooks = hooks+ , ctxLocks = locks+ , ctxPendingRecvActions = recvActionsRef+ , ctxPendingSendAction = sendActionRef+ , ctxRecordLayer = recordLayer+ , ctxHandshakeSync = HandshakeSync syncNoOp syncNoOp+ , ctxQUICMode = False+ , ctxTLS13State = st13ref+ , ctxHPKE = hpkeref+ } --- | create a new context on an handle.-contextNewOnHandle :: (MonadIO m, CPRG rng, TLSParams params)- => Handle -- ^ Handle of the connection.- -> params -- ^ Parameters of the context.- -> rng -- ^ Random number generator associated with this context.- -> m Context-contextNewOnHandle handle params st = contextNew handle params st-{-# DEPRECATED contextNewOnHandle "use contextNew" #-}+ syncNoOp _ _ = return () --- | create a new context on a socket.-contextNewOnSocket :: (MonadIO m, CPRG rng, TLSParams params)- => Socket -- ^ Socket of the connection.- -> params -- ^ Parameters of the context.- -> rng -- ^ Random number generator associated with this context.- -> m Context-contextNewOnSocket sock params st = contextNew sock params st-{-# DEPRECATED contextNewOnSocket "use contextNew" #-}+ recordLayer =+ RecordLayer+ { recordEncode12 = encodeRecord12+ , recordEncode13 = encodeRecord13+ , recordSendBytes = sendBytes+ , recordRecv12 = recvRecord12+ , recordRecv13 = recvRecord13+ } + return ctx+ contextHookSetHandshakeRecv :: Context -> (Handshake -> IO Handshake) -> IO () contextHookSetHandshakeRecv context f =- contextModifyHooks context (\hooks -> hooks { hookRecvHandshake = f })+ contextModifyHooks context (\hooks -> hooks{hookRecvHandshake = f}) +contextHookSetHandshake13Recv+ :: Context -> (Handshake13 -> IO Handshake13) -> IO ()+contextHookSetHandshake13Recv context f =+ contextModifyHooks context (\hooks -> hooks{hookRecvHandshake13 = f})+ contextHookSetCertificateRecv :: Context -> (CertificateChain -> IO ()) -> IO () contextHookSetCertificateRecv context f =- contextModifyHooks context (\hooks -> hooks { hookRecvCertificates = f })+ contextModifyHooks context (\hooks -> hooks{hookRecvCertificates = f}) contextHookSetLogging :: Context -> Logging -> IO () contextHookSetLogging context loggingCallbacks =- contextModifyHooks context (\hooks -> hooks { hookLogging = loggingCallbacks })+ contextModifyHooks context (\hooks -> hooks{hookLogging = loggingCallbacks})++{-# DEPRECATED getFinished "Use getTLSUnique instead" #-}++-- | Getting TLS Finished sent to peer.+getFinished :: Context -> IO (Maybe VerifyData)+getFinished ctx = usingState_ ctx getMyVerifyData++{-# DEPRECATED getPeerFinished "Use getTLSUnique instead" #-}++-- | Getting TLS Finished received from peer.+getPeerFinished :: Context -> IO (Maybe VerifyData)+getPeerFinished ctx = usingState_ ctx getPeerVerifyData++-- | Getting the "tls-unique" channel binding for TLS 1.2 (RFC5929).+-- For TLS 1.3, 'Nothing' is returned.+-- 'supportedExtendedMainSecret' must be 'RequireEMS'+-- But in general, it is highly recommended to upgrade to TLS 1.3+-- 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+ then do+ mx <- usingState_ ctx getFirstVerifyData+ case mx of+ Nothing -> return Nothing+ Just (VerifyData verifyData) -> return $ Just verifyData+ else return Nothing++-- | Getting the "tls-exporter" channel binding for TLS 1.3 (RFC9266).+-- For TLS 1.2, 'Nothing' is returned.+getTLSExporter :: Context -> IO (Maybe ByteString)+getTLSExporter ctx = do+ ver <- liftIO $ usingState_ ctx getVersion+ if ver == TLS13+ then exporter ctx "EXPORTER-Channel-Binding" "" 32+ else return Nothing++exporter :: Context -> ByteString -> ByteString -> Int -> IO (Maybe ByteString)+exporter ctx label context outlen = do+ msecret <- usingState_ ctx getTLS13ExporterSecret+ mcipher <- failOnEitherError $ runRxRecordState ctx $ gets stCipher+ return $ case (msecret, mcipher) of+ (Just secret, Just cipher) ->+ let h = cipherHash cipher+ secret' = deriveSecret h secret label $ TranscriptHash ""+ label' = "exporter"+ value' = hash h context+ key = hkdfExpandLabel h secret' label' value' outlen+ in Just key+ _ -> Nothing++-- | Getting the "tls-server-end-point" channel binding for TLS 1.2+-- (RFC5929). For 1.3, there is no specifications for how to create+-- it. In this implementation, a certificate chain without+-- extensions is hashed like TLS 1.2.+getTLSServerEndPoint :: Context -> IO (Maybe ByteString)+getTLSServerEndPoint ctx = do+ mcc <- usingState_ ctx getServerCertificateChain+ case mcc of+ Nothing -> return Nothing+ Just cc -> do+ (usedHash, _, _, _) <- getRxRecordState ctx+ return $ Just $ hash usedHash $ encodeCertificate cc
Network/TLS/Context/Internal.hs view
@@ -1,223 +1,531 @@--- |--- Module : Network.TLS.Context.Internal--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Context.Internal- (+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}++module Network.TLS.Context.Internal ( -- * Context configuration- ClientParams(..)- , ServerParams(..)- , defaultParamsClient- , SessionID- , SessionData(..)- , MaxFragmentEnum(..)- , Measurement(..)+ ClientParams (..),+ ServerParams (..),+ defaultParamsClient,+ SessionID,+ SessionData (..),+ MaxFragmentEnum (..),+ Measurement (..), -- * Context object and accessor- , Context(..)- , Hooks(..)- , ctxEOF- , ctxHasSSLv2ClientHello- , ctxDisableSSLv2ClientHello- , ctxEstablished- , withLog- , ctxWithHooks- , contextModifyHooks- , setEOF- , setEstablished- , contextFlush- , contextClose- , contextSend- , contextRecv- , updateMeasure- , withMeasure- , withReadLock- , withWriteLock- , withStateLock- , withRWLock+ Context (..),+ Hooks (..),+ Limit (..),+ Established (..),+ PendingRecvAction (..),+ RecordLayer (..),+ Locks (..),+ RoleParams (..),+ ctxEOF,+ ctxEstablished,+ withLog,+ ctxWithHooks,+ contextModifyHooks,+ setEOF,+ setEstablished,+ contextFlush,+ contextClose,+ contextSend,+ contextRecv,+ updateRecordLayer,+ updateMeasure,+ withMeasure,+ withReadLock,+ withWriteLock,+ withStateLock,+ withRWLock, -- * information- , Information(..)- , contextGetInformation+ Information (..),+ contextGetInformation, -- * Using context states- , throwCore- , usingState- , usingState_- , runTxState- , runRxState- , usingHState- , getHState- , getStateRNG- ) where+ throwCore,+ failOnEitherError,+ usingState,+ usingState_,+ runTxRecordState,+ runRxRecordState,+ usingHState,+ getHState,+ saveHState,+ restoreHState,+ getStateRNG,+ tls13orLater,+ decideRecordVersion, + -- * Misc+ HandshakeSync (..),+ TLS13State (..),+ defaultTLS13State,+ getTLS13State,+ modifyTLS13State,+ CipherChoice (..),+ makeCipherChoice,++ -- * RecordLimit+ setMyRecordLimit,+ enableMyRecordLimit,+ getMyRecordLimit,+ checkMyRecordLimit,+ setPeerRecordLimit,+ enablePeerRecordLimit,+ getPeerRecordLimit,+ checkPeerRecordLimit,+ newRecordLimitRef,++ -- * ECH+ HPKEF,+ getTLS13HPKE,+ setTLS13HPKE,+) where++import Control.Concurrent.MVar+import Control.Exception (throwIO)+import Control.Monad.State.Strict+import Data.ByteArray (convert)+import qualified Data.ByteArray as BA+import qualified Data.ByteString as B+import Data.IORef+import Data.Tuple+ import Network.TLS.Backend-import Network.TLS.Extension import Network.TLS.Cipher-import Network.TLS.Struct-import Network.TLS.Compression (Compression)-import Network.TLS.State+import Network.TLS.Crypto+import Network.TLS.Extension+import Network.TLS.Handshake.Control import Network.TLS.Handshake.State import Network.TLS.Hooks-import Network.TLS.Record.State-import Network.TLS.Parameters+import Network.TLS.Imports import Network.TLS.Measurement-import qualified Data.ByteString as B+import Network.TLS.Parameters+import Network.TLS.Record+import Network.TLS.Record.State+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Types+import Network.TLS.Util -import Control.Concurrent.MVar-import Control.Monad.State-import Control.Exception (throwIO, Exception())-import Data.IORef-import Data.Tuple+-- | A TLS Context keep tls specific state, parameters and backend information.+data Context+ = forall a.+ Monoid a =>+ Context+ { ctxBackend :: Backend+ -- ^ return the backend object associated with this context+ , ctxSupported :: Supported+ , ctxShared :: Shared+ , ctxDebug :: DebugParams+ , ctxTLSState :: MVar TLSState+ , ctxMeasurement :: IORef Measurement+ , ctxEOF_ :: IORef Bool+ -- ^ has the handle EOFed or not.+ , ctxEstablished_ :: IORef Established+ -- ^ has the handshake been done and been successful.+ , ctxTxRecordState :: MVar RecordState+ -- ^ current TX record state+ , ctxRxRecordState :: MVar RecordState+ -- ^ current RX record state+ , ctxHandshakeState :: MVar (Maybe HandshakeState)+ -- ^ optional handshake state+ , ctxRoleParams :: RoleParams+ -- ^ hooks for this context+ , ctxLocks :: Locks+ , ctxHooks :: IORef Hooks+ , -- TLS 1.3+ ctxTLS13State :: IORef TLS13State+ , ctxPendingRecvActions :: IORef [PendingRecvAction]+ , ctxPendingSendAction :: IORef (Maybe (Context -> IO ()))+ -- ^ pending post handshake authentication requests+ , -- QUIC+ ctxRecordLayer :: RecordLayer a+ , ctxHandshakeSync :: HandshakeSync+ , ctxQUICMode :: Bool+ , -- Misc+ ctxNeedEmptyPacket :: IORef Bool+ -- ^ empty packet workaround for CBC guessability.+ , ctxMyRecordLimit :: IORef RecordLimit+ -- ^ maximum size of plaintext fragments, val + 1 is used for TLS 1.3+ , ctxPeerRecordLimit :: IORef RecordLimit+ -- ^ maximum size of plaintext fragments, val + 1 is used for TLS 1.3+ , ctxHPKE :: IORef (Maybe (HPKEF, Int))+ } +type HPKEF = ByteString -> ByteString -> IO ByteString --- | Information related to a running context, e.g. current cipher-data Information = Information- { infoVersion :: Version- , infoCipher :: Cipher- , infoCompression :: Compression- } deriving (Show,Eq)+data RecordLimit+ = NoRecordLimit -- for QUIC+ | RecordLimit+ Int -- effective+ (Maybe Int) -- pending+ deriving (Eq, Show) --- | A TLS Context keep tls specific state, parameters and backend information.-data Context = Context- { ctxConnection :: Backend -- ^ return the backend object associated with this context- , ctxSupported :: Supported- , ctxShared :: Shared- , ctxCiphers :: [Cipher] -- ^ prepared list of allowed ciphers according to parameters- , ctxState :: MVar TLSState- , ctxMeasurement :: IORef Measurement- , ctxEOF_ :: IORef Bool -- ^ has the handle EOFed or not.- , ctxEstablished_ :: IORef Bool -- ^ has the handshake been done and been successful.- , ctxNeedEmptyPacket :: IORef Bool -- ^ empty packet workaround for CBC guessability.- , ctxSSLv2ClientHello :: IORef Bool -- ^ enable the reception of compatibility SSLv2 client hello.- -- the flag will be set to false regardless of its initial value- -- after the first packet received.- , ctxTxState :: MVar RecordState -- ^ current tx state- , ctxRxState :: MVar RecordState -- ^ current rx state- , ctxHandshake :: MVar (Maybe HandshakeState) -- ^ optional handshake state- , ctxDoHandshake :: Context -> IO ()- , ctxDoHandshakeWith :: Context -> Handshake -> IO ()- , ctxHooks :: IORef Hooks -- ^ hooks for this context- , ctxLockWrite :: MVar () -- ^ lock to use for writing data (including updating the state)- , ctxLockRead :: MVar () -- ^ lock to use for reading data (including updating the state)- , ctxLockState :: MVar () -- ^ lock used during read/write when receiving and sending packet.- -- it is usually nested in a write or read lock.+data RoleParams = RoleParams+ { doHandshake_ :: Context -> IO ()+ , doHandshakeWith_ :: Context -> HandshakeR -> IO ()+ , doRequestCertificate_ :: Context -> IO Bool+ , doPostHandshakeAuthWith_ :: Context -> Handshake13 -> IO () } +data Locks = Locks+ { lockWrite :: MVar ()+ -- ^ lock to use for writing data (including updating the state)+ , lockRead :: MVar ()+ -- ^ lock to use for reading data (including updating the state)+ , lockState :: MVar ()+ -- ^ lock used during read/write when receiving and sending packet.+ -- it is usually nested in a write or read lock.+ }++data CipherChoice = CipherChoice+ { cVersion :: Version+ , cCipher :: Cipher+ , cHash :: Hash+ , cZero :: Secret+ }+ deriving (Show)++makeCipherChoice :: Version -> Cipher -> CipherChoice+makeCipherChoice ver cipher = CipherChoice ver cipher h zero+ where+ h = cipherHash cipher+ zero = BA.replicate (hashDigestSize h) 0++data TLS13State = TLS13State+ { tls13stRecvNST :: Bool -- client+ , tls13stSentClientCert :: Bool -- client+ , tls13stRecvSF :: Bool -- client+ , tls13stSentCF :: Bool -- client+ , tls13stRecvCF :: Bool -- server+ , tls13stPendingRecvData :: Maybe ByteString -- client+ , tls13stPendingSentData :: [ByteString] -> [ByteString] -- client+ , tls13stRTT :: Millisecond+ , tls13st0RTT :: Bool -- client+ , tls13st0RTTAccepted :: Bool -- client+ , tls13stClientExtensions :: [ExtensionRaw] -- client+ , tls13stChoice :: ~CipherChoice -- client+ , tls13stHsKey :: Maybe (SecretTriple HandshakeSecret) -- client+ -- Actual session id for TLS 1.2, random value for TLS 1.3+ , tls13stSession :: Session+ , tls13stSentExtensions :: [ExtensionID]+ }++defaultTLS13State :: TLS13State+defaultTLS13State =+ TLS13State+ { tls13stRecvNST = False+ , tls13stSentClientCert = False+ , tls13stRecvSF = False+ , tls13stSentCF = False+ , tls13stRecvCF = False+ , tls13stPendingRecvData = Nothing+ , tls13stPendingSentData = id+ , tls13stRTT = 0+ , tls13st0RTT = False+ , tls13st0RTTAccepted = False+ , tls13stClientExtensions = []+ , tls13stChoice = undefined+ , tls13stHsKey = Nothing+ , tls13stSession = Session Nothing+ , tls13stSentExtensions = []+ }++getTLS13State :: Context -> IO TLS13State+getTLS13State Context{..} = readIORef ctxTLS13State++modifyTLS13State :: Context -> (TLS13State -> TLS13State) -> IO ()+modifyTLS13State Context{..} f = atomicModifyIORef' ctxTLS13State $ \st -> (f st, ())++data HandshakeSync+ = HandshakeSync+ (Context -> ClientState -> IO ())+ (Context -> ServerState -> IO ())++{- FOURMOLU_DISABLE -}+data RecordLayer a = RecordLayer+ { -- Writing.hs+ recordEncode12 :: Context -> Record Plaintext -> IO (Either TLSError a)+ , recordEncode13 :: Context -> Record Plaintext -> IO (Either TLSError a)+ , recordSendBytes :: Context -> a -> IO ()+ , -- Reading.hs+ recordRecv12 :: Context -> IO (Either TLSError (Record Plaintext))+ , recordRecv13 :: Context -> IO (Either TLSError (Record Plaintext))+ }+{- FOURMOLU_ENABLE -}++updateRecordLayer :: Monoid a => RecordLayer a -> Context -> Context+updateRecordLayer recordLayer Context{..} =+ Context{ctxRecordLayer = recordLayer, ..}++data Established+ = NotEstablished+ | EarlyDataAllowed Int -- server: remaining 0-RTT bytes allowed+ | EarlyDataNotAllowed Int -- sever: remaining 0-RTT packets allowed to skip+ | EarlyDataSending+ | Established+ deriving (Eq, Show)++data PendingRecvAction+ = -- | simple pending action. The first 'Bool' is necessity of alignment.+ PendingRecvAction Bool (Handshake13 -> IO ())+ | PendingRecvActionSelfUpdate Bool (Handshake13R -> IO ())+ | -- | pending action taking transcript hash up to preceding message+ -- The first 'Bool' is necessity of alignment.+ PendingRecvActionHash+ Bool+ (TranscriptHash -> Handshake13 -> IO ())+ updateMeasure :: Context -> (Measurement -> Measurement) -> IO ()-updateMeasure ctx f = do- x <- readIORef (ctxMeasurement ctx)- writeIORef (ctxMeasurement ctx) $! f x+updateMeasure ctx = modifyIORef' (ctxMeasurement ctx) withMeasure :: Context -> (Measurement -> IO a) -> IO a withMeasure ctx f = readIORef (ctxMeasurement ctx) >>= f +-- | A shortcut for 'backendFlush . ctxBackend'. contextFlush :: Context -> IO ()-contextFlush = backendFlush . ctxConnection+contextFlush = backendFlush . ctxBackend +-- | A shortcut for 'backendClose . ctxBackend'. contextClose :: Context -> IO ()-contextClose = backendClose . ctxConnection+contextClose = backendClose . ctxBackend -- | Information about the current context contextGetInformation :: Context -> IO (Maybe Information) contextGetInformation ctx = do- ver <- usingState_ ctx $ gets stVersion- (cipher,comp) <- failOnEitherError $ runRxState ctx $ gets $ \st -> (stCipher st, stCompression st)+ ver <- usingState_ ctx $ gets stVersion+ hstate <- getHState ctx+ let (ms, ems, cr, sr, hm13, grp, ech) =+ case hstate of+ Just st ->+ ( hstMainSecret st+ , hstExtendedMainSecret st+ , Just (hstClientRandom st)+ , hstServerRandom st+ , if ver == Just TLS13 then Just (hstTLS13HandshakeMode st) else Nothing+ , hstSupportedGroup st+ , hstTLS13ECHAccepted st+ )+ Nothing -> (Nothing, False, Nothing, Nothing, Nothing, Nothing, False)+ (cipher, comp) <-+ readMVar (ctxRxRecordState ctx) <&> \st -> (stCipher st, stCompression st)+ let accepted = case hstate of+ Just st -> hstTLS13RTT0Status st == RTT0Accepted+ Nothing -> False+ tls12resumption <- usingState_ ctx getTLS12SessionResuming case (ver, cipher) of- (Just v, Just c) -> return $ Just $ Information v c comp- _ -> return Nothing+ (Just v, Just c) ->+ return $+ Just $+ Information+ { infoVersion = v+ , infoCipher = c+ , infoCompression = comp+ , infoMainSecret = convert <$> ms+ , infoExtendedMainSecret = ems+ , infoClientRandom = cr+ , infoServerRandom = sr+ , infoSupportedGroup = grp+ , infoTLS12Resumption = tls12resumption+ , infoTLS13HandshakeMode = hm13+ , infoIsEarlyDataAccepted = accepted+ , infoIsECHAccepted = ech+ }+ _ -> return Nothing -contextSend :: Context -> Bytes -> IO ()-contextSend c b = updateMeasure c (addBytesSent $ B.length b) >> (backendSend $ ctxConnection c) b+contextSend :: Context -> ByteString -> IO ()+contextSend c b =+ updateMeasure c (addBytesSent $ B.length b) >> (backendSend $ ctxBackend c) b -contextRecv :: Context -> Int -> IO Bytes-contextRecv c sz = updateMeasure c (addBytesReceived sz) >> (backendRecv $ ctxConnection c) sz+contextRecv :: Context -> Int -> IO ByteString+contextRecv c sz = updateMeasure c (addBytesReceived sz) >> (backendRecv $ ctxBackend c) sz ctxEOF :: Context -> IO Bool ctxEOF ctx = readIORef $ ctxEOF_ ctx -ctxHasSSLv2ClientHello :: Context -> IO Bool-ctxHasSSLv2ClientHello ctx = readIORef $ ctxSSLv2ClientHello ctx--ctxDisableSSLv2ClientHello :: Context -> IO ()-ctxDisableSSLv2ClientHello ctx = writeIORef (ctxSSLv2ClientHello ctx) False- setEOF :: Context -> IO () setEOF ctx = writeIORef (ctxEOF_ ctx) True -ctxEstablished :: Context -> IO Bool+ctxEstablished :: Context -> IO Established ctxEstablished ctx = readIORef $ ctxEstablished_ ctx ctxWithHooks :: Context -> (Hooks -> IO a) -> IO a ctxWithHooks ctx f = readIORef (ctxHooks ctx) >>= f contextModifyHooks :: Context -> (Hooks -> Hooks) -> IO ()-contextModifyHooks ctx f = modifyIORef (ctxHooks ctx) f+contextModifyHooks ctx = modifyIORef' (ctxHooks ctx) -setEstablished :: Context -> Bool -> IO ()-setEstablished ctx v = writeIORef (ctxEstablished_ ctx) v+setEstablished :: Context -> Established -> IO ()+setEstablished ctx = writeIORef (ctxEstablished_ ctx) withLog :: Context -> (Logging -> IO ()) -> IO () withLog ctx f = ctxWithHooks ctx (f . hookLogging) -throwCore :: (MonadIO m, Exception e) => e -> m a-throwCore = liftIO . throwIO+throwCore :: MonadIO m => TLSError -> m a+throwCore = liftIO . throwIO . Uncontextualized failOnEitherError :: MonadIO m => m (Either TLSError a) -> m a failOnEitherError f = do ret <- f case ret of Left err -> throwCore err- Right r -> return r+ Right r -> return r usingState :: Context -> TLSSt a -> IO (Either TLSError a) usingState ctx f =- modifyMVar (ctxState ctx) $ \st ->- let (a, newst) = runTLSState f st- in newst `seq` return (newst, a)+ modifyMVar (ctxTLSState ctx) $ \st ->+ let (a, newst) = runTLSState f st+ in newst `seq` return (newst, a) usingState_ :: Context -> TLSSt a -> IO a usingState_ ctx f = failOnEitherError $ usingState ctx f -usingHState :: Context -> HandshakeM a -> IO a-usingHState ctx f = liftIO $ modifyMVar (ctxHandshake ctx) $ \mst ->- case mst of- Nothing -> throwCore $ Error_Misc "missing handshake"- Just st -> return $ swap (Just `fmap` runHandshake st f)+usingHState :: MonadIO m => Context -> HandshakeM a -> m a+usingHState ctx f = liftIO $ modifyMVar (ctxHandshakeState ctx) $ \case+ Nothing -> liftIO $ throwIO MissingHandshake+ Just st -> return $ swap (Just <$> runHandshake st f) -getHState :: Context -> IO (Maybe HandshakeState)-getHState ctx = liftIO $ readMVar (ctxHandshake ctx)+getHState :: MonadIO m => Context -> m (Maybe HandshakeState)+getHState ctx = liftIO $ readMVar (ctxHandshakeState ctx) -runTxState :: Context -> RecordM a -> IO (Either TLSError a)-runTxState ctx f = do- ver <- usingState_ ctx (getVersionWithDefault $ maximum $ supportedVersions $ ctxSupported ctx)- modifyMVar (ctxTxState ctx) $ \st ->- case runRecordM f ver st of- Left err -> return (st, Left err)+saveHState :: Context -> IO (Saved (Maybe HandshakeState))+saveHState ctx = saveMVar (ctxHandshakeState ctx)++restoreHState+ :: Context+ -> Saved (Maybe HandshakeState)+ -> IO (Saved (Maybe HandshakeState))+restoreHState ctx = restoreMVar (ctxHandshakeState ctx)++decideRecordVersion :: Context -> IO (Version, Bool)+decideRecordVersion ctx = usingState_ ctx $ do+ ver <- getVersionWithDefault (maximum $ supportedVersions $ ctxSupported ctx)+ hrr <- getTLS13HRR+ -- For TLS 1.3, ver' is only used in ClientHello.+ -- The record version of the first ClientHello SHOULD be TLS 1.0.+ -- The record version of the second ClientHello MUST be TLS 1.2.+ let ver'+ | ver >= TLS13 = if hrr then TLS12 else TLS10+ | otherwise = ver+ return (ver', ver >= TLS13)++runTxRecordState :: Context -> RecordM a -> IO (Either TLSError a)+runTxRecordState ctx f = do+ (ver, tls13) <- decideRecordVersion ctx+ let opt =+ RecordOptions+ { recordVersion = ver+ , recordTLS13 = tls13+ }+ modifyMVar (ctxTxRecordState ctx) $ \st ->+ case runRecordM f opt st of+ Left err -> return (st, Left err) Right (a, newSt) -> return (newSt, Right a) -runRxState :: Context -> RecordM a -> IO (Either TLSError a)-runRxState ctx f = do- ver <- usingState_ ctx getVersion- modifyMVar (ctxRxState ctx) $ \st ->- case runRecordM f ver st of- Left err -> return (st, Left err)+runRxRecordState :: Context -> RecordM a -> IO (Either TLSError a)+runRxRecordState ctx f = do+ ver <-+ usingState_+ ctx+ (getVersionWithDefault $ maximum $ supportedVersions $ ctxSupported ctx)+ -- For 1.3, ver is just ignored. So, it is not necessary to convert ver.+ let opt =+ RecordOptions+ { recordVersion = ver+ , recordTLS13 = ver >= TLS13+ }+ modifyMVar (ctxRxRecordState ctx) $ \st ->+ case runRecordM f opt st of+ Left err -> return (st, Left err) Right (a, newSt) -> return (newSt, Right a) -getStateRNG :: Context -> Int -> IO Bytes+getStateRNG :: Context -> Int -> IO ByteString getStateRNG ctx n = usingState_ ctx $ genRandom n withReadLock :: Context -> IO a -> IO a-withReadLock ctx f = withMVar (ctxLockRead ctx) (const f)+withReadLock ctx f = withMVar (lockRead $ ctxLocks ctx) (const f) withWriteLock :: Context -> IO a -> IO a-withWriteLock ctx f = withMVar (ctxLockWrite ctx) (const f)+withWriteLock ctx f = withMVar (lockWrite $ ctxLocks ctx) (const f) withRWLock :: Context -> IO a -> IO a withRWLock ctx f = withReadLock ctx $ withWriteLock ctx f withStateLock :: Context -> IO a -> IO a-withStateLock ctx f = withMVar (ctxLockState ctx) (const f)+withStateLock ctx f = withMVar (lockState $ ctxLocks ctx) (const f)++tls13orLater :: MonadIO m => Context -> m Bool+tls13orLater ctx = do+ ev <- liftIO $ usingState ctx $ getVersionWithDefault TLS12+ return $ case ev of+ Left _ -> False+ Right v -> v >= TLS13++--------------------------------++setMyRecordLimit :: Context -> Maybe Int -> IO ()+setMyRecordLimit ctx msiz = modifyIORef' (ctxMyRecordLimit ctx) change+ where+ change (RecordLimit n _) = RecordLimit n msiz+ change x = x++enableMyRecordLimit :: Context -> IO ()+enableMyRecordLimit ctx = modifyIORef' (ctxMyRecordLimit ctx) change+ where+ change (RecordLimit _ (Just n)) = RecordLimit n Nothing+ change x = x++getMyRecordLimit :: Context -> IO (Maybe Int)+getMyRecordLimit ctx = change <$> readIORef (ctxMyRecordLimit ctx)+ where+ change NoRecordLimit = Nothing+ change (RecordLimit n _) = Just n++checkMyRecordLimit :: Context -> IO Bool+checkMyRecordLimit ctx = chk <$> readIORef (ctxMyRecordLimit ctx)+ where+ chk NoRecordLimit = False+ chk (RecordLimit _ mx) = isJust mx++--------------------------------++setPeerRecordLimit :: Context -> Maybe Int -> IO ()+setPeerRecordLimit ctx msiz = modifyIORef' (ctxPeerRecordLimit ctx) change+ where+ change (RecordLimit n _) = RecordLimit n msiz+ change x = x++enablePeerRecordLimit :: Context -> IO ()+enablePeerRecordLimit ctx = modifyIORef' (ctxPeerRecordLimit ctx) change+ where+ change (RecordLimit _ (Just n)) = RecordLimit n Nothing+ change x = x++getPeerRecordLimit :: Context -> IO (Maybe Int)+getPeerRecordLimit ctx = change <$> readIORef (ctxPeerRecordLimit ctx)+ where+ change NoRecordLimit = Nothing+ change (RecordLimit n _) = Just n++checkPeerRecordLimit :: Context -> IO Bool+checkPeerRecordLimit ctx = chk <$> readIORef (ctxPeerRecordLimit ctx)+ where+ chk NoRecordLimit = False+ chk (RecordLimit _ mx) = isJust mx++newRecordLimitRef :: Maybe Int -> IO (IORef RecordLimit)+newRecordLimitRef Nothing = newIORef NoRecordLimit+newRecordLimitRef (Just n) = newIORef $ RecordLimit n Nothing++--------------------------------++setTLS13HPKE :: Context -> HPKEF -> Int -> IO ()+setTLS13HPKE ctx func nenc = writeIORef (ctxHPKE ctx) $ Just (func, nenc)++getTLS13HPKE :: Context -> IO (Maybe (HPKEF, Int))+getTLS13HPKE ctx = readIORef $ ctxHPKE ctx
Network/TLS/Core.hs view
@@ -1,123 +1,512 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_HADDOCK hide #-}-{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}--- |--- Module : Network.TLS.Core--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Core- (++module Network.TLS.Core ( -- * Internal packet sending and receiving- sendPacket- , recvPacket+ sendPacket12,+ recvPacket12, -- * Initialisation and Termination of context- , bye- , handshake+ bye,+ handshake, - -- * Next Protocol Negotiation- , getNegotiatedProtocol+ -- * Application Layer Protocol Negotiation+ getNegotiatedProtocol, + -- * Server Name Indication+ getClientSNI,+ -- * High level API- , sendData- , recvData- , recvData'- ) where+ sendData,+ recvData,+ recvData',+ updateKey,+ KeyUpdateRequest (..),+ requestCertificate,+) where +import qualified Control.Exception as E+import Control.Monad.State.Strict+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as C8+import qualified Data.ByteString.Lazy as L+import Data.IORef+import System.Timeout+ import Network.TLS.Context-import Network.TLS.Struct-import Network.TLS.State (getSession)-import Network.TLS.Parameters+import Network.TLS.Extension+import Network.TLS.Handshake+import Network.TLS.Handshake.Common+import Network.TLS.Handshake.Common13+import Network.TLS.Handshake.Server+import Network.TLS.Handshake.State+import Network.TLS.Handshake.State13+import Network.TLS.Handshake.TranscriptHash import Network.TLS.IO+import Network.TLS.Imports+import Network.TLS.Parameters+import Network.TLS.PostHandshake import Network.TLS.Session-import Network.TLS.Handshake-import Network.TLS.Util (catchException)+import Network.TLS.State (getRole, getSession) import qualified Network.TLS.State as S-import qualified Data.ByteString as B-import Data.ByteString.Char8 ()-import qualified Data.ByteString.Lazy as L-import qualified Control.Exception as E+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Types (+ HostName,+ Role (..),+ )+import Network.TLS.Util (catchException, mapChunks_) -import Control.Monad.State+-- | Handshake for a new TLS connection+-- This is to be called at the beginning of a connection, and during renegotiation.+-- Don't use this function as the acquire resource of 'bracket'.+handshake :: MonadIO m => Context -> m ()+handshake ctx = do+ handshake_ ctx+ -- Trying to receive an alert of client authentication failure+ liftIO $ do+ role <- usingState_ ctx getRole+ tls13 <- tls13orLater ctx+ 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} +rttFactor :: Int+rttFactor = 3 --- | notify the context that this side wants to close connection.--- this is important that it is called before closing the handle, otherwise+getRTT :: Context -> IO Int+getRTT ctx = do+ rtt <- tls13stRTT <$> getTLS13State ctx+ let rtt' = max (fromIntegral rtt) 10+ return (rtt' * rttFactor * 1000) -- ms to us++-- | Notify the context that this side wants to close connection.+-- This is important that it is called before closing the handle, otherwise -- the session might not be resumable (for version < TLS1.2).+-- This doesn't actually close the handle. ----- this doesn't actually close the handle+-- Proper usage is as follows:+--+-- > ctx <- contextNew <backend> <params>+-- > handshake ctx+-- > ...+-- > bye+--+-- The following code ensures nothing but is no harm.+--+-- > bracket (contextNew <backend> <params>) bye $ \ctx -> do+-- > handshake ctx+-- > ... bye :: MonadIO m => Context -> m ()-bye ctx = sendPacket ctx $ Alert [(AlertLevel_Warning, CloseNotify)]+bye ctx = liftIO $ do+ eof <- ctxEOF ctx+ tls13 <- tls13orLater ctx+ when (tls13 && not eof) $ do+ role <- usingState_ ctx getRole+ if role == ClientRole+ then do+ withWriteLock ctx $ sendCFifNecessary ctx+ -- receiving NewSessionTicket+ let chk = tls13stRecvNST <$> getTLS13State ctx+ recvNST <- chk+ unless recvNST $ do+ rtt <- getRTT ctx+ void $ timeout rtt $ recvHS13 ctx chk+ else do+ -- receiving Client Finished+ let chk = tls13stRecvCF <$> getTLS13State ctx+ recvCF <- chk+ unless recvCF $ do+ -- no chance to measure RTT before receiving CF+ -- fixme: 1sec is good enough?+ let rtt = 1000000+ void $ timeout rtt $ recvHS13 ctx chk+ bye_ ctx --- | If the Next Protocol Negotiation extension has been used, this will+bye_ :: MonadIO m => Context -> m ()+bye_ ctx = liftIO $ do+ -- Although setEOF is always protected by the read lock, here we don't try+ -- to wrap ctxEOF with it, so that function bye can still be called+ -- concurrently to a blocked recvData.+ eof <- ctxEOF ctx+ tls13 <- tls13orLater ctx+ unless eof $+ withWriteLock ctx $+ if tls13+ then sendPacket13 ctx $ Alert13 [(AlertLevel_Warning, CloseNotify)]+ else sendPacket12 ctx $ Alert [(AlertLevel_Warning, CloseNotify)]++-- | If the ALPN extensions have been used, this will -- return get the protocol agreed upon.-getNegotiatedProtocol :: MonadIO m => Context -> m (Maybe B.ByteString)+getNegotiatedProtocol :: MonadIO m => Context -> m (Maybe ByteString) getNegotiatedProtocol ctx = liftIO $ usingState_ ctx S.getNegotiatedProtocol +-- | If the Server Name Indication extension has been used, return the+-- hostname specified by the client.+getClientSNI :: MonadIO m => Context -> m (Maybe HostName)+getClientSNI ctx = liftIO $ usingState_ ctx S.getClientSNI++sendCFifNecessary :: Context -> IO ()+sendCFifNecessary ctx = do+ st <- getTLS13State ctx+ let recvSF = tls13stRecvSF st+ sentCF = tls13stSentCF st+ when (recvSF && not sentCF) $ do+ msend <- readIORef (ctxPendingSendAction ctx)+ case msend of+ Nothing -> return ()+ Just sendAction -> do+ sendAction ctx+ writeIORef (ctxPendingSendAction ctx) Nothing+ -- | sendData sends a bunch of data. -- It will automatically chunk data to acceptable packet size sendData :: MonadIO m => Context -> L.ByteString -> m ()-sendData ctx dataToSend = liftIO (checkValid ctx) >> mapM_ sendDataChunk (L.toChunks dataToSend)- where sendDataChunk d- | B.length d > 16384 = do- let (sending, remain) = B.splitAt 16384 d- sendPacket ctx $ AppData sending- sendDataChunk remain- | otherwise = sendPacket ctx $ AppData d+sendData _ "" = return ()+sendData ctx dataToSend = liftIO $ do+ tls13 <- tls13orLater ctx+ let sendP bs+ | tls13 = do+ sendPacket13 ctx $ AppData13 bs+ role <- usingState_ ctx getRole+ sentCF <- tls13stSentCF <$> getTLS13State ctx+ rtt0 <- tls13st0RTT <$> getTLS13State ctx+ when (role == ClientRole && rtt0 && not sentCF) $+ modifyTLS13State ctx $+ \st -> st{tls13stPendingSentData = tls13stPendingSentData st . (bs :)}+ | otherwise = sendPacket12 ctx $ AppData bs+ when tls13 $ withWriteLock ctx $ sendCFifNecessary ctx+ withWriteLock ctx $ do+ checkValid ctx+ -- 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, don't adjust for TLS 1.3+ mapM_ (mapChunks_ mlen sendP) (L.toChunks dataToSend) --- | recvData get data out of Data packet, and automatically renegotiate if--- a Handshake ClientHello is received-recvData :: MonadIO m => Context -> m B.ByteString+-- | Get data out of Data packet, and automatically renegotiate if a Handshake+-- ClientHello is received. An empty result means EOF.+recvData :: MonadIO m => Context -> m ByteString recvData ctx = liftIO $ do- checkValid ctx- E.catchJust safeHandleError_EOF- doRecv- (\() -> return B.empty)- where doRecv = do- pkt <- withReadLock ctx $ recvPacket ctx- either onError process pkt- - safeHandleError_EOF Error_EOF = Just ()- safeHandleError_EOF _ = Nothing- - onError err@(Error_Protocol (reason,fatal,desc)) =- terminate err (if fatal then AlertLevel_Fatal else AlertLevel_Warning) desc reason- onError err =- terminate err AlertLevel_Fatal InternalError (show err)+ tls13 <- tls13orLater ctx+ withReadLock ctx $ do+ checkValid ctx+ -- We protect with a read lock both reception and processing of the+ -- packet, because don't want another thread to receive a new packet+ -- before this one has been fully processed.+ --+ -- Even when recvData12/recvData13 loops, we only need to call function+ -- checkValid once. Since we hold the read lock, no concurrent call+ -- will impact the validity of the context.+ if tls13 then recvData13 ctx else recvData12 ctx - process (Handshake [ch@(ClientHello {})]) =- withRWLock ctx ((ctxDoHandshakeWith ctx) ctx ch) >> recvData ctx- process (Handshake [hr@HelloRequest]) =- withRWLock ctx ((ctxDoHandshakeWith ctx) ctx hr) >> recvData ctx+recvData12 :: Context -> IO ByteString+recvData12 ctx = do+ pkt <- recvPacket12 ctx+ either (onError terminate12) process pkt+ where+ process (Handshake [ch@ClientHello{}] [b]) =+ handshakeWith ctx (ch, b) >> recvData12 ctx+ process (Handshake [hr@HelloRequest] [b]) =+ handshakeWith ctx (hr, b) >> recvData12 ctx+ -- UserCanceled should be followed by a close_notify.+ -- fixme: is it safe to call recvData12?+ process (Alert [(AlertLevel_Warning, UserCanceled)]) = return B.empty+ process (Alert [(AlertLevel_Warning, CloseNotify)]) = tryBye ctx >> setEOF ctx >> return B.empty+ process (Alert [(AlertLevel_Fatal, desc)]) = do+ setEOF ctx+ E.throwIO+ ( Terminated+ True+ ("received fatal error: " ++ show desc)+ (Error_Protocol "remote side fatal error" desc)+ ) - process (Alert [(AlertLevel_Warning, CloseNotify)]) = tryBye >> setEOF ctx >> return B.empty- process (Alert [(AlertLevel_Fatal, desc)]) = do- setEOF ctx- E.throwIO (Terminated True ("received fatal error: " ++ show desc) (Error_Protocol ("remote side fatal error", True, desc)))+ -- when receiving empty appdata, we just retry to get some data.+ process (AppData "") = recvData12 ctx+ process (AppData x) = return x+ process p = do+ let reason = "unexpected message " ++ show p+ terminate12 (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason - -- when receiving empty appdata, we just retry to get some data.- process (AppData "") = recvData ctx- process (AppData x) = return x- process p = let reason = "unexpected message " ++ show p in- terminate (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason+ terminate12 = terminateWithWriteLock ctx (sendPacket12 ctx . Alert) - terminate :: TLSError -> AlertLevel -> AlertDescription -> String -> IO a- terminate err level desc reason = do- session <- usingState_ ctx getSession- case session of- Session Nothing -> return ()- Session (Just sid) -> sessionInvalidate (sharedSessionManager $ ctxShared ctx) sid- catchException (sendPacket ctx $ Alert [(level, desc)]) (\_ -> return ())- setEOF ctx- E.throwIO (Terminated False reason err)+recvData13 :: Context -> IO ByteString+recvData13 ctx = do+ mdat <- tls13stPendingRecvData <$> getTLS13State ctx+ case mdat of+ Nothing -> do+ pkt <- recvPacket13 ctx+ either (onError (terminate13 ctx)) process pkt+ Just dat -> do+ modifyTLS13State ctx $ \st -> st{tls13stPendingRecvData = Nothing}+ return dat+ where+ -- 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+ process (Alert13 [(AlertLevel_Fatal, desc)]) = do+ setEOF ctx+ E.throwIO+ ( Terminated+ True+ ("received fatal error: " ++ show desc)+ (Error_Protocol "remote side fatal error" desc)+ )+ process (Handshake13 hs bs) = do+ loopHandshake13 $ zip hs bs+ recvData13 ctx+ -- when receiving empty appdata, we just retry to get some data.+ process (AppData13 "") = recvData13 ctx+ process (AppData13 x) = do+ let chunkLen = C8.length x+ established <- ctxEstablished ctx+ case established of+ EarlyDataAllowed maxSize+ | chunkLen <= maxSize -> do+ setEstablished ctx $ EarlyDataAllowed (maxSize - chunkLen)+ return x+ | otherwise ->+ let reason = "early data overflow"+ in terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason+ EarlyDataNotAllowed n+ | n > 0 -> do+ setEstablished ctx $ EarlyDataNotAllowed (n - 1)+ recvData13 ctx -- ignore "x"+ | otherwise -> do+ let reason = "early data deprotect overflow"+ terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason+ Established -> return x+ _ -> throwCore $ Error_Protocol "data at not-established" UnexpectedMessage+ process ChangeCipherSpec13 = do+ established <- ctxEstablished ctx+ if established /= Established+ then recvData13 ctx+ else do+ let reason = "CSS after Finished"+ terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason+ process p = do+ let reason = "unexpected message " ++ show p+ terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason - -- the other side could have close the connection already, so wrap- -- this in a try and ignore all exceptions- tryBye = catchException (bye ctx) (\_ -> return ())+ loopHandshake13 [] = return ()+ -- fixme: some implementations send multiple NST at the same time.+ -- Only the first one is used at this moment.+ loopHandshake13 ((NewSessionTicket13 life add nonce (SessionIDorTicket_ ticket) exts, _b) : hbs) = do+ role <- usingState_ ctx S.getRole+ unless (role == ClientRole) $ do+ let reason = "Session ticket is allowed for client only"+ terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason+ -- This part is similar to handshake code, so protected with+ -- read+write locks (which is also what we use for all calls to the+ -- session manager).+ withWriteLock ctx $ do+ Just resumptionSecret <- usingHState ctx getTLS13ResumptionSecret+ (_, usedCipher, _, _) <- getTxRecordState ctx+ -- mMaxSize is always Just, but anyway+ let extract (EarlyDataIndication mMaxSize) =+ maybe 0 (fromIntegral . safeNonNegative32) mMaxSize+ let choice = makeCipherChoice TLS13 usedCipher+ psk = derivePSK choice resumptionSecret nonce+ maxSize =+ lookupAndDecode+ EID_EarlyData+ MsgTNewSessionTicket+ exts+ 0+ extract+ life7d = min life 604800 -- 7 days max+ tinfo <- createTLS13TicketInfo life7d (Right add) Nothing+ sdata <- getSessionData13 ctx usedCipher tinfo maxSize psk+ let ticket' = B.copy ticket+ void $ sessionEstablish (sharedSessionManager $ ctxShared ctx) ticket' sdata+ modifyTLS13State ctx $ \st -> st{tls13stRecvNST = True}+ loopHandshake13 hbs+ loopHandshake13 ((KeyUpdate13 mode, _b) : hbs) = do+ let multipleKeyUpdate = any (\(h, _) -> isKeyUpdate13 h) hbs+ when multipleKeyUpdate $ do+ let reason = "Multiple KeyUpdate is not allowed in one record"+ terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason+ when (ctxQUICMode ctx) $ do+ let reason = "KeyUpdate is not allowed for QUIC"+ terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason+ checkAlignment ctx+ established <- ctxEstablished ctx+ -- Though RFC 8446 Sec 4.6.3 does not clearly says,+ -- unidirectional key update is legal.+ -- So, we don't have to check if this key update is corresponding+ -- to key update (update_requested) which we sent.+ if established == Established+ then do+ 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+ -- updated.+ when (mode == UpdateRequested) $ withWriteLock ctx $ do+ sendPacket13 ctx $ Handshake13 [KeyUpdate13 UpdateNotRequested] []+ keyUpdate ctx getTxRecordState setTxRecordState+ loopHandshake13 hbs+ 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+ rtt0 <- tls13st0RTT <$> getTLS13State ctx+ when rtt0 $ case h of+ ServerHello13 SH{..} ->+ when (isHelloRetryRequest shRandom) $ do+ clearTxRecordState ctx+ let reason = "HRR is not allowed for 0-RTT"+ terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason+ _ -> return ()+ cont <- popAction ctx hb+ when cont $ loopHandshake13 hbs +recvHS13 :: Context -> IO Bool -> IO ()+recvHS13 ctx breakLoop = do+ pkt <- recvPacket13 ctx+ -- fixme: Left+ either (\_ -> return ()) process pkt+ where+ -- UserCanceled MUST be followed by a CloseNotify.+ process (Alert13 [(AlertLevel_Warning, CloseNotify)]) = tryBye ctx >> setEOF ctx+ process (Alert13 [(AlertLevel_Fatal, _desc)]) = setEOF ctx+ process (Handshake13 hs bs) = do+ loopHandshake13 $ zip hs bs+ stop <- breakLoop+ unless stop $ recvHS13 ctx breakLoop+ process _ = recvHS13 ctx breakLoop++ loopHandshake13 [] = return ()+ -- fixme: some implementations send multiple NST at the same time.+ -- Only the first one is used at this moment.+ loopHandshake13 ((NewSessionTicket13 life add nonce (SessionIDorTicket_ ticket) exts, _b) : hbs) = do+ role <- usingState_ ctx S.getRole+ unless (role == ClientRole) $ do+ let reason = "Session ticket is allowed for client only"+ terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason+ -- This part is similar to handshake code, so protected with+ -- read+write locks (which is also what we use for all calls to the+ -- session manager).+ withWriteLock ctx $ do+ Just resumptionSecret <- usingHState ctx getTLS13ResumptionSecret+ (_, usedCipher, _, _) <- getTxRecordState ctx+ let choice = makeCipherChoice TLS13 usedCipher+ psk = derivePSK choice resumptionSecret nonce+ maxSize =+ lookupAndDecode+ EID_EarlyData+ MsgTNewSessionTicket+ exts+ 0+ (\(EarlyDataIndication mms) -> fromIntegral $ safeNonNegative32 $ fromJust mms)+ life7d = min life 604800 -- 7 days max+ tinfo <- createTLS13TicketInfo life7d (Right add) Nothing+ sdata <- getSessionData13 ctx usedCipher tinfo maxSize psk+ let ticket' = B.copy ticket+ void $ sessionEstablish (sharedSessionManager $ ctxShared ctx) ticket' sdata+ modifyTLS13State ctx $ \st -> st{tls13stRecvNST = True}+ loopHandshake13 hbs+ loopHandshake13 (hb : hbs) = do+ cont <- popAction ctx hb+ when cont $ loopHandshake13 hbs++terminate13+ :: Context -> TLSError -> AlertLevel -> AlertDescription -> String -> IO a+terminate13 ctx = terminateWithWriteLock ctx (sendPacket13 ctx . Alert13)++popAction :: Context -> Handshake13R -> IO Bool+popAction ctx hb@(h, _b) = do+ mPendingRecvAction <- popPendingRecvAction ctx+ case mPendingRecvAction of+ Nothing -> return False+ Just action -> do+ -- Pending actions are executed with read+write locks, just+ -- like regular handshake code.+ withWriteLock ctx $+ handleException ctx $ do+ case action of+ PendingRecvAction needAligned pa -> do+ when needAligned $ checkAlignment ctx+ updateTranscriptHash13 ctx hb+ pa h+ PendingRecvActionSelfUpdate needAligned pa -> do+ when needAligned $ checkAlignment ctx+ pa hb+ PendingRecvActionHash needAligned pa -> do+ when needAligned $ checkAlignment ctx+ d <- transcriptHash ctx "Pending action"+ updateTranscriptHash13 ctx hb+ pa d h+ -- Client: after receiving SH, app data is coming.+ -- this loop tries to receive it.+ -- App key must be installed before receiving+ -- the app data.+ sendCFifNecessary ctx+ return True++checkAlignment :: Context -> IO ()+checkAlignment ctx = do+ complete <- isRecvComplete ctx+ unless complete $ do+ let reason = "received message not aligned with record boundary"+ terminate13 ctx (Error_Misc reason) AlertLevel_Fatal UnexpectedMessage reason++-- the other side could have close the connection already, so wrap+-- this in a try and ignore all exceptions+tryBye :: Context -> IO ()+tryBye ctx = catchException (bye_ ctx) (\_ -> return ())++onError+ :: Monad m+ => (TLSError -> AlertLevel -> AlertDescription -> String -> m ByteString)+ -> TLSError+ -> m ByteString+onError _ Error_EOF =+ -- Not really an error.+ return B.empty+onError terminate err = terminate err lvl ad reason+ where+ (lvl, ad) = errorToAlert err+ reason = errorToAlertMessage err++terminateWithWriteLock+ :: Context+ -> ([(AlertLevel, AlertDescription)] -> IO ())+ -> TLSError+ -> AlertLevel+ -> AlertDescription+ -> String+ -> IO a+terminateWithWriteLock ctx send err level desc reason = withWriteLock ctx $ do+ tls13 <- tls13orLater ctx+ unless tls13 $ do+ -- TLS 1.2 uses the same session ID and session data+ -- for all resumed sessions.+ --+ -- TLS 1.3 changes session data for every resumed session.+ session <- usingState_ ctx getSession+ case session of+ Session Nothing -> return ()+ Session (Just sid) ->+ -- calling even session ticket manager anyway+ sessionInvalidate (sharedSessionManager $ ctxShared ctx) sid+ catchException (send [(level, desc)]) (\_ -> return ())+ setEOF ctx+ debugError (ctxDebug ctx) reason+ E.throwIO (Terminated False reason err)+ {-# DEPRECATED recvData' "use recvData that returns strict bytestring" #-}+ -- | same as recvData but returns a lazy bytestring. recvData' :: MonadIO m => Context -> m L.ByteString-recvData' ctx = recvData ctx >>= return . L.fromChunks . (:[])+recvData' ctx = L.fromChunks . (: []) <$> recvData ctx
Network/TLS/Credentials.hs view
@@ -1,106 +1,186 @@--- |--- Module : Network.TLS.Credentials--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Credentials- ( Credential- , Credentials(..)- , credentialLoadX509- , credentialLoadX509FromMemory- , credentialsFindForSigning- , credentialsFindForDecrypting- , credentialsListSigningAlgorithms- ) where+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -Wno-orphans #-} -import Data.Monoid-import Data.Maybe (catMaybes)-import Data.List (find)-import Network.TLS.Struct-import Network.TLS.X509+module Network.TLS.Credentials (+ Credential,+ Credentials (..),+ credentialLoadX509,+ credentialLoadX509FromMemory,+ credentialLoadX509Chain,+ credentialLoadX509ChainFromMemory,+ credentialsFindForSigning,+ credentialsFindForDecrypting,+ credentialsListSigningAlgorithms,+ credentialPublicPrivateKeys,+ credentialMatchesHashSignatures,+) where++import Data.X509+import qualified Data.X509 as X509 import Data.X509.File import Data.X509.Memory-import Data.X509 +import Network.TLS.Crypto+import Network.TLS.Imports+import qualified Network.TLS.Struct as TLS+import Network.TLS.X509+ type Credential = (CertificateChain, PrivKey) -newtype Credentials = Credentials [Credential]+newtype Credentials = Credentials [Credential] deriving (Show) +instance Semigroup Credentials where+ Credentials l1 <> Credentials l2 = Credentials (l1 ++ l2)+ instance Monoid Credentials where mempty = Credentials []+#if !(MIN_VERSION_base(4,11,0)) mappend (Credentials l1) (Credentials l2) = Credentials (l1 ++ l2)+#endif -- | try to create a new credential object from a public certificate -- and the associated private key that are stored on the filesystem -- in PEM format.-credentialLoadX509 :: FilePath -- ^ public certificate (X.509 format)- -> FilePath -- ^ private key associated- -> IO (Either String Credential)-credentialLoadX509 certFile privateFile = do+credentialLoadX509+ :: FilePath+ -- ^ public certificate (X.509 format)+ -> FilePath+ -- ^ private key associated+ -> IO (Either String Credential)+credentialLoadX509 certFile = credentialLoadX509Chain certFile []++-- | similar to 'credentialLoadX509' but take the certificate+-- and private key from memory instead of from the filesystem.+credentialLoadX509FromMemory+ :: ByteString+ -> ByteString+ -> Either String Credential+credentialLoadX509FromMemory certData =+ credentialLoadX509ChainFromMemory certData []++-- | similar to 'credentialLoadX509' but also allow specifying chain+-- certificates.+credentialLoadX509Chain+ :: FilePath+ -- ^ public certificate (X.509 format)+ -> [FilePath]+ -- ^ chain certificates (X.509 format)+ -> FilePath+ -- ^ private key associated+ -> IO (Either String Credential)+credentialLoadX509Chain certFile chainFiles privateFile = do x509 <- readSignedObject certFile+ chains <- mapM readSignedObject chainFiles keys <- readKeyFile privateFile case keys of- [] -> return $ Left "no keys found"- (k:_) -> return $ Right (CertificateChain x509, k)+ [] -> return $ Left "no keys found"+ (k : _) -> return $ Right (CertificateChain . concat $ x509 : chains, k) --- | similar to 'credentialLoadX509' but take the certificate--- and private key from memory instead of from the filesystem.-credentialLoadX509FromMemory :: Bytes- -> Bytes- -> Either String Credential-credentialLoadX509FromMemory certData privateData = do+-- | similar to 'credentialLoadX509FromMemory' but also allow+-- specifying chain certificates.+credentialLoadX509ChainFromMemory+ :: ByteString+ -> [ByteString]+ -> ByteString+ -> Either String Credential+credentialLoadX509ChainFromMemory certData chainData privateData = let x509 = readSignedObjectFromMemory certData+ chains = map readSignedObjectFromMemory chainData keys = readKeyFileFromMemory privateData in case keys of- [] -> Left "no keys found"- (k:_) -> Right (CertificateChain x509, k)- where+ [] -> Left "no keys found"+ (k : _) -> Right (CertificateChain . concat $ x509 : chains, k) -credentialsListSigningAlgorithms :: Credentials -> [SignatureAlgorithm]-credentialsListSigningAlgorithms (Credentials l) = catMaybes $ map credentialCanSign l+credentialsListSigningAlgorithms :: Credentials -> [KeyExchangeSignatureAlg]+credentialsListSigningAlgorithms (Credentials l) = mapMaybe credentialCanSign l -credentialsFindForSigning :: SignatureAlgorithm -> Credentials -> Maybe (CertificateChain, PrivKey)-credentialsFindForSigning sigAlg (Credentials l) = find forSigning l- where forSigning cred = Just sigAlg == credentialCanSign cred+credentialsFindForSigning+ :: KeyExchangeSignatureAlg -> Credentials -> Maybe Credential+credentialsFindForSigning kxsAlg (Credentials l) = find forSigning l+ where+ forSigning cred = case credentialCanSign cred of+ Nothing -> False+ Just kxs -> kxs == kxsAlg -credentialsFindForDecrypting :: Credentials -> Maybe (CertificateChain, PrivKey)+credentialsFindForDecrypting :: Credentials -> Maybe Credential credentialsFindForDecrypting (Credentials l) = find forEncrypting l- where forEncrypting cred = Just () == credentialCanDecrypt cred+ where+ forEncrypting cred = Just () == credentialCanDecrypt cred -- here we assume that only RSA is supported for key encipherment (encryption/decryption) -- we keep the same construction as 'credentialCanSign', returning a Maybe of () in case -- this change in future. credentialCanDecrypt :: Credential -> Maybe () credentialCanDecrypt (chain, priv) =- case extensionGet (certExtensions cert) of- Nothing -> Just ()- Just (ExtKeyUsage flags)- | KeyUsage_keyEncipherment `elem` flags ->- case (pub, priv) of- (PubKeyRSA _, PrivKeyRSA _) -> Just ()- _ -> Nothing- | otherwise -> Nothing- where cert = signedObject $ getSigned signed- pub = certPubKey cert- signed = getCertificateChainLeaf chain+ case (pub, priv) of+ (PubKeyRSA _, PrivKeyRSA _) ->+ case extensionGet (certExtensions cert) of+ Nothing -> Just ()+ Just (ExtKeyUsage flags)+ | KeyUsage_keyEncipherment `elem` flags -> Just ()+ | otherwise -> Nothing+ _ -> Nothing+ where+ cert = getCertificate signed+ pub = certPubKey cert+ signed = getCertificateChainLeaf chain -credentialCanSign :: Credential -> Maybe SignatureAlgorithm+credentialCanSign :: Credential -> Maybe KeyExchangeSignatureAlg credentialCanSign (chain, priv) = case extensionGet (certExtensions cert) of- Nothing -> getSignatureAlg pub priv+ Nothing -> findKeyExchangeSignatureAlg (pub, priv) Just (ExtKeyUsage flags)- | KeyUsage_digitalSignature `elem` flags -> getSignatureAlg pub priv- | otherwise -> Nothing- where cert = signedObject $ getSigned signed- pub = certPubKey cert- signed = getCertificateChainLeaf chain+ | KeyUsage_digitalSignature `elem` flags ->+ findKeyExchangeSignatureAlg (pub, priv)+ | otherwise -> Nothing+ where+ cert = getCertificate signed+ pub = certPubKey cert+ signed = getCertificateChainLeaf chain -getSignatureAlg :: PubKey -> PrivKey -> Maybe SignatureAlgorithm-getSignatureAlg pub priv =- case (pub, priv) of- (PubKeyRSA _, PrivKeyRSA _) -> Just SignatureRSA- (PubKeyDSA _, PrivKeyDSA _) -> Just SignatureDSS- --(PubKeyECDSA _, PrivKeyECDSA _) -> Just SignatureECDSA- _ -> Nothing+credentialPublicPrivateKeys :: Credential -> (PubKey, PrivKey)+credentialPublicPrivateKeys (chain, priv) = pub `seq` (pub, priv)+ where+ cert = getCertificate signed+ pub = certPubKey cert+ signed = getCertificateChainLeaf chain++getHashSignature :: SignedCertificate -> Maybe TLS.HashAndSignatureAlgorithm+getHashSignature signed =+ case signedAlg $ getSigned signed of+ SignatureALG hashAlg PubKeyALG_RSA -> convertHash TLS.SignatureRSA hashAlg+ SignatureALG hashAlg PubKeyALG_DSA -> convertHash TLS.SignatureDSA hashAlg+ SignatureALG hashAlg PubKeyALG_EC -> convertHash TLS.SignatureECDSA hashAlg+ SignatureALG X509.HashSHA256 PubKeyALG_RSAPSS -> Just (TLS.HashIntrinsic, TLS.SignatureRSApssRSAeSHA256)+ SignatureALG X509.HashSHA384 PubKeyALG_RSAPSS -> Just (TLS.HashIntrinsic, TLS.SignatureRSApssRSAeSHA384)+ 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)+ _ -> Nothing+ where+ convertHash sig X509.HashMD5 = Just (TLS.HashMD5, sig)+ convertHash sig X509.HashSHA1 = Just (TLS.HashSHA1, sig)+ convertHash sig X509.HashSHA224 = Just (TLS.HashSHA224, sig)+ convertHash sig X509.HashSHA256 = Just (TLS.HashSHA256, sig)+ convertHash sig X509.HashSHA384 = Just (TLS.HashSHA384, sig)+ convertHash sig X509.HashSHA512 = Just (TLS.HashSHA512, sig)+ convertHash _ _ = Nothing++-- | Checks whether certificate signatures in the chain comply with a list of+-- hash/signature algorithm pairs. Currently the verification applies only to+-- the signature of the leaf certificate, and when not self-signed. This may+-- be extended to additional chain elements in the future.+credentialMatchesHashSignatures+ :: [TLS.HashAndSignatureAlgorithm] -> Credential -> Bool+credentialMatchesHashSignatures hashSigs (chain, _) =+ case chain of+ CertificateChain [] -> True+ CertificateChain (leaf : _) -> isSelfSigned leaf || matchHashSig leaf+ where+ matchHashSig signed = case getHashSignature signed of+ Nothing -> False+ Just hs -> hs `elem` hashSigs++ isSelfSigned signed =+ let cert = getCertificate signed+ in certSubjectDN cert == certIssuerDN cert
Network/TLS/Crypto.hs view
@@ -1,167 +1,502 @@-{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE ExistentialQuantification #-}-module Network.TLS.Crypto- ( HashCtx(..)- , hashInit- , hashUpdate- , hashUpdateSSL- , hashFinal+{-# LANGUAGE RankNTypes #-}+{-# OPTIONS_HADDOCK hide #-} - , module Network.TLS.Crypto.DH+module Network.TLS.Crypto (+ HashContext,+ HashCtx,+ hashInit,+ hashUpdate,+ hashUpdates,+ hashUpdateSSL,+ hashFinal,+ module Network.TLS.Crypto.DH,+ module Network.TLS.Crypto.IES,+ module Network.TLS.Crypto.Types, - -- * constructor- , hashMD5SHA1- , hashSHA1- , hashSHA256- , hashSHA512+ -- * Hash+ hash,+ hashChunks,+ Hash (..),+ hashName,+ hashDigestSize,+ hashBlockSize, -- * key exchange generic interface- , PubKey(..)- , PrivKey(..)- , PublicKey- , PrivateKey- , HashDescr(..)- , kxEncrypt- , kxDecrypt- , kxSign- , kxVerify- , KxError(..)- ) where+ PubKey (..),+ PrivKey (..),+ PublicKey,+ PrivateKey,+ SignatureParams (..),+ isKeyExchangeSignatureKey,+ findKeyExchangeSignatureAlg,+ findFiniteFieldGroup,+ findEllipticCurveGroup,+ kxEncrypt,+ kxDecrypt,+ kxSign,+ kxVerify,+ kxCanUseRSApkcs1,+ kxCanUseRSApss,+ kxSupportedPrivKeyEC,+ KxError (..),+ RSAEncoding (..),+) where -import qualified Crypto.Hash.SHA512 as SHA512-import qualified Crypto.Hash.SHA256 as SHA256-import qualified Crypto.Hash.SHA1 as SHA1-import qualified Crypto.Hash.MD5 as MD5-import qualified Data.ByteString as B-import Data.ByteString (ByteString)-import Crypto.PubKey.HashDescr+import qualified Crypto.ECC as ECDSA+import Crypto.Error+import qualified Crypto.Hash as H+import Crypto.Number.Basic (numBits)+import qualified Crypto.PubKey.DH as DH import qualified Crypto.PubKey.DSA as DSA+import qualified Crypto.PubKey.ECC.ECDSA as ECDSA_ECC+import qualified Crypto.PubKey.ECC.Types as ECC+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.RSA as RSA import qualified Crypto.PubKey.RSA.PKCS15 as RSA+import qualified Crypto.PubKey.RSA.PSS as PSS import Crypto.Random-import Data.X509 (PrivKey(..), PubKey(..))+import Data.ASN1.BinaryEncoding (BER (..), DER (..))+import Data.ASN1.Encoding+import Data.ASN1.Types+import Data.ByteArray (ByteArray, ByteArrayAccess, ScrubbedBytes, convert)+import qualified Data.ByteArray as BA+import qualified Data.ByteString as B+import Data.Proxy+import Data.X509 (+ PrivKey (..),+ PrivKeyEC (..),+ PubKey (..),+ PubKeyEC (..),+ SerializedPoint (..),+ )+import Data.X509.EC (ecPrivKeyCurveName, ecPubKeyCurveName, unserializePoint)+ import Network.TLS.Crypto.DH+import Network.TLS.Crypto.IES+import Network.TLS.Crypto.Types+import Network.TLS.Imports -import Data.ASN1.Types-import Data.ASN1.Encoding-import Data.ASN1.BinaryEncoding (DER(..), BER(..))+---------------------------------------------------------------- {-# DEPRECATED PublicKey "use PubKey" #-} type PublicKey = PubKey {-# DEPRECATED PrivateKey "use PrivKey" #-} type PrivateKey = PrivKey -data KxError =- RSAError RSA.Error+data KxError+ = RSAError RSA.Error | KxUnsupported deriving (Show) -class HashCtxC a where- hashCName :: a -> String- hashCInit :: a -> a- hashCUpdate :: a -> B.ByteString -> a- hashCUpdateSSL :: a -> (B.ByteString,B.ByteString) -> a- hashCFinal :: a -> B.ByteString+isKeyExchangeSignatureKey :: KeyExchangeSignatureAlg -> PubKey -> Bool+isKeyExchangeSignatureKey = f+ where+ f KX_RSA (PubKeyRSA _) = True+ f KX_DSA (PubKeyDSA _) = True+ f KX_ECDSA (PubKeyEC _) = True+ f KX_ECDSA (PubKeyEd25519 _) = True+ f KX_ECDSA (PubKeyEd448 _) = True+ f _ _ = False -data HashCtx = forall h . HashCtxC h => HashCtx h+findKeyExchangeSignatureAlg+ :: (PubKey, PrivKey) -> Maybe KeyExchangeSignatureAlg+findKeyExchangeSignatureAlg keyPair =+ case keyPair of+ (PubKeyRSA _, PrivKeyRSA _) -> Just KX_RSA+ (PubKeyDSA _, PrivKeyDSA _) -> Just KX_DSA+ (PubKeyEC _, PrivKeyEC _) -> Just KX_ECDSA+ (PubKeyEd25519 _, PrivKeyEd25519 _) -> Just KX_ECDSA+ (PubKeyEd448 _, PrivKeyEd448 _) -> Just KX_ECDSA+ _ -> Nothing -instance Show HashCtx where- show (HashCtx c) = hashCName c+findFiniteFieldGroup :: DH.Params -> Maybe Group+findFiniteFieldGroup params = lookup (pg params) table+ where+ pg (DH.Params p g _) = (p, g) -{- MD5 & SHA1 joined -}-data HashMD5SHA1 = HashMD5SHA1 SHA1.Ctx MD5.Ctx+ table =+ [ (pg prms, grp)+ | grp <- availableFFGroups+ , let prms = fromJust $ dhParamsForGroup grp+ ] -instance HashCtxC HashMD5SHA1 where- hashCName _ = "MD5-SHA1"- hashCInit _ = HashMD5SHA1 SHA1.init MD5.init- hashCUpdate (HashMD5SHA1 sha1ctx md5ctx) b = HashMD5SHA1 (SHA1.update sha1ctx b) (MD5.update md5ctx b)- hashCUpdateSSL (HashMD5SHA1 sha1ctx md5ctx) (b1,b2) = HashMD5SHA1 (SHA1.update sha1ctx b2) (MD5.update md5ctx b1)- hashCFinal (HashMD5SHA1 sha1ctx md5ctx) = B.concat [MD5.finalize md5ctx, SHA1.finalize sha1ctx]+findEllipticCurveGroup :: PubKeyEC -> Maybe Group+findEllipticCurveGroup ecPub =+ case ecPubKeyCurveName ecPub of+ Just ECC.SEC_p256r1 -> Just P256+ Just ECC.SEC_p384r1 -> Just P384+ Just ECC.SEC_p521r1 -> Just P521+ _ -> Nothing -newtype HashSHA1 = HashSHA1 SHA1.Ctx+-- functions to use the hidden class.+hashInit :: Hash -> HashContext+hashInit MD5 = HashContext $ ContextSimple (H.hashInit :: H.Context H.MD5)+hashInit SHA1 = HashContext $ ContextSimple (H.hashInit :: H.Context H.SHA1)+hashInit SHA224 = HashContext $ ContextSimple (H.hashInit :: H.Context H.SHA224)+hashInit SHA256 = HashContext $ ContextSimple (H.hashInit :: H.Context H.SHA256)+hashInit SHA384 = HashContext $ ContextSimple (H.hashInit :: H.Context H.SHA384)+hashInit SHA512 = HashContext $ ContextSimple (H.hashInit :: H.Context H.SHA512)+hashInit SHA1_MD5 = HashContextSSL H.hashInit H.hashInit -instance HashCtxC HashSHA1 where- hashCName _ = "SHA1"- hashCInit _ = HashSHA1 SHA1.init- hashCUpdate (HashSHA1 ctx) b = HashSHA1 (SHA1.update ctx b)- hashCUpdateSSL (HashSHA1 ctx) (_,b2) = HashSHA1 (SHA1.update ctx b2)- hashCFinal (HashSHA1 ctx) = SHA1.finalize ctx+hashUpdate :: HashContext -> ByteString -> HashCtx+hashUpdate (HashContext (ContextSimple h)) b = HashContext $ ContextSimple (H.hashUpdate h b)+hashUpdate (HashContextSSL sha1Ctx md5Ctx) b =+ HashContextSSL (H.hashUpdate sha1Ctx b) (H.hashUpdate md5Ctx b) -newtype HashSHA256 = HashSHA256 SHA256.Ctx+hashUpdates :: HashContext -> [ByteString] -> HashCtx+hashUpdates (HashContext (ContextSimple h)) xs = HashContext $ ContextSimple (H.hashUpdates h xs)+hashUpdates (HashContextSSL sha1Ctx md5Ctx) xs =+ HashContextSSL (H.hashUpdates sha1Ctx xs) (H.hashUpdates md5Ctx xs) -instance HashCtxC HashSHA256 where- hashCName _ = "SHA256"- hashCInit _ = HashSHA256 SHA256.init- hashCUpdate (HashSHA256 ctx) b = HashSHA256 (SHA256.update ctx b)- hashCUpdateSSL _ _ = error "CUpdateSSL with HashSHA256"- hashCFinal (HashSHA256 ctx) = SHA256.finalize ctx+hashChunks :: Hash -> [ByteString] -> ByteString+hashChunks h xs = hashFinal $ hashUpdates (hashInit h) xs -newtype HashSHA512 = HashSHA512 SHA512.Ctx+hashUpdateSSL+ :: HashCtx+ -> (ByteString, ByteString)+ -- ^ (for the md5 context, for the sha1 context)+ -> HashCtx+hashUpdateSSL (HashContext _) _ = error "internal error: update SSL without a SSL Context"+hashUpdateSSL (HashContextSSL sha1Ctx md5Ctx) (b1, b2) =+ HashContextSSL (H.hashUpdate sha1Ctx b2) (H.hashUpdate md5Ctx b1) -instance HashCtxC HashSHA512 where- hashCName _ = "SHA512"- hashCInit _ = HashSHA512 SHA512.init- hashCUpdate (HashSHA512 ctx) b = HashSHA512 (SHA512.update ctx b)- hashCUpdateSSL _ _ = error "CUpdateSSL with HashSHA512"- hashCFinal (HashSHA512 ctx) = SHA512.finalize ctx+hashFinal :: HashCtx -> ByteString+hashFinal (HashContext (ContextSimple h)) = convert $ H.hashFinalize h+hashFinal (HashContextSSL sha1Ctx md5Ctx) =+ B.concat [convert (H.hashFinalize md5Ctx), convert (H.hashFinalize sha1Ctx)] --- functions to use the hidden class.-hashInit :: HashCtx -> HashCtx-hashInit (HashCtx h) = HashCtx $ hashCInit h+data Hash = MD5 | SHA1 | SHA224 | SHA256 | SHA384 | SHA512 | SHA1_MD5+ deriving (Show, Eq) -hashUpdate :: HashCtx -> B.ByteString -> HashCtx-hashUpdate (HashCtx h) b = HashCtx $ hashCUpdate h b+data HashContext+ = HashContext ContextSimple+ | HashContextSSL (H.Context H.SHA1) (H.Context H.MD5) -hashUpdateSSL :: HashCtx- -> (B.ByteString,B.ByteString) -- ^ (for the md5 context, for the sha1 context)- -> HashCtx-hashUpdateSSL (HashCtx h) bs = HashCtx $ hashCUpdateSSL h bs+instance Show HashContext where+ show _ = "hash-context" -hashFinal :: HashCtx -> B.ByteString-hashFinal (HashCtx h) = hashCFinal h+data ContextSimple+ = forall alg. H.HashAlgorithm alg => ContextSimple (H.Context alg) --- real hash constructors-hashMD5SHA1, hashSHA1, hashSHA256, hashSHA512 :: HashCtx-hashMD5SHA1 = HashCtx (HashMD5SHA1 SHA1.init MD5.init)-hashSHA1 = HashCtx (HashSHA1 SHA1.init)-hashSHA256 = HashCtx (HashSHA256 SHA256.init)-hashSHA512 = HashCtx (HashSHA512 SHA512.init)+type HashCtx = HashContext +hash :: (ByteArray ba, ByteArrayAccess ba) => Hash -> ba -> ba+hash MD5 b = convert (H.hash b :: H.Digest H.MD5)+hash SHA1 b = convert (H.hash b :: H.Digest H.SHA1)+hash SHA224 b = convert (H.hash b :: H.Digest H.SHA224)+hash SHA256 b = convert (H.hash b :: H.Digest H.SHA256)+hash SHA384 b = convert (H.hash b :: H.Digest H.SHA384)+hash SHA512 b = convert (H.hash b :: H.Digest H.SHA512)+hash SHA1_MD5 b = BA.concat [hash MD5 b, hash SHA1 b]++hashName :: Hash -> String+hashName = show++-- | Digest size in bytes.+hashDigestSize :: Hash -> Int+hashDigestSize MD5 = 16+hashDigestSize SHA1 = 20+hashDigestSize SHA224 = 28+hashDigestSize SHA256 = 32+hashDigestSize SHA384 = 48+hashDigestSize SHA512 = 64+hashDigestSize SHA1_MD5 = 36++hashBlockSize :: Hash -> Int+hashBlockSize MD5 = 64+hashBlockSize SHA1 = 64+hashBlockSize SHA224 = 64+hashBlockSize SHA256 = 64+hashBlockSize SHA384 = 128+hashBlockSize SHA512 = 128+hashBlockSize SHA1_MD5 = 64+ {- key exchange methods encrypt and decrypt for each supported algorithm -} -generalizeRSAWithRNG :: CPRG g => (Either RSA.Error a, g) -> (Either KxError a, g)-generalizeRSAWithRNG (Left e, g) = (Left (RSAError e), g)-generalizeRSAWithRNG (Right x, g) = (Right x, g)+generalizeRSAError :: Either RSA.Error a -> Either KxError a+generalizeRSAError (Left e) = Left (RSAError e)+generalizeRSAError (Right x) = Right x -kxEncrypt :: CPRG g => g -> PublicKey -> ByteString -> (Either KxError ByteString, g)-kxEncrypt g (PubKeyRSA pk) b = generalizeRSAWithRNG $ RSA.encrypt g pk b-kxEncrypt g _ _ = (Left KxUnsupported, g)+kxEncrypt+ :: MonadRandom r => PublicKey -> ScrubbedBytes -> r (Either KxError ByteString)+kxEncrypt (PubKeyRSA pk) b = generalizeRSAError <$> RSA.encrypt pk b+kxEncrypt _ _ = return (Left KxUnsupported) -kxDecrypt :: CPRG g => g -> PrivateKey -> ByteString -> (Either KxError ByteString, g)-kxDecrypt g (PrivKeyRSA pk) b = generalizeRSAWithRNG $ RSA.decryptSafer g pk b-kxDecrypt g _ _ = (Left KxUnsupported, g)+kxDecrypt+ :: MonadRandom r => PrivateKey -> ByteString -> r (Either KxError ScrubbedBytes)+kxDecrypt (PrivKeyRSA pk) b = generalizeRSAError <$> RSA.decryptSafer pk b+kxDecrypt _ _ = return (Left KxUnsupported) +data RSAEncoding = RSApkcs1 | RSApss deriving (Show, Eq)++-- | Test the RSASSA-PKCS1 length condition described in RFC 8017 section 9.2,+-- i.e. @emLen >= tLen + 11@. Lengths are in bytes.+kxCanUseRSApkcs1 :: RSA.PublicKey -> Hash -> Bool+kxCanUseRSApkcs1 pk h = RSA.public_size pk >= tLen + 11+ where+ tLen = prefixSize h + hashDigestSize h++ prefixSize MD5 = 18+ prefixSize SHA1 = 15+ prefixSize SHA224 = 19+ prefixSize SHA256 = 19+ prefixSize SHA384 = 19+ prefixSize SHA512 = 19+ prefixSize _ = error (show h ++ " is not supported for RSASSA-PKCS1")++-- | Test the RSASSA-PSS length condition described in RFC 8017 section 9.1.1,+-- i.e. @emBits >= 8hLen + 8sLen + 9@. Lengths are in bits.+kxCanUseRSApss :: RSA.PublicKey -> Hash -> Bool+kxCanUseRSApss pk h = numBits (RSA.public_n pk) >= 16 * hashDigestSize h + 10++-- Signature algorithm and associated parameters.+--+-- FIXME add RSAPSSParams+data SignatureParams+ = RSAParams Hash RSAEncoding+ | DSAParams+ | ECDSAParams Hash+ | Ed25519Params+ | Ed448Params+ deriving (Show, Eq)+ -- Verify that the signature matches the given message, using the -- public key. ---kxVerify :: PublicKey -> HashDescr -> ByteString -> ByteString -> Bool-kxVerify (PubKeyRSA pk) hashDescr msg sign = RSA.verify hashDescr pk msg sign-kxVerify (PubKeyDSA pk) hashDescr msg signBS =- case signature of- Right (sig, []) -> DSA.verify (hashFunction hashDescr) pk sig msg- _ -> False- where signature = case decodeASN1' BER signBS of- Left err -> Left (show err)- Right asn1s -> fromASN1 asn1s-kxVerify _ _ _ _ = False +kxVerify :: PublicKey -> SignatureParams -> ByteString -> ByteString -> Bool+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+ _ -> 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 (,)+ signature <- maybeCryptoError $ ECDSA.signatureFromIntegers prx rs+ verifyF <- withAlg (ECDSA.verify prx)+ return $ verifyF pubkey signature msg+ verifyClassic pubkey = do+ signature <- decodeSignatureASN1 ECDSA_ECC.Signature+ verifyF <- withAlg ECDSA_ECC.verify+ return $ verifyF pubkey signature msg+ withAlg :: (forall hash. H.HashAlgorithm hash => hash -> a) -> Maybe a+ withAlg f = case alg of+ MD5 -> Just (f H.MD5)+ SHA1 -> Just (f H.SHA1)+ SHA224 -> Just (f H.SHA224)+ SHA256 -> Just (f H.SHA256)+ SHA384 -> Just (f H.SHA384)+ SHA512 -> Just (f H.SHA512)+ _ -> Nothing+kxVerify (PubKeyEd25519 key) Ed25519Params msg sigBS =+ case Ed25519.signature sigBS of+ CryptoPassed sig -> Ed25519.verify key msg sig+ _ -> False+kxVerify (PubKeyEd448 key) Ed448Params msg sigBS =+ case Ed448.signature sigBS of+ CryptoPassed sig -> Ed448.verify key msg sig+ _ -> False+kxVerify _ _ _ _ = False+ -- Sign the given message using the private key. ---kxSign :: CPRG g => g -> PrivateKey -> HashDescr -> ByteString -> (Either KxError ByteString, g)-kxSign g (PrivKeyRSA pk) hashDescr msg =- generalizeRSAWithRNG $ RSA.signSafer g hashDescr pk msg-kxSign g (PrivKeyDSA pk) hashDescr msg =- let (sign, g') = DSA.sign g pk (hashFunction hashDescr) msg- in (Right $ encodeASN1' DER $ toASN1 sign [], g')---kxSign g _ _ _ =--- (Left KxUnsupported, g)+kxSign+ :: MonadRandom r+ => PrivateKey+ -> PublicKey+ -> SignatureParams+ -> ByteString+ -> r (Either KxError ByteString)+kxSign (PrivKeyRSA pk) (PubKeyRSA _) (RSAParams hashAlg RSApkcs1) msg =+ generalizeRSAError <$> rsaSignHash hashAlg pk msg+kxSign (PrivKeyRSA pk) (PubKeyRSA _) (RSAParams hashAlg RSApss) msg =+ generalizeRSAError <$> rsapssSignHash hashAlg pk msg+kxSign (PrivKeyDSA pk) (PubKeyDSA _) DSAParams msg = do+ sign <- DSA.sign pk H.SHA1 msg+ return (Right $ encodeASN1' DER $ dsaSequence sign)+ where+ dsaSequence sign =+ [ Start Sequence+ , IntVal (DSA.sign_r sign)+ , IntVal (DSA.sign_s sign)+ , End Sequence+ ]+kxSign (PrivKeyEC pk) (PubKeyEC _) (ECDSAParams hashAlg) msg =+ case withPrivKeyEC pk doSign (const unsupported) unsupported of+ Nothing -> unsupported+ Just run -> fmap encode <$> run+ where+ encode (r, s) =+ encodeASN1'+ DER+ [Start Sequence, IntVal r, IntVal s, End Sequence]+ doSign prx privkey = do+ msig <- ecdsaSignHash prx hashAlg privkey msg+ return $ case msig of+ Nothing -> Left KxUnsupported+ Just sign -> Right (ECDSA.signatureToIntegers prx sign)+ unsupported = return $ Left KxUnsupported+kxSign (PrivKeyEd25519 pk) (PubKeyEd25519 pub) Ed25519Params msg =+ return $ Right $ convert $ Ed25519.sign pk pub msg+kxSign (PrivKeyEd448 pk) (PubKeyEd448 pub) Ed448Params msg =+ return $ Right $ convert $ Ed448.sign pk pub msg+kxSign _ _ _ _ =+ return (Left KxUnsupported)++rsaSignHash+ :: MonadRandom m+ => Hash+ -> 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++rsapssSignHash+ :: MonadRandom m+ => Hash+ -> RSA.PrivateKey+ -> ByteString+ -> m (Either RSA.Error ByteString)+rsapssSignHash SHA256 pk msg = PSS.signSafer (PSS.defaultPSSParams H.SHA256) pk msg+rsapssSignHash SHA384 pk msg = PSS.signSafer (PSS.defaultPSSParams H.SHA384) pk msg+rsapssSignHash SHA512 pk msg = PSS.signSafer (PSS.defaultPSSParams H.SHA512) pk msg+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)++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)+ => proxy curve+ -> Hash+ -> ECDSA.Scalar curve+ -> ByteString+ -> m (Maybe (ECDSA.Signature curve))+ecdsaSignHash prx SHA1 pk msg = Just <$> ECDSA.sign prx pk H.SHA1 msg+ecdsaSignHash prx SHA224 pk msg = Just <$> ECDSA.sign prx pk H.SHA224 msg+ecdsaSignHash prx SHA256 pk msg = Just <$> ECDSA.sign prx pk H.SHA256 msg+ecdsaSignHash prx SHA384 pk msg = Just <$> ECDSA.sign prx pk H.SHA384 msg+ecdsaSignHash prx SHA512 pk msg = Just <$> ECDSA.sign prx pk H.SHA512 msg+ecdsaSignHash _ _ _ _ = return Nothing++-- Currently we generate ECDSA signatures in constant time for P256 only.+kxSupportedPrivKeyEC :: PrivKeyEC -> Bool+kxSupportedPrivKeyEC privkey =+ case ecPrivKeyCurveName privkey of+ Just ECC.SEC_p256r1 -> True+ Just ECC.SEC_p384r1 -> True+ Just ECC.SEC_p521r1 -> True+ _ -> False++-- Perform a public-key operation with a parameterized ECC implementation when+-- available, otherwise fallback to the classic ECC implementation.+withPubKeyEC+ :: PubKeyEC+ -> ( forall curve+ . ECDSA.EllipticCurveECDSA curve+ => Proxy curve+ -> ECDSA.PublicKey curve+ -> a+ )+ -> (ECDSA_ECC.PublicKey -> a)+ -> a+ -> Maybe a+withPubKeyEC pubkey withProxy withClassic whenUnknown =+ case ecPubKeyCurveName pubkey of+ Nothing -> Just whenUnknown+ Just ECC.SEC_p256r1 ->+ maybeCryptoError $ withProxy p256 <$> ECDSA.decodePublic p256 bs+ Just ECC.SEC_p384r1 ->+ maybeCryptoError $ withProxy p384 <$> ECDSA.decodePublic p384 bs+ Just ECC.SEC_p521r1 ->+ maybeCryptoError $ withProxy p521 <$> ECDSA.decodePublic p521 bs+ Just curveName ->+ let curve = ECC.getCurveByName curveName+ pub = unserializePoint curve pt+ in withClassic . ECDSA_ECC.PublicKey curve <$> pub+ where+ pt@(SerializedPoint bs) = pubkeyEC_pub pubkey++-- Perform a private-key operation with a parameterized ECC implementation when+-- available. Calls for an unsupported curve can be prevented with+-- kxSupportedEcPrivKey.+withPrivKeyEC+ :: PrivKeyEC+ -> ( forall curve+ . ECDSA.EllipticCurveECDSA curve+ => Proxy curve+ -> ECDSA.PrivateKey curve+ -> a+ )+ -> (ECC.CurveName -> a)+ -> a+ -> Maybe a+withPrivKeyEC privkey withProxy withUnsupported whenUnknown =+ case ecPrivKeyCurveName privkey of+ Nothing -> Just whenUnknown+ Just ECC.SEC_p256r1 ->+ -- Private key should rather be stored as bytearray and converted+ -- using ECDSA.decodePrivate, unfortunately the data type chosen in+ -- x509 was Integer.+ maybeCryptoError $ withProxy p256 <$> ECDSA.scalarFromInteger p256 d+ Just ECC.SEC_p384r1 ->+ maybeCryptoError $ withProxy p384 <$> ECDSA.scalarFromInteger p384 d+ Just ECC.SEC_p521r1 ->+ maybeCryptoError $ withProxy p521 <$> ECDSA.scalarFromInteger p521 d+ Just curveName -> Just $ withUnsupported curveName+ where+ d = privkeyEC_priv privkey++p256 :: Proxy ECDSA.Curve_P256R1+p256 = Proxy+p384 :: Proxy ECDSA.Curve_P384R1+p384 = Proxy+p521 :: Proxy ECDSA.Curve_P521R1+p521 = Proxy
Network/TLS/Crypto/DH.hs view
@@ -1,31 +1,36 @@-module Network.TLS.Crypto.DH- (+module Network.TLS.Crypto.DH ( -- * DH types- DHParams- , DHPublic- , DHPrivate+ DHParams,+ DHPublic,+ DHPrivate,+ DHKey, -- * DH methods- , dhPublic- , dhPrivate- , dhParams- , dhGenerateKeyPair- , dhGetShared- , dhUnwrap- , dhUnwrapPublic- ) where+ dhPublic,+ dhPrivate,+ dhParams,+ dhParamsGetP,+ dhParamsGetG,+ dhParamsGetBits,+ dhGenerateKeyPair,+ dhGetShared,+ dhValid,+ dhUnwrap,+ dhUnwrapPublic,+) where -import Network.TLS.Util.Serialization (i2osp)+import Crypto.Number.Basic (numBits) import qualified Crypto.PubKey.DH as DH-import qualified Crypto.Types.PubKey.DH as DH-import Crypto.Random (CPRG)-import Data.ByteString (ByteString)+import Data.ByteArray (ScrubbedBytes)+import qualified Data.ByteArray as BA -type DHPublic = DH.PublicNumber-type DHPrivate = DH.PrivateNumber-type DHParams = DH.Params-type DHKey = ByteString+import Network.TLS.RNG +type DHPublic = DH.PublicNumber+type DHPrivate = DH.PrivateNumber+type DHParams = DH.Params+type DHKey = ScrubbedBytes+ dhPublic :: Integer -> DHPublic dhPublic = DH.PublicNumber @@ -33,21 +38,39 @@ dhPrivate = DH.PrivateNumber dhParams :: Integer -> Integer -> DHParams-dhParams = DH.Params+dhParams p g = DH.Params p g (numBits p) -dhGenerateKeyPair :: CPRG g => g -> DHParams -> ((DHPrivate, DHPublic), g)-dhGenerateKeyPair rng params =- let (priv, g') = DH.generatePrivate rng params- pub = DH.generatePublic params priv- in ((priv, pub), g')+dhGenerateKeyPair :: MonadRandom r => DHParams -> r (DHPrivate, DHPublic)+dhGenerateKeyPair params = do+ priv <- DH.generatePrivate params+ let pub = DH.calculatePublic params priv+ return (priv, pub) dhGetShared :: DHParams -> DHPrivate -> DHPublic -> DHKey-dhGetShared params priv pub =- let (DH.SharedKey sk) = DH.getShared params priv pub- in i2osp sk+dhGetShared params priv pub = stripLeadingZeros sec+ where+ DH.SharedKey sec = DH.getShared params priv pub+ -- strips leading zeros from the result of DH.getShared, as required+ -- for DH(E) pre-main secret in SSL/TLS before version 1.3.+ stripLeadingZeros sb = snd $ BA.span (== 0) sb +-- Check that group element in not in the 2-element subgroup { 1, p - 1 }.+-- See RFC 7919 section 3 and NIST SP 56A rev 2 section 5.6.2.3.1.+-- This verification is enough when using a safe prime.+dhValid :: DHParams -> Integer -> Bool+dhValid (DH.Params p _ _) y = 1 < y && y < p - 1+ dhUnwrap :: DHParams -> DHPublic -> [Integer]-dhUnwrap (DH.Params p g) (DH.PublicNumber y) = [p,g,y]+dhUnwrap (DH.Params p g _) (DH.PublicNumber y) = [p, g, y]++dhParamsGetP :: DHParams -> Integer+dhParamsGetP (DH.Params p _ _) = p++dhParamsGetG :: DHParams -> Integer+dhParamsGetG (DH.Params _ g _) = g++dhParamsGetBits :: DHParams -> Int+dhParamsGetBits (DH.Params _ _ b) = b dhUnwrapPublic :: DHPublic -> Integer dhUnwrapPublic (DH.PublicNumber y) = y
+ Network/TLS/Crypto/IES.hs view
@@ -0,0 +1,525 @@+-- | (Elliptic Curve) Integrated Encryption Scheme+-- KEM(Key Encapsulation Mechanism) based APIs+--+-- Module : Network.TLS.Crypto.IES+-- License : BSD-style+-- Maintainer : Kazu Yamamoto <kazu@iij.ad.jp>+-- Stability : experimental+-- Portability : unknown+module Network.TLS.Crypto.IES (+ GroupPublicA,+ GroupPublicB,+ GroupPrivate,+ GroupKey,++ -- * Group methods+ groupGenerateKeyPair,+ groupEncapsulate,+ groupDecapsulate,+ groupEncodePublicA,+ groupDecodePublicA,+ groupEncodePublicB,+ groupDecodePublicB,++ -- * Compatibility with 'Network.TLS.Crypto.DH'+ dhParamsForGroup,+ dhGroupGenerateKeyPair,+ dhGroupGetPubShared,+) where++import Control.Arrow+import Crypto.ECC as ECC+import Crypto.Error+import Crypto.Number.Generate+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 Data.ByteArray (ScrubbedBytes, convert)+import qualified Data.ByteArray as BA+import Data.Proxy++import Network.TLS.Crypto.Types+import Network.TLS.Extra.FFDHE+import Network.TLS.Imports+import Network.TLS.RNG+import Network.TLS.Util.Serialization (i2ospOf_, os2ip)++{- FOURMOLU_DISABLE -}+data GroupPrivate+ = GroupPri_P256 (Scalar Curve_P256R1)+ | GroupPri_P384 (Scalar Curve_P384R1)+ | GroupPri_P521 (Scalar Curve_P521R1)+ | GroupPri_X255 (Scalar Curve_X25519)+ | GroupPri_X448 (Scalar Curve_X448)+ | GroupPri_FFDHE2048 PrivateNumber+ | GroupPri_FFDHE3072 PrivateNumber+ | 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)+ deriving (Eq, Show)+{- FOURMOLU_ENABLE -}++{- FOURMOLU_DISABLE -}+data GroupPublicA+ = GroupPubA_P256 (Point Curve_P256R1)+ | GroupPubA_P384 (Point Curve_P384R1)+ | GroupPubA_P521 (Point Curve_P521R1)+ | GroupPubA_X255 (Point Curve_X25519)+ | GroupPubA_X448 (Point Curve_X448)+ | GroupPubA_FFDHE2048 PublicNumber+ | GroupPubA_FFDHE3072 PublicNumber+ | 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)+ deriving (Eq, Show)+{- FOURMOLU_ENABLE -}++{- FOURMOLU_DISABLE -}+data GroupPublicB+ = GroupPubB_P256 (Point Curve_P256R1)+ | GroupPubB_P384 (Point Curve_P384R1)+ | GroupPubB_P521 (Point Curve_P521R1)+ | GroupPubB_X255 (Point Curve_X25519)+ | GroupPubB_X448 (Point Curve_X448)+ | GroupPubB_FFDHE2048 PublicNumber+ | GroupPubB_FFDHE3072 PublicNumber+ | 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)+ deriving (Eq, Show)+{- FOURMOLU_ENABLE -}++type GroupKey = ScrubbedBytes++p256 :: Proxy Curve_P256R1+p256 = Proxy++p384 :: Proxy Curve_P384R1+p384 = Proxy++p521 :: Proxy Curve_P521R1+p521 = Proxy++x25519 :: Proxy Curve_X25519+x25519 = Proxy++x448 :: Proxy Curve_X448+x448 = Proxy++mlkem512 :: Proxy ML_KEM_512+mlkem512 = Proxy++mlkem768 :: Proxy ML_KEM_768+mlkem768 = Proxy++mlkem1024 :: Proxy ML_KEM_1024+mlkem1024 = Proxy++dhParamsForGroup :: Group -> Maybe DH.Params+dhParamsForGroup FFDHE2048 = Just ffdhe2048+dhParamsForGroup FFDHE3072 = Just ffdhe3072+dhParamsForGroup FFDHE4096 = Just ffdhe4096+dhParamsForGroup FFDHE6144 = Just ffdhe6144+dhParamsForGroup FFDHE8192 = Just ffdhe8192+dhParamsForGroup _ = Nothing++groupGenerateKeyPair :: MonadRandom r => Group -> r (GroupPrivate, GroupPublicA)+groupGenerateKeyPair P256 =+ (GroupPri_P256, GroupPubA_P256) `fs` curveGenerateKeyPair p256+groupGenerateKeyPair P384 =+ (GroupPri_P384, GroupPubA_P384) `fs` curveGenerateKeyPair p384+groupGenerateKeyPair P521 =+ (GroupPri_P521, GroupPubA_P521) `fs` curveGenerateKeyPair p521+groupGenerateKeyPair X25519 =+ (GroupPri_X255, GroupPubA_X255) `fs` curveGenerateKeyPair x25519+groupGenerateKeyPair X448 =+ (GroupPri_X448, GroupPubA_X448) `fs` curveGenerateKeyPair x448+groupGenerateKeyPair FFDHE2048 = gen ffdhe2048 exp2048 GroupPri_FFDHE2048 GroupPubA_FFDHE2048+groupGenerateKeyPair FFDHE3072 = gen ffdhe3072 exp3072 GroupPri_FFDHE3072 GroupPubA_FFDHE3072+groupGenerateKeyPair FFDHE4096 = gen ffdhe4096 exp4096 GroupPri_FFDHE4096 GroupPubA_FFDHE4096+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+ return (GroupPri_MLKEM512 d, GroupPubA_MLKEM512 e)+groupGenerateKeyPair MLKEM768 = do+ (e, d) <- ML.generate mlkem768+ return (GroupPri_MLKEM768 d, GroupPubA_MLKEM768 e)+groupGenerateKeyPair MLKEM1024 = do+ (e, d) <- ML.generate mlkem1024+ return (GroupPri_MLKEM1024 d, GroupPubA_MLKEM1024 e)+groupGenerateKeyPair X25519MLKEM768 = do+ (d1, e1) <- fs' $ curveGenerateKeyPair x25519+ (e2, d2) <- ML.generate mlkem768+ return (GroupPri_X25519MLKEM768 (d1, d2), GroupPubA_X25519MLKEM768 (e1, e2))+groupGenerateKeyPair P256MLKEM768 = do+ (d1, e1) <- fs' $ curveGenerateKeyPair p256+ (e2, d2) <- ML.generate mlkem768+ return (GroupPri_P256MLKEM768 (d1, d2), GroupPubA_P256MLKEM768 (e1, e2))+groupGenerateKeyPair P384MLKEM1024 = do+ (d1, e1) <- fs' $ curveGenerateKeyPair p384+ (e2, d2) <- ML.generate mlkem1024+ return (GroupPri_P384MLKEM1024 (d1, d2), GroupPubA_P384MLKEM1024 (e1, e2))+groupGenerateKeyPair _ = error "groupGenerateKeyPair"++dhGroupGenerateKeyPair+ :: MonadRandom r => Group -> r (DH.Params, PrivateNumber, PublicNumber)+dhGroupGenerateKeyPair FFDHE2048 = addParams ffdhe2048 (gen' ffdhe2048 exp2048)+dhGroupGenerateKeyPair FFDHE3072 = addParams ffdhe3072 (gen' ffdhe3072 exp3072)+dhGroupGenerateKeyPair FFDHE4096 = addParams ffdhe4096 (gen' ffdhe4096 exp4096)+dhGroupGenerateKeyPair FFDHE6144 = addParams ffdhe6144 (gen' ffdhe6144 exp6144)+dhGroupGenerateKeyPair FFDHE8192 = addParams ffdhe8192 (gen' ffdhe8192 exp8192)+dhGroupGenerateKeyPair grp = error ("invalid FFDHE group: " ++ show grp)++addParams :: Functor f => DH.Params -> f (a, b) -> f (DH.Params, a, b)+addParams params = fmap $ \(a, b) -> (params, a, b)++fs+ :: MonadRandom r+ => (Scalar a -> GroupPrivate, Point a -> GroupPublicA)+ -> r (KeyPair a)+ -> r (GroupPrivate, GroupPublicA)+(t1, t2) `fs` action = do+ keypair <- action+ let pub = keypairGetPublic keypair+ pri = keypairGetPrivate keypair+ return (t1 pri, t2 pub)++fs' :: Monad m => m (KeyPair curve) -> m (Scalar curve, Point curve)+fs' action = do+ keypair <- action+ let pub = keypairGetPublic keypair+ pri = keypairGetPrivate keypair+ return (pri, pub)++gen+ :: MonadRandom r+ => DH.Params+ -> Int+ -> (PrivateNumber -> GroupPrivate)+ -> (PublicNumber -> GroupPublicA)+ -> r (GroupPrivate, GroupPublicA)+gen params expBits priTag pubTag = (priTag *** pubTag) <$> gen' params expBits++gen'+ :: MonadRandom r+ => DH.Params+ -> Int+ -> r (PrivateNumber, PublicNumber)+gen' params expBits = (id &&& DH.calculatePublic params) <$> generatePriv expBits++groupEncapsulate+ :: MonadRandom r => GroupPublicA -> r (Maybe (GroupPublicB, GroupKey))+groupEncapsulate (GroupPubA_P256 pub) = getECDHPubShared GroupPubB_P256 p256 pub+groupEncapsulate (GroupPubA_P384 pub) = getECDHPubShared GroupPubB_P384 p384 pub+groupEncapsulate (GroupPubA_P521 pub) = getECDHPubShared GroupPubB_P521 p521 pub+groupEncapsulate (GroupPubA_X255 pub) = getECDHPubShared GroupPubB_X255 x25519 pub+groupEncapsulate (GroupPubA_X448 pub) = getECDHPubShared GroupPubB_X448 x448 pub+groupEncapsulate (GroupPubA_FFDHE2048 pub) = getDHPubShared ffdhe2048 exp2048 pub GroupPubB_FFDHE2048+groupEncapsulate (GroupPubA_FFDHE3072 pub) = getDHPubShared ffdhe3072 exp3072 pub GroupPubB_FFDHE3072+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)++dhGroupGetPubShared+ :: MonadRandom r => Group -> PublicNumber -> r (Maybe (PublicNumber, GroupKey))+dhGroupGetPubShared FFDHE2048 pub = getDHPubShared' ffdhe2048 exp2048 pub+dhGroupGetPubShared FFDHE3072 pub = getDHPubShared' ffdhe3072 exp3072 pub+dhGroupGetPubShared FFDHE4096 pub = getDHPubShared' ffdhe4096 exp4096 pub+dhGroupGetPubShared FFDHE6144 pub = getDHPubShared' ffdhe6144 exp6144 pub+dhGroupGetPubShared FFDHE8192 pub = getDHPubShared' ffdhe8192 exp8192 pub+dhGroupGetPubShared _ _ = return Nothing++getECDHPubShared+ :: (MonadRandom m, EllipticCurveDH curve)+ => (Point curve -> GroupPublicB)+ -> proxy curve+ -> Point curve+ -> m (Maybe (GroupPublicB, GroupKey))+getECDHPubShared tag proxy pub = do+ mx <- maybeCryptoError <$> deriveEncrypt proxy pub+ case mx of+ Nothing -> return Nothing+ Just (p, ECC.SharedSecret s) -> return $ Just (tag p, s)++getECDHPubShared'+ :: (MonadRandom m, EllipticCurveDH curve)+ => proxy curve+ -> Point curve+ -> m (Maybe (Point curve, GroupKey))+getECDHPubShared' proxy pub = do+ mx <- maybeCryptoError <$> deriveEncrypt proxy pub+ case mx of+ Nothing -> return Nothing+ Just (p, ECC.SharedSecret s) -> return $ Just (p, s)++getDHPubShared+ :: MonadRandom r+ => DH.Params+ -> Int+ -> PublicNumber+ -> (PublicNumber -> GroupPublicB)+ -> r (Maybe (GroupPublicB, GroupKey))+getDHPubShared params expBits pub pubTag+ | not (valid params pub) = return Nothing+ | otherwise = do+ mypri <- generatePriv expBits+ let mypub = DH.calculatePublic params mypri+ DH.SharedKey share = DH.getShared params mypri pub+ return $ Just (pubTag mypub, share)++getDHPubShared'+ :: MonadRandom r+ => DH.Params+ -> Int+ -> PublicNumber+ -> r (Maybe (PublicNumber, GroupKey))+getDHPubShared' params expBits pub+ | not (valid params pub) = return Nothing+ | otherwise = do+ mypri <- generatePriv expBits+ let share = stripLeadingZeros (DH.getShared params mypri pub)+ return $ Just (DH.calculatePublic params mypri, convert share)++unwrap :: SharedSecret -> GroupKey+unwrap (ECC.SharedSecret sec) = sec++groupDecapsulate :: GroupPublicB -> GroupPrivate -> Maybe GroupKey+groupDecapsulate (GroupPubB_P256 pub) (GroupPri_P256 pri) = (unwrap <$>) . maybeCryptoError $ deriveDecrypt p256 pub pri+groupDecapsulate (GroupPubB_P384 pub) (GroupPri_P384 pri) = (unwrap <$>) . maybeCryptoError $ deriveDecrypt p384 pub pri+groupDecapsulate (GroupPubB_P521 pub) (GroupPri_P521 pri) = (unwrap <$>) . maybeCryptoError $ deriveDecrypt p521 pub pri+groupDecapsulate (GroupPubB_X255 pub) (GroupPri_X255 pri) = (unwrap <$>) . maybeCryptoError $ deriveDecrypt x25519 pub pri+groupDecapsulate (GroupPubB_X448 pub) (GroupPri_X448 pri) = (unwrap <$>) . maybeCryptoError $ deriveDecrypt x448 pub pri+groupDecapsulate (GroupPubB_FFDHE2048 pub) (GroupPri_FFDHE2048 pri) = calcDHShared ffdhe2048 pub pri+groupDecapsulate (GroupPubB_FFDHE3072 pub) (GroupPri_FFDHE3072 pri) = calcDHShared ffdhe3072 pub pri+groupDecapsulate (GroupPubB_FFDHE4096 pub) (GroupPri_FFDHE4096 pri) = calcDHShared ffdhe4096 pub pri+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+groupDecapsulate (GroupPubB_MLKEM768 p) (GroupPri_MLKEM768 s) =+ Just $ convert $ ML.decapsulate s p+groupDecapsulate (GroupPubB_MLKEM1024 p) (GroupPri_MLKEM1024 s) =+ Just $ convert $ ML.decapsulate 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+ 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+ 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+ return (bs1 <> bs2)+groupDecapsulate _ _ = Nothing++calcDHShared :: DH.Params -> PublicNumber -> PrivateNumber -> Maybe GroupKey+calcDHShared params pub pri+ | valid params pub = Just $ convert share+ | otherwise = Nothing+ where+ share = DH.getShared params pri pub++groupEncodePublicA :: GroupPublicA -> ByteString+groupEncodePublicA (GroupPubA_P256 p) = encodePoint p256 p+groupEncodePublicA (GroupPubA_P384 p) = encodePoint p384 p+groupEncodePublicA (GroupPubA_P521 p) = encodePoint p521 p+groupEncodePublicA (GroupPubA_X255 p) = encodePoint x25519 p+groupEncodePublicA (GroupPubA_X448 p) = encodePoint x448 p+groupEncodePublicA (GroupPubA_FFDHE2048 p) = enc ffdhe2048 p+groupEncodePublicA (GroupPubA_FFDHE3072 p) = enc ffdhe3072 p+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_X25519MLKEM768 (p1, p2)) =+ ML.encode p2 <> encodePoint x25519 p1+groupEncodePublicA (GroupPubA_P256MLKEM768 (p1, p2)) =+ encodePoint p256 p1 <> ML.encode p2+groupEncodePublicA (GroupPubA_P384MLKEM1024 (p1, p2)) =+ encodePoint p384 p1 <> ML.encode p2++groupEncodePublicB :: GroupPublicB -> ByteString+groupEncodePublicB (GroupPubB_P256 p) = encodePoint p256 p+groupEncodePublicB (GroupPubB_P384 p) = encodePoint p384 p+groupEncodePublicB (GroupPubB_P521 p) = encodePoint p521 p+groupEncodePublicB (GroupPubB_X255 p) = encodePoint x25519 p+groupEncodePublicB (GroupPubB_X448 p) = encodePoint x448 p+groupEncodePublicB (GroupPubB_FFDHE2048 p) = enc ffdhe2048 p+groupEncodePublicB (GroupPubB_FFDHE3072 p) = enc ffdhe3072 p+groupEncodePublicB (GroupPubB_FFDHE4096 p) = enc ffdhe4096 p+groupEncodePublicB (GroupPubB_FFDHE6144 p) = enc ffdhe6144 p+groupEncodePublicB (GroupPubB_FFDHE8192 p) = enc ffdhe8192 p+groupEncodePublicB (GroupPubB_MLKEM512 p) = convert p+groupEncodePublicB (GroupPubB_MLKEM768 p) = convert p+groupEncodePublicB (GroupPubB_MLKEM1024 p) = convert p+groupEncodePublicB (GroupPubB_X25519MLKEM768 (p1, p2)) =+ convert p2 <> encodePoint x25519 p1+groupEncodePublicB (GroupPubB_P256MLKEM768 (p1, p2)) =+ encodePoint p256 p1 <> convert p2+groupEncodePublicB (GroupPubB_P384MLKEM1024 (p1, p2)) =+ encodePoint p384 p1 <> convert p2++enc :: DH.Params -> PublicNumber -> ByteString+enc params (PublicNumber p) = i2ospOf_ ((DH.params_bits params + 7) `div` 8) p++groupDecodePublicA :: Group -> ByteString -> Either CryptoError GroupPublicA+groupDecodePublicA P256 bs = eitherCryptoError $ GroupPubA_P256 <$> decodePoint p256 bs+groupDecodePublicA P384 bs = eitherCryptoError $ GroupPubA_P384 <$> decodePoint p384 bs+groupDecodePublicA P521 bs = eitherCryptoError $ GroupPubA_P521 <$> decodePoint p521 bs+groupDecodePublicA X25519 bs = eitherCryptoError $ GroupPubA_X255 <$> decodePoint x25519 bs+groupDecodePublicA X448 bs = eitherCryptoError $ GroupPubA_X448 <$> decodePoint x448 bs+groupDecodePublicA FFDHE2048 bs = Right . GroupPubA_FFDHE2048 . PublicNumber $ os2ip bs+groupDecodePublicA FFDHE3072 bs = Right . GroupPubA_FFDHE3072 . PublicNumber $ os2ip bs+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+ Nothing -> Left CryptoError_PointFormatInvalid+ Just p -> Right $ GroupPubA_MLKEM512 p+groupDecodePublicA MLKEM768 bs = case ML.decode mlkem768 bs of+ Nothing -> Left CryptoError_PointFormatInvalid+ Just p -> Right $ GroupPubA_MLKEM768 p+groupDecodePublicA MLKEM1024 bs = case ML.decode 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+ 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+ 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+ Nothing -> Left CryptoError_PointFormatInvalid+ Just p1 -> case maybeCryptoError $ decodePoint p384 bs1 of+ Nothing -> Left CryptoError_PointFormatInvalid+ Just p2 -> Right $ GroupPubA_P384MLKEM1024 (p2, p1)+groupDecodePublicA _ _ = error "groupDecodePublicA"++groupDecodePublicB :: Group -> ByteString -> Either CryptoError GroupPublicB+groupDecodePublicB P256 bs = eitherCryptoError $ GroupPubB_P256 <$> decodePoint p256 bs+groupDecodePublicB P384 bs = eitherCryptoError $ GroupPubB_P384 <$> decodePoint p384 bs+groupDecodePublicB P521 bs = eitherCryptoError $ GroupPubB_P521 <$> decodePoint p521 bs+groupDecodePublicB X25519 bs = eitherCryptoError $ GroupPubB_X255 <$> decodePoint x25519 bs+groupDecodePublicB X448 bs = eitherCryptoError $ GroupPubB_X448 <$> decodePoint x448 bs+groupDecodePublicB FFDHE2048 bs = Right . GroupPubB_FFDHE2048 . PublicNumber $ os2ip bs+groupDecodePublicB FFDHE3072 bs = Right . GroupPubB_FFDHE3072 . PublicNumber $ os2ip bs+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+ Nothing -> Left CryptoError_PointFormatInvalid+ Just p -> Right $ GroupPubB_MLKEM512 p+groupDecodePublicB MLKEM768 bs = case ML.decode mlkem768 bs of+ Nothing -> Left CryptoError_PointFormatInvalid+ Just p -> Right $ GroupPubB_MLKEM768 p+groupDecodePublicB MLKEM1024 bs = case ML.decode 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+ 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+ 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+ Nothing -> Left CryptoError_PointFormatInvalid+ Just p1 -> case maybeCryptoError $ decodePoint p384 bs1 of+ Nothing -> Left CryptoError_PointFormatInvalid+ Just p2 -> Right $ GroupPubB_P384MLKEM1024 (p2, p1)+groupDecodePublicB _ _ = error "groupDecodePublicB"++-- Check that group element in not in the 2-element subgroup { 1, p - 1 }.+-- See RFC 7919 section 3 and NIST SP 56A rev 2 section 5.6.2.3.1.+valid :: DH.Params -> PublicNumber -> Bool+valid (DH.Params p _ _) (PublicNumber y) = 1 < y && y < p - 1++-- strips leading zeros from the result of getShared, as required+-- for DH(E) pre-main secret in SSL/TLS before version 1.3.+stripLeadingZeros :: DH.SharedKey -> ScrubbedBytes+stripLeadingZeros (DH.SharedKey sb) = snd $ BA.span (== 0) sb++-- Use short exponents as optimization, see RFC 7919 section 5.2.+generatePriv :: MonadRandom r => Int -> r PrivateNumber+generatePriv e = PrivateNumber <$> generateParams e (Just SetHighest) False++-- Short exponent bit sizes from RFC 7919 appendix A, rounded to next+-- multiple of 16 bits, i.e. going through a function like:+-- let shortExp n = head [ e | i <- [1..], let e = n + i, e `mod` 16 == 0 ]+exp2048 :: Int+exp3072 :: Int+exp4096 :: Int+exp6144 :: Int+exp8192 :: Int+exp2048 = 240 -- shortExp 225+exp3072 = 288 -- shortExp 275+exp4096 = 336 -- shortExp 325+exp6144 = 384 -- shortExp 375+exp8192 = 416 -- shortExp 400
+ Network/TLS/Crypto/Types.hs view
@@ -0,0 +1,143 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE PatternSynonyms #-}++-- |+-- Module : Network.TLS.Crypto.Types+-- License : BSD-style+-- Maintainer : Kazu Yamamoto <kazu@iij.ad.jp>+-- Stability : experimental+-- Portability : unknown+module Network.TLS.Crypto.Types (+ Group (+ Group,+ P256,+ P384,+ P521,+ X25519,+ X448,+ FFDHE2048,+ FFDHE3072,+ FFDHE4096,+ FFDHE6144,+ FFDHE8192,+ MLKEM512,+ MLKEM768,+ MLKEM1024,+ X25519MLKEM768,+ P256MLKEM768,+ P384MLKEM1024+ ),+ availableFFGroups,+ availableECGroups,+ availableHybridGroups,+ supportedNamedGroups,+ supportedNamedGroupsTLS13,+ KeyExchangeSignatureAlg (..),+) where++import Codec.Serialise+import Data.Word+import GHC.Generics++newtype Group = Group Word16 deriving (Eq, Generic)+instance Serialise Group++{- FOURMOLU_DISABLE -}+pattern P256 :: Group+pattern P256 = Group 23+pattern P384 :: Group+pattern P384 = Group 24+pattern P521 :: Group+pattern P521 = Group 25+pattern X25519 :: Group+pattern X25519 = Group 29+pattern X448 :: Group+pattern X448 = Group 30+pattern FFDHE2048 :: Group+pattern FFDHE2048 = Group 256+pattern FFDHE3072 :: Group+pattern FFDHE3072 = Group 257+pattern FFDHE4096 :: Group+pattern FFDHE4096 = Group 258+pattern FFDHE6144 :: Group+pattern FFDHE6144 = Group 259+pattern FFDHE8192 :: Group+pattern FFDHE8192 = Group 260+pattern MLKEM512 :: Group+pattern MLKEM512 = Group 512+pattern MLKEM768 :: Group+pattern MLKEM768 = Group 513+pattern MLKEM1024 :: Group+pattern MLKEM1024 = Group 514+pattern X25519MLKEM768 :: Group+pattern X25519MLKEM768 = Group 4588+pattern P256MLKEM768 :: Group+pattern P256MLKEM768 = Group 4587+pattern P384MLKEM1024 :: Group+pattern P384MLKEM1024 = Group 4589++instance Show Group where+ show P256 = "P256"+ show P384 = "P384"+ show P521 = "P521"+ show X25519 = "X25519"+ show X448 = "X448"+ show FFDHE2048 = "FFDHE2048"+ show FFDHE3072 = "FFDHE3072"+ show FFDHE4096 = "FFDHE4096"+ show FFDHE6144 = "FFDHE6144"+ show FFDHE8192 = "FFDHE8192"+ show MLKEM512 = "MLKEM512"+ show MLKEM768 = "MLKEM768"+ show MLKEM1024 = "MLKEM1024"+ show X25519MLKEM768 = "X25519MLKEM768"+ show P256MLKEM768 = "P256MLKEM768"+ show P384MLKEM1024 = "P384MLKEM1024"+ show (Group x) = "Group " ++ show x+{- FOURMOLU_ENABLE -}++availableFFGroups :: [Group]+availableFFGroups = [FFDHE2048, FFDHE3072, FFDHE4096, FFDHE6144, FFDHE8192]++availableECGroups :: [Group]+availableECGroups = [P256, P384, P521, X25519, X448]++availableHybridGroups :: [Group]+availableHybridGroups = [X25519MLKEM768, P256MLKEM768, P384MLKEM1024]++-- | A list for named groups. The ordering is for client preference+-- because server preference is not used in our server+-- implementation.+supportedNamedGroups :: [Group]+supportedNamedGroups =+ [ X25519 -- 128 bits security+ , P256 -- 128 bits security+ , P384 -- 192 bits security+ , X448 -- 224 bits security+ , P521 -- 256 bits security+ -- , FFDHE2048 -- 103 bits security+ , FFDHE3072 -- 125 bits security+ , FFDHE4096 -- 150 bits security+ , FFDHE6144 -- 175 bits security+ , FFDHE8192 -- 192 bits security+ , X25519MLKEM768+ , P256MLKEM768+ , P384MLKEM1024+ , -- , MLKEM512+ MLKEM768+ , MLKEM1024+ ]++supportedNamedGroupsTLS13 :: [[Group]]+supportedNamedGroupsTLS13 =+ [ [X25519MLKEM768, P256MLKEM768, P384MLKEM1024]+ , [X25519, P256]+ , [P384, X448, P521]+ , [FFDHE2048, FFDHE3072, FFDHE4096, FFDHE6144, FFDHE8192]+ , [MLKEM768, MLKEM1024]+ ]++-- Key-exchange signature algorithm, in close relation to ciphers+-- (before TLS 1.3).+data KeyExchangeSignatureAlg = KX_RSA | KX_DSA | KX_ECDSA+ deriving (Show, Eq)
+ Network/TLS/ErrT.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE CPP #-}++-- | A simple compat ErrorT and other error stuff+module Network.TLS.ErrT (+ runErrT,+ ErrT,+ MonadError (..),+) where++import Control.Monad.Except (MonadError (..))+import Control.Monad.Trans.Except (ExceptT, runExceptT)++runErrT :: ExceptT e m a -> m (Either e a)+runErrT = runExceptT+type ErrT = ExceptT
+ Network/TLS/Error.hs view
@@ -0,0 +1,198 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE PatternSynonyms #-}++module Network.TLS.Error where++import Control.Exception (Exception (..))+import Data.Typeable++import Network.TLS.Imports++----------------------------------------------------------------++-- | TLSError that might be returned through the TLS stack.+--+-- Prior to version 1.8.0, this type had an @Exception@ instance.+-- In version 1.8.0, this instance was removed, and functions in+-- this library now only throw 'TLSException'.+data TLSError+ = -- | mainly for instance of Error+ Error_Misc String+ | -- | A fatal error condition was encountered at a low level. The+ -- elements of the tuple give (freeform text description, structured+ -- error description).+ Error_Protocol String AlertDescription+ | -- | A non-fatal error condition was encountered at a low level at a low+ -- level. The elements of the tuple give (freeform text description,+ -- structured error description).+ Error_Protocol_Warning String AlertDescription+ | Error_Certificate String+ | -- | handshake policy failed.+ Error_HandshakePolicy String+ | Error_EOF+ | Error_Packet String+ | Error_Packet_unexpected String String+ | Error_Packet_Parsing String+ | Error_TCP_Terminate+ deriving (Eq, Show, Typeable)++----------------------------------------------------------------++-- | TLS Exceptions. Some of the data constructors indicate incorrect use of+-- the library, and the documentation for those data constructors calls+-- this out. The others wrap 'TLSError' with some kind of context to explain+-- when the exception occurred.+data TLSException+ = -- | Early termination exception with the reason and the error associated+ Terminated Bool String TLSError+ | -- | Handshake failed for the reason attached.+ HandshakeFailed TLSError+ | -- | Failure occurred while sending or receiving data after the+ -- TLS handshake succeeded.+ PostHandshake TLSError+ | -- | Lifts a 'TLSError' into 'TLSException' without provided any context+ -- around when the error happened.+ Uncontextualized TLSError+ | -- | Usage error when the connection has not been established+ -- and the user is trying to send or receive data.+ -- Indicates that this library has been used incorrectly.+ ConnectionNotEstablished+ | -- | Expected that a TLS handshake had already taken place, but no TLS+ -- handshake had occurred.+ -- Indicates that this library has been used incorrectly.+ MissingHandshake+ deriving (Show, Eq, Typeable)++instance Exception TLSException++----------------------------------------------------------------++newtype AlertLevel = AlertLevel {fromAlertLevel :: Word8} deriving (Eq)++{- FOURMOLU_DISABLE -}+pattern AlertLevel_Warning :: AlertLevel+pattern AlertLevel_Warning = AlertLevel 1+pattern AlertLevel_Fatal :: AlertLevel+pattern AlertLevel_Fatal = AlertLevel 2++instance Show AlertLevel where+ show AlertLevel_Warning = "AlertLevel_Warning"+ show AlertLevel_Fatal = "AlertLevel_Fatal"+ show (AlertLevel x) = "AlertLevel " ++ show x+{- FOURMOLU_ENABLE -}++----------------------------------------------------------------++newtype AlertDescription = AlertDescription {fromAlertDescription :: Word8}+ deriving (Eq)++{- FOURMOLU_DISABLE -}+pattern CloseNotify :: AlertDescription+pattern CloseNotify = AlertDescription 0+pattern UnexpectedMessage :: AlertDescription+pattern UnexpectedMessage = AlertDescription 10+pattern BadRecordMac :: AlertDescription+pattern BadRecordMac = AlertDescription 20+pattern DecryptionFailed :: AlertDescription+pattern DecryptionFailed = AlertDescription 21+pattern RecordOverflow :: AlertDescription+pattern RecordOverflow = AlertDescription 22+pattern DecompressionFailure :: AlertDescription+pattern DecompressionFailure = AlertDescription 30+pattern HandshakeFailure :: AlertDescription+pattern HandshakeFailure = AlertDescription 40+pattern BadCertificate :: AlertDescription+pattern BadCertificate = AlertDescription 42+pattern UnsupportedCertificate :: AlertDescription+pattern UnsupportedCertificate = AlertDescription 43+pattern CertificateRevoked :: AlertDescription+pattern CertificateRevoked = AlertDescription 44+pattern CertificateExpired :: AlertDescription+pattern CertificateExpired = AlertDescription 45+pattern CertificateUnknown :: AlertDescription+pattern CertificateUnknown = AlertDescription 46+pattern IllegalParameter :: AlertDescription+pattern IllegalParameter = AlertDescription 47+pattern UnknownCa :: AlertDescription+pattern UnknownCa = AlertDescription 48+pattern AccessDenied :: AlertDescription+pattern AccessDenied = AlertDescription 49+pattern DecodeError :: AlertDescription+pattern DecodeError = AlertDescription 50+pattern DecryptError :: AlertDescription+pattern DecryptError = AlertDescription 51+pattern ExportRestriction :: AlertDescription+pattern ExportRestriction = AlertDescription 60+pattern ProtocolVersion :: AlertDescription+pattern ProtocolVersion = AlertDescription 70+pattern InsufficientSecurity :: AlertDescription+pattern InsufficientSecurity = AlertDescription 71+pattern InternalError :: AlertDescription+pattern InternalError = AlertDescription 80+pattern InappropriateFallback :: AlertDescription+pattern InappropriateFallback = AlertDescription 86 -- RFC7507+pattern UserCanceled :: AlertDescription+pattern UserCanceled = AlertDescription 90+pattern NoRenegotiation :: AlertDescription+pattern NoRenegotiation = AlertDescription 100+pattern MissingExtension :: AlertDescription+pattern MissingExtension = AlertDescription 109+pattern UnsupportedExtension :: AlertDescription+pattern UnsupportedExtension = AlertDescription 110+pattern CertificateUnobtainable :: AlertDescription+pattern CertificateUnobtainable = AlertDescription 111+pattern UnrecognizedName :: AlertDescription+pattern UnrecognizedName = AlertDescription 112+pattern BadCertificateStatusResponse :: AlertDescription+pattern BadCertificateStatusResponse = AlertDescription 113+pattern BadCertificateHashValue :: AlertDescription+pattern BadCertificateHashValue = AlertDescription 114+pattern UnknownPskIdentity :: AlertDescription+pattern UnknownPskIdentity = AlertDescription 115+pattern CertificateRequired :: AlertDescription+pattern CertificateRequired = AlertDescription 116+pattern GeneralError :: AlertDescription+pattern GeneralError = AlertDescription 117+pattern NoApplicationProtocol :: AlertDescription+pattern NoApplicationProtocol = AlertDescription 120 -- RFC7301+pattern EchRequired :: AlertDescription+pattern EchRequired = AlertDescription 121 -- draft++instance Show AlertDescription where+ show CloseNotify = "CloseNotify"+ show UnexpectedMessage = "UnexpectedMessage"+ show BadRecordMac = "BadRecordMac"+ show DecryptionFailed = "DecryptionFailed"+ show RecordOverflow = "RecordOverflow"+ show DecompressionFailure = "DecompressionFailure"+ show HandshakeFailure = "HandshakeFailure"+ show BadCertificate = "BadCertificate"+ show UnsupportedCertificate = "UnsupportedCertificate"+ show CertificateRevoked = "CertificateRevoked"+ show CertificateExpired = "CertificateExpired"+ show CertificateUnknown = "CertificateUnknown"+ show IllegalParameter = "IllegalParameter"+ show UnknownCa = "UnknownCa"+ show AccessDenied = "AccessDenied"+ show DecodeError = "DecodeError"+ show DecryptError = "DecryptError"+ show ExportRestriction = "ExportRestriction"+ show ProtocolVersion = "ProtocolVersion"+ show InsufficientSecurity = "InsufficientSecurity"+ show InternalError = "InternalError"+ show InappropriateFallback = "InappropriateFallback"+ show UserCanceled = "UserCanceled"+ show NoRenegotiation = "NoRenegotiation"+ show MissingExtension = "MissingExtension"+ show UnsupportedExtension = "UnsupportedExtension"+ show CertificateUnobtainable = "CertificateUnobtainable"+ show UnrecognizedName = "UnrecognizedName"+ show BadCertificateStatusResponse = "BadCertificateStatusResponse"+ show BadCertificateHashValue = "BadCertificateHashValue"+ show UnknownPskIdentity = "UnknownPskIdentity"+ show CertificateRequired = "CertificateRequired"+ show GeneralError = "GeneralError"+ show NoApplicationProtocol = "NoApplicationProtocol"+ show EchRequired = "EchRequired"+ show (AlertDescription x) = "AlertDescription " ++ show x+{- FOURMOLU_ENABLE -}
Network/TLS/Extension.hs view
@@ -1,133 +1,1175 @@--- |--- Module : Network.TLS.Extension--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ basic extensions are defined in RFC 6066----module Network.TLS.Extension- ( Extension(..)- , supportedExtensions- -- all extensions ID supported- , extensionID_ServerName- , extensionID_MaxFragmentLength- , extensionID_SecureRenegotiation- , extensionID_NextProtocolNegotiation- -- all implemented extensions- , ServerNameType(..)- , ServerName(..)- , MaxFragmentLength(..)- , MaxFragmentEnum(..)- , SecureRenegotiation(..)- , NextProtocolNegotiation(..)- ) where--import Control.Applicative ((<$>))-import Control.Monad--import Data.Word-import Data.Maybe (fromMaybe)-import Data.ByteString (ByteString)-import qualified Data.ByteString as B-import qualified Data.ByteString.Char8 as BC--import Network.TLS.Struct (ExtensionID)-import Network.TLS.Wire-import Network.BSD (HostName)--extensionID_ServerName, extensionID_MaxFragmentLength- , extensionID_SecureRenegotiation- , extensionID_NextProtocolNegotiation :: ExtensionID-extensionID_ServerName = 0x0-extensionID_MaxFragmentLength = 0x1-extensionID_SecureRenegotiation = 0xff01-extensionID_NextProtocolNegotiation = 0x3374---- | all supported extensions by the implementation-supportedExtensions :: [ExtensionID]-supportedExtensions = [ extensionID_ServerName- , extensionID_MaxFragmentLength- , extensionID_SecureRenegotiation- , extensionID_NextProtocolNegotiation- ]---- | Extension class to transform bytes to and from a high level Extension type.-class Extension a where- extensionID :: a -> ExtensionID- extensionDecode :: Bool -> ByteString -> Maybe a- extensionEncode :: a -> ByteString---- | Server Name extension including the name type and the associated name.--- the associated name decoding is dependant of its name type.--- name type = 0 : hostname-data ServerName = ServerName [ServerNameType]- deriving (Show,Eq)--data ServerNameType = ServerNameHostName HostName- | ServerNameOther (Word8, ByteString)- deriving (Show,Eq)--instance Extension ServerName where- extensionID _ = extensionID_ServerName- extensionEncode (ServerName l) = runPut $ putOpaque16 (runPut $ mapM_ encodeNameType l)- 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 _ = runGetMaybe (getWord16 >>= \len -> getList (fromIntegral len) getServerName >>= return . ServerName)- where getServerName = do- ty <- getWord8- sname <- getOpaque16- return (1+2+B.length sname, case ty of- 0 -> ServerNameHostName $ BC.unpack sname -- FIXME: should be puny code conversion- _ -> ServerNameOther (ty, sname))---- | Max fragment extension with length from 512 bytes to 4096 bytes-data MaxFragmentLength = MaxFragmentLength MaxFragmentEnum- deriving (Show,Eq)-data MaxFragmentEnum = MaxFragment512 | MaxFragment1024 | MaxFragment2048 | MaxFragment4096- deriving (Show,Eq)--instance Extension MaxFragmentLength where- extensionID _ = extensionID_MaxFragmentLength- extensionEncode (MaxFragmentLength e) = B.singleton $ marshallSize e- where marshallSize MaxFragment512 = 1- marshallSize MaxFragment1024 = 2- marshallSize MaxFragment2048 = 3- marshallSize MaxFragment4096 = 4- extensionDecode _ = runGetMaybe (MaxFragmentLength . unmarshallSize <$> getWord8)- where unmarshallSize 1 = MaxFragment512- unmarshallSize 2 = MaxFragment1024- unmarshallSize 3 = MaxFragment2048- unmarshallSize 4 = MaxFragment4096- unmarshallSize n = error ("unknown max fragment size " ++ show n)---- | Secure Renegotiation-data SecureRenegotiation = SecureRenegotiation ByteString (Maybe ByteString)- deriving (Show,Eq)--instance Extension SecureRenegotiation where- extensionID _ = extensionID_SecureRenegotiation- extensionEncode (SecureRenegotiation cvd svd) =- runPut $ putOpaque8 (cvd `B.append` fromMaybe B.empty svd)- extensionDecode isServerHello = runGetMaybe $ do- opaque <- getOpaque8- if isServerHello- then let (cvd, svd) = B.splitAt (B.length opaque `div` 2) opaque- in return $ SecureRenegotiation cvd (Just svd)- else return $ SecureRenegotiation opaque Nothing---- | Next Protocol Negotiation-data NextProtocolNegotiation = NextProtocolNegotiation [ByteString]- deriving (Show,Eq)--instance Extension NextProtocolNegotiation where- extensionID _ = extensionID_NextProtocolNegotiation- extensionEncode (NextProtocolNegotiation bytes) =- runPut $ mapM_ putOpaque8 bytes- extensionDecode _ = runGetMaybe (NextProtocolNegotiation <$> getNPN)- where getNPN = do- avail <- remaining- case avail of- 0 -> return []- _ -> do liftM2 (:) getOpaque8 getNPN+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RecordWildCards #-}++-- | Basic extensions are defined in RFC 6066+module Network.TLS.Extension (+ -- * Extension identifiers+ ExtensionID (+ ..,+ EID_ServerName,+ EID_MaxFragmentLength,+ EID_ClientCertificateUrl,+ EID_TrustedCAKeys,+ EID_TruncatedHMAC,+ EID_StatusRequest,+ EID_UserMapping,+ EID_ClientAuthz,+ EID_ServerAuthz,+ EID_CertType,+ EID_SupportedGroups,+ EID_EcPointFormats,+ EID_SRP,+ EID_SignatureAlgorithms,+ EID_SRTP,+ EID_Heartbeat,+ EID_ApplicationLayerProtocolNegotiation,+ EID_StatusRequestv2,+ EID_SignedCertificateTimestamp,+ EID_ClientCertificateType,+ EID_ServerCertificateType,+ EID_Padding,+ EID_EncryptThenMAC,+ EID_ExtendedMainSecret,+ EID_CompressCertificate,+ EID_RecordSizeLimit,+ EID_SessionTicket,+ EID_PreSharedKey,+ EID_EarlyData,+ EID_SupportedVersions,+ EID_Cookie,+ EID_PskKeyExchangeModes,+ EID_CertificateAuthorities,+ EID_OidFilters,+ EID_PostHandshakeAuth,+ EID_SignatureAlgorithmsCert,+ EID_KeyShare,+ EID_QuicTransportParameters,+ EID_EchOuterExtensions,+ EID_EncryptedClientHello,+ EID_SecureRenegotiation+ ),+ definedExtensions,+ supportedExtensions,++ -- * Extension raw+ ExtensionRaw (..),+ toExtensionRaw,+ extensionLookup,+ lookupAndDecode,+ lookupAndDecodeAndDo,++ -- * Class+ Extension (..),++ -- * Extensions+ ServerNameType (..),+ ServerName (..),+ MaxFragmentLength (..),+ MaxFragmentEnum (..),+ SecureRenegotiation (..),+ ApplicationLayerProtocolNegotiation (..),+ ExtendedMainSecret (..),+ CertificateCompressionAlgorithm (.., CCA_Zlib, CCA_Brotli, CCA_Zstd),+ CompressCertificate (..),+ SupportedGroups (..),+ Group (..),+ EcPointFormatsSupported (..),+ EcPointFormat (+ EcPointFormat,+ EcPointFormat_Uncompressed,+ EcPointFormat_AnsiX962_compressed_prime,+ EcPointFormat_AnsiX962_compressed_char2+ ),+ RecordSizeLimit (..),+ SessionTicket (..),+ HeartBeat (..),+ HeartBeatMode (+ HeartBeatMode,+ HeartBeat_PeerAllowedToSend,+ HeartBeat_PeerNotAllowedToSend+ ),+ SignatureAlgorithms (..),+ SignatureAlgorithmsCert (..),+ SupportedVersions (..),+ KeyShare (..),+ KeyShareEntry (..),+ MessageType (..),+ PostHandshakeAuth (..),+ PskKexMode (PskKexMode, PSK_KE, PSK_DHE_KE),+ PskKeyExchangeModes (..),+ PskIdentity (..),+ PreSharedKey (..),+ EarlyDataIndication (..),+ Cookie (..),+ CertificateAuthorities (..),+ EchOuterExtensions (..),+ EncryptedClientHello (..),+) where++import qualified Control.Exception as E+import Crypto.HPKE+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC+import Data.X509 (DistinguishedName)++import Network.TLS.ECH.Config++import Network.TLS.Crypto.Types+import Network.TLS.Error+import Network.TLS.HashAndSignature+import Network.TLS.Imports+import Network.TLS.Packet (+ getBinaryVersion,+ getDNames,+ getSignatureHashAlgorithm,+ putBinaryVersion,+ putDNames,+ putSignatureHashAlgorithm,+ )+import Network.TLS.Types (HostName, Ticket, Version)+import Network.TLS.Wire++----------------------------------------------------------------+-- Extension identifiers++-- | Identifier of a TLS extension.+-- <http://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.txt>+newtype ExtensionID = ExtensionID {fromExtensionID :: Word16} deriving (Eq)++{- FOURMOLU_DISABLE -}+pattern EID_ServerName :: ExtensionID -- RFC6066+pattern EID_ServerName = ExtensionID 0x0+pattern EID_MaxFragmentLength :: ExtensionID -- RFC6066+pattern EID_MaxFragmentLength = ExtensionID 0x1+pattern EID_ClientCertificateUrl :: ExtensionID -- RFC6066+pattern EID_ClientCertificateUrl = ExtensionID 0x2+pattern EID_TrustedCAKeys :: ExtensionID -- RFC6066+pattern EID_TrustedCAKeys = ExtensionID 0x3+pattern EID_TruncatedHMAC :: ExtensionID -- RFC6066+pattern EID_TruncatedHMAC = ExtensionID 0x4+pattern EID_StatusRequest :: ExtensionID -- RFC6066+pattern EID_StatusRequest = ExtensionID 0x5+pattern EID_UserMapping :: ExtensionID -- RFC4681+pattern EID_UserMapping = ExtensionID 0x6+pattern EID_ClientAuthz :: ExtensionID -- RFC5878+pattern EID_ClientAuthz = ExtensionID 0x7+pattern EID_ServerAuthz :: ExtensionID -- RFC5878+pattern EID_ServerAuthz = ExtensionID 0x8+pattern EID_CertType :: ExtensionID -- RFC6091+pattern EID_CertType = ExtensionID 0x9+pattern EID_SupportedGroups :: ExtensionID -- RFC8422,8446+pattern EID_SupportedGroups = ExtensionID 0xa+pattern EID_EcPointFormats :: ExtensionID -- RFC4492+pattern EID_EcPointFormats = ExtensionID 0xb+pattern EID_SRP :: ExtensionID -- RFC5054+pattern EID_SRP = ExtensionID 0xc+pattern EID_SignatureAlgorithms :: ExtensionID -- RFC5246,8446+pattern EID_SignatureAlgorithms = ExtensionID 0xd+pattern EID_SRTP :: ExtensionID -- RFC5764+pattern EID_SRTP = ExtensionID 0xe+pattern EID_Heartbeat :: ExtensionID -- RFC6520+pattern EID_Heartbeat = ExtensionID 0xf+pattern EID_ApplicationLayerProtocolNegotiation :: ExtensionID -- RFC7301+pattern EID_ApplicationLayerProtocolNegotiation = ExtensionID 0x10+pattern EID_StatusRequestv2 :: ExtensionID -- RFC6961+pattern EID_StatusRequestv2 = ExtensionID 0x11+pattern EID_SignedCertificateTimestamp :: ExtensionID -- RFC6962+pattern EID_SignedCertificateTimestamp = ExtensionID 0x12+pattern EID_ClientCertificateType :: ExtensionID -- RFC7250+pattern EID_ClientCertificateType = ExtensionID 0x13+pattern EID_ServerCertificateType :: ExtensionID -- RFC7250+pattern EID_ServerCertificateType = ExtensionID 0x14+pattern EID_Padding :: ExtensionID -- RFC5246+pattern EID_Padding = ExtensionID 0x15+pattern EID_EncryptThenMAC :: ExtensionID -- RFC7366+pattern EID_EncryptThenMAC = ExtensionID 0x16+pattern EID_ExtendedMainSecret :: ExtensionID -- REF7627+pattern EID_ExtendedMainSecret = ExtensionID 0x17+pattern EID_CompressCertificate :: ExtensionID -- RFC8879+pattern EID_CompressCertificate = ExtensionID 0x1b+pattern EID_RecordSizeLimit :: ExtensionID -- RFC8449+pattern EID_RecordSizeLimit = ExtensionID 0x1c+pattern EID_SessionTicket :: ExtensionID -- RFC4507+pattern EID_SessionTicket = ExtensionID 0x23+pattern EID_PreSharedKey :: ExtensionID -- RFC8446+pattern EID_PreSharedKey = ExtensionID 0x29+pattern EID_EarlyData :: ExtensionID -- RFC8446+pattern EID_EarlyData = ExtensionID 0x2a+pattern EID_SupportedVersions :: ExtensionID -- RFC8446+pattern EID_SupportedVersions = ExtensionID 0x2b+pattern EID_Cookie :: ExtensionID -- RFC8446+pattern EID_Cookie = ExtensionID 0x2c+pattern EID_PskKeyExchangeModes :: ExtensionID -- RFC8446+pattern EID_PskKeyExchangeModes = ExtensionID 0x2d+pattern EID_CertificateAuthorities :: ExtensionID -- RFC8446+pattern EID_CertificateAuthorities = ExtensionID 0x2f+pattern EID_OidFilters :: ExtensionID -- RFC8446+pattern EID_OidFilters = ExtensionID 0x30+pattern EID_PostHandshakeAuth :: ExtensionID -- RFC8446+pattern EID_PostHandshakeAuth = ExtensionID 0x31+pattern EID_SignatureAlgorithmsCert :: ExtensionID -- RFC8446+pattern EID_SignatureAlgorithmsCert = ExtensionID 0x32+pattern EID_KeyShare :: ExtensionID -- RFC8446+pattern EID_KeyShare = ExtensionID 0x33+pattern EID_QuicTransportParameters :: ExtensionID -- RFC9001+pattern EID_QuicTransportParameters = ExtensionID 0x39+pattern EID_EchOuterExtensions :: ExtensionID -- draft+pattern EID_EchOuterExtensions = ExtensionID 0xfd00+pattern EID_EncryptedClientHello :: ExtensionID -- draft+pattern EID_EncryptedClientHello = ExtensionID 0xfe0d+pattern EID_SecureRenegotiation :: ExtensionID -- RFC5746+pattern EID_SecureRenegotiation = ExtensionID 0xff01++instance Show ExtensionID where+ show EID_ServerName = "ServerName"+ show EID_MaxFragmentLength = "MaxFragmentLength"+ show EID_ClientCertificateUrl = "ClientCertificateUrl"+ show EID_TrustedCAKeys = "TrustedCAKeys"+ show EID_TruncatedHMAC = "TruncatedHMAC"+ show EID_StatusRequest = "StatusRequest"+ show EID_UserMapping = "UserMapping"+ show EID_ClientAuthz = "ClientAuthz"+ show EID_ServerAuthz = "ServerAuthz"+ show EID_CertType = "CertType"+ show EID_SupportedGroups = "SupportedGroups"+ show EID_EcPointFormats = "EcPointFormats"+ show EID_SRP = "SRP"+ show EID_SignatureAlgorithms = "SignatureAlgorithms"+ show EID_SRTP = "SRTP"+ show EID_Heartbeat = "Heartbeat"+ show EID_ApplicationLayerProtocolNegotiation = "ApplicationLayerProtocolNegotiation"+ show EID_StatusRequestv2 = "StatusRequestv2"+ show EID_SignedCertificateTimestamp = "SignedCertificateTimestamp"+ show EID_ClientCertificateType = "ClientCertificateType"+ show EID_ServerCertificateType = "ServerCertificateType"+ show EID_Padding = "Padding"+ show EID_EncryptThenMAC = "EncryptThenMAC"+ show EID_ExtendedMainSecret = "ExtendedMainSecret"+ show EID_CompressCertificate = "CompressCertificate"+ show EID_RecordSizeLimit = "RecordSizeLimit"+ show EID_SessionTicket = "SessionTicket"+ show EID_PreSharedKey = "PreSharedKey"+ show EID_EarlyData = "EarlyData"+ show EID_SupportedVersions = "SupportedVersions"+ show EID_Cookie = "Cookie"+ show EID_PskKeyExchangeModes = "PskKeyExchangeModes"+ show EID_CertificateAuthorities = "CertificateAuthorities"+ show EID_OidFilters = "OidFilters"+ show EID_PostHandshakeAuth = "PostHandshakeAuth"+ show EID_SignatureAlgorithmsCert = "SignatureAlgorithmsCert"+ show EID_KeyShare = "KeyShare"+ show EID_QuicTransportParameters = "QuicTransportParameters"+ show EID_EchOuterExtensions = "EchOuterExtensions"+ show EID_EncryptedClientHello = "EncryptedClientHello"+ show EID_SecureRenegotiation = "SecureRenegotiation"+ show (ExtensionID x) = "ExtensionID " ++ show x+{- FOURMOLU_ENABLE -}++------------------------------------------------------------++definedExtensions :: [ExtensionID]+definedExtensions =+ [ EID_ServerName+ , EID_MaxFragmentLength+ , EID_ClientCertificateUrl+ , EID_TrustedCAKeys+ , EID_TruncatedHMAC+ , EID_StatusRequest+ , EID_UserMapping+ , EID_ClientAuthz+ , EID_ServerAuthz+ , EID_CertType+ , EID_SupportedGroups+ , EID_EcPointFormats+ , EID_SRP+ , EID_SignatureAlgorithms+ , EID_SRTP+ , EID_Heartbeat+ , EID_ApplicationLayerProtocolNegotiation+ , EID_StatusRequestv2+ , EID_SignedCertificateTimestamp+ , EID_ClientCertificateType+ , EID_ServerCertificateType+ , EID_Padding+ , EID_EncryptThenMAC+ , EID_ExtendedMainSecret+ , EID_CompressCertificate+ , EID_RecordSizeLimit+ , EID_SessionTicket+ , EID_PreSharedKey+ , EID_EarlyData+ , EID_SupportedVersions+ , EID_Cookie+ , EID_PskKeyExchangeModes+ , EID_CertificateAuthorities+ , EID_OidFilters+ , EID_PostHandshakeAuth+ , EID_SignatureAlgorithmsCert+ , EID_KeyShare+ , EID_QuicTransportParameters+ , EID_EchOuterExtensions+ , EID_EncryptedClientHello+ , EID_SecureRenegotiation+ ]++-- | all supported extensions by the implementation+{- FOURMOLU_DISABLE -}+supportedExtensions :: [ExtensionID]+supportedExtensions =+ [ EID_ServerName -- 0x00+ , EID_SupportedGroups -- 0x0a+ , EID_EcPointFormats -- 0x0b+ , EID_SignatureAlgorithms -- 0x0d+ , EID_ApplicationLayerProtocolNegotiation -- 0x10+ , EID_ExtendedMainSecret -- 0x17+ , EID_CompressCertificate -- 0x1b+ , EID_RecordSizeLimit -- 0x1c+ , EID_SessionTicket -- 0x23+ , EID_PreSharedKey -- 0x29+ , EID_EarlyData -- 0x2a+ , EID_SupportedVersions -- 0x2b+ , EID_Cookie -- 0x2c+ , EID_PskKeyExchangeModes -- 0x2d+ , EID_CertificateAuthorities -- 0x2f+ , EID_PostHandshakeAuth -- 0x31+ , EID_SignatureAlgorithmsCert -- 0x32+ , EID_KeyShare -- 0x33+ , EID_QuicTransportParameters -- 0x39+ , EID_EchOuterExtensions -- 0xfd00+ , EID_EncryptedClientHello -- 0xfe0d+ , EID_SecureRenegotiation -- 0xff01+ ]+{- FOURMOLU_ENABLE -}++----------------------------------------------------------------++-- | The raw content of a TLS extension.+data ExtensionRaw = ExtensionRaw ExtensionID ByteString+ deriving (Eq)++instance Show ExtensionRaw where+ show (ExtensionRaw eid@EID_ServerName bs) = showExtensionRaw eid bs decodeServerName+ show (ExtensionRaw eid@EID_MaxFragmentLength bs) = showExtensionRaw eid bs decodeMaxFragmentLength+ show (ExtensionRaw eid@EID_SupportedGroups bs) = showExtensionRaw eid bs decodeSupportedGroups+ show (ExtensionRaw eid@EID_EcPointFormats bs) = showExtensionRaw eid bs decodeEcPointFormatsSupported+ show (ExtensionRaw eid@EID_SignatureAlgorithms bs) = showExtensionRaw eid bs decodeSignatureAlgorithms+ show (ExtensionRaw eid@EID_Heartbeat bs) = showExtensionRaw eid bs decodeHeartBeat+ show (ExtensionRaw eid@EID_ApplicationLayerProtocolNegotiation bs) = showExtensionRaw eid bs decodeApplicationLayerProtocolNegotiation+ show (ExtensionRaw eid@EID_ExtendedMainSecret _) = show eid+ show (ExtensionRaw eid@EID_CompressCertificate bs) = showExtensionRaw eid bs decodeCompressCertificate+ show (ExtensionRaw eid@EID_RecordSizeLimit bs) = showExtensionRaw eid bs decodeRecordSizeLimit+ show (ExtensionRaw eid@EID_SessionTicket bs) = showExtensionRaw eid bs decodeSessionTicket+ show (ExtensionRaw eid@EID_PreSharedKey bs) = showExtensionRaw eid bs decodePreSharedKey+ show (ExtensionRaw eid@EID_EarlyData _) = show eid+ show (ExtensionRaw eid@EID_SupportedVersions bs) = showExtensionRaw eid bs decodeSupportedVersions+ show (ExtensionRaw eid@EID_Cookie bs) = show eid ++ " " ++ showBytesHex bs+ show (ExtensionRaw eid@EID_PskKeyExchangeModes bs) = showExtensionRaw eid bs decodePskKeyExchangeModes+ show (ExtensionRaw eid@EID_CertificateAuthorities bs) = showExtensionRaw eid bs decodeCertificateAuthorities+ show (ExtensionRaw eid@EID_PostHandshakeAuth _) = show eid+ show (ExtensionRaw eid@EID_SignatureAlgorithmsCert bs) = showExtensionRaw eid bs decodeSignatureAlgorithmsCert+ show (ExtensionRaw eid@EID_KeyShare bs) = showExtensionRaw eid bs decodeKeyShare+ show (ExtensionRaw eid@EID_EchOuterExtensions bs) = showExtensionRaw eid bs decodeEchOuterExtensions+ show (ExtensionRaw eid@EID_EncryptedClientHello bs) = showExtensionRaw eid bs decodeECH+ show (ExtensionRaw eid@EID_SecureRenegotiation bs) = show eid ++ " " ++ showBytesHex bs+ show (ExtensionRaw eid bs) = "ExtensionRaw " ++ show eid ++ " " ++ showBytesHex bs++showExtensionRaw+ :: Show a => ExtensionID -> ByteString -> (ByteString -> Maybe a) -> String+showExtensionRaw eid bs decode = case decode bs of+ Nothing -> show eid ++ " broken"+ Just x -> show x++toExtensionRaw :: Extension e => e -> ExtensionRaw+toExtensionRaw ext = ExtensionRaw (extensionID ext) (extensionEncode ext)++extensionLookup :: ExtensionID -> [ExtensionRaw] -> Maybe ByteString+extensionLookup toFind exts = extract <$> find idEq exts+ where+ extract (ExtensionRaw _ content) = content+ idEq (ExtensionRaw eid _) = eid == toFind++lookupAndDecode+ :: Extension e+ => ExtensionID+ -> MessageType+ -> [ExtensionRaw]+ -> a+ -> (e -> a)+ -> a+lookupAndDecode eid msgtyp exts defval conv = case extensionLookup eid exts of+ Nothing -> defval+ Just bs -> case extensionDecode msgtyp bs of+ Nothing ->+ E.throw $+ Uncontextualized $+ Error_Protocol ("Illegal " ++ show eid) DecodeError+ Just val -> conv val++lookupAndDecodeAndDo+ :: Extension a+ => ExtensionID+ -> MessageType+ -> [ExtensionRaw]+ -> IO b+ -> (a -> IO b)+ -> IO b+lookupAndDecodeAndDo eid msgtyp exts defAction action = case extensionLookup eid exts of+ Nothing -> defAction+ Just bs -> case extensionDecode msgtyp bs of+ Nothing ->+ E.throwIO $+ Uncontextualized $+ Error_Protocol ("Illegal " ++ show eid) DecodeError+ Just val -> action val++------------------------------------------------------------++-- | Extension class to transform bytes to and from a high level Extension type.+class Extension a where+ extensionID :: a -> ExtensionID+ extensionDecode :: MessageType -> ByteString -> Maybe a+ extensionEncode :: a -> ByteString++data MessageType+ = MsgTClientHello+ | MsgTServerHello+ | MsgTHelloRetryRequest+ | MsgTEncryptedExtensions+ | MsgTNewSessionTicket+ | MsgTCertificateRequest+ deriving (Eq, Show)++------------------------------------------------------------++-- | Server Name extension including the name type and the associated name.+-- the associated name decoding is dependent of its name type.+-- name type = 0 : hostname+newtype ServerName = ServerName [ServerNameType] deriving (Show, Eq)++data ServerNameType+ = ServerNameHostName HostName+ | ServerNameOther (Word8, ByteString)+ deriving (Eq)++instance Show ServerNameType where+ show (ServerNameHostName host) = "\"" ++ host ++ "\""+ show (ServerNameOther (w, _)) = "(" ++ show w ++ ", )"++instance Extension ServerName where+ extensionID _ = EID_ServerName++ -- dirty hack for servers+ extensionEncode (ServerName []) = ""+ -- for clients+ extensionEncode (ServerName l) = runPut $ putOpaque16 (runPut $ mapM_ encodeNameType l)+ 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 MsgTServerHello = decodeServerName+ extensionDecode MsgTEncryptedExtensions = decodeServerName+ extensionDecode _ = error "extensionDecode: ServerName"++decodeServerName :: ByteString -> Maybe ServerName+decodeServerName "" = Just $ ServerName [] -- dirty hack for servers+decodeServerName bs = runGetMaybe decode bs+ where+ decode = do+ len <- fromIntegral <$> getWord16+ ServerName <$> getList len getServerName+ getServerName = do+ ty <- getWord8+ snameParsed <- getOpaque16+ let sname = B.copy snameParsed+ name = case ty of+ 0 -> ServerNameHostName $ BC.unpack sname -- FIXME: should be puny code conversion+ _ -> ServerNameOther (ty, sname)+ return (1 + 2 + B.length sname, name)++------------------------------------------------------------++-- | Max fragment extension with length from 512 bytes to 4096 bytes+--+-- RFC 6066 defines:+-- If a server receives a maximum fragment length negotiation request+-- for a value other than the allowed values, it MUST abort the+-- handshake with an "illegal_parameter" alert.+--+-- So, if a server receives MaxFragmentLengthOther, it must send the alert.+data MaxFragmentLength+ = MaxFragmentLength MaxFragmentEnum+ | MaxFragmentLengthOther Word8+ deriving (Show, Eq)++data MaxFragmentEnum+ = MaxFragment512+ | MaxFragment1024+ | MaxFragment2048+ | MaxFragment4096+ deriving (Show, Eq)++instance Extension MaxFragmentLength where+ extensionID _ = EID_MaxFragmentLength+ extensionEncode (MaxFragmentLength l) = runPut $ putWord8 $ fromMaxFragmentEnum l+ where+ fromMaxFragmentEnum MaxFragment512 = 1+ fromMaxFragmentEnum MaxFragment1024 = 2+ fromMaxFragmentEnum MaxFragment2048 = 3+ fromMaxFragmentEnum MaxFragment4096 = 4+ extensionEncode (MaxFragmentLengthOther l) = runPut $ putWord8 l+ extensionDecode MsgTClientHello = decodeMaxFragmentLength+ extensionDecode MsgTServerHello = decodeMaxFragmentLength+ extensionDecode MsgTEncryptedExtensions = decodeMaxFragmentLength+ extensionDecode _ = error "extensionDecode: MaxFragmentLength"++decodeMaxFragmentLength :: ByteString -> Maybe MaxFragmentLength+decodeMaxFragmentLength = runGetMaybe $ toMaxFragmentEnum <$> getWord8+ where+ toMaxFragmentEnum 1 = MaxFragmentLength MaxFragment512+ toMaxFragmentEnum 2 = MaxFragmentLength MaxFragment1024+ toMaxFragmentEnum 3 = MaxFragmentLength MaxFragment2048+ toMaxFragmentEnum 4 = MaxFragmentLength MaxFragment4096+ toMaxFragmentEnum n = MaxFragmentLengthOther n++------------------------------------------------------------++newtype SupportedGroups = SupportedGroups [Group] deriving (Show, Eq)++-- on decode, filter all unknown curves+instance Extension SupportedGroups where+ extensionID _ = EID_SupportedGroups+ extensionEncode (SupportedGroups groups) = runPut $ putWords16 $ map (\(Group g) -> g) groups+ extensionDecode MsgTClientHello = decodeSupportedGroups+ extensionDecode MsgTEncryptedExtensions = decodeSupportedGroups+ extensionDecode _ = error "extensionDecode: SupportedGroups"++decodeSupportedGroups :: ByteString -> Maybe SupportedGroups+decodeSupportedGroups =+ runGetMaybe (SupportedGroups . map Group <$> getWords16)++------------------------------------------------------------++newtype EcPointFormatsSupported = EcPointFormatsSupported [EcPointFormat]+ deriving (Show, Eq)++newtype EcPointFormat = EcPointFormat {fromEcPointFormat :: Word8}+ deriving (Eq)++{- FOURMOLU_DISABLE -}+pattern EcPointFormat_Uncompressed :: EcPointFormat+pattern EcPointFormat_Uncompressed = EcPointFormat 0+pattern EcPointFormat_AnsiX962_compressed_prime :: EcPointFormat+pattern EcPointFormat_AnsiX962_compressed_prime = EcPointFormat 1+pattern EcPointFormat_AnsiX962_compressed_char2 :: EcPointFormat+pattern EcPointFormat_AnsiX962_compressed_char2 = EcPointFormat 2++instance Show EcPointFormat where+ show EcPointFormat_Uncompressed = "EcPointFormat_Uncompressed"+ show EcPointFormat_AnsiX962_compressed_prime = "EcPointFormat_AnsiX962_compressed_prime"+ show EcPointFormat_AnsiX962_compressed_char2 = "EcPointFormat_AnsiX962_compressed_char2"+ show (EcPointFormat x) = "EcPointFormat " ++ show x+{- FOURMOLU_ENABLE -}++-- on decode, filter all unknown formats+instance Extension EcPointFormatsSupported where+ extensionID _ = EID_EcPointFormats+ extensionEncode (EcPointFormatsSupported formats) = runPut $ putWords8 $ map fromEcPointFormat formats+ extensionDecode MsgTClientHello = decodeEcPointFormatsSupported+ extensionDecode MsgTServerHello = decodeEcPointFormatsSupported+ extensionDecode _ = error "extensionDecode: EcPointFormatsSupported"++decodeEcPointFormatsSupported :: ByteString -> Maybe EcPointFormatsSupported+decodeEcPointFormatsSupported =+ runGetMaybe (EcPointFormatsSupported . map EcPointFormat <$> getWords8)++------------------------------------------------------------++newtype SignatureAlgorithms = SignatureAlgorithms [HashAndSignatureAlgorithm]+ deriving (Show, Eq)++instance Extension SignatureAlgorithms where+ extensionID _ = EID_SignatureAlgorithms+ extensionEncode (SignatureAlgorithms algs) =+ runPut $+ putWord16 (fromIntegral (length algs * 2))+ >> mapM_ putSignatureHashAlgorithm algs+ extensionDecode MsgTClientHello = decodeSignatureAlgorithms+ extensionDecode MsgTCertificateRequest = decodeSignatureAlgorithms+ extensionDecode _ = error "extensionDecode: SignatureAlgorithms"++decodeSignatureAlgorithms :: ByteString -> Maybe SignatureAlgorithms+decodeSignatureAlgorithms = runGetMaybe $ do+ len <- getWord16+ sas <-+ getList (fromIntegral len) (getSignatureHashAlgorithm >>= \sh -> return (2, sh))+ leftoverLen <- remaining+ when (leftoverLen /= 0) $ fail "decodeSignatureAlgorithms: broken length"+ when (null sas) $ fail "signature algorithms are empty"+ return $ SignatureAlgorithms sas++------------------------------------------------------------++newtype HeartBeat = HeartBeat HeartBeatMode deriving (Show, Eq)++newtype HeartBeatMode = HeartBeatMode {fromHeartBeatMode :: Word8}+ deriving (Eq)++{- FOURMOLU_DISABLE -}+pattern HeartBeat_PeerAllowedToSend :: HeartBeatMode+pattern HeartBeat_PeerAllowedToSend = HeartBeatMode 1+pattern HeartBeat_PeerNotAllowedToSend :: HeartBeatMode+pattern HeartBeat_PeerNotAllowedToSend = HeartBeatMode 2++instance Show HeartBeatMode where+ show HeartBeat_PeerAllowedToSend = "HeartBeat_PeerAllowedToSend"+ show HeartBeat_PeerNotAllowedToSend = "HeartBeat_PeerNotAllowedToSend"+ show (HeartBeatMode x) = "HeartBeatMode " ++ show x+{- FOURMOLU_ENABLE -}++instance Extension HeartBeat where+ extensionID _ = EID_Heartbeat+ extensionEncode (HeartBeat mode) = runPut $ putWord8 $ fromHeartBeatMode mode+ extensionDecode MsgTClientHello = decodeHeartBeat+ extensionDecode MsgTServerHello = decodeHeartBeat+ extensionDecode _ = error "extensionDecode: HeartBeat"++decodeHeartBeat :: ByteString -> Maybe HeartBeat+decodeHeartBeat = runGetMaybe $ HeartBeat . HeartBeatMode <$> getWord8++------------------------------------------------------------++-- | Application Layer Protocol Negotiation (ALPN)+newtype ApplicationLayerProtocolNegotiation+ = ApplicationLayerProtocolNegotiation [ByteString]+ deriving (Show, Eq)++instance Extension ApplicationLayerProtocolNegotiation where+ extensionID _ = EID_ApplicationLayerProtocolNegotiation+ extensionEncode (ApplicationLayerProtocolNegotiation bytes) =+ runPut $ putOpaque16 $ runPut $ mapM_ putOpaque8 bytes+ extensionDecode MsgTClientHello = decodeApplicationLayerProtocolNegotiation+ extensionDecode MsgTServerHello = decodeApplicationLayerProtocolNegotiation+ extensionDecode MsgTEncryptedExtensions = decodeApplicationLayerProtocolNegotiation+ extensionDecode _ = error "extensionDecode: ApplicationLayerProtocolNegotiation"++decodeApplicationLayerProtocolNegotiation+ :: ByteString -> Maybe ApplicationLayerProtocolNegotiation+decodeApplicationLayerProtocolNegotiation = runGetMaybe $ do+ len <- getWord16+ ApplicationLayerProtocolNegotiation <$> getList (fromIntegral len) getALPN+ where+ getALPN = do+ alpnParsed <- getOpaque8+ let alpn = B.copy alpnParsed+ return (B.length alpn + 1, alpn)++------------------------------------------------------------++-- | Extended Main Secret+data ExtendedMainSecret = ExtendedMainSecret deriving (Show, Eq)++instance Extension ExtendedMainSecret where+ extensionID _ = EID_ExtendedMainSecret+ extensionEncode ExtendedMainSecret = B.empty+ extensionDecode MsgTClientHello "" = Just ExtendedMainSecret+ extensionDecode MsgTServerHello "" = Just ExtendedMainSecret+ extensionDecode _ _ = error "extensionDecode: ExtendedMainSecret"++------------------------------------------------------------++newtype CertificateCompressionAlgorithm+ = CertificateCompressionAlgorithm Word16+ deriving (Eq)++{- FOURMOLU_DISABLE -}+pattern CCA_Zlib :: CertificateCompressionAlgorithm+pattern CCA_Zlib = CertificateCompressionAlgorithm 1+pattern CCA_Brotli :: CertificateCompressionAlgorithm+pattern CCA_Brotli = CertificateCompressionAlgorithm 2+pattern CCA_Zstd :: CertificateCompressionAlgorithm+pattern CCA_Zstd = CertificateCompressionAlgorithm 3++instance Show CertificateCompressionAlgorithm where+ show CCA_Zlib = "zlib"+ show CCA_Brotli = "brotli"+ show CCA_Zstd = "zstd"+ show (CertificateCompressionAlgorithm n) = "CertificateCompressionAlgorithm " ++ show n+{- FOURMOLU_ENABLE -}++newtype CompressCertificate = CompressCertificate [CertificateCompressionAlgorithm]+ deriving (Show, Eq)++instance Extension CompressCertificate where+ extensionID _ = EID_CompressCertificate+ extensionEncode (CompressCertificate cs) = runPut $ do+ putWord8 $ fromIntegral (length cs * 2)+ mapM_ putCCA cs+ where+ putCCA (CertificateCompressionAlgorithm n) = putWord16 n+ extensionDecode _ = decodeCompressCertificate++decodeCompressCertificate :: ByteString -> Maybe CompressCertificate+decodeCompressCertificate = runGetMaybe $ do+ len <- fromIntegral <$> getWord8+ cs <- getList len getCCA+ when (null cs) $ fail "empty list of CertificateCompressionAlgorithm"+ leftoverLen <- remaining+ when (leftoverLen /= 0) $ fail "decodeCompressCertificate: broken length"+ return $ CompressCertificate cs+ where+ getCCA = do+ cca <- CertificateCompressionAlgorithm <$> getWord16+ return (2, cca)++------------------------------------------------------------++newtype RecordSizeLimit = RecordSizeLimit Word16 deriving (Eq, Show)++instance Extension RecordSizeLimit where+ extensionID _ = EID_RecordSizeLimit+ extensionEncode (RecordSizeLimit n) = runPut $ putWord16 n+ extensionDecode _ = decodeRecordSizeLimit++decodeRecordSizeLimit :: ByteString -> Maybe RecordSizeLimit+decodeRecordSizeLimit = runGetMaybe $ do+ r <- RecordSizeLimit <$> getWord16+ leftoverLen <- remaining+ when (leftoverLen /= 0) $ fail "decodeRecordSizeLimit: broken length"+ return r++------------------------------------------------------------++newtype SessionTicket = SessionTicket Ticket+ deriving (Show, Eq)++-- https://datatracker.ietf.org/doc/html/rfc5077#appendix-A+instance Extension SessionTicket where+ extensionID _ = EID_SessionTicket+ extensionEncode (SessionTicket ticket) = runPut $ putBytes ticket+ extensionDecode MsgTClientHello = decodeSessionTicket+ extensionDecode MsgTServerHello = decodeSessionTicket+ extensionDecode _ = error "extensionDecode: SessionTicket"++decodeSessionTicket :: ByteString -> Maybe SessionTicket+decodeSessionTicket = runGetMaybe $ SessionTicket <$> (remaining >>= getBytes)++------------------------------------------------------------++data PskIdentity = PskIdentity ByteString Word32 deriving (Eq)++instance Show PskIdentity where+ show (PskIdentity bs n) = "PskId " ++ showBytesHex bs ++ " " ++ show n++data PreSharedKey+ = PreSharedKeyClientHello [PskIdentity] [ByteString]+ | PreSharedKeyServerHello Int+ deriving (Eq)++instance Show PreSharedKey where+ show (PreSharedKeyClientHello ids bndrs) =+ "PreSharedKey "+ ++ show ids+ ++ " "+ ++ "["+ ++ intercalate ", " (map showBytesHex bndrs)+ ++ "]"+ show (PreSharedKeyServerHello n) = "PreSharedKey " ++ show n++instance Extension PreSharedKey where+ extensionID _ = EID_PreSharedKey+ extensionEncode (PreSharedKeyClientHello ids bds) = runPut $ do+ putOpaque16 $ runPut (mapM_ putIdentity ids)+ putOpaque16 $ runPut (mapM_ putBinder bds)+ where+ putIdentity (PskIdentity bs w) = do+ putOpaque16 bs+ putWord32 w+ putBinder = putOpaque8+ extensionEncode (PreSharedKeyServerHello w16) =+ runPut $+ putWord16 $+ fromIntegral w16+ extensionDecode MsgTClientHello = decodePreSharedKeyClientHello+ extensionDecode MsgTServerHello = decodePreSharedKeyServerHello+ extensionDecode _ = error "extensionDecode: PreShareKey"++decodePreSharedKeyClientHello :: ByteString -> Maybe PreSharedKey+decodePreSharedKeyClientHello = runGetMaybe $ do+ len1 <- fromIntegral <$> getWord16+ identities <- getList len1 getIdentity+ len2 <- fromIntegral <$> getWord16+ binders <- getList len2 getBinder+ return $ PreSharedKeyClientHello identities binders+ where+ getIdentity = do+ identity <- getOpaque16+ age <- getWord32+ let len = 2 + B.length identity + 4+ return (len, PskIdentity identity age)+ getBinder = do+ l <- fromIntegral <$> getWord8+ binder <- getBytes l+ let len = l + 1+ return (len, binder)++decodePreSharedKeyServerHello :: ByteString -> Maybe PreSharedKey+decodePreSharedKeyServerHello =+ runGetMaybe $+ PreSharedKeyServerHello . fromIntegral <$> getWord16++decodePreSharedKey :: ByteString -> Maybe PreSharedKey+decodePreSharedKey bs =+ decodePreSharedKeyClientHello bs+ <|> decodePreSharedKeyServerHello bs++------------------------------------------------------------++newtype EarlyDataIndication = EarlyDataIndication (Maybe Word32)+ deriving (Eq, Show)++instance Extension EarlyDataIndication where+ extensionID _ = EID_EarlyData+ extensionEncode (EarlyDataIndication Nothing) = runPut $ putBytes B.empty+ extensionEncode (EarlyDataIndication (Just w32)) = runPut $ putWord32 w32+ extensionDecode MsgTClientHello = return $ Just (EarlyDataIndication Nothing)+ extensionDecode MsgTEncryptedExtensions = return $ Just (EarlyDataIndication Nothing)+ extensionDecode MsgTNewSessionTicket =+ runGetMaybe $+ EarlyDataIndication . Just <$> getWord32+ extensionDecode _ = error "extensionDecode: EarlyDataIndication"++------------------------------------------------------------++data SupportedVersions+ = SupportedVersionsClientHello [Version]+ | SupportedVersionsServerHello Version+ deriving (Eq)++instance Show SupportedVersions where+ show (SupportedVersionsClientHello vers) = "Versions " ++ show vers+ show (SupportedVersionsServerHello ver) = "Versions " ++ show ver++instance Extension SupportedVersions where+ extensionID _ = EID_SupportedVersions+ extensionEncode (SupportedVersionsClientHello vers) = runPut $ do+ putWord8 (fromIntegral (length vers * 2))+ mapM_ putBinaryVersion vers+ extensionEncode (SupportedVersionsServerHello ver) =+ runPut $+ putBinaryVersion ver+ extensionDecode MsgTClientHello = decodeSupportedVersionsClientHello+ extensionDecode MsgTServerHello = decodeSupportedVersionsServerHello+ extensionDecode _ = error "extensionDecode: SupportedVersionsServerHello"++decodeSupportedVersionsClientHello :: ByteString -> Maybe SupportedVersions+decodeSupportedVersionsClientHello = runGetMaybe $ do+ len <- fromIntegral <$> getWord8+ SupportedVersionsClientHello <$> getList len getVer+ where+ getVer = do+ ver <- getBinaryVersion+ return (2, ver)++decodeSupportedVersionsServerHello :: ByteString -> Maybe SupportedVersions+decodeSupportedVersionsServerHello =+ runGetMaybe (SupportedVersionsServerHello <$> getBinaryVersion)++decodeSupportedVersions :: ByteString -> Maybe SupportedVersions+decodeSupportedVersions bs =+ decodeSupportedVersionsClientHello bs+ <|> decodeSupportedVersionsServerHello bs++------------------------------------------------------------++newtype Cookie = Cookie ByteString deriving (Eq, Show)++instance Extension Cookie where+ extensionID _ = EID_Cookie+ extensionEncode (Cookie opaque) = runPut $ putOpaque16 opaque+ extensionDecode MsgTServerHello = runGetMaybe (Cookie <$> getOpaque16)+ extensionDecode _ = error "extensionDecode: Cookie"++------------------------------------------------------------++newtype PskKexMode = PskKexMode {fromPskKexMode :: Word8} deriving (Eq)++{- FOURMOLU_DISABLE -}+pattern PSK_KE :: PskKexMode+pattern PSK_KE = PskKexMode 0+pattern PSK_DHE_KE :: PskKexMode+pattern PSK_DHE_KE = PskKexMode 1++instance Show PskKexMode where+ show PSK_KE = "PSK_KE"+ show PSK_DHE_KE = "PSK_DHE_KE"+ show (PskKexMode x) = "PskKexMode " ++ show x+{- FOURMOLU_ENABLE -}++newtype PskKeyExchangeModes = PskKeyExchangeModes [PskKexMode]+ deriving (Eq, Show)++instance Extension PskKeyExchangeModes where+ extensionID _ = EID_PskKeyExchangeModes+ extensionEncode (PskKeyExchangeModes pkms) =+ runPut $+ putWords8 $+ map fromPskKexMode pkms+ extensionDecode MsgTClientHello = decodePskKeyExchangeModes+ extensionDecode _ = error "extensionDecode: PskKeyExchangeModes"++decodePskKeyExchangeModes :: ByteString -> Maybe PskKeyExchangeModes+decodePskKeyExchangeModes =+ runGetMaybe $+ PskKeyExchangeModes . map PskKexMode <$> getWords8++------------------------------------------------------------++newtype CertificateAuthorities = CertificateAuthorities [DistinguishedName]+ deriving (Eq, Show)++instance Extension CertificateAuthorities where+ extensionID _ = EID_CertificateAuthorities+ extensionEncode (CertificateAuthorities names) =+ runPut $+ putDNames names+ extensionDecode MsgTClientHello = decodeCertificateAuthorities+ extensionDecode MsgTCertificateRequest = decodeCertificateAuthorities+ extensionDecode _ = error "extensionDecode: CertificateAuthorities"++decodeCertificateAuthorities :: ByteString -> Maybe CertificateAuthorities+decodeCertificateAuthorities =+ runGetMaybe (CertificateAuthorities <$> getDNames)++------------------------------------------------------------++data PostHandshakeAuth = PostHandshakeAuth deriving (Show, Eq)++instance Extension PostHandshakeAuth where+ extensionID _ = EID_PostHandshakeAuth+ extensionEncode _ = B.empty+ extensionDecode MsgTClientHello = runGetMaybe $ return PostHandshakeAuth+ extensionDecode _ = error "extensionDecode: PostHandshakeAuth"++------------------------------------------------------------++newtype SignatureAlgorithmsCert = SignatureAlgorithmsCert [HashAndSignatureAlgorithm]+ deriving (Show, Eq)++instance Extension SignatureAlgorithmsCert where+ extensionID _ = EID_SignatureAlgorithmsCert+ extensionEncode (SignatureAlgorithmsCert algs) =+ runPut $+ putWord16 (fromIntegral (length algs * 2))+ >> mapM_ putSignatureHashAlgorithm algs+ extensionDecode MsgTClientHello = decodeSignatureAlgorithmsCert+ extensionDecode MsgTCertificateRequest = decodeSignatureAlgorithmsCert+ extensionDecode _ = error "extensionDecode: SignatureAlgorithmsCert"++decodeSignatureAlgorithmsCert :: ByteString -> Maybe SignatureAlgorithmsCert+decodeSignatureAlgorithmsCert = runGetMaybe $ do+ len <- getWord16+ SignatureAlgorithmsCert+ <$> getList (fromIntegral len) (getSignatureHashAlgorithm >>= \sh -> return (2, sh))++------------------------------------------------------------++data KeyShareEntry = KeyShareEntry+ { keyShareEntryGroup :: Group+ , keyShareEntryKeyExchange :: ByteString+ }+ deriving (Eq)++instance Show KeyShareEntry where+ show kse = show $ keyShareEntryGroup kse++getKeyShareEntry :: Get (Int, Maybe KeyShareEntry)+getKeyShareEntry = do+ grp <- Group <$> getWord16+ l <- fromIntegral <$> getWord16+ key <- getBytes l+ let len = l + 4+ return (len, Just $ KeyShareEntry grp key)++putKeyShareEntry :: KeyShareEntry -> Put+putKeyShareEntry (KeyShareEntry (Group grp) key) = do+ putWord16 grp+ putWord16 $ fromIntegral $ B.length key+ putBytes key++data KeyShare+ = KeyShareClientHello [KeyShareEntry]+ | KeyShareServerHello KeyShareEntry+ | KeyShareHRR Group+ deriving (Eq)++{- FOURMOLU_DISABLE -}+instance Show KeyShare where+ show (KeyShareClientHello kses) = "KeyShare " ++ show kses+ show (KeyShareServerHello kse) = "KeyShare " ++ show kse+ show (KeyShareHRR g) = "KeyShareHRR " ++ show g+{- FOURMOLU_ENABLE -}++instance Extension KeyShare where+ extensionID _ = EID_KeyShare+ extensionEncode (KeyShareClientHello kses) = runPut $ do+ let len = sum [B.length key + 4 | KeyShareEntry _ key <- kses]+ putWord16 $ fromIntegral len+ mapM_ putKeyShareEntry kses+ extensionEncode (KeyShareServerHello kse) = runPut $ putKeyShareEntry kse+ extensionEncode (KeyShareHRR (Group grp)) = runPut $ putWord16 grp+ extensionDecode MsgTClientHello = decodeKeyShareClientHello+ extensionDecode MsgTServerHello = decodeKeyShareServerHello+ extensionDecode MsgTHelloRetryRequest = decodeKeyShareHRR+ extensionDecode _ = error "extensionDecode: KeyShare"++decodeKeyShareClientHello :: ByteString -> Maybe KeyShare+decodeKeyShareClientHello = runGetMaybe $ do+ len <- fromIntegral <$> getWord16+ -- len == 0 allows for HRR+ grps <- getList len getKeyShareEntry+ return $ KeyShareClientHello $ catMaybes grps++decodeKeyShareServerHello :: ByteString -> Maybe KeyShare+decodeKeyShareServerHello = runGetMaybe $ do+ (_, ment) <- getKeyShareEntry+ case ment of+ Nothing -> fail "decoding KeyShare for ServerHello"+ Just ent -> return $ KeyShareServerHello ent++decodeKeyShareHRR :: ByteString -> Maybe KeyShare+decodeKeyShareHRR =+ runGetMaybe $+ KeyShareHRR . Group <$> getWord16++decodeKeyShare :: ByteString -> Maybe KeyShare+decodeKeyShare bs =+ decodeKeyShareClientHello bs+ <|> decodeKeyShareServerHello bs+ <|> decodeKeyShareHRR bs++------------------------------------------------------------++newtype EchOuterExtensions = EchOuterExtensions [ExtensionID]+ deriving (Eq, Show)++instance Extension EchOuterExtensions where+ extensionID _ = EID_EchOuterExtensions+ extensionEncode (EchOuterExtensions ids) = runPut $ do+ putWord8 $ fromIntegral (length ids * 2)+ mapM_ (putWord16 . fromExtensionID) ids+ extensionDecode MsgTClientHello = decodeEchOuterExtensions+ extensionDecode _ = error "extensionDecode: EchOuterExtensions"++decodeEchOuterExtensions :: ByteString -> Maybe EchOuterExtensions+decodeEchOuterExtensions = runGetMaybe $ do+ len <- fromIntegral <$> getWord8+ eids <- getList len $ do+ eid <- ExtensionID <$> getWord16+ return (2, eid)+ return $ EchOuterExtensions eids++------------------------------------------------------------++-- | Encrypted Client Hello+data EncryptedClientHello+ = ECHClientHelloInner+ | ECHClientHelloOuter+ { echCipherSuite :: (KDF_ID, AEAD_ID)+ , echConfigId :: ConfigId+ , echEnc :: EncodedPublicKey+ , echPayload :: ByteString+ }+ | ECHEncryptedExtensions ECHConfigList+ | ECHHelloRetryRequest ByteString+ deriving (Eq)++instance Show EncryptedClientHello where+ show ECHClientHelloInner = "ECHClientHelloInner"+ show ECHClientHelloOuter{..} =+ "ECHClientHelloOuter {"+ ++ show (fst echCipherSuite)+ ++ " "+ ++ show (snd echCipherSuite)+ ++ " "+ ++ show echConfigId+ ++ " "+ ++ showBytesHex enc+ ++ " "+ ++ showBytesHex echPayload+ ++ "}"+ where+ EncodedPublicKey enc = echEnc+ show (ECHEncryptedExtensions cnflst) = "ECHEncryptedExtensions " ++ show cnflst+ show (ECHHelloRetryRequest cnfm) = "ECHHelloRetryRequest " ++ showBytesHex cnfm++instance Extension EncryptedClientHello where+ extensionID _ = EID_EncryptedClientHello+ extensionEncode ECHClientHelloInner = runPut $ putWord8 1+ extensionEncode ECHClientHelloOuter{..} = runPut $ do+ putWord8 0+ let (kdfid, aeadid) = echCipherSuite+ putWord16 $ fromKDF_ID kdfid+ putWord16 $ fromAEAD_ID aeadid+ putWord8 echConfigId+ let EncodedPublicKey enc = echEnc+ putOpaque16 enc+ putOpaque16 echPayload+ extensionEncode (ECHEncryptedExtensions cnflist) = encodeECHConfigList cnflist+ extensionEncode (ECHHelloRetryRequest cnfm) = runPut $ putBytes cnfm+ extensionDecode MsgTClientHello = decodeECHClientHello+ extensionDecode MsgTEncryptedExtensions = decodeECHEncryptedExtensions+ extensionDecode MsgTHelloRetryRequest = decodeECHHelloRetryRequest+ extensionDecode _ = error "extensionDecode: EncryptedClientHello"++decodeECH :: ByteString -> Maybe EncryptedClientHello+decodeECH bs =+ decodeECHClientHello bs+ <|> decodeECHEncryptedExtensions bs+ <|> decodeECHHelloRetryRequest bs++decodeECHClientHello :: ByteString -> Maybe EncryptedClientHello+decodeECHClientHello = runGetMaybe $ do+ typ <- getWord8+ if typ == 1+ then return ECHClientHelloInner+ else do+ kdfid <- KDF_ID <$> getWord16+ aeadid <- AEAD_ID <$> getWord16+ cnfid <- getWord8+ enc <- EncodedPublicKey <$> getOpaque16+ payload <- getOpaque16+ return $+ ECHClientHelloOuter+ { echCipherSuite = (kdfid, aeadid)+ , echConfigId = cnfid+ , echEnc = enc+ , echPayload = payload+ }++decodeECHEncryptedExtensions :: ByteString -> Maybe EncryptedClientHello+decodeECHEncryptedExtensions bs =+ ECHEncryptedExtensions <$> decodeECHConfigList bs++decodeECHHelloRetryRequest :: ByteString -> Maybe EncryptedClientHello+decodeECHHelloRetryRequest = runGetMaybe $ do+ ECHHelloRetryRequest <$> getBytes 8++------------------------------------------------------------++-- | Secure Renegotiation+data SecureRenegotiation = SecureRenegotiation ByteString ByteString+ deriving (Show, Eq)++instance Extension SecureRenegotiation where+ extensionID _ = EID_SecureRenegotiation+ extensionEncode (SecureRenegotiation cvd svd) =+ runPut $ putOpaque8 (cvd `B.append` svd)+ extensionDecode MsgTClientHello = runGetMaybe $ do+ opaque <- getOpaque8+ return $ SecureRenegotiation opaque ""+ extensionDecode MsgTServerHello = runGetMaybe $ do+ opaque <- getOpaque8+ let (cvd, svd) = B.splitAt (B.length opaque `div` 2) opaque+ return $ SecureRenegotiation cvd svd+ extensionDecode _ = error "extensionDecode: SecureRenegotiation"
+ Network/TLS/Extension.hs-boot view
@@ -0,0 +1,22 @@+-- This is a breaker for cyclic imports:+--+-- - Network.TLS.Extension imports Network.TLS.Struct+-- - Network.TLS.Extension imports Network.TLS.Packet+--+-- - Network.TLS.Struct imports Network.TLS.Extension+--+-- - Network.TLS.Packet imports Network.TLS.Struct+--+-- Originally, ExtensionRaw was defined in Network.TLS.Struct and no+-- cyclic imports exist. It is moved into Network.TLS.Extension for+-- pretty-printing, so the cyclic imports happen.+module Network.TLS.Extension where++import Data.ByteString+import Data.Word++data ExtensionRaw = ExtensionRaw ExtensionID ByteString+instance Eq ExtensionRaw+instance Show ExtensionRaw++newtype ExtensionID = ExtensionID {fromExtensionID :: Word16}
Network/TLS/Extra.hs view
@@ -1,13 +1,8 @@--- |--- Module : Network.TLS.Extra--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ default values and ciphers-module Network.TLS.Extra- ( module Network.TLS.Extra.Cipher- ) where+-- | Default values and ciphers+module Network.TLS.Extra (+ module Network.TLS.Extra.Cipher,+ module Network.TLS.Extra.FFDHE,+) where import Network.TLS.Extra.Cipher+import Network.TLS.Extra.FFDHE
Network/TLS/Extra/Cipher.hs view
@@ -1,432 +1,808 @@--- |--- Module : Network.TLS.Extra.Cipher--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----{-# LANGUAGE CPP #-}-{-# LANGUAGE PackageImports #-}-module Network.TLS.Extra.Cipher- (- -- * cipher suite- ciphersuite_all- , ciphersuite_medium- , ciphersuite_strong- , ciphersuite_unencrypted- , ciphersuite_dhe_rsa- , ciphersuite_dhe_dss- -- * individual ciphers- , cipher_null_SHA1- , cipher_null_MD5- , cipher_RC4_128_MD5- , cipher_RC4_128_SHA1- , cipher_AES128_SHA1- , cipher_AES256_SHA1- , cipher_AES128_SHA256- , cipher_AES256_SHA256- , cipher_RSA_3DES_EDE_CBC_SHA1- , cipher_DHE_RSA_AES128_SHA1- , cipher_DHE_RSA_AES256_SHA1- , cipher_DHE_RSA_AES128_SHA256- , cipher_DHE_RSA_AES256_SHA256- , cipher_DHE_DSS_AES128_SHA1- , cipher_DHE_DSS_AES256_SHA1- , cipher_DHE_DSS_RC4_SHA1- ) where--import qualified Data.ByteString as B--import Network.TLS (Version(..))-import Network.TLS.Cipher-import qualified "cipher-rc4" Crypto.Cipher.RC4 as RC4--import qualified Crypto.Hash.SHA256 as SHA256-import qualified Crypto.Hash.SHA1 as SHA1-import qualified Crypto.Hash.MD5 as MD5--import qualified "cipher-aes" Crypto.Cipher.AES as AES-import Crypto.Cipher.TripleDES-import Crypto.Cipher.Types (makeKey, makeIV, cipherInit, cbcEncrypt, cbcDecrypt)-import qualified Crypto.Cipher.Types as T--aes_cbc_encrypt :: Key -> IV -> B.ByteString -> B.ByteString-aes_cbc_encrypt key iv d = AES.encryptCBC (AES.initAES key) iv d--aes_cbc_decrypt :: Key -> IV -> B.ByteString -> B.ByteString-aes_cbc_decrypt key iv d = AES.decryptCBC (AES.initAES key) iv d--aes128_cbc_encrypt- , aes128_cbc_decrypt- , aes256_cbc_encrypt- , aes256_cbc_decrypt :: Key -> IV -> B.ByteString -> B.ByteString-aes128_cbc_encrypt = aes_cbc_encrypt-aes128_cbc_decrypt = aes_cbc_decrypt-aes256_cbc_encrypt = aes_cbc_encrypt-aes256_cbc_decrypt = aes_cbc_decrypt--tripledes_ede_cbc_encrypt :: Key -> IV -> B.ByteString -> B.ByteString-tripledes_ede_cbc_encrypt key iv bs =- cbcEncrypt (cipherInit $ tripledes_key key) (tripledes_iv iv) bs--tripledes_ede_cbc_decrypt :: Key -> IV -> B.ByteString -> B.ByteString-tripledes_ede_cbc_decrypt key iv bs =- cbcDecrypt (cipherInit $ tripledes_key key) (tripledes_iv iv) bs--tripledes_key :: Key -> T.Key DES_EDE3-tripledes_key key = either (\ke -> error ("tripledes cipher key internal error: " ++ show ke)) id $ makeKey key--tripledes_iv :: IV -> T.IV DES_EDE3-tripledes_iv iv = maybe (error "tripledes cipher iv internal error") id $ makeIV iv--toIV :: RC4.Ctx -> IV-toIV (RC4.Ctx ctx) = ctx--toCtx :: IV -> RC4.Ctx-toCtx iv = RC4.Ctx iv--initF_rc4 :: Key -> IV-initF_rc4 key = toIV $ RC4.initCtx key--encryptF_rc4 :: IV -> B.ByteString -> (B.ByteString, IV)-encryptF_rc4 iv d = (\(ctx, e) -> (e, toIV ctx)) $ RC4.combine (toCtx iv) d--decryptF_rc4 :: IV -> B.ByteString -> (B.ByteString, IV)-decryptF_rc4 iv e = (\(ctx, d) -> (d, toIV ctx)) $ RC4.combine (toCtx iv) e----- | all encrypted ciphers supported ordered from strong to weak.--- this choice of ciphersuite should satisfy most normal need-ciphersuite_all :: [Cipher]-ciphersuite_all =- [ cipher_DHE_RSA_AES256_SHA256, cipher_DHE_RSA_AES128_SHA256- , cipher_DHE_RSA_AES256_SHA1, cipher_DHE_RSA_AES128_SHA1- , cipher_DHE_DSS_AES256_SHA1, cipher_DHE_DSS_AES128_SHA1- , cipher_AES128_SHA256, cipher_AES256_SHA256- , cipher_AES128_SHA1, cipher_AES256_SHA1- , cipher_DHE_DSS_RC4_SHA1, cipher_RC4_128_SHA1, cipher_RC4_128_MD5- , cipher_RSA_3DES_EDE_CBC_SHA1- ]---- | list of medium ciphers.-ciphersuite_medium :: [Cipher]-ciphersuite_medium = [cipher_RC4_128_MD5, cipher_RC4_128_SHA1, cipher_AES128_SHA1, cipher_AES256_SHA1]---- | the strongest ciphers supported.-ciphersuite_strong :: [Cipher]-ciphersuite_strong = [cipher_DHE_RSA_AES256_SHA256, cipher_AES256_SHA256, cipher_AES256_SHA1]---- | DHE-RSA cipher suite-ciphersuite_dhe_rsa :: [Cipher]-ciphersuite_dhe_rsa = [cipher_DHE_RSA_AES256_SHA256, cipher_DHE_RSA_AES128_SHA256- , cipher_DHE_RSA_AES256_SHA1, cipher_DHE_RSA_AES128_SHA1]--ciphersuite_dhe_dss :: [Cipher]-ciphersuite_dhe_dss = [cipher_DHE_DSS_AES256_SHA1, cipher_DHE_DSS_AES128_SHA1, cipher_DHE_DSS_RC4_SHA1]---- | all unencrypted ciphers, do not use on insecure network.-ciphersuite_unencrypted :: [Cipher]-ciphersuite_unencrypted = [cipher_null_MD5, cipher_null_SHA1]--bulk_null, bulk_rc4, bulk_aes128, bulk_aes256, bulk_tripledes_ede :: Bulk-bulk_null = Bulk- { bulkName = "null"- , bulkKeySize = 0- , bulkIVSize = 0- , bulkBlockSize = 0- , bulkF = BulkStreamF (const B.empty) streamId streamId- }- where streamId = \iv b -> (b,iv)--bulk_rc4 = Bulk- { bulkName = "RC4-128"- , bulkKeySize = 16- , bulkIVSize = 0- , bulkBlockSize = 0- , bulkF = BulkStreamF initF_rc4 encryptF_rc4 decryptF_rc4- }--bulk_aes128 = Bulk- { bulkName = "AES128"- , bulkKeySize = 16- , bulkIVSize = 16- , bulkBlockSize = 16- , bulkF = BulkBlockF aes128_cbc_encrypt aes128_cbc_decrypt- }--bulk_aes256 = Bulk- { bulkName = "AES256"- , bulkKeySize = 32- , bulkIVSize = 16- , bulkBlockSize = 16- , bulkF = BulkBlockF aes256_cbc_encrypt aes256_cbc_decrypt- }--bulk_tripledes_ede = Bulk- { bulkName = "3DES-EDE-CBC"- , bulkKeySize = 24- , bulkIVSize = 8- , bulkBlockSize = 8- , bulkF = BulkBlockF tripledes_ede_cbc_encrypt tripledes_ede_cbc_decrypt- }--hash_md5, hash_sha1, hash_sha256 :: Hash-hash_md5 = Hash- { hashName = "MD5"- , hashSize = 16- , hashF = MD5.hash- }--hash_sha1 = Hash- { hashName = "SHA1"- , hashSize = 20- , hashF = SHA1.hash- }--hash_sha256 = Hash- { hashName = "SHA256"- , hashSize = 32- , hashF = SHA256.hash- }---- | unencrypted cipher using RSA for key exchange and MD5 for digest-cipher_null_MD5 :: Cipher-cipher_null_MD5 = Cipher- { cipherID = 0x1- , cipherName = "RSA-null-MD5"- , cipherBulk = bulk_null- , cipherHash = hash_md5- , cipherKeyExchange = CipherKeyExchange_RSA- , cipherMinVer = Nothing- }---- | unencrypted cipher using RSA for key exchange and SHA1 for digest-cipher_null_SHA1 :: Cipher-cipher_null_SHA1 = Cipher- { cipherID = 0x2- , cipherName = "RSA-null-SHA1"- , cipherBulk = bulk_null- , cipherHash = hash_sha1- , cipherKeyExchange = CipherKeyExchange_RSA- , cipherMinVer = Nothing- }---- | RC4 cipher, RSA key exchange and MD5 for digest-cipher_RC4_128_MD5 :: Cipher-cipher_RC4_128_MD5 = Cipher- { cipherID = 0x04- , cipherName = "RSA-rc4-128-md5"- , cipherBulk = bulk_rc4- , cipherHash = hash_md5- , cipherKeyExchange = CipherKeyExchange_RSA- , cipherMinVer = Nothing- }---- | RC4 cipher, RSA key exchange and SHA1 for digest-cipher_RC4_128_SHA1 :: Cipher-cipher_RC4_128_SHA1 = Cipher- { cipherID = 0x05- , cipherName = "RSA-rc4-128-sha1"- , cipherBulk = bulk_rc4- , cipherHash = hash_sha1- , cipherKeyExchange = CipherKeyExchange_RSA- , cipherMinVer = Nothing- }---- | AES cipher (128 bit key), RSA key exchange and SHA1 for digest-cipher_AES128_SHA1 :: Cipher-cipher_AES128_SHA1 = Cipher- { cipherID = 0x2f- , cipherName = "RSA-aes128-sha1"- , cipherBulk = bulk_aes128- , cipherHash = hash_sha1- , cipherKeyExchange = CipherKeyExchange_RSA- , cipherMinVer = Just SSL3- }---- | AES cipher (256 bit key), RSA key exchange and SHA1 for digest-cipher_AES256_SHA1 :: Cipher-cipher_AES256_SHA1 = Cipher- { cipherID = 0x35- , cipherName = "RSA-aes256-sha1"- , cipherBulk = bulk_aes256- , cipherHash = hash_sha1- , cipherKeyExchange = CipherKeyExchange_RSA- , cipherMinVer = Just SSL3- }---- | AES cipher (128 bit key), RSA key exchange and SHA256 for digest-cipher_AES128_SHA256 :: Cipher-cipher_AES128_SHA256 = Cipher- { cipherID = 0x3c- , cipherName = "RSA-aes128-sha256"- , cipherBulk = bulk_aes128- , cipherHash = hash_sha256- , cipherKeyExchange = CipherKeyExchange_RSA- , cipherMinVer = Just TLS12- }---- | AES cipher (256 bit key), RSA key exchange and SHA256 for digest-cipher_AES256_SHA256 :: Cipher-cipher_AES256_SHA256 = Cipher- { cipherID = 0x3d- , cipherName = "RSA-aes256-sha256"- , cipherBulk = bulk_aes256- , cipherHash = hash_sha256- , cipherKeyExchange = CipherKeyExchange_RSA- , cipherMinVer = Just TLS12- }---- | AES cipher (128 bit key), DHE key exchanged signed by RSA and SHA1 for digest-cipher_DHE_RSA_AES128_SHA1 :: Cipher-cipher_DHE_RSA_AES128_SHA1 = Cipher- { cipherID = 0x33- , cipherName = "DHE-RSA-AES128-SHA1"- , cipherBulk = bulk_aes128- , cipherHash = hash_sha1- , cipherKeyExchange = CipherKeyExchange_DHE_RSA- , cipherMinVer = Nothing- }---- | AES cipher (256 bit key), DHE key exchanged signed by RSA and SHA1 for digest-cipher_DHE_RSA_AES256_SHA1 :: Cipher-cipher_DHE_RSA_AES256_SHA1 = cipher_DHE_RSA_AES128_SHA1- { cipherID = 0x39- , cipherName = "DHE-RSA-AES256-SHA1"- , cipherBulk = bulk_aes256- }---- | AES cipher (128 bit key), DHE key exchanged signed by DSA and SHA1 for digest-cipher_DHE_DSS_AES128_SHA1 :: Cipher-cipher_DHE_DSS_AES128_SHA1 = Cipher- { cipherID = 0x32- , cipherName = "DHE-DSA-AES128-SHA1"- , cipherBulk = bulk_aes128- , cipherHash = hash_sha1- , cipherKeyExchange = CipherKeyExchange_DHE_DSS- , cipherMinVer = Nothing- }---- | AES cipher (256 bit key), DHE key exchanged signed by DSA and SHA1 for digest-cipher_DHE_DSS_AES256_SHA1 :: Cipher-cipher_DHE_DSS_AES256_SHA1 = cipher_DHE_DSS_AES128_SHA1- { cipherID = 0x38- , cipherName = "DHE-DSA-AES256-SHA1"- , cipherBulk = bulk_aes256- }--cipher_DHE_DSS_RC4_SHA1 :: Cipher-cipher_DHE_DSS_RC4_SHA1 = cipher_DHE_DSS_AES128_SHA1- { cipherID = 0x66- , cipherName = "DHE-DSA-RC4-SHA1"- , cipherBulk = bulk_rc4- }--cipher_DHE_RSA_AES128_SHA256 :: Cipher-cipher_DHE_RSA_AES128_SHA256 = cipher_DHE_RSA_AES128_SHA1- { cipherID = 0x67- , cipherName = "DHE-RSA-AES128-SHA256"- , cipherHash = hash_sha256- , cipherMinVer = Just TLS12- }--cipher_DHE_RSA_AES256_SHA256 :: Cipher-cipher_DHE_RSA_AES256_SHA256 = cipher_DHE_RSA_AES128_SHA256- { cipherID = 0x6b- , cipherName = "DHE-RSA-AES256-SHA256"- , cipherBulk = bulk_aes256- }---- | 3DES cipher (168 bit key), RSA key exchange and SHA1 for digest-cipher_RSA_3DES_EDE_CBC_SHA1 :: Cipher-cipher_RSA_3DES_EDE_CBC_SHA1 = Cipher- { cipherID = 0x0a- , cipherName = "RSA-3DES-EDE-CBC-SHA1"- , cipherBulk = bulk_tripledes_ede- , cipherHash = hash_sha1- , cipherKeyExchange = CipherKeyExchange_RSA- , cipherMinVer = Nothing- }---{--TLS 1.0 ciphers definition--CipherSuite TLS_NULL_WITH_NULL_NULL = { 0x00,0x00 };-CipherSuite TLS_RSA_WITH_NULL_MD5 = { 0x00,0x01 };-CipherSuite TLS_RSA_WITH_NULL_SHA = { 0x00,0x02 };-CipherSuite TLS_RSA_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x03 };-CipherSuite TLS_RSA_WITH_RC4_128_MD5 = { 0x00,0x04 };-CipherSuite TLS_RSA_WITH_RC4_128_SHA = { 0x00,0x05 };-CipherSuite TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = { 0x00,0x06 };-CipherSuite TLS_RSA_WITH_IDEA_CBC_SHA = { 0x00,0x07 };-CipherSuite TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x08 };-CipherSuite TLS_RSA_WITH_DES_CBC_SHA = { 0x00,0x09 };-CipherSuite TLS_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0A };-CipherSuite TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x0B };-CipherSuite TLS_DH_DSS_WITH_DES_CBC_SHA = { 0x00,0x0C };-CipherSuite TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0D };-CipherSuite TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x0E };-CipherSuite TLS_DH_RSA_WITH_DES_CBC_SHA = { 0x00,0x0F };-CipherSuite TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x10 };-CipherSuite TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x11 };-CipherSuite TLS_DHE_DSS_WITH_DES_CBC_SHA = { 0x00,0x12 };-CipherSuite TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x13 };-CipherSuite TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x14 };-CipherSuite TLS_DHE_RSA_WITH_DES_CBC_SHA = { 0x00,0x15 };-CipherSuite TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x16 };-CipherSuite TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x17 };-CipherSuite TLS_DH_anon_WITH_RC4_128_MD5 = { 0x00,0x18 };-CipherSuite TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x19 };-CipherSuite TLS_DH_anon_WITH_DES_CBC_SHA = { 0x00,0x1A };-CipherSuite TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = { 0x00,0x1B };--TLS-DHE-RSA-WITH-AES-128-CBC-SHA {0x00,0x33}-TLS-DHE-RSA-WITH-AES-256-CBC-SHA {0x00,0x39}-TLS-DHE-RSA-WITH-AES-128-CBC-SHA256 {0x00,0x67}-TLS-DHE-RSA-WITH-AES-256-CBC-SHA256 {0x00,0x6B}-TLS-DHE-RSA-WITH-AES-128-GCM-SHA256 {0x00,0x9E}-TLS-DHE-RSA-WITH-AES-256-GCM-SHA384 {0x00,0x9F}-TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA {0x00,0x45}-TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA {0x00,0x88}-TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA256 {0x00,0xBE}-TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA256 {0x00,0xC4}-TLS-DHE-RSA-WITH-CAMELLIA-128-GCM-SHA256 {0x00,0x7C}-TLS-DHE-RSA-WITH-CAMELLIA-256-GCM-SHA256 {0x00,0x7D}-TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA {0x00,0x16}-TLS-DHE-RSA-WITH-DES-CBC-SHA {0x00,0x15}--TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA {0xC0,0x13}-TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA {0xC0,0x14}-TLS-ECDHE-RSA-WITH-AES-128-CBC-SHA256 {0xC0,0x27}-TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384 {0xC0,0x28}-TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256 {0xC0,0x2F}-TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384 {0xC0,0x30}-TLS-ECDHE-RSA-WITH-CAMELLIA-128-CBC-SHA256 {0xC0,0x76}-TLS-ECDHE-RSA-WITH-CAMELLIA-256-CBC-SHA384 {0xC0,0x77}-TLS-ECDHE-RSA-WITH-CAMELLIA-128-GCM-SHA256 {0xC0,0x8A}-TLS-ECDHE-RSA-WITH-CAMELLIA-256-GCM-SHA384 {0xC0,0x8B}-TLS-ECDHE-RSA-WITH-3DES-EDE-CBC-SHA {0xC0,0x12}-TLS-ECDHE-RSA-WITH-RC4-128-SHA {0xC0,0x11}-TLS-ECDHE-RSA-WITH-NULL-SHA {0xC0,0x10}--TLS-PSK-WITH-RC4-128-SHA {0x00,0x8A}-TLS-PSK-WITH-3DES-EDE-CBC-SHA {0x00,0x8B}-TLS-PSK-WITH-AES-128-CBC-SHA {0x00,0x8C}-TLS-PSK-WITH-AES-256-CBC-SHA {0x00,0x8D}-TLS-PSK-WITH-AES-128-CBC-SHA256 {0x00,0xAE}-TLS-PSK-WITH-AES-256-CBC-SHA384 {0x00,0xAF}-TLS-PSK-WITH-AES-128-GCM-SHA256 {0x00,0xA8}-TLS-PSK-WITH-AES-256-GCM-SHA384 {0x00,0xA9}-TLS-PSK-WITH-CAMELLIA-128-CBC-SHA256 {0xC0,0x94}-TLS-PSK-WITH-CAMELLIA-256-CBC-SHA384 {0xC0,0x95}-TLS-PSK-WITH-CAMELLIA-128-GCM-SHA256 {0xC0,0x8D}-TLS-PSK-WITH-CAMELLIA-256-GCM-SHA384 {0xC0,0x8F}-TLS-PSK-WITH-NULL-SHA {0x00,0x2C}-TLS-PSK-WITH-NULL-SHA256 {0x00,0xB4}-TLS-PSK-WITH-NULL-SHA384 {0x00,0xB5}--best ciphers suite description:- <http://www.thesprawl.org/research/tls-and-ssl-cipher-suites/>---}+module Network.TLS.Extra.Cipher (+ -- * Cipher suite+ ciphersuite_default,+ ciphersuite_default_det,+ ciphersuite_all,+ ciphersuite_all_det,+ ciphersuite_strong,+ ciphersuite_strong_det,+ ciphersuite_dhe_rsa,++ -- * Individual ciphers++ -- ** RFC 5288+ cipher_DHE_RSA_WITH_AES_128_GCM_SHA256,+ cipher_DHE_RSA_WITH_AES_256_GCM_SHA384,++ -- ** RFC 8446+ 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,++ -- ** RFC 5289+ cipher_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,+ cipher_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,+ cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256,+ cipher_ECDHE_RSA_WITH_AES_256_GCM_SHA384,++ -- ** RFC 7251+ cipher_ECDHE_ECDSA_WITH_AES_128_CCM,+ cipher_ECDHE_ECDSA_WITH_AES_256_CCM,+ cipher_ECDHE_ECDSA_WITH_AES_128_CCM_8,+ cipher_ECDHE_ECDSA_WITH_AES_256_CCM_8,++ -- ** RFC 7905+ cipher_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,+ cipher_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,+ cipher_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,++ -- * Deprecated names++ -- ** RFC 5288+ cipher_DHE_RSA_AES128GCM_SHA256,+ cipher_DHE_RSA_AES256GCM_SHA384,++ -- ** RFC 8446+ cipher_TLS13_AES128GCM_SHA256,+ cipher_TLS13_AES256GCM_SHA384,+ cipher_TLS13_CHACHA20POLY1305_SHA256,+ cipher_TLS13_AES128CCM_SHA256,+ cipher_TLS13_AES128CCM8_SHA256,++ -- ** RFC 5289+ cipher_ECDHE_ECDSA_AES128GCM_SHA256,+ cipher_ECDHE_ECDSA_AES256GCM_SHA384,+ cipher_ECDHE_RSA_AES128GCM_SHA256,+ cipher_ECDHE_RSA_AES256GCM_SHA384,++ -- ** RFC 7251+ cipher_ECDHE_ECDSA_AES128CCM_SHA256,+ cipher_ECDHE_ECDSA_AES256CCM_SHA256,+ cipher_ECDHE_ECDSA_AES128CCM8_SHA256,+ cipher_ECDHE_ECDSA_AES256CCM8_SHA256,++ -- ** RFC 7905+ cipher_ECDHE_RSA_CHACHA20POLY1305_SHA256,+ cipher_ECDHE_ECDSA_CHACHA20POLY1305_SHA256,+ cipher_DHE_RSA_CHACHA20POLY1305_SHA256,+) where++import Crypto.Cipher.AES+import qualified Crypto.Cipher.ChaChaPoly1305 as ChaChaPoly1305+import Crypto.Cipher.Types hiding (Cipher, cipherName)+import Crypto.Error+import qualified Crypto.MAC.Poly1305 as Poly1305+import Crypto.System.CPU+import qualified Data.ByteString as B+import Data.Tuple (swap)++import Network.TLS.Cipher+import Network.TLS.Imports+import Network.TLS.Types++----------------------------------------------------------------++-- | All AES and ChaCha20-Poly1305 ciphers supported ordered from strong to+-- weak. This choice of ciphersuites should satisfy most normal needs. For+-- otherwise strong ciphers we make little distinction between AES128 and+-- AES256, and list each but the weakest of the AES128 ciphers ahead of the+-- corresponding AES256 ciphers.+--+-- AEAD ciphers with equivalent security properties are ordered based on CPU+-- hardware-acceleration support. If this dynamic runtime behavior is not+-- desired, use 'ciphersuite_default_det' instead.+ciphersuite_default :: [Cipher]+ciphersuite_default = ciphersuite_strong++-- | Same as 'ciphersuite_default', but using deterministic preference not+-- influenced by the CPU.+ciphersuite_default_det :: [Cipher]+ciphersuite_default_det = ciphersuite_strong_det++----------------------------------------------------------------++-- | The default ciphersuites + some not recommended last resort ciphers.+--+-- AEAD ciphers with equivalent security properties are ordered based on CPU+-- hardware-acceleration support. If this dynamic runtime behavior is not+-- desired, use 'ciphersuite_all_det' instead.+ciphersuite_all :: [Cipher]+ciphersuite_all = ciphersuite_default ++ complement_all++-- | Same as 'ciphersuite_all', but using deterministic preference not+-- influenced by the CPU.+ciphersuite_all_det :: [Cipher]+ciphersuite_all_det = ciphersuite_default_det ++ complement_all++complement_all :: [Cipher]+complement_all =+ [ cipher_ECDHE_ECDSA_WITH_AES_128_CCM_8+ , cipher_ECDHE_ECDSA_WITH_AES_256_CCM_8+ , cipher13_AES_128_CCM_8_SHA256+ ]++-- | The strongest ciphers supported. For ciphers with PFS, AEAD and SHA2, we+-- list each AES128 variant after the corresponding AES256 and ChaCha20-Poly1305+-- variants. For weaker constructs, we use just the AES256 form.+--+-- AEAD ciphers with equivalent security properties are ordered based on CPU+-- hardware-acceleration support. If this dynamic runtime behavior is not+-- desired, use 'ciphersuite_strong_det' instead.+ciphersuite_strong :: [Cipher]+ciphersuite_strong = sortOptimized sets_strong++-- | Same as 'ciphersuite_strong', but using deterministic preference not+-- influenced by the CPU.+ciphersuite_strong_det :: [Cipher]+ciphersuite_strong_det = sortDeterministic sets_strong++sets_strong :: [CipherSet]+sets_strong =+ [ -- If we have PFS + AEAD + SHA2, then allow AES128, else just 256+ SetAead+ [cipher_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384]+ [cipher_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256]+ [cipher_ECDHE_ECDSA_WITH_AES_256_CCM]+ , SetAead+ [cipher_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256]+ []+ [cipher_ECDHE_ECDSA_WITH_AES_128_CCM]+ , SetAead+ [cipher_ECDHE_RSA_WITH_AES_256_GCM_SHA384]+ [cipher_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256]+ []+ , SetAead+ [cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256]+ []+ []+ , -- TLS13 (listed at the end but version is negotiated first)+ SetAead+ [cipher13_AES_256_GCM_SHA384]+ [cipher13_CHACHA20_POLY1305_SHA256]+ []+ , SetAead+ [cipher13_AES_128_GCM_SHA256]+ []+ [cipher13_AES_128_CCM_SHA256]+ ]++-- | DHE-RSA cipher suite. This only includes ciphers bound specifically to+-- DHE-RSA so TLS 1.3 ciphers must be added separately.+--+-- @since 2.1.5+ciphersuite_dhe_rsa :: [Cipher]+ciphersuite_dhe_rsa =+ [ cipher_DHE_RSA_WITH_AES_256_GCM_SHA384+ , cipher_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256+ , cipher_DHE_RSA_WITH_AES_128_GCM_SHA256+ ]++----------------------------------------------------------------+----------------------------------------------------------------++-- A list of cipher suite is found from:+-- https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4++----------------------------------------------------------------+-- RFC 5288++-- TLS_DHE_RSA_WITH_AES_128_GCM_SHA256+cipher_DHE_RSA_WITH_AES_128_GCM_SHA256 :: Cipher+cipher_DHE_RSA_WITH_AES_128_GCM_SHA256 =+ Cipher+ { cipherID = 0x009E+ , cipherName = "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"+ , cipherBulk = bulk_aes128gcm+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_DHE_RSA+ , cipherMinVer = Just TLS12 -- RFC 5288 Sec 4+ }++{-# DEPRECATED+ cipher_DHE_RSA_AES128GCM_SHA256+ "Use cipher_DHE_RSA_WITH_AES_128_GCM_SHA256 instead"+ #-}+cipher_DHE_RSA_AES128GCM_SHA256 :: Cipher+cipher_DHE_RSA_AES128GCM_SHA256 = cipher_DHE_RSA_WITH_AES_128_GCM_SHA256++-- TLS_DHE_RSA_WITH_AES_256_GCM_SHA384+cipher_DHE_RSA_WITH_AES_256_GCM_SHA384 :: Cipher+cipher_DHE_RSA_WITH_AES_256_GCM_SHA384 =+ Cipher+ { cipherID = 0x009F+ , cipherName = "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384"+ , cipherBulk = bulk_aes256gcm+ , cipherHash = SHA384+ , cipherPRFHash = Just SHA384+ , cipherKeyExchange = CipherKeyExchange_DHE_RSA+ , cipherMinVer = Just TLS12+ }++{-# DEPRECATED+ cipher_DHE_RSA_AES256GCM_SHA384+ "Use cipher_DHE_RSA_WITH_AES_256_GCM_SHA384 instead"+ #-}+cipher_DHE_RSA_AES256GCM_SHA384 :: Cipher+cipher_DHE_RSA_AES256GCM_SHA384 = cipher_DHE_RSA_WITH_AES_256_GCM_SHA384++----------------------------------------------------------------+-- RFC 8446++-- TLS_AES_128_GCM_SHA256+cipher13_AES_128_GCM_SHA256 :: Cipher+cipher13_AES_128_GCM_SHA256 =+ Cipher+ { cipherID = 0x1301+ , cipherName = "TLS_AES_128_GCM_SHA256"+ , cipherBulk = bulk_aes128gcm_13+ , cipherHash = SHA256+ , cipherPRFHash = Nothing+ , cipherKeyExchange = CipherKeyExchange_TLS13+ , cipherMinVer = Just TLS13+ }++cipher_TLS13_AES128GCM_SHA256 :: Cipher+cipher_TLS13_AES128GCM_SHA256 = cipher13_AES_128_GCM_SHA256+{-# DEPRECATED+ cipher_TLS13_AES128GCM_SHA256+ "Use cipher13_AES_128_GCM_SHA256 instead"+ #-}++-- TLS_AES_256_GCM_SHA384+cipher13_AES_256_GCM_SHA384 :: Cipher+cipher13_AES_256_GCM_SHA384 =+ Cipher+ { cipherID = 0x1302+ , cipherName = "TLS_AES_256_GCM_SHA384"+ , cipherBulk = bulk_aes256gcm_13+ , cipherHash = SHA384+ , cipherPRFHash = Nothing+ , cipherKeyExchange = CipherKeyExchange_TLS13+ , cipherMinVer = Just TLS13+ }++cipher_TLS13_AES256GCM_SHA384 :: Cipher+cipher_TLS13_AES256GCM_SHA384 = cipher13_AES_256_GCM_SHA384+{-# DEPRECATED+ cipher_TLS13_AES256GCM_SHA384+ "Use cipher13_AES_256_GCM_SHA384 instead"+ #-}++-- TLS_CHACHA20_POLY1305_SHA256+cipher13_CHACHA20_POLY1305_SHA256 :: Cipher+cipher13_CHACHA20_POLY1305_SHA256 =+ Cipher+ { cipherID = 0x1303+ , cipherName = "TLS_CHACHA20_POLY1305_SHA256"+ , cipherBulk = bulk_chacha20poly1305+ , cipherHash = SHA256+ , cipherPRFHash = Nothing+ , cipherKeyExchange = CipherKeyExchange_TLS13+ , cipherMinVer = Just TLS13+ }++cipher_TLS13_CHACHA20POLY1305_SHA256 :: Cipher+cipher_TLS13_CHACHA20POLY1305_SHA256 = cipher13_CHACHA20_POLY1305_SHA256+{-# DEPRECATED+ cipher_TLS13_CHACHA20POLY1305_SHA256+ "Use cipher13_CHACHA20_POLY1305_SHA256 instead"+ #-}++-- TLS_AES_128_CCM_SHA256+cipher13_AES_128_CCM_SHA256 :: Cipher+cipher13_AES_128_CCM_SHA256 =+ Cipher+ { cipherID = 0x1304+ , cipherName = "TLS_AES_128_CCM_SHA256"+ , cipherBulk = bulk_aes128ccm_13+ , cipherHash = SHA256+ , cipherPRFHash = Nothing+ , cipherKeyExchange = CipherKeyExchange_TLS13+ , cipherMinVer = Just TLS13+ }++cipher_TLS13_AES128CCM_SHA256 :: Cipher+cipher_TLS13_AES128CCM_SHA256 = cipher13_AES_128_CCM_SHA256+{-# DEPRECATED+ cipher_TLS13_AES128CCM_SHA256+ "Use cipher13_AES_128_CCM_SHA256 instead"+ #-}++-- TLS_AES_128_CCM_8_SHA256+cipher13_AES_128_CCM_8_SHA256 :: Cipher+cipher13_AES_128_CCM_8_SHA256 =+ Cipher+ { cipherID = 0x1305+ , cipherName = "TLS_AES_128_CCM_8_SHA256"+ , cipherBulk = bulk_aes128ccm8_13+ , cipherHash = SHA256+ , cipherPRFHash = Nothing+ , cipherKeyExchange = CipherKeyExchange_TLS13+ , cipherMinVer = Just TLS13+ }++cipher_TLS13_AES128CCM8_SHA256 :: Cipher+cipher_TLS13_AES128CCM8_SHA256 = cipher13_AES_128_CCM_8_SHA256+{-# DEPRECATED+ cipher_TLS13_AES128CCM8_SHA256+ "Use cipher13_AES_128_CCM_8_SHA256 instead"+ #-}++----------------------------------------------------------------+-- GCM: RFC 5289++-- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256+cipher_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 :: Cipher+cipher_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 =+ Cipher+ { cipherID = 0xC02B+ , cipherName = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"+ , cipherBulk = bulk_aes128gcm+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_ECDHE_ECDSA+ , cipherMinVer = Just TLS12 -- RFC 5289+ }++cipher_ECDHE_ECDSA_AES128GCM_SHA256 :: Cipher+cipher_ECDHE_ECDSA_AES128GCM_SHA256 = cipher_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256+{-# DEPRECATED+ cipher_ECDHE_ECDSA_AES128GCM_SHA256+ "Use cipher_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 instead"+ #-}++-- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384+cipher_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 :: Cipher+cipher_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 =+ Cipher+ { cipherID = 0xC02C+ , cipherName = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"+ , cipherBulk = bulk_aes256gcm+ , cipherHash = SHA384+ , cipherPRFHash = Just SHA384+ , cipherKeyExchange = CipherKeyExchange_ECDHE_ECDSA+ , cipherMinVer = Just TLS12 -- RFC 5289+ }++cipher_ECDHE_ECDSA_AES256GCM_SHA384 :: Cipher+cipher_ECDHE_ECDSA_AES256GCM_SHA384 = cipher_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384+{-# DEPRECATED+ cipher_ECDHE_ECDSA_AES256GCM_SHA384+ "Use cipher_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 instead"+ #-}++-- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256+cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256 :: Cipher+cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256 =+ Cipher+ { cipherID = 0xC02F+ , cipherName = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"+ , cipherBulk = bulk_aes128gcm+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_ECDHE_RSA+ , cipherMinVer = Just TLS12 -- RFC 5288 Sec 4+ }++cipher_ECDHE_RSA_AES128GCM_SHA256 :: Cipher+cipher_ECDHE_RSA_AES128GCM_SHA256 = cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256+{-# DEPRECATED+ cipher_ECDHE_RSA_AES128GCM_SHA256+ "Use cipher_ECDHE_RSA_WITH_AES_128_GCM_SHA256 instead"+ #-}++-- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384+cipher_ECDHE_RSA_WITH_AES_256_GCM_SHA384 :: Cipher+cipher_ECDHE_RSA_WITH_AES_256_GCM_SHA384 =+ Cipher+ { cipherID = 0xC030+ , cipherName = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"+ , cipherBulk = bulk_aes256gcm+ , cipherHash = SHA384+ , cipherPRFHash = Just SHA384+ , cipherKeyExchange = CipherKeyExchange_ECDHE_RSA+ , cipherMinVer = Just TLS12 -- RFC 5289+ }++cipher_ECDHE_RSA_AES256GCM_SHA384 :: Cipher+cipher_ECDHE_RSA_AES256GCM_SHA384 = cipher_ECDHE_RSA_WITH_AES_256_GCM_SHA384+{-# DEPRECATED+ cipher_ECDHE_RSA_AES256GCM_SHA384+ "Use cipher_ECDHE_RSA_WITH_AES_256_GCM_SHA384 instead"+ #-}++----------------------------------------------------------------+-- CCM/ECC: RFC 7251++-- TLS_ECDHE_ECDSA_WITH_AES_128_CCM+cipher_ECDHE_ECDSA_WITH_AES_128_CCM :: Cipher+cipher_ECDHE_ECDSA_WITH_AES_128_CCM =+ Cipher+ { cipherID = 0xC0AC+ , cipherName = "TLS_ECDHE_ECDSA_WITH_AES_128_CCM"+ , cipherBulk = bulk_aes128ccm+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_ECDHE_ECDSA+ , cipherMinVer = Just TLS12 -- RFC 7251+ }++cipher_ECDHE_ECDSA_AES128CCM_SHA256 :: Cipher+cipher_ECDHE_ECDSA_AES128CCM_SHA256 = cipher_ECDHE_ECDSA_WITH_AES_128_CCM+{-# DEPRECATED+ cipher_ECDHE_ECDSA_AES128CCM_SHA256+ "User cipher_ECDHE_ECDSA_WITH_AES_128_CCM instead"+ #-}++-- TLS_ECDHE_ECDSA_WITH_AES_256_CCM+cipher_ECDHE_ECDSA_WITH_AES_256_CCM :: Cipher+cipher_ECDHE_ECDSA_WITH_AES_256_CCM =+ Cipher+ { cipherID = 0xC0AD+ , cipherName = "TLS_ECDHE_ECDSA_WITH_AES_256_CCM"+ , cipherBulk = bulk_aes256ccm+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_ECDHE_ECDSA+ , cipherMinVer = Just TLS12 -- RFC 7251+ }++cipher_ECDHE_ECDSA_AES256CCM_SHA256 :: Cipher+cipher_ECDHE_ECDSA_AES256CCM_SHA256 = cipher_ECDHE_ECDSA_WITH_AES_256_CCM+{-# DEPRECATED+ cipher_ECDHE_ECDSA_AES256CCM_SHA256+ "Use cipher_ECDHE_ECDSA_WITH_AES_256_CCM instead"+ #-}++-- TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8+cipher_ECDHE_ECDSA_WITH_AES_128_CCM_8 :: Cipher+cipher_ECDHE_ECDSA_WITH_AES_128_CCM_8 =+ Cipher+ { cipherID = 0xC0AE+ , cipherName = "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8"+ , cipherBulk = bulk_aes128ccm8+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_ECDHE_ECDSA+ , cipherMinVer = Just TLS12 -- RFC 7251+ }++cipher_ECDHE_ECDSA_AES128CCM8_SHA256 :: Cipher+cipher_ECDHE_ECDSA_AES128CCM8_SHA256 = cipher_ECDHE_ECDSA_WITH_AES_128_CCM_8+{-# DEPRECATED+ cipher_ECDHE_ECDSA_AES128CCM8_SHA256+ "Use cipher_ECDHE_ECDSA_WITH_AES_128_CCM_8 instead"+ #-}++-- TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8+cipher_ECDHE_ECDSA_WITH_AES_256_CCM_8 :: Cipher+cipher_ECDHE_ECDSA_WITH_AES_256_CCM_8 =+ Cipher+ { cipherID = 0xC0AF+ , cipherName = "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8"+ , cipherBulk = bulk_aes256ccm8+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_ECDHE_ECDSA+ , cipherMinVer = Just TLS12 -- RFC 7251+ }++cipher_ECDHE_ECDSA_AES256CCM8_SHA256 :: Cipher+cipher_ECDHE_ECDSA_AES256CCM8_SHA256 = cipher_ECDHE_ECDSA_WITH_AES_256_CCM_8+{-# DEPRECATED+ cipher_ECDHE_ECDSA_AES256CCM8_SHA256+ "Use cipher_ECDHE_ECDSA_WITH_AES_256_CCM_8 instead"+ #-}++----------------------------------------------------------------+-- RFC 7905++-- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256+cipher_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 :: Cipher+cipher_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 =+ Cipher+ { cipherID = 0xCCA8+ , cipherName = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"+ , cipherBulk = bulk_chacha20poly1305+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_ECDHE_RSA+ , cipherMinVer = Just TLS12+ }++cipher_ECDHE_RSA_CHACHA20POLY1305_SHA256 :: Cipher+cipher_ECDHE_RSA_CHACHA20POLY1305_SHA256 = cipher_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256+{-# DEPRECATED+ cipher_ECDHE_RSA_CHACHA20POLY1305_SHA256+ "Use cipher_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 instead"+ #-}++-- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256+cipher_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 :: Cipher+cipher_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 =+ Cipher+ { cipherID = 0xCCA9+ , cipherName = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"+ , cipherBulk = bulk_chacha20poly1305+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_ECDHE_ECDSA+ , cipherMinVer = Just TLS12+ }++cipher_ECDHE_ECDSA_CHACHA20POLY1305_SHA256 :: Cipher+cipher_ECDHE_ECDSA_CHACHA20POLY1305_SHA256 = cipher_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256+{-# DEPRECATED+ cipher_ECDHE_ECDSA_CHACHA20POLY1305_SHA256+ "Use cipher_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 instead"+ #-}++-- TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256+cipher_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 :: Cipher+cipher_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 =+ Cipher+ { cipherID = 0xCCAA+ , cipherName = "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256"+ , cipherBulk = bulk_chacha20poly1305+ , cipherHash = SHA256+ , cipherPRFHash = Just SHA256+ , cipherKeyExchange = CipherKeyExchange_DHE_RSA+ , cipherMinVer = Just TLS12+ }++cipher_DHE_RSA_CHACHA20POLY1305_SHA256 :: Cipher+cipher_DHE_RSA_CHACHA20POLY1305_SHA256 = cipher_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256+{-# DEPRECATED+ cipher_DHE_RSA_CHACHA20POLY1305_SHA256+ "Use cipher_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 instead"+ #-}++----------------------------------------------------------------+----------------------------------------------------------------++data CipherSet+ = SetAead [Cipher] [Cipher] [Cipher] -- gcm, chacha, ccm+ | SetOther [Cipher]++-- Preference between AEAD ciphers having equivalent properties is based on+-- hardware-acceleration support in the crypton implementation.+sortOptimized :: [CipherSet] -> [Cipher]+sortOptimized = concatMap f+ where+ f (SetAead gcm chacha ccm)+ | AESNI `notElem` processorOptions = chacha ++ gcm ++ ccm+ | PCLMUL `notElem` processorOptions = ccm ++ chacha ++ gcm+ | otherwise = gcm ++ ccm ++ chacha+ f (SetOther ciphers) = ciphers++-- Order which is deterministic but not optimized for the CPU.+sortDeterministic :: [CipherSet] -> [Cipher]+sortDeterministic = concatMap f+ where+ f (SetAead gcm chacha ccm) = gcm ++ chacha ++ ccm+ f (SetOther ciphers) = ciphers++----------------------------------------------------------------++aes128ccm :: BulkDirection -> BulkKey -> BulkAEAD+aes128ccm BulkEncrypt key =+ let ctx = noFail (cipherInit key) :: AES128+ in ( \nonce d ad ->+ let mode = AEAD_CCM (B.length d) CCM_M16 CCM_L3+ aeadIni = noFail (aeadInit mode ctx nonce)+ in swap $ aeadSimpleEncrypt aeadIni ad d 16+ )+aes128ccm BulkDecrypt key =+ let ctx = noFail (cipherInit key) :: AES128+ in ( \nonce d ad ->+ let mode = AEAD_CCM (B.length d) CCM_M16 CCM_L3+ aeadIni = noFail (aeadInit mode ctx nonce)+ in simpleDecrypt aeadIni ad d 16+ )++aes128ccm8 :: BulkDirection -> BulkKey -> BulkAEAD+aes128ccm8 BulkEncrypt key =+ let ctx = noFail (cipherInit key) :: AES128+ in ( \nonce d ad ->+ let mode = AEAD_CCM (B.length d) CCM_M8 CCM_L3+ aeadIni = noFail (aeadInit mode ctx nonce)+ in swap $ aeadSimpleEncrypt aeadIni ad d 8+ )+aes128ccm8 BulkDecrypt key =+ let ctx = noFail (cipherInit key) :: AES128+ in ( \nonce d ad ->+ let mode = AEAD_CCM (B.length d) CCM_M8 CCM_L3+ aeadIni = noFail (aeadInit mode ctx nonce)+ in simpleDecrypt aeadIni ad d 8+ )++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+ )++aes256ccm :: BulkDirection -> BulkKey -> BulkAEAD+aes256ccm BulkEncrypt key =+ let ctx = noFail (cipherInit key) :: AES256+ in ( \nonce d ad ->+ let mode = AEAD_CCM (B.length d) CCM_M16 CCM_L3+ aeadIni = noFail (aeadInit mode ctx nonce)+ in swap $ aeadSimpleEncrypt aeadIni ad d 16+ )+aes256ccm BulkDecrypt key =+ let ctx = noFail (cipherInit key) :: AES256+ in ( \nonce d ad ->+ let mode = AEAD_CCM (B.length d) CCM_M16 CCM_L3+ aeadIni = noFail (aeadInit mode ctx nonce)+ in simpleDecrypt aeadIni ad d 16+ )++aes256ccm8 :: BulkDirection -> BulkKey -> BulkAEAD+aes256ccm8 BulkEncrypt key =+ let ctx = noFail (cipherInit key) :: AES256+ in ( \nonce d ad ->+ let mode = AEAD_CCM (B.length d) CCM_M8 CCM_L3+ aeadIni = noFail (aeadInit mode ctx nonce)+ in swap $ aeadSimpleEncrypt aeadIni ad d 8+ )+aes256ccm8 BulkDecrypt key =+ let ctx = noFail (cipherInit key) :: AES256+ in ( \nonce d ad ->+ let mode = AEAD_CCM (B.length d) CCM_M8 CCM_L3+ aeadIni = noFail (aeadInit mode ctx nonce)+ 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)+ where+ aead = aeadAppendHeader aeadIni header+ (output, aeadFinal) = aeadDecrypt aead input+ tag = aeadFinalize aeadFinal taglen++noFail :: CryptoFailable a -> a+noFail = throwCryptoError++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)+ )++----------------------------------------------------------------++bulk_aes128ccm :: Bulk+bulk_aes128ccm =+ Bulk+ { bulkName = "AES128CCM"+ , bulkKeySize = 16 -- RFC 5116 Sec 5.1: K_LEN+ , bulkIVSize = 4 -- RFC 6655 CCMNonce.salt, fixed_iv_length+ , bulkExplicitIV = 8+ , bulkAuthTagLen = 16+ , bulkBlockSize = 0 -- dummy, not used+ , bulkF = BulkAeadF aes128ccm+ }++bulk_aes128ccm8 :: Bulk+bulk_aes128ccm8 =+ Bulk+ { bulkName = "AES128CCM8"+ , bulkKeySize = 16 -- RFC 5116 Sec 5.1: K_LEN+ , bulkIVSize = 4 -- RFC 6655 CCMNonce.salt, fixed_iv_length+ , bulkExplicitIV = 8+ , bulkAuthTagLen = 8+ , bulkBlockSize = 0 -- dummy, not used+ , bulkF = BulkAeadF aes128ccm8+ }++bulk_aes128gcm :: Bulk+bulk_aes128gcm =+ Bulk+ { bulkName = "AES128GCM"+ , bulkKeySize = 16 -- RFC 5116 Sec 5.1: K_LEN+ , bulkIVSize = 4 -- RFC 5288 GCMNonce.salt, fixed_iv_length+ , bulkExplicitIV = 8+ , bulkAuthTagLen = 16+ , bulkBlockSize = 0 -- dummy, not used+ , bulkF = BulkAeadF aes128gcm+ }++bulk_aes256ccm :: Bulk+bulk_aes256ccm =+ Bulk+ { bulkName = "AES256CCM"+ , bulkKeySize = 32 -- RFC 5116 Sec 5.1: K_LEN+ , bulkIVSize = 4 -- RFC 6655 CCMNonce.salt, fixed_iv_length+ , bulkExplicitIV = 8+ , bulkAuthTagLen = 16+ , bulkBlockSize = 0 -- dummy, not used+ , bulkF = BulkAeadF aes256ccm+ }++bulk_aes256ccm8 :: Bulk+bulk_aes256ccm8 =+ Bulk+ { bulkName = "AES256CCM8"+ , bulkKeySize = 32 -- RFC 5116 Sec 5.1: K_LEN+ , bulkIVSize = 4 -- RFC 6655 CCMNonce.salt, fixed_iv_length+ , bulkExplicitIV = 8+ , bulkAuthTagLen = 8+ , bulkBlockSize = 0 -- dummy, not used+ , bulkF = BulkAeadF aes256ccm8+ }++bulk_aes256gcm :: Bulk+bulk_aes256gcm =+ Bulk+ { bulkName = "AES256GCM"+ , bulkKeySize = 32 -- RFC 5116 Sec 5.1: K_LEN+ , bulkIVSize = 4 -- RFC 5288 GCMNonce.salt, fixed_iv_length+ , bulkExplicitIV = 8+ , bulkAuthTagLen = 16+ , bulkBlockSize = 0 -- dummy, not used+ , bulkF = BulkAeadF aes256gcm+ }++bulk_chacha20poly1305 :: Bulk+bulk_chacha20poly1305 =+ Bulk+ { bulkName = "CHACHA20POLY1305"+ , bulkKeySize = 32+ , bulkIVSize = 12 -- RFC 7905 section 2, fixed_iv_length+ , bulkExplicitIV = 0+ , bulkAuthTagLen = 16+ , bulkBlockSize = 0 -- dummy, not used+ , bulkF = BulkAeadF chacha20poly1305+ }++-- TLS13 bulks are same as TLS12 except they never have explicit IV+bulk_aes128gcm_13 :: Bulk+bulk_aes128gcm_13 = bulk_aes128gcm{bulkIVSize = 12, bulkExplicitIV = 0}++bulk_aes256gcm_13 :: Bulk+bulk_aes256gcm_13 = bulk_aes256gcm{bulkIVSize = 12, bulkExplicitIV = 0}++bulk_aes128ccm_13 :: Bulk+bulk_aes128ccm_13 = bulk_aes128ccm{bulkIVSize = 12, bulkExplicitIV = 0}++bulk_aes128ccm8_13 :: Bulk+bulk_aes128ccm8_13 = bulk_aes128ccm8{bulkIVSize = 12, bulkExplicitIV = 0}
+ 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/Extra/FFDHE.hs view
@@ -0,0 +1,72 @@+-- |+-- Module : Network.TLS.Extra+-- License : BSD-style+-- Maintainer : Kazu Yamamoto <kazu@iij.ad.jp>+-- Stability : experimental+-- Portability : unknown+--+-- Finite Field Diffie-Hellman Ephemeral Parameters defined in RFC 7919.+module Network.TLS.Extra.FFDHE where++import Crypto.PubKey.DH+import Network.TLS.Crypto.DH (DHParams)++-- | 2048 bits finite field Diffie-Hellman ephemeral parameters+-- defined in RFC 7919.+-- The estimated symmetric-equivalent strength is 103 bits.+ffdhe2048 :: DHParams+ffdhe2048 =+ Params+ { params_p =+ 0xFFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF97D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD65612433F51F5F066ED0856365553DED1AF3B557135E7F57C935984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE73530ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FBB96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB190B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F619172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD733BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA886B423861285C97FFFFFFFFFFFFFFFF+ , params_g = 2+ , params_bits = 2048+ }++-- | 3072 bits finite field Diffie-Hellman ephemeral parameters+-- defined in RFC 7919.+-- The estimated symmetric-equivalent strength is 125 bits.+ffdhe3072 :: DHParams+ffdhe3072 =+ Params+ { params_p =+ 0xFFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF97D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD65612433F51F5F066ED0856365553DED1AF3B557135E7F57C935984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE73530ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FBB96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB190B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F619172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD733BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C023861B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91CAEFE130985139270B4130C93BC437944F4FD4452E2D74DD364F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0DABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF3C1B20EE3FD59D7C25E41D2B66C62E37FFFFFFFFFFFFFFFF+ , params_g = 2+ , params_bits = 3072+ }++-- | 4096 bits finite field Diffie-Hellman ephemeral parameters+-- defined in RFC 7919.+-- The estimated symmetric-equivalent strength is 150 bits.+ffdhe4096 :: DHParams+ffdhe4096 =+ Params+ { params_p =+ 0xFFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF97D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD65612433F51F5F066ED0856365553DED1AF3B557135E7F57C935984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE73530ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FBB96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB190B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F619172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD733BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C023861B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91CAEFE130985139270B4130C93BC437944F4FD4452E2D74DD364F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0DABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB7930E9E4E58857B6AC7D5F42D69F6D187763CF1D5503400487F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832A907600A918130C46DC778F971AD0038092999A333CB8B7A1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E655F6AFFFFFFFFFFFFFFFF+ , params_g = 2+ , params_bits = 4096+ }++-- | 6144 bits finite field Diffie-Hellman ephemeral parameters+-- defined in RFC 7919.+-- The estimated symmetric-equivalent strength is 175 bits.+ffdhe6144 :: DHParams+ffdhe6144 =+ Params+ { params_p =+ 0xFFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF97D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD65612433F51F5F066ED0856365553DED1AF3B557135E7F57C935984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE73530ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FBB96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB190B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F619172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD733BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C023861B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91CAEFE130985139270B4130C93BC437944F4FD4452E2D74DD364F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0DABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB7930E9E4E58857B6AC7D5F42D69F6D187763CF1D5503400487F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832A907600A918130C46DC778F971AD0038092999A333CB8B7A1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD9020BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA63BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3ACDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477A52471F7A9A96910B855322EDB6340D8A00EF092350511E30ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538CD72B03746AE77F5E62292C311562A846505DC82DB854338AE49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B045B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1A41D570D7938DAD4A40E329CD0E40E65FFFFFFFFFFFFFFFF+ , params_g = 2+ , params_bits = 6144+ }++-- | 8192 bits finite field Diffie-Hellman ephemeral parameters+-- defined in RFC 7919.+-- The estimated symmetric-equivalent strength is 192 bits.+ffdhe8192 :: DHParams+ffdhe8192 =+ Params+ { params_p =+ 0xFFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF97D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD65612433F51F5F066ED0856365553DED1AF3B557135E7F57C935984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE73530ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FBB96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB190B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F619172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD733BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C023861B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91CAEFE130985139270B4130C93BC437944F4FD4452E2D74DD364F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0DABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB7930E9E4E58857B6AC7D5F42D69F6D187763CF1D5503400487F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832A907600A918130C46DC778F971AD0038092999A333CB8B7A1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD9020BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA63BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3ACDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477A52471F7A9A96910B855322EDB6340D8A00EF092350511E30ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538CD72B03746AE77F5E62292C311562A846505DC82DB854338AE49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B045B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1A41D570D7938DAD4A40E329CCFF46AAA36AD004CF600C8381E425A31D951AE64FDB23FCEC9509D43687FEB69EDD1CC5E0B8CC3BDF64B10EF86B63142A3AB8829555B2F747C932665CB2C0F1CC01BD70229388839D2AF05E454504AC78B7582822846C0BA35C35F5C59160CC046FD8251541FC68C9C86B022BB7099876A460E7451A8A93109703FEE1C217E6C3826E52C51AA691E0E423CFC99E9E31650C1217B624816CDAD9A95F9D5B8019488D9C0A0A1FE3075A577E23183F81D4A3F2FA4571EFC8CE0BA8A4FE8B6855DFE72B0A66EDED2FBABFBE58A30FAFABE1C5D71A87E2F741EF8C1FE86FEA6BBFDE530677F0D97D11D49F7A8443D0822E506A9F4614E011E2A94838FF88CD68C8BB7C5C6424CFFFFFFFFFFFFFFFF+ , params_g = 2+ , params_bits = 8192+ }
Network/TLS/Handshake.hs view
@@ -1,37 +1,33 @@--- |--- Module : Network.TLS.Handshake--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Handshake- ( handshake- , handshakeClientWith- , handshakeServerWith- , handshakeClient- , handshakeServer- ) where+module Network.TLS.Handshake (+ handshake_,+ handshakeWith,+ handshakeClientWith,+ handshakeServerWith,+ handshakeClient,+ handshakeServer,+) where import Network.TLS.Context.Internal-import Network.TLS.Struct-import Network.TLS.IO-import Network.TLS.Util (catchException)--import Network.TLS.Handshake.Common import Network.TLS.Handshake.Client+import Network.TLS.Handshake.Common import Network.TLS.Handshake.Server+import Network.TLS.Struct -import Control.Monad.State-import Control.Exception (fromException)+import Control.Monad.State.Strict --- | Handshake for a new TLS connection--- This is to be called at the beginning of a connection, and during renegotiation-handshake :: MonadIO m => Context -> m ()-handshake ctx =- liftIO $ handleException $ withRWLock ctx (ctxDoHandshake ctx $ ctx)- where handleException f = catchException f $ \exception -> do- let tlserror = maybe (Error_Misc $ show exception) id $ fromException exception- setEstablished ctx False- sendPacket ctx (errorToAlert tlserror)- handshakeFailed tlserror+handshake_ :: MonadIO m => Context -> m ()+handshake_ ctx =+ liftIO $+ withRWLock ctx $+ handleException ctx (doHandshake_ (ctxRoleParams ctx) ctx)++-- Handshake when requested by the remote end+-- This is called automatically by 'recvData', in a context where the read lock+-- is already taken. So contrary to 'handshake' above, here we only need to+-- call withWriteLock.+handshakeWith :: MonadIO m => Context -> HandshakeR -> m ()+handshakeWith ctx hsr =+ liftIO $+ withWriteLock ctx $+ handleException ctx $+ doHandshakeWith_ (ctxRoleParams ctx) ctx hsr
Network/TLS/Handshake/Certificate.hs view
@@ -1,31 +1,71 @@--- |--- Module : Network.TLS.Handshake.Certificate--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Handshake.Certificate- ( certificateRejected- , rejectOnException- ) where+module Network.TLS.Handshake.Certificate (+ certificateRejected,+ badCertificate,+ rejectOnException,+ verifyLeafKeyUsage,+ verifyLeafKeyUsagePurpose,+ extractCAname,+) where +import Control.Exception (SomeException)+import Control.Monad (unless)+import Control.Monad.State.Strict+import Data.X509 (+ ExtExtendedKeyUsage (..),+ ExtKeyUsage (..),+ ExtKeyUsageFlag,+ ExtKeyUsagePurpose (..),+ extensionGet,+ )+ import Network.TLS.Context.Internal import Network.TLS.Struct import Network.TLS.X509-import Control.Monad.State-import Control.Exception (SomeException) -- on certificate reject, throw an exception with the proper protocol alert error. certificateRejected :: MonadIO m => CertificateRejectReason -> m a certificateRejected CertificateRejectRevoked =- throwCore $ Error_Protocol ("certificate is revoked", True, CertificateRevoked)+ throwCore $ Error_Protocol "certificate is revoked" CertificateRevoked certificateRejected CertificateRejectExpired =- throwCore $ Error_Protocol ("certificate has expired", True, CertificateExpired)+ throwCore $ Error_Protocol "certificate has expired" CertificateExpired certificateRejected CertificateRejectUnknownCA =- throwCore $ Error_Protocol ("certificate has unknown CA", True, UnknownCa)+ throwCore $ Error_Protocol "certificate has unknown CA" UnknownCa+certificateRejected CertificateRejectAbsent =+ throwCore $ Error_Protocol "certificate is missing" CertificateRequired certificateRejected (CertificateRejectOther s) =- throwCore $ Error_Protocol ("certificate rejected: " ++ s, True, CertificateUnknown)+ throwCore $ Error_Protocol ("certificate rejected: " ++ s) CertificateUnknown +badCertificate :: MonadIO m => String -> m a+badCertificate msg = throwCore $ Error_Protocol msg BadCertificate+ rejectOnException :: SomeException -> IO CertificateUsage rejectOnException e = return $ CertificateUsageReject $ CertificateRejectOther $ show e++verifyLeafKeyUsage :: MonadIO m => [ExtKeyUsageFlag] -> CertificateChain -> m ()+verifyLeafKeyUsage _ (CertificateChain []) = return ()+verifyLeafKeyUsage validFlags (CertificateChain (signed : _)) =+ unless verified $+ badCertificate $+ "certificate is not allowed for any of " ++ show validFlags+ where+ cert = getCertificate signed+ verified =+ 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
@@ -1,355 +1,177 @@-{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}--- |--- Module : Network.TLS.Handshake.Client--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Handshake.Client- ( handshakeClient- , handshakeClientWith- ) where+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-} -import Network.TLS.Crypto+module Network.TLS.Handshake.Client (+ handshakeClient,+ handshakeClientWith,+ postHandshakeAuthClientWith,+) where+ import Network.TLS.Context.Internal-import Network.TLS.Parameters-import Network.TLS.Struct-import Network.TLS.Cipher-import Network.TLS.Compression-import Network.TLS.Packet+import Network.TLS.Crypto import Network.TLS.Extension+import Network.TLS.Handshake.Client.ClientHello+import Network.TLS.Handshake.Client.Common+import Network.TLS.Handshake.Client.ServerHello+import Network.TLS.Handshake.Client.TLS12+import Network.TLS.Handshake.Client.TLS13+import Network.TLS.Handshake.Common13+import Network.TLS.Handshake.State+import Network.TLS.Handshake.State13 import Network.TLS.IO-import Network.TLS.State hiding (getNegotiatedProtocol)+import Network.TLS.Imports import Network.TLS.Measurement-import Network.TLS.Wire (encodeWord16)-import Network.TLS.Util (bytesEq, catchException)-import Network.TLS.Types-import Network.TLS.X509-import Data.Maybe-import Data.List (find)-import qualified Data.ByteString as B-import Data.ByteString.Char8 ()--import Control.Applicative ((<$>), (<*>))-import Control.Monad.State-import Control.Monad.Error-import Control.Exception (SomeException)+import Network.TLS.Parameters+import Network.TLS.State+import Network.TLS.Struct -import Network.TLS.Handshake.Common-import Network.TLS.Handshake.Process-import Network.TLS.Handshake.Certificate-import Network.TLS.Handshake.Signature-import Network.TLS.Handshake.Key-import Network.TLS.Handshake.State+---------------------------------------------------------------- -handshakeClientWith :: ClientParams -> Context -> Handshake -> IO ()-handshakeClientWith cparams ctx HelloRequest = handshakeClient cparams ctx-handshakeClientWith _ _ _ = throwCore $ Error_Protocol ("unexpected handshake message received in handshakeClientWith", True, HandshakeFailure)+handshakeClientWith+ :: ClientParams -> Context -> HandshakeR -> IO ()+handshakeClientWith cparams ctx (HelloRequest, _b) = handshakeClient cparams ctx -- xxx+handshakeClientWith _ _ _ =+ throwCore $+ Error_Protocol+ "unexpected handshake message received in handshakeClientWith"+ HandshakeFailure -- client part of handshake. send a bunch of handshake of client -- values intertwined with response from the server. handshakeClient :: ClientParams -> Context -> IO () handshakeClient cparams ctx = do- updateMeasure ctx incrementNbHandshakes- sentExtensions <- sendClientHello- recvServerHello sentExtensions- sessionResuming <- usingState_ ctx isSessionResuming- if sessionResuming- then sendChangeCipherAndFinish sendMaybeNPN ctx ClientRole- else do sendClientData cparams ctx- sendChangeCipherAndFinish sendMaybeNPN ctx ClientRole- recvChangeCipherAndFinish ctx- handshakeTerminate ctx- where ciphers = ctxCiphers ctx- compressions = supportedCompressions $ ctxSupported ctx- getExtensions = sequence [sniExtension,secureReneg,npnExtention] >>= return . catMaybes-- toExtensionRaw :: Extension e => e -> ExtensionRaw- toExtensionRaw ext = (extensionID ext, extensionEncode ext)-- secureReneg =- if supportedSecureRenegotiation $ ctxSupported ctx- then usingState_ ctx (getVerifiedData ClientRole) >>= \vd -> return $ Just $ toExtensionRaw $ SecureRenegotiation vd Nothing- else return Nothing- npnExtention = if isJust $ onNPNServerSuggest $ clientHooks cparams- then return $ Just $ toExtensionRaw $ NextProtocolNegotiation []- else return Nothing- sniExtension = if clientUseServerNameIndication cparams- then return $ Just $ toExtensionRaw $ ServerName [ServerNameHostName $ fst $ clientServerIdentification cparams]- else return Nothing- sendClientHello = do- crand <- getStateRNG ctx 32 >>= return . ClientRandom- let clientSession = Session . maybe Nothing (Just . fst) $ clientWantSessionResume cparams- highestVer = maximum $ supportedVersions $ ctxSupported ctx- extensions <- getExtensions- startHandshake ctx highestVer crand- usingState_ ctx $ setVersionIfUnset highestVer- sendPacket ctx $ Handshake- [ ClientHello highestVer crand clientSession (map cipherID ciphers)- (map compressionID compressions) extensions Nothing- ]- return $ map fst extensions-- sendMaybeNPN = do- suggest <- usingState_ ctx $ getServerNextProtocolSuggest- case (onNPNServerSuggest $ clientHooks cparams, suggest) of- -- client offered, server picked up. send NPN handshake.- (Just io, Just protos) -> do proto <- liftIO $ io protos- sendPacket ctx (Handshake [HsNextProtocolNegotiation proto])- usingState_ ctx $ setNegotiatedProtocol proto- -- client offered, server didn't pick up. do nothing.- (Just _, Nothing) -> return ()- -- client didn't offer. do nothing.- (Nothing, _) -> return ()-- recvServerHello sentExts = runRecvState ctx recvState- where recvState = RecvStateNext $ \p ->- case p of- Handshake hs -> onRecvStateHandshake ctx (RecvStateHandshake $ onServerHello ctx cparams sentExts) hs- Alert a ->- case a of- [(AlertLevel_Warning, UnrecognizedName)] ->- if clientUseServerNameIndication cparams- then return recvState- else throwAlert a- _ -> throwAlert a- _ -> fail ("unexepected type received. expecting handshake and got: " ++ show p)- throwAlert a = usingState_ ctx $ throwError $ Error_Protocol ("expecting server hello, got alert : " ++ show a, True, HandshakeFailure)---- | send client Data after receiving all server data (hello/certificates/key).------ -> [certificate]--- -> client key exchange--- -> [cert verify]-sendClientData :: ClientParams -> Context -> IO ()-sendClientData cparams ctx = sendCertificate >> sendClientKeyXchg >> sendCertificateVerify+ grps <- case clientSessions cparams of+ [] ->+ return $+ Groups+ { grpsSupported = groupsSupported+ , grpsSelected = groupsSelected+ }+ (_, sdata) : _ -> case sessionGroup sdata of+ Nothing ->+ -- TLS 1.2 or earlier+ return $+ Groups+ { grpsSupported = groupsSupported -- for ciphers+ , grpsSelected = []+ }+ Just grp+ | grp `elem` groupsSupported -> do+ let supported = grp : filter (/= grp) groupsSupported+ return $+ Groups+ { grpsSupported = supported+ , grpsSelected = [grp]+ }+ | otherwise -> throwCore $ Error_Misc "groupsSupported is incorrect"+ handshake cparams ctx grps Nothing where- -- When the server requests a client certificate, we- -- fetch a certificate chain from the callback in the- -- client parameters and send it to the server.- -- Additionally, we store the private key associated- -- with the first certificate in the chain for later- -- use.- --- sendCertificate = do- certRequested <- usingHState ctx getClientCertRequest- case certRequested of- Nothing ->- return ()-- Just req -> do- certChain <- liftIO $ (onCertificateRequest $ clientHooks cparams) req `catchException`- throwMiscErrorOnException "certificate request callback failed"-- usingHState ctx $ setClientCertSent False- case certChain of- Nothing -> sendPacket ctx $ Handshake [Certificates (CertificateChain [])]- Just (CertificateChain [], _) -> sendPacket ctx $ Handshake [Certificates (CertificateChain [])]- Just (cc@(CertificateChain (c:_)), pk) -> do- case certPubKey $ getCertificate c of- PubKeyRSA _ -> return ()- _ -> throwCore $ Error_Protocol ("no supported certificate type", True, HandshakeFailure)- usingHState ctx $ setPrivateKey pk- usingHState ctx $ setClientCertSent True- sendPacket ctx $ Handshake [Certificates cc]-- sendClientKeyXchg = do- cipher <- usingHState ctx getPendingCipher- ckx <- case cipherKeyExchange cipher of- CipherKeyExchange_RSA -> do- clientVersion <- usingHState ctx $ gets hstClientVersion- (xver, prerand) <- usingState_ ctx $ (,) <$> getVersion <*> genRandom 46-- let premaster = encodePreMasterSecret clientVersion prerand- usingHState ctx $ setMasterSecretFromPre xver ClientRole premaster- encryptedPreMaster <- 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 premaster- let extra = if xver < TLS10- then B.empty- else encodeWord16 $ fromIntegral $ B.length e- return $ extra `B.append` e- return $ CKX_RSA encryptedPreMaster- CipherKeyExchange_DHE_RSA -> getCKX_DHE- CipherKeyExchange_DHE_DSS -> getCKX_DHE- _ -> throwCore $ Error_Protocol ("client key exchange unsupported type", True, HandshakeFailure)- sendPacket ctx $ Handshake [ClientKeyXchg ckx]- where getCKX_DHE = do- xver <- usingState_ ctx getVersion- (ServerDHParams dhparams serverDHPub) <- fromJust <$> usingHState ctx (gets hstServerDHParams)- (clientDHPriv, clientDHPub) <- generateDHE ctx dhparams-- let premaster = dhGetShared dhparams clientDHPriv serverDHPub- usingHState ctx $ setMasterSecretFromPre xver ClientRole premaster-- return $ CKX_DH clientDHPub-- -- 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 = do- usedVersion <- usingState_ ctx getVersion-- -- Only send a certificate verify message when we- -- have sent a non-empty list of certificates.- --- certSent <- usingHState ctx $ getClientCertSent- case certSent of- True -> do- malg <- case usedVersion of- TLS12 -> do- Just (_, Just hashSigs, _) <- usingHState ctx $ getClientCertRequest- let suppHashSigs = supportedHashSignatures $ ctxSupported ctx- hashSigs' = filter (\ a -> a `elem` hashSigs) suppHashSigs-- when (null hashSigs') $- throwCore $ Error_Protocol ("no hash/signature algorithms in common with the server", True, HandshakeFailure)- return $ Just $ head hashSigs'- _ -> return Nothing-- -- Fetch all handshake messages up to now.- msgs <- usingHState ctx $ B.concat <$> getHandshakeMessages- (hashMethod, toSign) <- prepareCertificateVerifySignatureData ctx usedVersion malg msgs-- sigDig <- signatureCreate ctx malg hashMethod toSign- sendPacket ctx $ Handshake [CertVerify sigDig]-- _ -> return ()--processServerExtension :: (ExtensionID, Bytes) -> TLSSt ()-processServerExtension (0xff01, content) = do- cv <- getVerifiedData ClientRole- sv <- getVerifiedData ServerRole- let bs = extensionEncode (SecureRenegotiation cv $ Just sv)- unless (bs `bytesEq` content) $ throwError $ Error_Protocol ("server secure renegotiation data not matching", True, HandshakeFailure)- return ()-processServerExtension _ = return ()--throwMiscErrorOnException :: String -> SomeException -> IO a-throwMiscErrorOnException msg e =- throwCore $ Error_Misc $ msg ++ ": " ++ show e+ groupsSupported = supportedGroups (ctxSupported ctx)+ groupsSelected = onSelectKeyShareGroups (clientHooks cparams) groupsSupported --- | onServerHello process the ServerHello message on the client.------ 1) check the version chosen by the server is one allowed by parameters.--- 2) check that our compression and cipher algorithms are part of the list we sent--- 3) check extensions received are part of the one we sent--- 4) process the session parameter to see if the server want to start a new session or can resume--- 5) process NPN extension--- 6) if no resume switch to processCertificate SM or in resume switch to expectChangeCipher+-- https://tools.ietf.org/html/rfc8446#section-4.1.2 says:+-- "The client will also send a+-- ClientHello when the server has responded to its ClientHello with a+-- HelloRetryRequest. In that case, the client MUST send the same+-- ClientHello without modification, except as follows:" ---onServerHello :: Context -> ClientParams -> [ExtensionID] -> Handshake -> IO (RecvState IO)-onServerHello ctx cparams sentExts (ServerHello rver serverRan serverSession cipher compression exts) = do- when (rver == SSL2) $ throwCore $ Error_Protocol ("ssl2 is not supported", True, ProtocolVersion)- case find ((==) rver) (supportedVersions $ ctxSupported ctx) of- Nothing -> throwCore $ Error_Protocol ("server version " ++ show rver ++ " is not supported", True, ProtocolVersion)- Just _ -> return ()- -- find the compression and cipher methods that the server want to use.- cipherAlg <- case find ((==) cipher . cipherID) (ctxCiphers ctx) of- Nothing -> throwCore $ Error_Protocol ("server choose unknown cipher", True, HandshakeFailure)- Just alg -> return alg- compressAlg <- case find ((==) compression . compressionID) (supportedCompressions $ ctxSupported ctx) of- Nothing -> throwCore $ Error_Protocol ("server choose unknown compression", True, HandshakeFailure)- Just alg -> return alg-- -- intersect sent extensions in client and the received extensions from server.- -- if server returns extensions that we didn't request, fail.- when (not $ null $ filter (not . flip elem sentExts . fst) exts) $- throwCore $ Error_Protocol ("spurious extensions received", True, UnsupportedExtension)-- let resumingSession =- case clientWantSessionResume cparams of- Just (sessionId, sessionData) -> if serverSession == Session (Just sessionId) then Just sessionData else Nothing- Nothing -> Nothing- usingState_ ctx $ do- setSession serverSession (isJust resumingSession)- mapM_ processServerExtension exts- setVersion rver- usingHState ctx $ setServerHelloParameters rver serverRan cipherAlg compressAlg-- case extensionDecode False `fmap` (lookup extensionID_NextProtocolNegotiation exts) of- Just (Just (NextProtocolNegotiation protos)) -> usingState_ ctx $ do- setExtensionNPN True- setServerNextProtocolSuggest protos- _ -> return ()-- case resumingSession of- Nothing -> return $ RecvStateHandshake (processCertificate cparams ctx)- Just sessionData -> do- usingHState ctx (setMasterSecret rver ClientRole $ sessionSecret sessionData)- return $ RecvStateNext expectChangeCipher-onServerHello _ _ _ p = unexpected (show p) (Just "server hello")--processCertificate :: ClientParams -> Context -> Handshake -> IO (RecvState IO)-processCertificate cparams ctx (Certificates certs) = do- -- run certificate recv hook- ctxWithHooks ctx (\hooks -> hookRecvCertificates hooks $ certs)- -- then run certificate validation- usage <- catchException (wrapCertificateChecks <$> checkCert) rejectOnException- case usage of- CertificateUsageAccept -> return ()- CertificateUsageReject reason -> certificateRejected reason- return $ RecvStateHandshake (processServerKeyExchange ctx)- where shared = clientShared cparams- checkCert = (onServerCertificate $ clientHooks cparams) (sharedCAStore shared)- (sharedValidationCache shared)- (clientServerIdentification cparams)- certs-processCertificate _ ctx p = processServerKeyExchange ctx p--expectChangeCipher :: Packet -> IO (RecvState IO)-expectChangeCipher ChangeCipherSpec = return $ RecvStateHandshake expectFinish-expectChangeCipher p = unexpected (show p) (Just "change cipher")--expectFinish :: Handshake -> IO (RecvState IO)-expectFinish (Finished _) = return RecvStateDone-expectFinish p = unexpected (show p) (Just "Handshake Finished")--processServerKeyExchange :: Context -> Handshake -> IO (RecvState IO)-processServerKeyExchange ctx (ServerKeyXchg origSkx) = do- cipher <- usingHState ctx getPendingCipher- processWithCipher cipher origSkx- return $ RecvStateHandshake (processCertificateRequest ctx)- where processWithCipher cipher skx =- case (cipherKeyExchange cipher, skx) of- (CipherKeyExchange_DHE_RSA, SKX_DHE_RSA dhparams signature) -> do- doDHESignature dhparams signature SignatureRSA- (CipherKeyExchange_DHE_DSS, SKX_DHE_DSS dhparams signature) -> do- doDHESignature dhparams signature SignatureDSS- (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, True, 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, True, HandshakeFailure)- doDHESignature dhparams signature signatureType = do- -- TODO verify DHParams- expectedData <- generateSignedDHParams ctx dhparams- verified <- signatureVerify ctx signatureType expectedData signature- when (not verified) $ throwCore $ Error_Protocol ("bad " ++ show signatureType ++ " for dhparams", True, HandshakeFailure)- usingHState ctx $ setServerDHParams dhparams+-- So, the ClientRandom in the first client hello is necessary.+handshake+ :: ClientParams+ -> Context+ -> Groups+ -> Maybe (ClientRandom, Session, Version)+ -> IO ()+handshake cparams ctx grps@Groups{..} mparams = do+ --------------------------------+ -- Sending ClientHello+ pskinfo@(_, _, rtt0) <- getPreSharedKeyInfo cparams ctx+ when rtt0 $ modifyTLS13State ctx $ \st -> st{tls13st0RTT = True}+ let async = rtt0 && not (ctxQUICMode ctx)+ when async $ do+ chSentTime <- getCurrentTimeFromBase+ asyncServerHello13 cparams ctx grpsSelected chSentTime+ updateMeasure ctx incrementNbHandshakes+ crand <-+ sendClientHello cparams ctx grps mparams pskinfo+ --------------------------------+ -- Receiving ServerHello+ unless async $ do+ (ver, hbs, hrr) <- receiveServerHello cparams ctx mparams+ -- Switching to HRR, TLS 1.2 or TLS 1.3+ case ver of+ TLS13+ | hrr ->+ helloRetry cparams ctx mparams ver crand grpsSupported grpsSelected+ | otherwise -> do+ recvServerSecondFlight13 cparams ctx grpsSelected+ sendClientSecondFlight13 cparams ctx+ _+ | rtt0 ->+ throwCore $+ Error_Protocol+ "server denied TLS 1.3 when connecting with early data"+ HandshakeFailure+ | otherwise -> do+ recvServerFirstFlight12 cparams ctx hbs+ sendClientSecondFlight12 cparams ctx+ recvServerSecondFlight12 cparams ctx -processServerKeyExchange ctx p = processCertificateRequest ctx p+---------------------------------------------------------------- -processCertificateRequest :: Context -> Handshake -> IO (RecvState IO)-processCertificateRequest ctx (CertRequest cTypes sigAlgs dNames) = do- -- When the server requests a client- -- certificate, we simply store the- -- information for later.- --- usingHState ctx $ setClientCertRequest (cTypes, sigAlgs, dNames)- return $ RecvStateHandshake (processServerHelloDone ctx)-processCertificateRequest ctx p = processServerHelloDone ctx p+helloRetry+ :: ClientParams+ -> Context+ -> Maybe a+ -> Version+ -> ClientRandom+ -> [Group]+ -> [Group]+ -> IO ()+helloRetry cparams ctx mparams ver crand groupsSupported groupsSelected = do+ when (null groupsSupported) $+ throwCore $+ Error_Protocol "no supported groups on the client side" IllegalParameter+ when (isJust mparams) $+ throwCore $+ Error_Protocol "server sent too many hello retries" UnexpectedMessage+ mks <- usingState_ ctx getTLS13KeyShare+ case mks of+ Just (KeyShareHRR selectedGroup)+ -- 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+ -- 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 = [selectedGroup]+ } -processServerHelloDone :: Context -> Handshake -> IO (RecvState m)-processServerHelloDone _ ServerHelloDone = return RecvStateDone-processServerHelloDone _ p = unexpected (show p) (Just "server hello data")+ handshake+ cparams'+ 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+ Just _ -> error "handshake: invalid KeyShare value"+ Nothing ->+ throwCore $+ Error_Protocol+ "key exchange not implemented in HRR, expected key_share extension"+ HandshakeFailure
+ Network/TLS/Handshake/Client/ClientHello.hs view
@@ -0,0 +1,610 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Network.TLS.Handshake.Client.ClientHello (+ sendClientHello,+ getPreSharedKeyInfo,+ Groups (..),+) where++import qualified Control.Exception as E+import Crypto.HPKE hiding (CipherText, PlainText)+import Data.ByteArray (convert)+import qualified Data.ByteArray as BA+import qualified Data.ByteString as B+import Network.TLS.ECH.Config+import System.Random++#if !MIN_VERSION_random(1,3,0)+import Data.ByteString.Internal (unsafeCreate)+import Foreign.Ptr+import Foreign.Storable+#endif++import Network.TLS.Cipher+import Network.TLS.Context.Internal+import Network.TLS.Crypto+import Network.TLS.Extension+import Network.TLS.Handshake.Client.Common+import Network.TLS.Handshake.Common+import Network.TLS.Handshake.Common13+import Network.TLS.Handshake.Control+import Network.TLS.Handshake.Random+import Network.TLS.Handshake.State+import Network.TLS.Handshake.State13+import Network.TLS.Handshake.TranscriptHash+import Network.TLS.IO+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.Types++----------------------------------------------------------------++data Groups = Groups+ { grpsSupported :: [Group]+ -- ^ For supported_group, head is identical to key_share+ , grpsSelected :: [Group]+ -- ^ For key_share+ }+ deriving (Eq, Show)++----------------------------------------------------------------++sendClientHello+ :: ClientParams+ -> Context+ -> Groups+ -> Maybe (ClientRandom, Session, Version)+ -> PreSharedKeyInfo+ -> IO ClientRandom+sendClientHello cparams ctx grps mparams pskinfo = do+ crand <- generateClientHelloParams mparams -- Inner for ECH+ sendClientHello' cparams ctx grps crand pskinfo+ return crand+ where+ highestVer = maximum $ supportedVersions $ ctxSupported ctx+ tls13 = highestVer >= TLS13+ ems = supportedExtendedMainSecret $ ctxSupported ctx++ -- Client random and session in the second client hello for+ -- retry must be the same as the first one.+ generateClientHelloParams (Just (crand, clientSession, _)) = do+ modifyTLS13State ctx $ \st -> st{tls13stSession = clientSession}+ return crand+ generateClientHelloParams Nothing = do+ crand <- clientRandom ctx+ let paramSession = case clientSessions cparams of+ [] -> Session Nothing+ (sidOrTkt, sdata) : _+ | sessionVersion sdata >= TLS13 -> Session Nothing+ | ems == RequireEMS && noSessionEMS -> Session Nothing+ | isTicket sidOrTkt -> Session $ Just $ toSessionID sidOrTkt+ | otherwise -> Session (Just sidOrTkt)+ where+ noSessionEMS = SessionEMS `notElem` sessionFlags sdata+ -- In compatibility mode a client not offering a pre-TLS 1.3+ -- session MUST generate a new 32-byte value+ if tls13 && paramSession == Session Nothing && not (ctxQUICMode ctx)+ then do+ randomSession <- newSession ctx+ modifyTLS13State ctx $ \st -> st{tls13stSession = randomSession}+ return crand+ else do+ modifyTLS13State ctx $ \st -> st{tls13stSession = paramSession}+ return crand++----------------------------------------------------------------++sendClientHello'+ :: ClientParams+ -> Context+ -> Groups+ -> ClientRandom+ -> ( Maybe ([ByteString], SessionData, CipherChoice, Word32)+ , Maybe CipherChoice+ , Bool+ )+ -> IO ()+sendClientHello' cparams ctx Groups{..} crand (pskInfo, rtt0info, rtt0) = do+ let ver = if tls13 then TLS12 else highestVer+ clientSession <- tls13stSession <$> getTLS13State ctx+ hrr <- usingState_ ctx getTLS13HRR+ unless hrr $ startHandshake ctx ver crand+ usingState_ ctx $ setVersionIfUnset highestVer+ let cipherIds = map (CipherId . cipherID) ciphers+ mkClientHello exts =+ CH+ { chVersion = ver+ , chRandom = crand+ , chSession = clientSession+ , chCiphers = cipherIds+ , chComps = [0]+ , chExtensions = exts+ }+ setMyRecordLimit ctx $ limitRecordSize $ sharedLimit $ ctxShared ctx+ extensions0 <- catMaybes <$> getExtensions+ let extensions1 = sharedHelloExtensions (clientShared cparams) ++ extensions0+ extensions <- adjustPreSharedKeyExt extensions1 $ mkClientHello extensions1+ let ch0 = mkClientHello extensions+ updateTranscriptHashI ctx "ClientHelloI" $ encodeHandshake $ ClientHello ch0+ let nhpks = supportedHPKE $ clientSupported cparams+ echcnfs = sharedECHConfigList $ clientShared cparams+ mEchParams = lookupECHConfigList nhpks echcnfs+ ch <-+ if clientUseECH cparams+ then case mEchParams of+ Nothing -> do+ if hrr+ then do+ (chI, _) <- fromJust <$> usingHState ctx getClientHello+ let ch0' = ch0{chExtensions = take 1 (chExtensions chI) ++ drop 1 (chExtensions ch0)}+ -- [] will be overridden via+ -- encodeUpdateTranscriptHash12+ usingHState ctx $ setClientHello ch0' []+ return ch0'+ else do+ gEchExt <- greasingEchExt+ let ch0' = ch0{chExtensions = gEchExt : drop 1 (chExtensions ch0)}+ -- [] will be overridden via+ -- encodeUpdateTranscriptHash12+ usingHState ctx $ setClientHello ch0' []+ return ch0'+ Just echParams -> do+ let encoded = encodeHandshake $ ClientHello ch0+ usingHState ctx $ setClientHello ch0 [encoded]+ mcrandO <- usingHState ctx getOuterClientRandom+ crandO <- case mcrandO of+ Nothing -> clientRandom ctx+ Just x -> return x+ usingHState ctx $ do+ setClientRandom crandO+ setOuterClientRandom $ Just crandO+ mpskExt <- randomPreSharedKeyExt+ createEncryptedClientHello ctx ch0 echParams crandO mpskExt+ else do+ -- [] will be overridden via+ -- encodeUpdateTranscriptHash12+ usingHState ctx $ setClientHello ch0 []+ return ch0+ sendPacket12 ctx $ Handshake [ClientHello ch] []+ mEarlySecInfo <- case rtt0info of+ Nothing -> return Nothing+ Just info -> Just <$> getEarlySecretInfo info+ unless hrr $ contextSync ctx $ SendClientHello mEarlySecInfo+ let sentExtensions = map (\(ExtensionRaw i _) -> i) extensions+ modifyTLS13State ctx $ \st -> st{tls13stSentExtensions = sentExtensions}+ where+ ciphers = supportedCiphers $ ctxSupported ctx+ highestVer = maximum $ supportedVersions $ ctxSupported ctx+ tls13 = highestVer >= TLS13+ ems = supportedExtendedMainSecret $ ctxSupported ctx++ -- List of extensions to send in ClientHello, ordered such that we never+ -- terminate with a zero-length extension. Some buggy implementations+ -- are allergic to an extension with empty data at final position.+ --+ -- Without TLS 1.3, the list ends with extension "signature_algorithms"+ -- with length >= 2 bytes. When TLS 1.3 is enabled, extensions+ -- "psk_key_exchange_modes" (currently always sent) and "pre_shared_key"+ -- (not always present) have length > 0.+ getExtensions =+ sequence+ [ {- 0xfe0d -} echExt+ , {- 0x00 -} sniExt+ , {- 0x0a -} groupExt+ , {- 0x0b -} ecPointExt+ , {- 0x0d -} signatureAlgExt+ , {- 0x10 -} alpnExt+ , {- 0x17 -} emsExt+ , {- 0x1b -} compCertExt+ , {- 0x1c -} recordSizeLimitExt+ , {- 0x23 -} sessionTicketExt+ , {- 0x2a -} earlyDataExt+ , {- 0x2b -} versionExt+ , {- 0x2c -} cookieExt+ , {- 0x2d -} pskExchangeModeExt+ , {- 0x31 -} postHandshakeAuthExt+ , {- 0x33 -} keyShareExt+ , {- 0xff01 -} secureRenegExt+ , {- 0x29 -} preSharedKeyExt -- MUST be last (RFC 8446)+ ]++ --------------------++ sniExt =+ if clientUseServerNameIndication cparams+ then do+ let sni = fst $ clientServerIdentification cparams+ usingState_ ctx $ setClientSNI sni+ return $ Just $ toExtensionRaw $ ServerName [ServerNameHostName sni]+ else return Nothing++ -- RFC 8446 Sec 4.2.8 says: Each KeyShareEntry value MUST correspond+ -- to a group offered in the "supported_groups" extension and MUST+ -- appear in the same order.+ groupExt = return $ Just $ toExtensionRaw $ SupportedGroups grpsSupported++ ecPointExt =+ return $+ Just $+ toExtensionRaw $+ EcPointFormatsSupported [EcPointFormat_Uncompressed]++ signatureAlgExt =+ return $+ Just $+ toExtensionRaw $+ SignatureAlgorithms $+ supportedHashSignatures $+ clientSupported cparams++ alpnExt = do+ mprotos <- onSuggestALPN $ clientHooks cparams+ case mprotos of+ Nothing -> return Nothing+ Just protos -> do+ usingState_ ctx $ setClientALPNSuggest protos+ return $ Just $ toExtensionRaw $ ApplicationLayerProtocolNegotiation protos++ emsExt =+ return $+ if ems == NoEMS || all (>= TLS13) (supportedVersions $ ctxSupported ctx)+ then Nothing+ else Just $ toExtensionRaw ExtendedMainSecret++ compCertExt = return $ Just $ toExtensionRaw (CompressCertificate [CCA_Zlib])++ recordSizeLimitExt = case limitRecordSize $ sharedLimit $ ctxShared ctx of+ Nothing -> return Nothing+ Just siz -> return $ Just $ toExtensionRaw $ RecordSizeLimit $ fromIntegral siz++ sessionTicketExt =+ case clientSessions cparams of+ (sidOrTkt, _) : _+ | isTicket sidOrTkt -> return $ Just $ toExtensionRaw $ SessionTicket sidOrTkt+ _ | clientWantTicket cparams -> return $ Just $ toExtensionRaw $ SessionTicket ""+ | otherwise -> return $ Nothing++ earlyDataExt+ | rtt0 = return $ Just $ toExtensionRaw (EarlyDataIndication Nothing)+ | otherwise = return Nothing++ versionExt+ | clientUseECH cparams = do+ let vers = supportedVersions $ ctxSupported ctx+ if TLS13 `elem` vers+ then+ return $ Just $ toExtensionRaw $ SupportedVersionsClientHello [TLS13]+ else+ throwCore $ Error_Misc "TLS 1.3 must be specified for Encrypted Client Hello"+ | tls13 = do+ let vers = filter (>= TLS12) $ supportedVersions $ ctxSupported ctx+ return $ Just $ toExtensionRaw $ SupportedVersionsClientHello vers+ | otherwise = return Nothing++ cookieExt = do+ mcookie <- usingState_ ctx getTLS13Cookie+ case mcookie of+ Nothing -> return Nothing+ Just cookie -> return $ Just $ toExtensionRaw cookie++ pskExchangeModeExt+ | tls13 = return $ Just $ toExtensionRaw $ PskKeyExchangeModes [PSK_DHE_KE]+ | otherwise = return Nothing++ postHandshakeAuthExt+ | ctxQUICMode ctx = return Nothing+ | tls13 = return $ Just $ toExtensionRaw PostHandshakeAuth+ | otherwise = return Nothing++ keyShareExt+ | tls13 = do+ (grpCpris, ents) <- unzip <$> mapM (makeClientKeyShare ctx) grpsSelected+ usingHState ctx $ setGroupPrivate grpCpris+ return $ Just $ toExtensionRaw $ KeyShareClientHello ents+ | otherwise = return Nothing++ secureRenegExt =+ if supportedSecureRenegotiation $ ctxSupported ctx+ then do+ VerifyData cvd <- usingState_ ctx $ getVerifyData ClientRole+ return $ Just $ toExtensionRaw $ SecureRenegotiation cvd ""+ else return Nothing++ -- ECHClientHelloInner should be replaced if ECHConfigList is not available.+ echExt+ | clientUseECH cparams = return $ Just $ toExtensionRaw ECHClientHelloInner+ | otherwise = return Nothing++ preSharedKeyExt =+ case pskInfo of+ Nothing -> return Nothing+ Just (identities, _, choice, obfAge) -> do+ let zero = cZero choice+ pskIdentities = map (\x -> PskIdentity x obfAge) identities+ -- [zero] is a place holds.+ -- adjustPreSharedKeyExt will replace them.+ binders = replicate (length pskIdentities) $ convert zero+ offeredPsks = PreSharedKeyClientHello pskIdentities binders+ return $ Just $ toExtensionRaw offeredPsks++ randomPreSharedKeyExt :: IO (Maybe ExtensionRaw)+ randomPreSharedKeyExt =+ case pskInfo of+ Nothing -> return Nothing+ Just (identities, _, choice, _) -> do+ let zero = cZero choice+ zeroR <- getStdRandom $ uniformByteString $ BA.length zero+ obfAgeR <- getStdRandom genWord32+ let genPskId x = do+ xR <- getStdRandom $ uniformByteString $ B.length x+ return $ PskIdentity xR obfAgeR+ pskIdentitiesR <- mapM genPskId identities+ let bindersR = replicate (length pskIdentitiesR) zeroR+ offeredPsksR = PreSharedKeyClientHello pskIdentitiesR bindersR+ return $ Just $ toExtensionRaw offeredPsksR++ ----------------------------------------++ adjustPreSharedKeyExt exts ch =+ case pskInfo of+ Nothing -> return exts+ Just (identities, sdata, choice, _) -> do+ let psk = sessionSecret sdata+ earlySecret = initEarlySecret choice (Just psk)+ usingHState ctx $ setTLS13EarlySecret earlySecret+ let ech = encodeHandshake $ ClientHello ch+ h = cHash choice+ siz = (hashDigestSize h + 1) * length identities + 2+ binder = makePSKBinder earlySecret h siz ech+ -- PSK is shared by the previous TLS session.+ -- So, PSK is unique for identities.+ let binders = replicate (length identities) binder+ let exts' = init exts ++ [adjust (last exts)]+ adjust (ExtensionRaw eid withoutBinders) = ExtensionRaw eid withBinders+ where+ withBinders = replacePSKBinder withoutBinders binders+ return exts'++ getEarlySecretInfo choice = do+ let usedCipher = cCipher choice+ usedHash = cHash choice+ Just earlySecret <- usingHState ctx getTLS13EarlySecret+ earlyKey <- calculateEarlySecret ctx choice (Right earlySecret)+ let clientEarlySecret = pairClient earlyKey+ unless (ctxQUICMode ctx) $ do+ runPacketFlight ctx $ sendChangeCipherSpec13 ctx+ setTxRecordState ctx usedHash usedCipher clientEarlySecret+ setEstablished ctx EarlyDataSending+ -- We set RTT0Sent even in quicMode+ usingHState ctx $ setTLS13RTT0Status RTT0Sent+ return $ EarlySecretInfo usedCipher clientEarlySecret++----------------------------------------------------------------++type PreSharedKeyInfo =+ ( Maybe ([SessionIDorTicket], SessionData, CipherChoice, Second)+ , Maybe CipherChoice+ , Bool+ )++getPreSharedKeyInfo+ :: ClientParams+ -> Context+ -> IO PreSharedKeyInfo+getPreSharedKeyInfo cparams ctx = do+ pskInfo <- getPskInfo+ let rtt0info = pskInfo >>= get0RTTinfo+ rtt0 = isJust rtt0info+ return (pskInfo, rtt0info, rtt0)+ where+ ciphers = supportedCiphers $ ctxSupported ctx+ highestVer = maximum $ supportedVersions $ ctxSupported ctx+ tls13 = highestVer >= TLS13++ sessions = case clientSessions cparams of+ [] -> Nothing+ (sid, sdata) : xs -> do+ guard tls13+ guard (sessionVersion sdata >= TLS13)+ let cid = sessionCipher sdata+ sids = map fst xs+ sCipher <- findCipher cid ciphers+ Just (sid : sids, sdata, sCipher)++ getPskInfo = case sessions of+ Nothing -> return Nothing+ Just (identity, sdata, sCipher) -> do+ let tinfo = fromJust $ sessionTicketInfo sdata+ age <- getAge tinfo+ return $+ if isAgeValid age tinfo+ then+ Just+ ( identity+ , sdata+ , makeCipherChoice TLS13 sCipher+ , ageToObfuscatedAge age tinfo+ )+ else Nothing++ get0RTTinfo (_, sdata, choice, _)+ | clientUseEarlyData cparams && sessionMaxEarlyDataSize sdata > 0 = Just choice+ | otherwise = Nothing++----------------------------------------------------------------++createEncryptedClientHello+ :: Context+ -> ClientHello+ -> (KDF_ID, AEAD_ID, ECHConfig)+ -> ClientRandom+ -> Maybe ExtensionRaw+ -> IO ClientHello+createEncryptedClientHello ctx ch0@CH{..} echParams@(kdfid, aeadid, conf) crO mpskExt = E.handle hpkeHandler $ do+ let (chExtsO, chExtsI) = dupCompExts (cnfPublicName conf) mpskExt chExtensions+ chI =+ ch0+ { chSession = Session Nothing+ , chExtensions = chExtsI+ }+ Just (func, enc, taglen) <- getHPKE ctx echParams+ let bsI = encodeHandshake' $ ClientHello chI+ padLen = 32 - (B.length bsI .&. 31)+ bsI' = bsI <> B.replicate padLen 0+ let outerZ =+ ECHClientHelloOuter+ { echCipherSuite = (kdfid, aeadid)+ , echConfigId = cnfConfigId conf+ , echEnc = enc+ , echPayload = B.replicate (B.length bsI' + taglen) 0+ }+ echOZ = extensionEncode outerZ+ chExtsOTail = drop 1 chExtsO+ chOZ =+ ch0+ { chRandom = crO+ , chExtensions =+ ExtensionRaw EID_EncryptedClientHello echOZ : chExtsOTail+ }+ aad = encodeHandshake' $ ClientHello chOZ+ bsO <- func aad bsI'+ let outer =+ ECHClientHelloOuter+ { echCipherSuite = (kdfid, aeadid)+ , echConfigId = cnfConfigId conf+ , echEnc = enc+ , echPayload = bsO+ }+ echO = extensionEncode outer+ chO =+ chOZ+ { chExtensions =+ ExtensionRaw EID_EncryptedClientHello echO : chExtsOTail+ }+ return chO+ where+ hpkeHandler :: HPKEError -> IO ClientHello+ hpkeHandler _ = return ch0++dupCompExts+ :: HostName+ -> Maybe ExtensionRaw+ -> [ExtensionRaw]+ -> ([ExtensionRaw], [ExtensionRaw]) -- Outer, inner+dupCompExts host mpskExt chExts = step1 chExts+ where+ step1 (echExtI@(ExtensionRaw EID_EncryptedClientHello _) : exts) =+ (echExtO : os, echExtI : is)+ where+ echExtO = ExtensionRaw EID_EncryptedClientHello ""+ (os, is) = step2 exts+ step1 _ = error "step1"+ step2 (sniExtI@(ExtensionRaw EID_ServerName _) : exts) =+ (sniExtO : os, sniExtI : is)+ where+ sniExtO = toExtensionRaw $ ServerName [ServerNameHostName host]+ (os, is) = step3 exts id+ step2 _ = error "step2"+ step3 [] build = ([], [echOuterExt])+ where+ echOuterExt = toExtensionRaw $ EchOuterExtensions $ build []+ step3 [pskExtI@(ExtensionRaw EID_PreSharedKey _)] build =+ ([pskExtO], [echOuterExt, pskExtI])+ where+ echOuterExt = toExtensionRaw $ EchOuterExtensions $ build []+ pskExtO = fromJust mpskExt+ step3 (i@(ExtensionRaw eid _) : is) build = (i : os', is')+ where+ (os', is') = step3 is (build . (eid :))++getHPKE+ :: Context+ -> (KDF_ID, AEAD_ID, ECHConfig)+ -> IO (Maybe (AAD -> PlainText -> IO CipherText, EncodedPublicKey, Int))+getHPKE ctx (kdfid, aeadid, conf) = do+ mfunc <- getTLS13HPKE ctx+ case mfunc of+ Nothing -> do+ let encodedConfig = encodeECHConfig conf+ info = "tls ech\x00" <> encodedConfig+ (pkSm, ctxS) <- setupBaseS kemid kdfid aeadid Nothing Nothing mpkR info+ let func = seal ctxS+ setTLS13HPKE ctx func 0+ return $ Just (func, pkSm, nT)+ Just (func, _) -> return $ Just (func, EncodedPublicKey "", nT)+ where+ mpkR = cnfEncodedPublicKey conf+ kemid = cnfKemId conf+ nT = nTag aeadid++----------------------------------------------------------------++lookupECHConfigList+ :: [(KEM_ID, KDF_ID, AEAD_ID)]+ -> ECHConfigList+ -> Maybe (KDF_ID, AEAD_ID, ECHConfig)+lookupECHConfigList [] _ = Nothing+lookupECHConfigList ((kemid, kdfid, aeadid) : xs) cnfs =+ case find (\cnf -> cnfKemId cnf == kemid) cnfs of+ Nothing -> lookupECHConfigList xs cnfs+ Just cnf+ | (kdfid, aeadid) `elem` cnfCipherSuite cnf ->+ Just (kdfid, aeadid, cnf)+ | otherwise -> lookupECHConfigList xs cnfs++cnfKemId :: ECHConfig -> KEM_ID+cnfKemId ECHConfig{..} = KEM_ID $ kem_id $ key_config contents++cnfCipherSuite :: ECHConfig -> [(KDF_ID, AEAD_ID)]+cnfCipherSuite ECHConfig{..} = map conv $ cipher_suites $ key_config contents+ where+ conv HpkeSymmetricCipherSuite{..} = (KDF_ID kdf_id, AEAD_ID aead_id)++cnfEncodedPublicKey :: ECHConfig -> EncodedPublicKey+cnfEncodedPublicKey ECHConfig{..} = EncodedPublicKey pk+ where+ EncodedServerPublicKey pk = public_key $ key_config contents++cnfPublicName :: ECHConfig -> HostName+cnfPublicName ECHConfig{..} = public_name contents++cnfConfigId :: ECHConfig -> ConfigId+cnfConfigId ECHConfig{..} = config_id $ key_config contents++----------------------------------------------------------------++-- Pretending X25519 is used because it is the de-facto and+-- its public key is easily created.+greasingEchExt :: IO ExtensionRaw+greasingEchExt = do+ cid <- getStdRandom genWord8+ enc <- getStdRandom $ uniformByteString 32+ n <- getStdRandom $ randomR (4, 6)+ payload <- getStdRandom $ uniformByteString (n * 32 + 16)+ let outer =+ ECHClientHelloOuter+ { echCipherSuite = (HKDF_SHA256, AES_128_GCM)+ , echConfigId = cid+ , echEnc = EncodedPublicKey enc+ , echPayload = payload+ }+ return $ toExtensionRaw outer++#if !MIN_VERSION_random(1,3,0)+uniformByteString :: RandomGen g => Int -> g -> (ByteString, g)+uniformByteString l g0 = (bs, g2)+ where+ (g1, g2) = split g0+ bs = unsafeCreate l $ go 0 g1+ go n g ptr+ | n == l = return ()+ | otherwise = do+ let (w, g') = genWord8 g+ poke ptr w+ go (n + 1) g' (plusPtr ptr 1)+#endif
+ Network/TLS/Handshake/Client/Common.hs view
@@ -0,0 +1,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]
+ Network/TLS/Handshake/Client/ServerHello.hs view
@@ -0,0 +1,306 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Network.TLS.Handshake.Client.ServerHello (+ receiveServerHello,+ processServerHello13,+) where++import Data.ByteArray (convert)+import qualified Data.ByteString as B++import Network.TLS.Cipher+import Network.TLS.Compression+import Network.TLS.Context.Internal+import Network.TLS.ErrT+import Network.TLS.Extension+import Network.TLS.Handshake.Client.Common+import Network.TLS.Handshake.Common+import Network.TLS.Handshake.Common13+import Network.TLS.Handshake.Key+import Network.TLS.Handshake.Random+import Network.TLS.Handshake.State+import Network.TLS.Handshake.TranscriptHash+import Network.TLS.IO+import Network.TLS.Imports+import Network.TLS.Packet+import Network.TLS.Parameters+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Types++----------------------------------------------------------------++receiveServerHello+ :: ClientParams+ -> Context+ -> Maybe (ClientRandom, Session, Version)+ -> IO (Version, [HandshakeR], Bool)+receiveServerHello cparams ctx mparams = do+ chSentTime <- getCurrentTimeFromBase+ (shb@(sh, _), hbs) <- recvSH+ processServerHello cparams ctx sh+ updateTranscriptHash12 ctx shb+ setRTT ctx chSentTime+ ver <- usingState_ ctx getVersion+ unless (maybe True (\(_, _, v) -> v == ver) mparams) $+ throwCore $+ Error_Protocol "version changed after hello retry" IllegalParameter+ -- recvServerHello sets TLS13HRR according to the server random.+ -- For 1st server hello, getTLS13HR returns True if it is HRR and+ -- False otherwise. For 2nd server hello, getTLS13HR returns+ -- False since it is NOT HRR.+ hrr <- usingState_ ctx getTLS13HRR+ return (ver, hbs, hrr)+ where+ recvSH = do+ epkt <- recvPacket12 ctx+ case epkt of+ Left e -> throwCore e+ Right pkt -> case pkt of+ Alert a -> throwAlert a+ Handshake (h : hs) (b : bs) -> return ((h, b), zip hs bs)+ _ -> unexpected (show pkt) (Just "handshake")+ throwAlert a =+ throwCore $+ Error_Protocol+ ("expecting server hello, got alert : " ++ show a)+ HandshakeFailure++----------------------------------------------------------------++processServerHello13+ :: ClientParams -> Context -> Handshake13 -> IO ()+processServerHello13 cparams ctx (ServerHello13 sh13) = do+ let sh12 = ServerHello sh13+ processServerHello cparams ctx sh12+processServerHello13 _ _ h = unexpected (show h) (Just "server hello")++-- | processServerHello processes the ServerHello message on the client.+--+-- 1) check the version chosen by the server is one allowed by+-- parameters.+-- 2) check that our compression and cipher algorithms are part of the+-- list we sent+-- 3) check extensions received are part of the one we sent+-- 4) process the session parameter to see if the server want to start+-- a new session or can resume+processServerHello+ :: ClientParams -> Context -> Handshake -> IO ()+processServerHello cparams ctx (ServerHello sh@SH{..}) = do+ -- A server which receives a legacy_version value not equal to+ -- 0x0303 MUST abort the handshake with an "illegal_parameter"+ -- alert.+ when (shVersion /= TLS12) $+ throwCore $+ Error_Protocol (show shVersion ++ " is not supported") IllegalParameter+ -- find the compression and cipher methods that the server want to use.+ clientSession <- tls13stSession <$> getTLS13State ctx+ chExts <- tls13stSentExtensions <$> getTLS13State ctx+ let clientCiphers = supportedCiphers $ ctxSupported ctx+ usedCipher <- case findCipher (fromCipherId shCipher) clientCiphers of+ Nothing -> throwCore $ Error_Protocol "server choose unknown cipher" IllegalParameter+ Just alg -> return alg+ compressAlg <- case find+ ((==) shComp . compressionID)+ (supportedCompressions $ ctxSupported ctx) of+ Nothing ->+ throwCore $ Error_Protocol "server choose unknown compression" IllegalParameter+ Just alg -> return alg+ ensureNullCompression shComp++ -- intersect sent extensions in client and the received extensions+ -- from server. if server returns extensions that we didn't+ -- request, fail.+ let checkExt (ExtensionRaw i _)+ | i == EID_Cookie = False -- for HRR+ | otherwise = i `notElem` chExts+ when (any checkExt shExtensions) $+ throwCore $+ Error_Protocol "spurious extensions received" UnsupportedExtension++ let isHRR = isHelloRetryRequest shRandom+ usingState_ ctx $ do+ setTLS13HRR isHRR+ when isHRR $+ setTLS13Cookie $+ lookupAndDecode+ EID_Cookie+ MsgTServerHello+ shExtensions+ Nothing+ (\cookie@(Cookie _) -> Just cookie)+ setVersion shVersion -- must be before processing supportedVersions ext+ mapM_ processServerExtension shExtensions++ setALPN ctx MsgTServerHello shExtensions++ ver <- usingState_ ctx getVersion++ when (ver == TLS12) $+ setServerHelloParameters12 ctx shVersion shRandom usedCipher compressAlg++ let supportedVers = supportedVersions $ clientSupported cparams++ when (ver == TLS13) $ do+ -- TLS 1.3 server MUST echo the session id+ when (clientSession /= shSession) $+ throwCore $+ Error_Protocol+ "session is not matched in compatibility mode"+ IllegalParameter+ when (ver `notElem` supportedVers) $+ throwCore $+ Error_Protocol+ ("server version " ++ show ver ++ " is not supported")+ ProtocolVersion++ -- Some servers set TLS 1.2 as the legacy server hello version,+ -- and TLS 1.3 in the supported_versions extension, *AND ALSO* set+ -- the TLS 1.2 downgrade signal in the server random. If we+ -- support TLS 1.3 and actually negotiate TLS 1.3, we must ignore+ -- the server random downgrade signal. Therefore, 'isDowngraded'+ -- needs to take into account the negotiated version and the+ -- server random, as well as the list of client-side enabled+ -- protocol versions.+ --+ when (isDowngraded ver supportedVers shRandom) $+ throwCore $+ Error_Protocol "version downgrade detected" IllegalParameter++ if ver == TLS13+ then do+ -- Session is dummy in TLS 1.3.+ usingState_ ctx $ setSession shSession+ processRecordSizeLimit ctx shExtensions True+ enableMyRecordLimit ctx+ enablePeerRecordLimit ctx+ let usedHash = cipherHash usedCipher+ transitTranscriptHashI ctx "transitI" usedHash isHRR+ accepted <- checkECHacceptance ctx isHRR usedHash sh+ when accepted $ do+ (CH{..}, _b) <- fromJust <$> usingHState ctx getClientHello+ usingHState ctx $ setClientRandom chRandom -- inner random+ when (accepted && not isHRR) $ do+ copyTranscriptHash ctx "copy"+ usingHState ctx $ setECHAccepted True+ updateContext13 ctx usedCipher isHRR+ updateTranscriptHashI ctx "ServerHelloI" $ encodeHandshake $ ServerHello sh+ else do+ let resumingSession = case clientSessions cparams of+ (_, sessionData) : _ ->+ if shSession == clientSession then Just sessionData else Nothing+ _ -> Nothing++ usingState_ ctx $ do+ setSession shSession+ setTLS12SessionResuming $ isJust resumingSession+ processRecordSizeLimit ctx shExtensions False+ updateContext12 ctx shExtensions resumingSession+processServerHello _ _ p = unexpected (show p) (Just "server hello")++----------------------------------------------------------------++processServerExtension :: ExtensionRaw -> TLSSt ()+processServerExtension (ExtensionRaw extID content)+ | extID == EID_SecureRenegotiation = do+ VerifyData cvd <- getVerifyData ClientRole+ VerifyData svd <- getVerifyData ServerRole+ let bs = extensionEncode $ SecureRenegotiation cvd svd+ unless (bs == content) $+ throwError $+ Error_Protocol "server secure renegotiation data not matching" HandshakeFailure+ | extID == EID_SupportedVersions = case extensionDecode MsgTServerHello content of+ Just (SupportedVersionsServerHello ver) -> setVersion ver+ _ -> return ()+ | extID == EID_KeyShare = do+ hrr <- getTLS13HRR+ let msgt = if hrr then MsgTHelloRetryRequest else MsgTServerHello+ setTLS13KeyShare $ extensionDecode msgt content+ | extID == EID_PreSharedKey =+ setTLS13PreSharedKey $ extensionDecode MsgTServerHello content+ | extID == EID_SessionTicket = setTLS12SessionTicket "" -- empty ticket+processServerExtension _ = return ()++----------------------------------------------------------------++updateContext13 :: Context -> Cipher -> Bool -> IO ()+updateContext13 ctx usedCipher isHRR = do+ established <- ctxEstablished ctx+ eof <- ctxEOF ctx+ when (established == Established && not eof) $+ throwCore $+ Error_Protocol+ "renegotiation to TLS 1.3 or later is not allowed"+ ProtocolVersion+ failOnEitherError $ setServerHelloParameters13 ctx usedCipher isHRR++updateContext12 :: Context -> [ExtensionRaw] -> Maybe SessionData -> IO ()+updateContext12 ctx shExtensions resumingSession = do+ ems <- processExtendedMainSecret ctx TLS12 MsgTServerHello shExtensions+ case resumingSession of+ Nothing -> return ()+ Just sessionData -> do+ let emsSession = SessionEMS `elem` sessionFlags sessionData+ when (ems /= emsSession) $+ let err = "server resumes a session which is not EMS consistent"+ in throwCore $ Error_Protocol err HandshakeFailure+ let mainSecret = convert $ sessionSecret sessionData+ usingHState ctx $ setMainSecret TLS12 ClientRole mainSecret+ logKey ctx (MainSecret mainSecret)++----------------------------------------------------------------++processRecordSizeLimit+ :: Context -> [ExtensionRaw] -> Bool -> IO ()+processRecordSizeLimit ctx shExtensions tls13 = do+ let mmylim = limitRecordSize $ sharedLimit $ ctxShared ctx+ case mmylim of+ Nothing -> return ()+ Just mylim -> do+ lookupAndDecodeAndDo+ EID_RecordSizeLimit+ MsgTClientHello+ shExtensions+ (return ())+ (setPeerRecordSizeLimit ctx tls13)+ ack <- checkPeerRecordLimit ctx+ -- When a client sends RecordSizeLimit, it does not know+ -- which TLS version the server selects. RecordLimit is+ -- the length of plaintext. But RecordSizeLimit also+ -- includes CT: and padding for TLS 1.3. To convert+ -- RecordSizeLimit to RecordLimit, we should reduce the+ -- value by 1, which is the length of CT:.+ when (ack && tls13) $ setMyRecordLimit ctx $ Just (mylim - 1)++----------------------------------------------------------------++checkECHacceptance :: Context -> Bool -> Hash -> ServerHello -> IO Bool+checkECHacceptance ctx False usedHash sh@SH{..} = do+ let (prefix, confirm) = B.splitAt 24 $ unServerRandom shRandom+ sr' = ServerRandom (prefix <> "\x00\x00\x00\x00\x00\x00\x00\x00")+ verified <-+ computeConfirm ctx usedHash sh{shRandom = sr'} "ech accept confirmation"+ return (confirm == verified)+checkECHacceptance ctx True usedHash sh@SH{..} = do+ case replace shExtensions of+ Nothing -> return False+ Just (confirm, shExts') -> do+ verified <-+ computeConfirm+ ctx+ usedHash+ sh{shExtensions = shExts'}+ "hrr ech accept confirmation"+ return (confirm == verified)+ where+ replace [] = Nothing+ replace (ExtensionRaw EID_EncryptedClientHello confirm : es) =+ Just+ ( confirm+ , ExtensionRaw EID_EncryptedClientHello "\x00\x00\x00\x00\x00\x00\x00\x00" : es+ )+ replace (e : es) = case replace es of+ Nothing -> Nothing+ Just (confirm, es') -> Just (confirm, e : es')
+ Network/TLS/Handshake/Client/TLS12.hs view
@@ -0,0 +1,288 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}++module Network.TLS.Handshake.Client.TLS12 (+ recvServerFirstFlight12,+ sendClientSecondFlight12,+ recvServerSecondFlight12,+) where++import Control.Monad.State.Strict+import Data.ByteArray (convert)+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 -> [HandshakeR] -> IO ()+recvServerFirstFlight12 cparams ctx hbs = do+ resuming <- usingState_ ctx getTLS12SessionResuming+ if resuming+ then recvNSTandCCSandFinished ctx+ else do+ let st = RecvStateHandshake (expectCertificate cparams ctx)+ runRecvStateHS ctx st hbs++expectCertificate :: ClientParams -> Context -> Handshake -> IO (RecvState IO)+expectCertificate cparams ctx (Certificate (CertificateChain_ 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)+ finishHandshake12 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 (CertificateChain_ 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 Secret)+getCKX_RSA ctx = do+ clientVersion <- usingHState ctx $ gets hstClientVersion+ (xver, prerand) <- usingState_ ctx $ (,) <$> getVersion <*> genRandom 46++ let preMain = convert $ 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 Secret)+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 Secret)+getCKX_ECDHE ctx = do+ ServerECDHParams grp srvpub <- usingHState ctx getServerECDHParams+ checkSupportedGroup ctx grp+ usingHState ctx $ setSupportedGroup grp+ ecdhePair <- encapsulateGroup 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 $ groupEncodePublicB 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] []
+ Network/TLS/Handshake/Client/TLS13.hs view
@@ -0,0 +1,425 @@+{-# LANGUAGE OverloadedStrings #-}++module Network.TLS.Handshake.Client.TLS13 (+ recvServerSecondFlight13,+ sendClientSecondFlight13,+ asyncServerHello13,+ postHandshakeAuthClientWith,+) where++import Control.Exception (bracket)+import Control.Monad.State.Strict+import qualified Data.ByteArray as BA+import Data.IORef++import Network.TLS.Cipher+import Network.TLS.Context.Internal+import Network.TLS.Crypto+import Network.TLS.Error+import Network.TLS.Extension+import Network.TLS.Handshake.Client.Common+import Network.TLS.Handshake.Client.ServerHello+import Network.TLS.Handshake.Common hiding (expectFinished)+import Network.TLS.Handshake.Common13+import Network.TLS.Handshake.Control+import Network.TLS.Handshake.Key+import Network.TLS.Handshake.Signature+import Network.TLS.Handshake.State+import Network.TLS.Handshake.State13+import Network.TLS.Handshake.TranscriptHash+import Network.TLS.IO+import Network.TLS.Imports+import Network.TLS.Parameters+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Types+import Network.TLS.X509++----------------------------------------------------------------+----------------------------------------------------------------++recvServerSecondFlight13 :: ClientParams -> Context -> [Group] -> IO ()+recvServerSecondFlight13 cparams ctx groupSent = do+ resuming <- prepareSecondFlight13 ctx groupSent+ runRecvHandshake13 $ do+ recvHandshake13 ctx $ expectEncryptedExtensions ctx+ unless resuming $ recvHandshake13 ctx $ expectCertRequest cparams ctx+ recvHandshake13hash ctx "Finished" $ expectFinished cparams ctx++----------------------------------------------------------------++prepareSecondFlight13+ :: Context -> [Group] -> IO Bool+prepareSecondFlight13 ctx groupSent = do+ choice <- makeCipherChoice TLS13 <$> usingHState ctx getPendingCipher+ prepareSecondFlight13' ctx groupSent choice++prepareSecondFlight13'+ :: Context+ -> [Group]+ -> CipherChoice+ -> IO Bool+prepareSecondFlight13' ctx groupSent choice = do+ (_, hkey, resuming) <- switchToHandshakeSecret+ let clientHandshakeSecret = triClient hkey+ serverHandshakeSecret = triServer hkey+ handSecInfo = HandshakeSecretInfo usedCipher (clientHandshakeSecret, serverHandshakeSecret)+ contextSync ctx $ RecvServerHello handSecInfo+ modifyTLS13State ctx $ \st ->+ st+ { tls13stChoice = choice+ , tls13stHsKey = Just hkey+ }+ return resuming+ where+ usedCipher = cCipher choice+ usedHash = cHash choice++ hashSize = hashDigestSize usedHash++ switchToHandshakeSecret = do+ ensureRecvComplete ctx+ ecdhe <- calcSharedKey+ (earlySecret, resuming) <- makeEarlySecret+ handKey <- calculateHandshakeSecret ctx choice earlySecret ecdhe+ let serverHandshakeSecret = triServer handKey+ setRxRecordState ctx usedHash usedCipher serverHandshakeSecret+ return (usedCipher, handKey, resuming)++ calcSharedKey = do+ serverKeyShare <- do+ mks <- usingState_ ctx getTLS13KeyShare+ case mks of+ Just (KeyShareServerHello ks) -> return ks+ Just _ ->+ throwCore $ Error_Protocol "invalid key_share value" IllegalParameter+ Nothing ->+ throwCore $+ Error_Protocol+ "key exchange not implemented, expected key_share extension"+ HandshakeFailure+ let grp = keyShareEntryGroup serverKeyShare+ unless (checkServerKeyShareKeyLength serverKeyShare) $+ throwCore $+ Error_Protocol "broken key_share" IllegalParameter+ unless (grp `elem` groupSent) $+ throwCore $+ Error_Protocol "received incompatible group for (EC)DHE" IllegalParameter+ usingHState ctx $ setSupportedGroup grp+ usingHState ctx getGroupPrivate >>= fromServerKeyShare serverKeyShare++ makeEarlySecret = do+ mEarlySecretPSK <- usingHState ctx getTLS13EarlySecret+ case mEarlySecretPSK of+ Nothing -> return (initEarlySecret choice Nothing, False)+ Just earlySecretPSK@(BaseSecret sec) -> do+ mSelectedIdentity <- usingState_ ctx getTLS13PreSharedKey+ case mSelectedIdentity of+ Nothing ->+ return (initEarlySecret choice Nothing, False)+ Just (PreSharedKeyServerHello 0) -> do+ unless (BA.length sec == hashSize) $+ throwCore $+ Error_Protocol+ "selected cipher is incompatible with selected PSK"+ IllegalParameter+ usingHState ctx $ setTLS13HandshakeMode PreSharedKey+ return (earlySecretPSK, True)+ Just _ ->+ throwCore $ Error_Protocol "selected identity out of range" IllegalParameter++----------------------------------------------------------------++expectEncryptedExtensions+ :: MonadIO m => Context -> Handshake13 -> m ()+expectEncryptedExtensions ctx (EncryptedExtensions13 eexts) = do+ liftIO $ do+ 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+ 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+expectEncryptedExtensions _ h = unexpected (show h) (Just "encrypted extensions")++----------------------------------------------------------------+-- not used in 0-RTT+expectCertRequest+ :: MonadIO m+ => ClientParams -> Context -> Handshake13 -> RecvHandshake13M m ()+expectCertRequest cparams ctx (CertRequest13 token exts) = do+ processCertRequest13 ctx token exts+ recvHandshake13 ctx $ expectCertAndVerify cparams ctx+expectCertRequest cparams ctx other = do+ usingHState ctx $ do+ setCertReqToken Nothing+ setCertReqCBdata Nothing+ -- setCertReqSigAlgsCert Nothing+ expectCertAndVerify cparams ctx other++processCertRequest13+ :: MonadIO m => Context -> CertReqContext -> [ExtensionRaw] -> m ()+processCertRequest13 ctx token exts = do+ let hsextID = EID_SignatureAlgorithms+ -- caextID = EID_SignatureAlgorithmsCert+ dNames <- canames+ -- The @signature_algorithms@ extension is mandatory.+ hsAlgs <- extalgs hsextID unsighash+ cTypes <- case hsAlgs of+ Just as ->+ let validAs = filter isHashSignatureValid13 as+ in return $ sigAlgsToCertTypes ctx validAs+ Nothing -> throwCore $ Error_Protocol "invalid certificate request" HandshakeFailure+ -- Unused:+ -- caAlgs <- extalgs caextID uncertsig+ let zlib =+ lookupAndDecode+ EID_CompressCertificate+ MsgTClientHello+ exts+ False+ (\(CompressCertificate ccas) -> CCA_Zlib `elem` ccas)+ usingHState ctx $ do+ setCertReqToken $ Just token+ setCertReqCBdata $ Just (cTypes, hsAlgs, dNames)+ setTLS13CertComp zlib+ where+ -- setCertReqSigAlgsCert caAlgs++ canames = case extensionLookup EID_CertificateAuthorities exts of+ Nothing -> return []+ Just ext -> case extensionDecode MsgTCertificateRequest ext of+ Just (CertificateAuthorities names) -> return names+ _ -> throwCore $ Error_Protocol "invalid certificate request" HandshakeFailure+ extalgs extID decons = case extensionLookup extID exts of+ Nothing -> return Nothing+ Just ext -> case extensionDecode MsgTCertificateRequest ext of+ Just e ->+ return $ decons e+ _ -> throwCore $ Error_Protocol "invalid certificate request" HandshakeFailure+ unsighash+ :: SignatureAlgorithms+ -> Maybe [HashAndSignatureAlgorithm]+ unsighash (SignatureAlgorithms a) = Just a++----------------------------------------------------------------+-- not used in 0-RTT+expectCertAndVerify+ :: MonadIO m+ => ClientParams -> Context -> Handshake13 -> RecvHandshake13M m ()+expectCertAndVerify cparams ctx (Certificate13 _ (CertificateChain_ cc) _) = processCertAndVerify cparams ctx cc+expectCertAndVerify cparams ctx (CompressedCertificate13 _ (CertificateChain_ cc) _) = processCertAndVerify cparams ctx cc+expectCertAndVerify _ _ h = unexpected (show h) (Just "server certificate")++processCertAndVerify+ :: MonadIO m+ => ClientParams -> Context -> CertificateChain -> RecvHandshake13M m ()+processCertAndVerify 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++----------------------------------------------------------------++expectCertVerify+ :: MonadIO m+ => Context -> PubKey -> TranscriptHash -> Handshake13 -> m ()+expectCertVerify ctx pubkey (TranscriptHash hChSc) (CertVerify13 (DigitallySigned sigAlg sig)) = do+ ok <- checkCertVerify ctx pubkey sigAlg sig hChSc+ unless ok $ decryptError "cannot verify CertificateVerify"+expectCertVerify _ _ _ h = unexpected (show h) (Just "certificate verify")++----------------------------------------------------------------++expectFinished+ :: MonadIO m+ => ClientParams+ -> Context+ -> TranscriptHash+ -> Handshake13+ -> m ()+expectFinished cparams ctx hashValue (Finished13 verifyData) = do+ st <- liftIO $ getTLS13State ctx+ let usedHash = cHash $ tls13stChoice st+ ServerTrafficSecret baseKey = triServer $ fromJust $ tls13stHsKey st+ checkFinished ctx usedHash baseKey hashValue verifyData+ liftIO $ do+ minfo <- contextGetInformation ctx+ case minfo of+ Nothing -> return ()+ Just info -> onServerFinished (clientHooks cparams) info+ liftIO $ modifyTLS13State ctx $ \s -> s{tls13stRecvSF = True}+expectFinished _ _ _ p = unexpected (show p) (Just "server finished")++----------------------------------------------------------------+----------------------------------------------------------------++sendClientSecondFlight13 :: ClientParams -> Context -> IO ()+sendClientSecondFlight13 cparams ctx = do+ st <- getTLS13State ctx+ let choice = tls13stChoice st+ hkey = fromJust $ tls13stHsKey st+ rtt0accepted = tls13st0RTTAccepted st+ eexts = tls13stClientExtensions st+ sendClientSecondFlight13' cparams ctx choice hkey rtt0accepted eexts+ modifyTLS13State ctx $ \s -> s{tls13stSentCF = True}+ echAccepted <- usingHState ctx getECHAccepted+ when (clientUseECH cparams && not echAccepted) $+ throwCore $+ Error_Protocol "ECH is not accepted" EchRequired++sendClientSecondFlight13'+ :: ClientParams+ -> Context+ -> CipherChoice+ -> SecretTriple HandshakeSecret+ -> Bool+ -> [ExtensionRaw]+ -> IO ()+sendClientSecondFlight13' cparams ctx choice hkey rtt0accepted eexts = do+ hChSf <- transcriptHash ctx "CH..SF"+ unless (ctxQUICMode ctx) $+ runPacketFlight ctx $+ sendChangeCipherSpec13 ctx+ when (rtt0accepted && not (ctxQUICMode ctx)) $+ sendPacket13 ctx (Handshake13 [EndOfEarlyData13] [])+ let clientHandshakeSecret = triClient hkey+ setTxRecordState ctx usedHash usedCipher clientHandshakeSecret+ sendClientFlight13 cparams ctx usedHash clientHandshakeSecret+ appKey <- switchToApplicationSecret hChSf+ let applicationSecret = triBase appKey+ setResumptionSecret applicationSecret+ let appSecInfo = ApplicationSecretInfo (triClient appKey, triServer appKey)+ contextSync ctx $ SendClientFinished eexts appSecInfo+ modifyTLS13State ctx $ \st -> st{tls13stHsKey = Nothing}+ finishHandshake13 ctx+ rtt0 <- tls13st0RTT <$> getTLS13State ctx+ when rtt0 $ do+ builder <- tls13stPendingSentData <$> getTLS13State ctx+ modifyTLS13State ctx $ \st -> st{tls13stPendingSentData = id}+ unless rtt0accepted $+ mapM_ (sendPacket13 ctx . AppData13) $+ builder []+ where+ usedCipher = cCipher choice+ usedHash = cHash choice++ switchToApplicationSecret hChSf = do+ ensureRecvComplete ctx+ let handshakeSecret = triBase hkey+ appKey <- calculateApplicationSecret ctx choice handshakeSecret hChSf+ let serverApplicationSecret0 = triServer appKey+ let clientApplicationSecret0 = triClient appKey+ setTxRecordState ctx usedHash usedCipher clientApplicationSecret0+ setRxRecordState ctx usedHash usedCipher serverApplicationSecret0+ return appKey++ setResumptionSecret applicationSecret = do+ resumptionSecret <- calculateResumptionSecret ctx choice applicationSecret+ usingHState ctx $ setTLS13ResumptionSecret resumptionSecret++{- Unused for now+uncertsig :: SignatureAlgorithmsCert+ -> Maybe [HashAndSignatureAlgorithm]+uncertsig (SignatureAlgorithmsCert a) = Just a+-}++sendClientFlight13+ :: ClientParams -> Context -> Hash -> ClientTrafficSecret a -> IO ()+sendClientFlight13 cparams ctx usedHash (ClientTrafficSecret baseKey) = do+ mcc <- clientChain cparams ctx+ runPacketFlight ctx $ do+ case mcc of+ Nothing -> return ()+ Just cc -> do+ reqtoken <- usingHState ctx getCertReqToken+ certComp <- usingHState ctx getTLS13CertComp+ loadClientData13 cc reqtoken certComp+ rawFinished <- makeFinished ctx usedHash baseKey+ loadPacket13 ctx $ Handshake13 [rawFinished] []+ when (isJust mcc) $+ modifyTLS13State ctx $+ \st -> st{tls13stSentClientCert = True}+ where+ loadClientData13 chain (Just token) certComp = do+ let (CertificateChain certs) = chain+ certExts = replicate (length certs) []+ cHashSigs = filter isHashSignatureValid13 $ supportedHashSignatures $ ctxSupported ctx+ let certtag = if certComp then CompressedCertificate13 else Certificate13+ loadPacket13 ctx $+ Handshake13 [certtag token (CertificateChain_ chain) certExts] []+ case certs of+ [] -> return ()+ _ -> do+ hChSc <- transcriptHash ctx "CH..SC"+ pubKey <- getLocalPublicKey ctx+ sigAlg <-+ liftIO $ getLocalHashSigAlg ctx signatureCompatible13 cHashSigs pubKey+ vfy <- makeCertVerify ctx pubKey sigAlg hChSc+ loadPacket13 ctx $ Handshake13 [vfy] []+ --+ loadClientData13 _ _ _ =+ throwCore $+ Error_Protocol "missing TLS 1.3 certificate request context token" InternalError++----------------------------------------------------------------+----------------------------------------------------------------++postHandshakeAuthClientWith+ :: ClientParams -> Context -> Handshake13 -> IO ()+postHandshakeAuthClientWith cparams ctx (CertRequest13 certReqCtx exts) =+ bracket (saveHState ctx) (restoreHState ctx) $ \_ -> do+ -- updateTranscriptHash13 ctx h b+ processCertRequest13 ctx certReqCtx exts+ (usedHash, _, level, applicationSecretN) <- getTxRecordState ctx+ unless (level == CryptApplicationSecret) $+ throwCore $+ Error_Protocol+ "unexpected post-handshake authentication request"+ UnexpectedMessage+ sendClientFlight13+ cparams+ ctx+ usedHash+ (ClientTrafficSecret applicationSecretN)+postHandshakeAuthClientWith _ _ _ =+ throwCore $+ Error_Protocol+ "unexpected handshake message received in postHandshakeAuthClientWith"+ UnexpectedMessage++----------------------------------------------------------------+----------------------------------------------------------------++asyncServerHello13+ :: ClientParams -> Context -> [Group] -> Millisecond -> IO ()+asyncServerHello13 cparams ctx groupSent chSentTime = do+ setPendingRecvActions+ ctx+ [ PendingRecvActionSelfUpdate True expectServerHello+ , PendingRecvAction True (expectEncryptedExtensions ctx)+ , PendingRecvActionHash True expectFinishedAndSet+ ]+ where+ expectServerHello shb@(sh, _) = do+ setRTT ctx chSentTime+ processServerHello13 cparams ctx sh+ updateTranscriptHash13 ctx shb -- update by myself+ void $ prepareSecondFlight13 ctx groupSent+ expectFinishedAndSet h sf = do+ expectFinished cparams ctx h sf+ liftIO $+ writeIORef (ctxPendingSendAction ctx) $+ Just $+ sendClientSecondFlight13 cparams
Network/TLS/Handshake/Common.hs view
@@ -1,127 +1,442 @@-{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}-module Network.TLS.Handshake.Common- ( handshakeFailed- , errorToAlert- , unexpected- , newSession- , handshakeTerminate+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}++module Network.TLS.Handshake.Common (+ handshakeFailed,+ handleException,+ unexpected,+ newSession,+ ensureNullCompression,+ ticketOrSessionID12,+ -- * sending packets- , sendChangeCipherAndFinish+ sendCCSandFinished,+ -- * receiving packets- , recvChangeCipherAndFinish- , RecvState(..)- , runRecvState- , recvPacketHandshake- , onRecvStateHandshake- ) where+ RecvState (..),+ runRecvState,+ runRecvStateHS,+ recvPacketHandshake,+ onRecvStateHandshake,+ ensureRecvComplete,+ processExtendedMainSecret,+ getSessionData,+ storePrivInfo,+ isSupportedGroup,+ checkSupportedGroup,+ errorToAlert,+ errorToAlertMessage,+ expectFinished,+ processCertificate,+ --+ setPeerRecordSizeLimit,+ generateFinished,+ encodeUpdateTranscriptHash12,+ updateTranscriptHash12,+ --+ startHandshake,+ finishHandshake12,+ setServerHelloParameters12,+) where import Control.Concurrent.MVar+import Control.Exception (IOException, fromException, handle, throwIO)+import Control.Monad.State.Strict+import Data.ByteArray (convert)+import qualified Data.ByteString as B -import Network.TLS.Parameters+import Network.TLS.Cipher+import Network.TLS.Compression import Network.TLS.Context.Internal-import Network.TLS.Session-import Network.TLS.Struct-import Network.TLS.IO-import Network.TLS.State hiding (getNegotiatedProtocol)-import Network.TLS.Handshake.Process+import Network.TLS.Crypto+import Network.TLS.Extension+import Network.TLS.Handshake.Key+import Network.TLS.Handshake.Signature import Network.TLS.Handshake.State-import Network.TLS.Record.State+import Network.TLS.Handshake.State13+import Network.TLS.Handshake.TranscriptHash+import Network.TLS.IO+import Network.TLS.IO.Encode+import Network.TLS.Imports import Network.TLS.Measurement+import Network.TLS.Packet+import Network.TLS.Parameters+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Struct13 import Network.TLS.Types-import Network.TLS.Cipher import Network.TLS.Util-import Data.ByteString.Char8 ()--import Control.Monad.State-import Control.Exception (throwIO)+import Network.TLS.X509 handshakeFailed :: TLSError -> IO () handshakeFailed err = throwIO $ HandshakeFailed err -errorToAlert :: TLSError -> Packet-errorToAlert (Error_Protocol (_, _, ad)) = Alert [(AlertLevel_Fatal, ad)]-errorToAlert _ = Alert [(AlertLevel_Fatal, InternalError)]+handleException :: Context -> IO () -> IO ()+handleException ctx f = catchException f $ \exception -> do+ debugError (ctxDebug ctx) $ show exception+ -- 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+ Just e | Uncontextualized e' <- e -> e'+ _ -> Error_Misc (show exception)+ established <- ctxEstablished ctx+ setEstablished ctx NotEstablished+ handle ignoreIOErr $ do+ tls13 <- tls13orLater ctx+ if tls13+ then do+ when (established == EarlyDataSending) $ clearTxRecordState ctx+ when (tlserror /= Error_TCP_Terminate) $+ sendPacket13 ctx $+ Alert13 [errorToAlert tlserror]+ else sendPacket12 ctx $ Alert [errorToAlert tlserror]+ handshakeFailed tlserror+ where+ ignoreIOErr :: IOException -> IO ()+ ignoreIOErr _ = return () -unexpected :: String -> Maybe [Char] -> IO a-unexpected msg expected = throwCore $ Error_Packet_unexpected msg (maybe "" (" expected: " ++) expected)+errorToAlert :: TLSError -> (AlertLevel, AlertDescription)+errorToAlert (Error_Protocol _ ad) = (AlertLevel_Fatal, ad)+errorToAlert (Error_Protocol_Warning _ ad) = (AlertLevel_Warning, ad)+errorToAlert (Error_Packet_unexpected _ _) = (AlertLevel_Fatal, UnexpectedMessage)+errorToAlert (Error_Packet_Parsing msg)+ | "invalid version" `isInfixOf` msg = (AlertLevel_Fatal, ProtocolVersion)+ | "request_update" `isInfixOf` msg = (AlertLevel_Fatal, IllegalParameter)+ | otherwise = (AlertLevel_Fatal, DecodeError)+errorToAlert _ = (AlertLevel_Fatal, InternalError) -newSession :: Context -> IO Session-newSession ctx- | supportedSession $ ctxSupported ctx = getStateRNG ctx 32 >>= return . Session . Just- | otherwise = return $ Session Nothing+-- | Return the message that a TLS endpoint can add to its local log for the+-- specified library error.+errorToAlertMessage :: TLSError -> String+errorToAlertMessage (Error_Protocol msg _) = msg+errorToAlertMessage (Error_Protocol_Warning msg _) = msg+errorToAlertMessage (Error_Packet_unexpected msg _) = msg+errorToAlertMessage (Error_Packet_Parsing msg) = msg+errorToAlertMessage e = show e --- | when a new handshake is done, wrap up & clean up.-handshakeTerminate :: Context -> IO ()-handshakeTerminate ctx = do- session <- usingState_ ctx getSession- -- only callback the session established if we have a session- case session of- Session (Just sessionId) -> do- sessionData <- getSessionData ctx- liftIO $ sessionEstablish (sharedSessionManager $ ctxShared ctx) sessionId (fromJust "session-data" sessionData)- _ -> return ()- -- forget all handshake data now and reset bytes counters.- liftIO $ modifyMVar_ (ctxHandshake ctx) (return . const Nothing)- updateMeasure ctx resetBytesCounters- -- mark the secure connection up and running.- setEstablished ctx True- return ()+unexpected :: MonadIO m => String -> Maybe String -> m a+unexpected msg expected =+ throwCore $ Error_Packet_unexpected msg (maybe "" (" expected: " ++) expected) -sendChangeCipherAndFinish :: IO () -- ^ message possibly sent between ChangeCipherSpec and Finished.- -> Context- -> Role- -> IO ()-sendChangeCipherAndFinish betweenCall ctx role = do- sendPacket ctx ChangeCipherSpec- betweenCall- liftIO $ contextFlush ctx- cf <- usingState_ ctx getVersion >>= \ver -> usingHState ctx $ getHandshakeDigest ver role- sendPacket ctx (Handshake [Finished cf])- liftIO $ contextFlush ctx+newSession :: Context -> IO Session+newSession ctx+ | supportedSession $ ctxSupported ctx = Session . Just <$> getStateRNG ctx 32+ | otherwise = return $ Session Nothing -recvChangeCipherAndFinish :: Context -> IO ()-recvChangeCipherAndFinish ctx = runRecvState ctx (RecvStateNext expectChangeCipher)- where expectChangeCipher ChangeCipherSpec = return $ RecvStateHandshake expectFinish- expectChangeCipher p = unexpected (show p) (Just "change cipher")- expectFinish (Finished _) = return RecvStateDone- expectFinish p = unexpected (show p) (Just "Handshake Finished")+sendCCSandFinished+ :: Context+ -> Role+ -> IO ()+sendCCSandFinished ctx role = do+ sendPacket12 ctx ChangeCipherSpec+ contextFlush ctx+ enablePeerRecordLimit ctx+ ver <- usingState_ ctx getVersion+ verifyData <- VerifyData <$> generateFinished ctx ver role+ sendPacket12 ctx (Handshake [Finished verifyData] [])+ usingState_ ctx $ setVerifyDataForSend verifyData+ contextFlush ctx -data RecvState m =- RecvStateNext (Packet -> m (RecvState m))+data RecvState m+ = RecvStatePacket (Packet -> m (RecvState m)) -- CCS is not Handshake | RecvStateHandshake (Handshake -> m (RecvState m)) | RecvStateDone -recvPacketHandshake :: Context -> IO [Handshake]+recvPacketHandshake :: Context -> IO [HandshakeR] recvPacketHandshake ctx = do- pkts <- recvPacket ctx+ pkts <- recvPacket12 ctx case pkts of- Right (Handshake l) -> return l- Right x -> fail ("unexpected type received. expecting handshake and got: " ++ show x)- Left err -> throwCore err+ Right (Handshake hss bss) -> return $ zip hss bss+ Right x@(AppData _) -> do+ -- If a TLS13 server decides to reject RTT0 data, the server should+ -- skip records for RTT0 data up to the maximum limit.+ established <- ctxEstablished ctx+ case established of+ EarlyDataNotAllowed n+ | n > 0 -> do+ setEstablished ctx $ EarlyDataNotAllowed (n - 1)+ recvPacketHandshake ctx+ _ -> unexpected (show x) (Just "handshake")+ Right x -> unexpected (show x) (Just "handshake")+ Left err -> throwCore err -- | process a list of handshakes message in the recv state machine.-onRecvStateHandshake :: Context -> RecvState IO -> [Handshake] -> IO (RecvState IO)-onRecvStateHandshake _ recvState [] = return recvState-onRecvStateHandshake ctx (RecvStateHandshake f) (x:xs) = do- nstate <- f x- processHandshake ctx x- onRecvStateHandshake ctx nstate xs-onRecvStateHandshake _ _ _ = unexpected "spurious handshake" Nothing+onRecvStateHandshake+ :: Context -> RecvState IO -> [HandshakeR] -> IO (RecvState IO)+onRecvStateHandshake _ recvState [] = return recvState+onRecvStateHandshake _ (RecvStatePacket f) hbs = do+ let (hss, bss) = unzip hbs+ f (Handshake hss bss)+onRecvStateHandshake ctx (RecvStateHandshake f) (hb@(h, _) : hbs) = do+ let finished = isFinished h+ unless finished $ void $ updateTranscriptHash12 ctx hb+ nstate <- f h+ when finished $ void $ updateTranscriptHash12 ctx hb+ onRecvStateHandshake ctx nstate hbs+onRecvStateHandshake _ _ _ = unexpected "spurious handshake" Nothing +isFinished :: Handshake -> Bool+isFinished Finished{} = True+isFinished _ = False+ runRecvState :: Context -> RecvState IO -> IO ()-runRecvState _ (RecvStateDone) = return ()-runRecvState ctx (RecvStateNext f) = recvPacket ctx >>= either throwCore f >>= runRecvState ctx-runRecvState ctx iniState = recvPacketHandshake ctx >>= onRecvStateHandshake ctx iniState >>= runRecvState ctx+runRecvState _ RecvStateDone = return ()+runRecvState ctx (RecvStatePacket f) = recvPacket12 ctx >>= either throwCore f >>= runRecvState ctx+runRecvState ctx iniState =+ recvPacketHandshake ctx+ >>= onRecvStateHandshake ctx iniState+ >>= runRecvState ctx +runRecvStateHS+ :: Context -> RecvState IO -> [HandshakeR] -> IO ()+runRecvStateHS ctx iniState hbs = onRecvStateHandshake ctx iniState hbs >>= runRecvState ctx++ensureRecvComplete :: MonadIO m => Context -> m ()+ensureRecvComplete ctx = do+ complete <- liftIO $ isRecvComplete ctx+ unless complete $+ throwCore $+ Error_Protocol "received incomplete message at key change" UnexpectedMessage++processExtendedMainSecret+ :: MonadIO m => Context -> Version -> MessageType -> [ExtensionRaw] -> m Bool+processExtendedMainSecret ctx ver msgt exts+ | ver < TLS10 = return False+ | ver > TLS12 = error "EMS processing is not compatible with TLS 1.3"+ | ems == NoEMS = return False+ | otherwise =+ liftIO $+ lookupAndDecodeAndDo+ EID_ExtendedMainSecret+ msgt+ exts+ nonExistAction+ existAction+ where+ ems = supportedExtendedMainSecret $ ctxSupported ctx+ err = "peer does not support Extended Main Secret"+ nonExistAction =+ if ems == RequireEMS+ then throwCore $ Error_Protocol err HandshakeFailure+ else return False+ existAction ExtendedMainSecret = do+ usingHState ctx $ setExtendedMainSecret True+ return True+ getSessionData :: Context -> IO (Maybe SessionData) getSessionData ctx = do ver <- usingState_ ctx getVersion- mms <- usingHState ctx (gets hstMasterSecret)- tx <- liftIO $ readMVar (ctxTxState ctx)+ sni <- usingState_ ctx getClientSNI+ mms <- usingHState ctx $ gets hstMainSecret+ ems <- usingHState ctx getExtendedMainSecret+ cipher <- cipherID <$> usingHState ctx getPendingCipher+ alpn <- usingState_ ctx getNegotiatedProtocol+ let compression = 0+ flags = [SessionEMS | ems] case mms of Nothing -> return Nothing- Just ms -> return $ Just $ SessionData+ Just ms ->+ return $+ Just+ SessionData { sessionVersion = ver- , sessionCipher = cipherID $ fromJust "cipher" $ stCipher tx- , sessionSecret = ms+ , sessionCipher = cipher+ , sessionCompression = compression+ , sessionClientSNI = sni+ , sessionSecret = convert ms+ , sessionGroup = Nothing+ , sessionTicketInfo = Nothing+ , sessionALPN = alpn+ , sessionMaxEarlyDataSize = 0+ , sessionFlags = flags }++-- | Store the specified keypair. Whether the public key and private key+-- actually match is left for the peer to discover. We're not presently+-- burning CPU to detect that misconfiguration. We verify only that the+-- types of keys match and that it does not include an algorithm that would+-- not be safe.+storePrivInfo+ :: MonadIO m+ => Context+ -> CertificateChain+ -> PrivKey+ -> m PubKey+storePrivInfo ctx cc privkey = do+ let c = fromCC cc+ pubkey = certPubKey $ getCertificate c+ unless (isDigitalSignaturePair (pubkey, privkey)) $+ throwCore $+ Error_Protocol "mismatched or unsupported private key pair" InternalError+ usingHState ctx $ setPublicPrivateKeys (pubkey, privkey)+ return pubkey+ where+ fromCC (CertificateChain (c : _)) = c+ fromCC _ = error "fromCC"++-- verify that the group selected by the peer is supported in the local+-- configuration+checkSupportedGroup :: Context -> Group -> IO ()+checkSupportedGroup ctx grp =+ unless (isSupportedGroup ctx grp) $+ let msg = "unsupported (EC)DHE group: " ++ show grp+ in throwCore $ Error_Protocol msg IllegalParameter++isSupportedGroup :: Context -> Group -> Bool+isSupportedGroup ctx grp = grp `elem` supportedGroups (ctxSupported ctx)++ensureNullCompression :: MonadIO m => CompressionID -> m ()+ensureNullCompression compression =+ when (compression /= compressionID nullCompression) $+ throwCore $+ Error_Protocol "compression is not allowed in TLS 1.3" IllegalParameter++expectFinished :: Context -> Handshake -> IO (RecvState IO)+expectFinished ctx (Finished verifyData) = do+ processFinished ctx verifyData+ return RecvStateDone+expectFinished _ p = unexpected (show p) (Just "Handshake Finished")++processFinished :: Context -> VerifyData -> IO ()+processFinished ctx verifyData = do+ (cc, ver) <- usingState_ ctx $ (,) <$> getRole <*> getVersion+ expected <- VerifyData <$> generateFinished ctx ver (invertRole cc)+ when (expected /= verifyData) $ decryptError "finished verification failed"+ usingState_ ctx $ setVerifyDataForRecv verifyData++processCertificate :: Context -> Role -> CertificateChain -> IO ()+processCertificate _ ServerRole (CertificateChain []) = return ()+processCertificate _ ClientRole (CertificateChain []) =+ throwCore $ Error_Protocol "server certificate missing" HandshakeFailure+processCertificate ctx _ (CertificateChain (c : _)) =+ usingHState ctx $ setPublicKey pubkey+ where+ pubkey = certPubKey $ getCertificate c++-- TLS 1.2 distinguishes session ID and session ticket. session+-- ticket. Session ticket is prioritized over session ID.+ticketOrSessionID12+ :: Maybe Ticket -> Session -> Maybe SessionIDorTicket+ticketOrSessionID12 (Just ticket) _+ | ticket /= "" = Just $ B.copy ticket+ticketOrSessionID12 _ (Session (Just sessionId)) = Just $ B.copy sessionId+ticketOrSessionID12 _ _ = Nothing++setPeerRecordSizeLimit :: Context -> Bool -> RecordSizeLimit -> IO ()+setPeerRecordSizeLimit ctx tls13 (RecordSizeLimit n0) = do+ when (n0 < 64) $+ throwCore $+ Error_Protocol ("too small recode size limit: " ++ show n0) IllegalParameter++ -- RFC 8449 Section 4:+ -- Even if a larger record size limit is provided by a peer, an+ -- endpoint MUST NOT send records larger than the protocol-defined+ -- limit, unless explicitly allowed by a future TLS version or+ -- extension.+ let n1 = fromIntegral n0+ n2+ | n1 > protolim = protolim+ | otherwise = n1+ -- Even if peer's value is larger than the protocol-defined+ -- limitation, call "setPeerRecordLimit" to send+ -- "record_size_limit" as ACK. In this case, the protocol-defined+ -- limitation is used.+ let lim = if tls13 then n2 - 1 else n2+ setPeerRecordLimit ctx $ Just lim+ where+ protolim+ | tls13 = defaultRecordSizeLimit + 1+ | otherwise = defaultRecordSizeLimit++----------------------------------------------------------------++generateFinished :: Context -> Version -> Role -> IO ByteString+generateFinished ctx ver role = do+ thash <- transcriptHash ctx "generateFinished"+ (mainSecret, cipher) <- usingHState ctx $ gets $ \hst ->+ (fromJust $ hstMainSecret hst, fromJust $ hstPendingCipher hst)+ return $+ if role == ClientRole+ then+ generateClientFinished ver cipher mainSecret thash+ else+ generateServerFinished ver cipher mainSecret thash++generateFinished'+ :: PRF -> ByteString -> Secret -> TranscriptHash -> ByteString+generateFinished' prf label mainSecret (TranscriptHash thash) = convert $ prf mainSecret seed 12+ where+ seed = label <> thash++generateClientFinished+ :: Version+ -> Cipher+ -> Secret+ -> TranscriptHash+ -> ByteString+generateClientFinished ver ciph =+ generateFinished' (getPRF ver ciph) "client finished"++generateServerFinished+ :: Version+ -> Cipher+ -> Secret+ -> TranscriptHash+ -> ByteString+generateServerFinished ver ciph =+ generateFinished' (getPRF ver ciph) "server finished"++----------------------------------------------------------------++-- initialize a new Handshake context (initial handshake or renegotiations)+startHandshake :: Context -> Version -> ClientRandom -> IO ()+startHandshake ctx ver crand =+ void $ swapMVar (ctxHandshakeState ctx) $ Just hs+ where+ hs = newEmptyHandshake ver crand++setServerHelloParameters12+ :: Context+ -> Version+ -- ^ chosen version+ -> ServerRandom+ -> Cipher+ -> Compression+ -> IO ()+setServerHelloParameters12 ctx ver sran cipher compression = do+ usingHState ctx $+ modify' $ \hst ->+ hst+ { hstServerRandom = Just sran+ , hstPendingCipher = Just cipher+ , hstPendingCompression = compression+ }+ transitTranscriptHash ctx "transit" (getHash ver cipher) False++-- The TLS12 Hash is cipher specific, and some TLS12 algorithms use SHA384+-- instead of the default SHA256.+getHash :: Version -> Cipher -> Hash+getHash ver ciph+ | ver < TLS12 = SHA1_MD5+ | maybe True (< TLS12) (cipherMinVer ciph) = SHA256+ | otherwise = cipherHash ciph++-- | when a new handshake is done, wrap up & clean up.+finishHandshake12 :: Context -> IO ()+finishHandshake12 ctx = do+ -- forget most handshake data and reset bytes counters.+ modifyMVar_ (ctxHandshakeState ctx) $ \case+ Nothing -> return Nothing+ Just hshake ->+ return $+ Just+ (newEmptyHandshake (hstClientVersion hshake) (hstClientRandom hshake))+ { hstServerRandom = hstServerRandom hshake+ , hstMainSecret = hstMainSecret hshake+ , hstExtendedMainSecret = hstExtendedMainSecret hshake+ , hstSupportedGroup = hstSupportedGroup hshake+ }+ updateMeasure ctx resetBytesCounters+ -- mark the secure connection up and running.+ setEstablished ctx Established
+ Network/TLS/Handshake/Common13.hs view
@@ -0,0 +1,693 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Network.TLS.Handshake.Common13 (+ makeFinished,+ checkFinished,+ makeServerKeyShare,+ makeClientKeyShare,+ fromServerKeyShare,+ makeCertVerify,+ checkCertVerify,+ makePSKBinder,+ replacePSKBinder,+ sendChangeCipherSpec13,+ makeCertRequest,+ createTLS13TicketInfo,+ ageToObfuscatedAge,+ isAgeValid,+ getAge,+ checkFreshness,+ getCurrentTimeFromBase,+ getSessionData13,+ isHashSignatureValid13,+ safeNonNegative32,+ RecvHandshake13M,+ runRecvHandshake13,+ recvHandshake13,+ recvHandshake13hash,+ CipherChoice (..),+ makeCipherChoice,+ initEarlySecret,+ calculateEarlySecret,+ calculateHandshakeSecret,+ calculateApplicationSecret,+ calculateResumptionSecret,+ derivePSK,+ checkClientKeyShareKeyLength,+ checkServerKeyShareKeyLength,+ setRTT,+ computeConfirm,+ updateTranscriptHash13,+ setServerHelloParameters13,+ finishHandshake13,+) where++import Control.Concurrent.MVar+import Control.Monad.State.Strict+import Data.ByteArray (convert)+import qualified Data.ByteString as B+import Data.UnixTime+import Foreign.C.Types (CTime (..))+import Network.TLS.Cipher+import Network.TLS.Context.Internal+import Network.TLS.Crypto+import qualified Network.TLS.Crypto.IES as IES++import Network.TLS.Compression+import Network.TLS.Extension+import Network.TLS.Handshake.Certificate (extractCAname)+import Network.TLS.Handshake.Common (unexpected)+import Network.TLS.Handshake.Key+import Network.TLS.Handshake.Signature+import Network.TLS.Handshake.State+import Network.TLS.Handshake.TranscriptHash+import Network.TLS.IO+import Network.TLS.IO.Encode+import Network.TLS.Imports+import Network.TLS.KeySchedule+import Network.TLS.MAC+import Network.TLS.Packet13+import Network.TLS.Parameters+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Types+import Network.TLS.Wire++----------------------------------------------------------------++makeFinished :: MonadIO m => Context -> Hash -> Secret -> m Handshake13+makeFinished ctx usedHash baseKey = do+ verifyData <-+ VerifyData . makeVerifyData usedHash baseKey+ <$> transcriptHash ctx "makeFinished"+ liftIO $ usingState_ ctx $ setVerifyDataForSend verifyData+ pure $ Finished13 verifyData++checkFinished+ :: MonadIO m+ => Context -> Hash -> Secret -> TranscriptHash -> VerifyData -> m ()+checkFinished ctx usedHash baseKey (TranscriptHash hashValue) vd@(VerifyData verifyData) = do+ let verifyData' = makeVerifyData usedHash baseKey $ TranscriptHash hashValue+ when (B.length verifyData /= B.length verifyData') $+ throwCore $+ Error_Protocol "broken Finished" DecodeError+ unless (verifyData' == verifyData) $ decryptError "finished verification failed"+ liftIO $ usingState_ ctx $ setVerifyDataForRecv vd++makeVerifyData :: Hash -> Secret -> TranscriptHash -> ByteString+makeVerifyData usedHash baseKey (TranscriptHash th) =+ hmac usedHash finishedKey th+ where+ hashSize = hashDigestSize usedHash+ finishedKey = hkdfExpandLabel usedHash baseKey "finished" "" hashSize++----------------------------------------------------------------++makeClientKeyShare+ :: Context -> Group -> IO ((Group, IES.GroupPrivate), KeyShareEntry)+makeClientKeyShare ctx grp = do+ (cpri, cpub) <- generateGroup ctx grp+ let wcpub = IES.groupEncodePublicA cpub+ clientKeyShare = KeyShareEntry grp wcpub+ return ((grp, cpri), clientKeyShare)++makeServerKeyShare :: Context -> KeyShareEntry -> IO (Secret, KeyShareEntry)+makeServerKeyShare ctx (KeyShareEntry grp wcpub) = case ecpub of+ Left e -> throwCore $ Error_Protocol (show e) IllegalParameter+ Right cpub -> do+ ecdhePair <- encapsulateGroup ctx cpub+ case ecdhePair of+ Nothing -> throwCore $ Error_Protocol msgInvalidPublic IllegalParameter+ Just (spub, share) ->+ let wspub = IES.groupEncodePublicB spub+ serverKeyShare = KeyShareEntry grp wspub+ in return (share, serverKeyShare)+ where+ ecpub = IES.groupDecodePublicA grp wcpub+ msgInvalidPublic = "invalid client " ++ show grp ++ " public key"++fromServerKeyShare+ :: KeyShareEntry -> [(Group, IES.GroupPrivate)] -> IO Secret+fromServerKeyShare (KeyShareEntry grp wspub) grpCpris = case espub of+ Left e -> throwCore $ Error_Protocol (show e) IllegalParameter+ Right spub -> case lookup grp grpCpris of+ Nothing -> throwCore err+ Just cpri -> case IES.groupDecapsulate spub cpri of+ Just shared -> return shared+ Nothing -> throwCore err+ where+ err = Error_Protocol "cannot generate a shared secret on (EC)DH" IllegalParameter+ espub = IES.groupDecodePublicB grp wspub++----------------------------------------------------------------++serverContextString :: ByteString+serverContextString = "TLS 1.3, server CertificateVerify"++clientContextString :: ByteString+clientContextString = "TLS 1.3, client CertificateVerify"++makeCertVerify+ :: MonadIO m+ => Context+ -> PubKey+ -> HashAndSignatureAlgorithm+ -> TranscriptHash+ -> m Handshake13+makeCertVerify ctx pub hs (TranscriptHash hashValue) = do+ role <- liftIO $ usingState_ ctx getRole+ let ctxStr+ | role == ClientRole = clientContextString+ | otherwise = serverContextString+ target = makeTarget ctxStr hashValue+ CertVerify13 . DigitallySigned hs <$> sign ctx pub hs target++checkCertVerify+ :: MonadIO m+ => Context+ -> PubKey+ -> HashAndSignatureAlgorithm+ -> 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++makeTarget :: ByteString -> ByteString -> ByteString+makeTarget contextString hashValue = runPut $ do+ putBytes $ B.replicate 64 32+ putBytes contextString+ putWord8 0+ putBytes hashValue++sign+ :: MonadIO m+ => Context+ -> PubKey+ -> HashAndSignatureAlgorithm+ -> ByteString+ -> m Signature+sign ctx pub hs target = liftIO $ do+ role <- usingState_ ctx getRole+ let sigParams = signatureParams pub hs+ signPrivate ctx role sigParams target++----------------------------------------------------------------++makePSKBinder+ :: BaseSecret EarlySecret+ -> Hash+ -> Int+ -> ByteString+ -- ^ Encoded client hello+ -> ByteString+makePSKBinder (BaseSecret sec) usedHash truncLen ech =+ makeVerifyData usedHash binderKey hChTruncated+ where+ hChTruncated = TranscriptHash $ hash usedHash $ trunc ech+ th = TranscriptHash $ hash usedHash ""+ binderKey = deriveSecret usedHash sec "res binder" th+ trunc x = B.take takeLen x+ where+ totalLen = B.length x+ takeLen = totalLen - truncLen++replacePSKBinder :: ByteString -> [ByteString] -> ByteString+replacePSKBinder pskz bds = tLidentities <> binders+ where+ tLidentities = B.take (B.length pskz - B.length binders) pskz+ -- See instance Extension PreSharedKey+ binders = runPut $ putOpaque16 $ runPut (mapM_ putBinder bds)+ putBinder = putOpaque8++----------------------------------------------------------------++sendChangeCipherSpec13 :: Monoid b => Context -> PacketFlightM b ()+sendChangeCipherSpec13 ctx = do+ sent <- usingHState ctx $ do+ b <- getCCS13Sent+ unless b $ setCCS13Sent True+ return b+ unless sent $ loadPacket13 ctx ChangeCipherSpec13++----------------------------------------------------------------++makeCertRequest+ :: ServerParams -> Context -> CertReqContext -> Bool -> Handshake13+makeCertRequest sparams ctx certReqCtx zlib =+ let sigAlgs = SignatureAlgorithms $ supportedHashSignatures $ ctxSupported ctx+ signatureAlgExt = Just $ toExtensionRaw sigAlgs++ compCertExt+ | zlib = Just $ toExtensionRaw $ CompressCertificate [CCA_Zlib]+ | otherwise = Nothing++ caDns = map extractCAname $ serverCACertificates sparams+ caExt+ | null caDns = Nothing+ | otherwise = Just $ toExtensionRaw $ CertificateAuthorities caDns++ crexts =+ catMaybes+ [ {- 0x0d -} signatureAlgExt+ , {- 0x1b -} compCertExt+ , {- 0x2f -} caExt+ ]+ in CertRequest13 certReqCtx crexts++----------------------------------------------------------------++createTLS13TicketInfo+ :: Second -> Either Context Second -> Maybe Millisecond -> IO TLS13TicketInfo+createTLS13TicketInfo life ecw mrtt = do+ -- Left: serverSendTime+ -- Right: clientReceiveTime+ bTime <- getCurrentTimeFromBase+ add <- case ecw of+ Left ctx -> B.foldl' (*+) 0 <$> getStateRNG ctx 4+ Right ad -> return ad+ return $+ TLS13TicketInfo+ { lifetime = life+ , ageAdd = add+ , txrxTime = bTime+ , estimatedRTT = mrtt+ }+ where+ x *+ y = x * 256 + fromIntegral y++ageToObfuscatedAge :: Second -> TLS13TicketInfo -> Second+ageToObfuscatedAge age TLS13TicketInfo{..} = obfage+ where+ obfage = age + ageAdd++obfuscatedAgeToAge :: Second -> TLS13TicketInfo -> Second+obfuscatedAgeToAge obfage TLS13TicketInfo{..} = age+ where+ age = obfage - ageAdd++isAgeValid :: Second -> TLS13TicketInfo -> Bool+isAgeValid age TLS13TicketInfo{..} = age <= lifetime * 1000++getAge :: TLS13TicketInfo -> IO Second+getAge TLS13TicketInfo{..} = do+ let clientReceiveTime = txrxTime+ clientSendTime <- getCurrentTimeFromBase+ return $ fromIntegral (clientSendTime - clientReceiveTime) -- milliseconds++checkFreshness :: TLS13TicketInfo -> Second -> IO Bool+checkFreshness tinfo@TLS13TicketInfo{..} obfAge = do+ serverReceiveTime <- getCurrentTimeFromBase+ let freshness =+ if expectedArrivalTime > serverReceiveTime+ then expectedArrivalTime - serverReceiveTime+ else serverReceiveTime - expectedArrivalTime+ -- Some implementations round age up to second.+ -- We take max of 2000 and rtt in the case where rtt is too small.+ let tolerance = max 2000 rtt+ isFresh = freshness < tolerance+ return $ isAlive && isFresh+ where+ serverSendTime = txrxTime+ rtt = fromJust estimatedRTT+ age = obfuscatedAgeToAge obfAge tinfo+ expectedArrivalTime = serverSendTime + rtt + fromIntegral age+ isAlive = isAgeValid age tinfo++getCurrentTimeFromBase :: IO Millisecond+getCurrentTimeFromBase = millisecondsFromBase <$> getUnixTime++millisecondsFromBase :: UnixTime -> Millisecond+millisecondsFromBase (UnixTime (CTime s) us) =+ fromIntegral ((s - base) * 1000) + fromIntegral (us `div` 1000)+ where+ base = 1483228800++-- UnixTime (CTime base) _= parseUnixTimeGMT webDateFormat "Sun, 01 Jan 2017 00:00:00 GMT"++----------------------------------------------------------------++getSessionData13+ :: Context -> Cipher -> TLS13TicketInfo -> Int -> ByteString -> IO SessionData+getSessionData13 ctx usedCipher tinfo maxSize psk = do+ ver <- usingState_ ctx getVersion+ malpn <- usingState_ ctx getNegotiatedProtocol+ sni <- usingState_ ctx getClientSNI+ mgrp <- usingHState ctx getSupportedGroup+ return+ SessionData+ { sessionVersion = ver+ , sessionCipher = cipherID usedCipher+ , sessionCompression = 0+ , sessionClientSNI = sni+ , sessionSecret = psk+ , sessionGroup = mgrp+ , sessionTicketInfo = Just tinfo+ , sessionALPN = malpn+ , sessionMaxEarlyDataSize = maxSize+ , sessionFlags = []+ }++----------------------------------------------------------------++-- Word32 is used in TLS 1.3 protocol.+-- Int is used for API for Haskell TLS because it is natural.+-- If Int is 64 bits, users can specify bigger number than Word32.+-- If Int is 32 bits, 2^31 or larger may be converted into minus numbers.+safeNonNegative32 :: (Num a, Ord a, FiniteBits a) => a -> a+safeNonNegative32 x+ | x <= 0 = 0+ | finiteBitSize x <= 32 = x+ | otherwise = x `min` fromIntegral (maxBound :: Word32)++----------------------------------------------------------------++newtype RecvHandshake13M m a+ = RecvHandshake13M (StateT [Handshake13R] m a)+ deriving (Functor, Applicative, Monad, MonadIO)++recvHandshake13+ :: MonadIO m+ => Context+ -> (Handshake13 -> RecvHandshake13M m a)+ -> RecvHandshake13M m a+recvHandshake13 ctx f = getHandshake13 ctx >>= \(h, _b) -> f h++recvHandshake13hash+ :: MonadIO m+ => Context+ -> String+ -> (TranscriptHash -> Handshake13 -> RecvHandshake13M m a)+ -> RecvHandshake13M m a+recvHandshake13hash ctx label f = do+ d <- transcriptHash ctx label+ getHandshake13 ctx >>= \(h, _b) -> f d h++getHandshake13+ :: MonadIO m => Context -> RecvHandshake13M m Handshake13R+getHandshake13 ctx = RecvHandshake13M $ do+ currentState <- get+ case currentState of+ hb : hbs -> found hb hbs+ _ -> recvLoop+ where+ found hb hbs = liftIO (updateTranscriptHash13 ctx hb) >> put hbs >> return hb+ recvLoop = do+ epkt <- liftIO (recvPacket13 ctx)+ case epkt of+ Right (Handshake13 [] _) -> error "invalid recvPacket13 result"+ Right (Handshake13 (h : hs) (b : bs)) -> found (h, b) $ zip hs bs+ Right ChangeCipherSpec13 -> do+ alreadyReceived <- liftIO $ usingHState ctx getCCS13Recv+ if alreadyReceived+ then+ liftIO $ throwCore $ Error_Protocol "multiple CSS in TLS 1.3" UnexpectedMessage+ else do+ liftIO $ usingHState ctx $ setCCS13Recv True+ recvLoop+ Right (Alert13 _) -> throwCore Error_TCP_Terminate+ Right x -> unexpected (show x) (Just "handshake 13")+ Left err -> throwCore err++runRecvHandshake13 :: MonadIO m => RecvHandshake13M m a -> m a+runRecvHandshake13 (RecvHandshake13M f) = do+ (result, new) <- runStateT f []+ unless (null new) $ unexpected "spurious handshake 13" Nothing+ return result++----------------------------------------------------------------++-- some hash/signature combinations have been deprecated in TLS13 and should+-- not be used+checkHashSignatureValid13 :: HashAndSignatureAlgorithm -> IO ()+checkHashSignatureValid13 hs =+ unless (isHashSignatureValid13 hs) $+ let msg = "invalid TLS13 hash and signature algorithm: " ++ show hs+ in throwCore $ Error_Protocol msg IllegalParameter++isHashSignatureValid13 :: HashAndSignatureAlgorithm -> Bool+isHashSignatureValid13 hs = hs `elem` signatureSchemesForTLS13++{-+isHashSignatureValid13 (HashIntrinsic, s) =+ s+ `elem` [ SignatureRSApssRSAeSHA256+ , SignatureRSApssRSAeSHA384+ , SignatureRSApssRSAeSHA512+ , SignatureEd25519+ , SignatureEd448+ , SignatureRSApsspssSHA256+ , SignatureRSApsspssSHA384+ , SignatureRSApsspssSHA512+ ]+isHashSignatureValid13 (h, SignatureECDSA) =+ h `elem` [HashSHA256, HashSHA384, HashSHA512]+isHashSignatureValid13 _ = False+-}++----------------------------------------------------------------++calculateEarlySecret+ :: Context+ -> CipherChoice+ -> Either ByteString (BaseSecret EarlySecret)+ -> IO (SecretPair EarlySecret)+calculateEarlySecret ctx choice maux = do+ (_ch, b) <- fromJust <$> usingHState ctx getClientHello+ let hCh = TranscriptHash $ hashChunks usedHash b+ let earlySecret = case maux of+ Right (BaseSecret sec) -> sec+ Left psk -> hkdfExtract usedHash zero (convert psk)+ clientEarlySecret = deriveSecret usedHash earlySecret "c e traffic" hCh+ cets = ClientTrafficSecret clientEarlySecret :: ClientTrafficSecret EarlySecret+ logKey ctx cets+ return $ SecretPair (BaseSecret earlySecret) cets+ where+ usedHash = cHash choice+ zero = cZero choice++initEarlySecret :: CipherChoice -> Maybe ByteString -> BaseSecret EarlySecret+initEarlySecret choice mpsk = BaseSecret sec+ where+ sec = hkdfExtract usedHash zero zeroOrPSK+ usedHash = cHash choice+ zero = cZero choice+ zeroOrPSK = fromMaybe zero (convert <$> mpsk)++calculateHandshakeSecret+ :: Context+ -> CipherChoice+ -> BaseSecret EarlySecret+ -> Secret+ -> IO (SecretTriple HandshakeSecret)+calculateHandshakeSecret ctx choice (BaseSecret sec) ecdhe = do+ hChSh <- transcriptHash ctx "CH..SH"+ let th = TranscriptHash $ hash usedHash ""+ handshakeSecret =+ hkdfExtract+ usedHash+ (deriveSecret usedHash sec "derived" th)+ ecdhe+ let clientHandshakeSecret = deriveSecret usedHash handshakeSecret "c hs traffic" hChSh+ serverHandshakeSecret = deriveSecret usedHash handshakeSecret "s hs traffic" hChSh+ let shts =+ ServerTrafficSecret serverHandshakeSecret :: ServerTrafficSecret HandshakeSecret+ chts =+ ClientTrafficSecret clientHandshakeSecret :: ClientTrafficSecret HandshakeSecret+ logKey ctx shts+ logKey ctx chts+ return $ SecretTriple (BaseSecret handshakeSecret) chts shts+ where+ usedHash = cHash choice++calculateApplicationSecret+ :: Context+ -> CipherChoice+ -> BaseSecret HandshakeSecret+ -> TranscriptHash+ -> IO (SecretTriple ApplicationSecret)+calculateApplicationSecret ctx choice (BaseSecret sec) hChSf = do+ let th = TranscriptHash $ hash usedHash ""+ applicationSecret =+ hkdfExtract+ usedHash+ (deriveSecret usedHash sec "derived" th)+ zero+ let clientApplicationSecret0 = deriveSecret usedHash applicationSecret "c ap traffic" hChSf+ serverApplicationSecret0 = deriveSecret usedHash applicationSecret "s ap traffic" hChSf+ exporterSecret = deriveSecret usedHash applicationSecret "exp master" hChSf+ usingState_ ctx $ setTLS13ExporterSecret exporterSecret+ let sts0 =+ ServerTrafficSecret serverApplicationSecret0+ :: ServerTrafficSecret ApplicationSecret+ let cts0 =+ ClientTrafficSecret clientApplicationSecret0+ :: ClientTrafficSecret ApplicationSecret+ logKey ctx sts0+ logKey ctx cts0+ return $ SecretTriple (BaseSecret applicationSecret) cts0 sts0+ where+ usedHash = cHash choice+ zero = cZero choice++calculateResumptionSecret+ :: Context+ -> CipherChoice+ -> BaseSecret ApplicationSecret+ -> IO (BaseSecret ResumptionSecret)+calculateResumptionSecret ctx choice (BaseSecret sec) = do+ hChCf <- transcriptHash ctx "CH..CF"+ let resumptionSecret = deriveSecret usedHash sec "res master" hChCf+ return $ BaseSecret resumptionSecret+ where+ usedHash = cHash choice++derivePSK+ :: CipherChoice -> BaseSecret ResumptionSecret -> TicketNonce -> ByteString+derivePSK choice (BaseSecret sec) (TicketNonce nonce) =+ hkdfExpandLabel usedHash sec "resumption" nonce hashSize+ where+ usedHash = cHash choice+ hashSize = hashDigestSize usedHash++----------------------------------------------------------------++checkClientKeyShareKeyLength :: KeyShareEntry -> Bool+checkClientKeyShareKeyLength ks = clientKeyShareKeyLength grp == B.length key+ where+ grp = keyShareEntryGroup ks+ key = keyShareEntryKeyExchange ks++{- FOURMOLU_DISABLE -}+clientKeyShareKeyLength :: Group -> Int+clientKeyShareKeyLength P256 = 65 -- 32 * 2 + 1+clientKeyShareKeyLength P384 = 97 -- 48 * 2 + 1+clientKeyShareKeyLength P521 = 133 -- 66 * 2 + 1+clientKeyShareKeyLength X25519 = 32+clientKeyShareKeyLength X448 = 56+clientKeyShareKeyLength FFDHE2048 = 256+clientKeyShareKeyLength FFDHE3072 = 384+clientKeyShareKeyLength FFDHE4096 = 512+clientKeyShareKeyLength FFDHE6144 = 768+clientKeyShareKeyLength FFDHE8192 = 1024+clientKeyShareKeyLength MLKEM512 = 800+clientKeyShareKeyLength MLKEM768 = 1184+clientKeyShareKeyLength MLKEM1024 = 1568+clientKeyShareKeyLength X25519MLKEM768 = 1216+clientKeyShareKeyLength P256MLKEM768 = 1249+clientKeyShareKeyLength P384MLKEM1024 = 1665+clientKeyShareKeyLength _ = error "clientKeyShareKeyLength"+{- FOURMOLU_ENABLE -}++checkServerKeyShareKeyLength :: KeyShareEntry -> Bool+checkServerKeyShareKeyLength ks = serverKeyShareKeyLength grp == B.length key+ where+ grp = keyShareEntryGroup ks+ key = keyShareEntryKeyExchange ks++{- FOURMOLU_DISABLE -}+serverKeyShareKeyLength :: Group -> Int+serverKeyShareKeyLength P256 = 65 -- 32 * 2 + 1+serverKeyShareKeyLength P384 = 97 -- 48 * 2 + 1+serverKeyShareKeyLength P521 = 133 -- 66 * 2 + 1+serverKeyShareKeyLength X25519 = 32+serverKeyShareKeyLength X448 = 56+serverKeyShareKeyLength FFDHE2048 = 256+serverKeyShareKeyLength FFDHE3072 = 384+serverKeyShareKeyLength FFDHE4096 = 512+serverKeyShareKeyLength FFDHE6144 = 768+serverKeyShareKeyLength FFDHE8192 = 1024+serverKeyShareKeyLength MLKEM512 = 768+serverKeyShareKeyLength MLKEM768 = 1088+serverKeyShareKeyLength MLKEM1024 = 1568+serverKeyShareKeyLength X25519MLKEM768 = 1120+serverKeyShareKeyLength P256MLKEM768 = 1153+serverKeyShareKeyLength P384MLKEM1024 = 1665+serverKeyShareKeyLength _ = error "clientKeyShareKeyLength"+{- FOURMOLU_ENABLE -}++setRTT :: Context -> Millisecond -> IO ()+setRTT ctx chSentTime = do+ shRecvTime <- getCurrentTimeFromBase+ let rtt' = shRecvTime - chSentTime+ rtt = if rtt' == 0 then 10 else rtt'+ modifyTLS13State ctx $ \st -> st{tls13stRTT = rtt}++computeConfirm+ :: (MonadFail m, MonadIO m)+ => Context -> Hash -> ServerHello -> ByteString -> m ByteString+computeConfirm ctx usedHash sh label = do+ (CH{..}, _b) <- fromJust <$> liftIO (usingHState ctx getClientHello)+ TranscriptHash echConf <-+ transcriptHashWith ctx "ECH acceptance" $ encodeHandshake13 $ ServerHello13 sh+ let prk = hkdfExtract usedHash "" $ unClientRandom chRandom+ return $ hkdfExpandLabel usedHash (convert prk) label echConf 8++----------------------------------------------------------------++setServerHelloParameters13+ :: Context -> Cipher -> Bool -> IO (Either TLSError ())+setServerHelloParameters13 ctx cipher isHRR = do+ transitTranscriptHash ctx "transit" (cipherHash cipher) isHRR+ usingHState ctx $ do+ hst <- get+ case hstPendingCipher hst of+ Nothing -> do+ put+ hst+ { hstPendingCipher = Just cipher+ , hstPendingCompression = nullCompression+ }+ return $ Right ()+ Just oldcipher+ | cipher == oldcipher -> return $ Right ()+ | otherwise ->+ return $+ Left $+ Error_Protocol "TLS 1.3 cipher changed after hello retry" IllegalParameter++-- | TLS13 handshake wrap up & clean up. Contrary to+-- @finishHandshake12@, this does not handle session, which is managed+-- separately for TLS 1.3. This does not reset byte counters because+-- renegotiation is not allowed. And a few more state attributes are+-- preserved, necessary for TLS13 handshake modes, session tickets and+-- post-handshake authentication.+finishHandshake13 :: Context -> IO ()+finishHandshake13 ctx = do+ -- forget most handshake data+ modifyMVar_ (ctxHandshakeState ctx) $ \case+ Nothing -> return Nothing+ Just hshake ->+ return $+ Just+ (newEmptyHandshake (hstClientVersion hshake) (hstClientRandom hshake))+ { hstServerRandom = hstServerRandom hshake+ , hstMainSecret = hstMainSecret hshake+ , hstSupportedGroup = hstSupportedGroup hshake+ , hstTransHashState = hstTransHashState hshake+ , hstTLS13HandshakeMode = hstTLS13HandshakeMode hshake+ , hstTLS13RTT0Status = hstTLS13RTT0Status hshake+ , hstTLS13ResumptionSecret = hstTLS13ResumptionSecret hshake+ , hstTLS13ECHAccepted = hstTLS13ECHAccepted hshake+ }+ -- forget handshake data stored in TLS state+ usingState_ ctx $ do+ setTLS13KeyShare Nothing+ setTLS13PreSharedKey Nothing+ -- mark the secure connection up and running.+ setEstablished ctx Established
+ Network/TLS/Handshake/Control.hs view
@@ -0,0 +1,43 @@+module Network.TLS.Handshake.Control (+ ClientState (..),+ ServerState (..),+ EarlySecretInfo (..),+ HandshakeSecretInfo (..),+ ApplicationSecretInfo (..),+ NegotiatedProtocol,+) where++import Network.TLS.Cipher+import Network.TLS.Imports+import Network.TLS.Struct+import Network.TLS.Types++----------------------------------------------------------------++-- | ID of the application-level protocol negotiated between client and server.+-- See values listed in the <https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids IANA registry>.+type NegotiatedProtocol = ByteString++-- | Handshake information generated for traffic at 0-RTT level.+data EarlySecretInfo = EarlySecretInfo Cipher (ClientTrafficSecret EarlySecret)+ deriving (Show)++-- | Handshake information generated for traffic at handshake level.+data HandshakeSecretInfo+ = HandshakeSecretInfo Cipher (TrafficSecrets HandshakeSecret)+ deriving (Show)++-- | Handshake information generated for traffic at application level.+newtype ApplicationSecretInfo = ApplicationSecretInfo (TrafficSecrets ApplicationSecret)+ deriving (Show)++----------------------------------------------------------------++data ClientState+ = SendClientHello (Maybe EarlySecretInfo)+ | RecvServerHello HandshakeSecretInfo+ | SendClientFinished [ExtensionRaw] ApplicationSecretInfo++data ServerState+ = SendServerHello [ExtensionRaw] (Maybe EarlySecretInfo) HandshakeSecretInfo+ | SendServerFinished ApplicationSecretInfo
Network/TLS/Handshake/Key.hs view
@@ -1,62 +1,182 @@--- |--- Module : Network.TLS.Handshake.Key--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ functions for RSA operations----module Network.TLS.Handshake.Key- ( encryptRSA- , signRSA- , decryptRSA- , verifyRSA- , generateDHE- ) where+{-# LANGUAGE FlexibleInstances #-} -import Data.ByteString (ByteString)+-- | Functions for RSA operations+module Network.TLS.Handshake.Key (+ encryptRSA,+ signPrivate,+ decryptRSA,+ verifyPublic,+ generateDHE,+ generateGroup,+ encapsulateGroup,+ generateFFDHE,+ generateFFDHEShared,+ versionCompatible,+ isDigitalSignaturePair,+ checkDigitalSignatureKey,+ getLocalPublicKey,+ satisfiesEcPredicate,+ logKey,+) where++import Control.Monad.State.Strict+import Data.ByteArray (convert) import qualified Data.ByteString as B -import Network.TLS.Handshake.State-import Network.TLS.State (withRNG, getVersion)+import Network.TLS.Context.Internal import Network.TLS.Crypto+import Network.TLS.Handshake.State+import Network.TLS.Imports+import Network.TLS.Parameters+import Network.TLS.State (withRNG)+import Network.TLS.Struct import Network.TLS.Types-import Network.TLS.Context.Internal+import Network.TLS.X509 {- if the RSA encryption fails we just return an empty bytestring, and let the protocol - fail by itself; however it would be probably better to just report it since it's an internal problem. -}-encryptRSA :: Context -> ByteString -> IO ByteString+encryptRSA :: Context -> Secret -> IO ByteString encryptRSA ctx content = do publicKey <- usingHState ctx getRemotePublicKey usingState_ ctx $ do- v <- withRNG (\g -> kxEncrypt g publicKey content)+ v <- withRNG $ kxEncrypt publicKey content case v of- Left err -> fail ("rsa encrypt failed: " ++ show err)+ Left err -> error ("rsa encrypt failed: " ++ show err) Right econtent -> return econtent -signRSA :: Context -> Role -> HashDescr -> ByteString -> IO ByteString-signRSA ctx _ hsh content = do- privateKey <- usingHState ctx getLocalPrivateKey+signPrivate :: Context -> Role -> SignatureParams -> ByteString -> IO ByteString+signPrivate ctx _ params content = do+ (publicKey, privateKey) <- usingHState ctx getLocalPublicPrivateKeys usingState_ ctx $ do- r <- withRNG (\g -> kxSign g privateKey hsh content)+ r <- withRNG $ kxSign privateKey publicKey params content case r of- Left err -> fail ("rsa sign failed: " ++ show err)+ Left err -> error ("sign failed: " ++ show err) Right econtent -> return econtent -decryptRSA :: Context -> ByteString -> IO (Either KxError ByteString)+decryptRSA :: Context -> ByteString -> IO (Either KxError Secret) decryptRSA ctx econtent = do- privateKey <- usingHState ctx getLocalPrivateKey+ (_, privateKey) <- usingHState ctx getLocalPublicPrivateKeys usingState_ ctx $ do- ver <- getVersion- let cipher = if ver < TLS10 then econtent else B.drop 2 econtent- withRNG (\g -> kxDecrypt g privateKey cipher)+ let cipher = B.drop 2 econtent+ withRNG $ kxDecrypt privateKey cipher -verifyRSA :: Context -> Role -> HashDescr -> ByteString -> ByteString -> IO Bool-verifyRSA ctx _ hsh econtent sign = do+verifyPublic+ :: Context -> SignatureParams -> ByteString -> ByteString -> IO Bool+verifyPublic ctx params econtent sign = do publicKey <- usingHState ctx getRemotePublicKey- return $ kxVerify publicKey hsh econtent sign+ return $ kxVerify publicKey params econtent sign generateDHE :: Context -> DHParams -> IO (DHPrivate, DHPublic)-generateDHE ctx dhp = usingState_ ctx $ withRNG $ \rng -> dhGenerateKeyPair rng dhp+generateDHE ctx dhp = usingState_ ctx $ withRNG $ dhGenerateKeyPair dhp++generateGroup :: Context -> Group -> IO (GroupPrivate, GroupPublicA)+generateGroup ctx grp = usingState_ ctx $ withRNG $ groupGenerateKeyPair grp++encapsulateGroup+ :: Context -> GroupPublicA -> IO (Maybe (GroupPublicB, GroupKey))+encapsulateGroup ctx pub = usingState_ ctx $ withRNG $ groupEncapsulate pub++generateFFDHE :: Context -> Group -> IO (DHParams, DHPrivate, DHPublic)+generateFFDHE ctx grp = usingState_ ctx $ withRNG $ dhGroupGenerateKeyPair grp++generateFFDHEShared+ :: Context -> Group -> DHPublic -> IO (Maybe (DHPublic, DHKey))+generateFFDHEShared ctx grp pub = usingState_ ctx $ withRNG $ dhGroupGetPubShared grp pub++{- FOURMOLU_DISABLE -}+isDigitalSignatureKey :: PubKey -> Bool+isDigitalSignatureKey (PubKeyRSA _) = True+isDigitalSignatureKey (PubKeyDSA _) = True+isDigitalSignatureKey (PubKeyEC _) = True+isDigitalSignatureKey (PubKeyEd25519 _) = True+isDigitalSignatureKey (PubKeyEd448 _) = True+isDigitalSignatureKey _ = False++versionCompatible :: PubKey -> Version -> Bool+versionCompatible (PubKeyRSA _) _ = True+versionCompatible (PubKeyDSA _) v = v <= TLS12+versionCompatible (PubKeyEC _) v = v >= TLS10+versionCompatible (PubKeyEd25519 _) v = v >= TLS12+versionCompatible (PubKeyEd448 _) v = v >= TLS12+versionCompatible _ _ = False+{- FOURMOLU_ENABLE -}++-- | Test whether the argument is a public key supported for signature at the+-- specified TLS version. This also accepts a key for RSA encryption. This+-- test is performed by clients or servers before verifying a remote+-- Certificate Verify.+checkDigitalSignatureKey :: MonadIO m => Version -> PubKey -> m ()+checkDigitalSignatureKey usedVersion key = do+ unless (isDigitalSignatureKey key) $+ throwCore $+ Error_Protocol "unsupported remote public key type" HandshakeFailure+ unless (key `versionCompatible` usedVersion) $+ throwCore $+ Error_Protocol+ (show usedVersion ++ " has no support for " ++ pubkeyType key)+ IllegalParameter++-- | Test whether the argument is matching key pair supported for signature.+-- This also accepts material for RSA encryption. This test is performed by+-- servers or clients before using a credential from the local configuration.+isDigitalSignaturePair :: (PubKey, PrivKey) -> Bool+isDigitalSignaturePair keyPair =+ case keyPair of+ (PubKeyRSA _, PrivKeyRSA _) -> True+ (PubKeyDSA _, PrivKeyDSA _) -> True+ (PubKeyEC _, PrivKeyEC k) -> kxSupportedPrivKeyEC k+ (PubKeyEd25519 _, PrivKeyEd25519 _) -> True+ (PubKeyEd448 _, PrivKeyEd448 _) -> True+ _ -> False++getLocalPublicKey :: MonadIO m => Context -> m PubKey+getLocalPublicKey ctx =+ usingHState ctx (fst <$> getLocalPublicPrivateKeys)++-- | Test whether the public key satisfies a predicate about the elliptic curve.+-- When the public key is not suitable for ECDSA, like RSA for instance, the+-- predicate is not used and the result is 'True'.+satisfiesEcPredicate :: (Group -> Bool) -> PubKey -> Bool+satisfiesEcPredicate p (PubKeyEC ecPub) =+ maybe False p $ findEllipticCurveGroup ecPub+satisfiesEcPredicate _ _ = True++----------------------------------------------------------------++class LogLabel a where+ labelAndKey :: a -> (String, Secret)++instance LogLabel MainSecret where+ labelAndKey (MainSecret key) = ("CLIENT_RANDOM", key)++instance LogLabel (ClientTrafficSecret EarlySecret) where+ labelAndKey (ClientTrafficSecret key) = ("CLIENT_EARLY_TRAFFIC_SECRET", key)++instance LogLabel (ServerTrafficSecret HandshakeSecret) where+ labelAndKey (ServerTrafficSecret key) = ("SERVER_HANDSHAKE_TRAFFIC_SECRET", key)++instance LogLabel (ClientTrafficSecret HandshakeSecret) where+ labelAndKey (ClientTrafficSecret key) = ("CLIENT_HANDSHAKE_TRAFFIC_SECRET", key)++instance LogLabel (ServerTrafficSecret ApplicationSecret) where+ labelAndKey (ServerTrafficSecret key) = ("SERVER_TRAFFIC_SECRET_0", key)++instance LogLabel (ClientTrafficSecret ApplicationSecret) where+ labelAndKey (ClientTrafficSecret key) = ("CLIENT_TRAFFIC_SECRET_0", key)++-- NSS Key Log Format+-- See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format+logKey :: LogLabel a => Context -> a -> IO ()+logKey ctx logkey = do+ mhst <- getHState ctx+ case mhst of+ Nothing -> return ()+ Just hst -> do+ let crm = fromMaybe (hstClientRandom hst) (hstTLS13OuterClientRandom hst)+ cr = unClientRandom crm+ (label, key) = labelAndKey logkey+ debugKeyLogger (ctxDebug ctx) $+ label ++ " " ++ dump cr ++ " " ++ dump (convert key)+ where+ dump = init . drop 1 . showBytesHex
− Network/TLS/Handshake/Process.hs
@@ -1,109 +0,0 @@--- |--- Module : Network.TLS.Handshake.Process--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ process handshake message received----module Network.TLS.Handshake.Process- ( processHandshake- , startHandshake- , getHandshakeDigest- ) where--import Control.Applicative-import Control.Concurrent.MVar-import Control.Monad.Error-import Control.Monad.State (gets)--import Network.TLS.Types (Role(..), invertRole)-import Network.TLS.Util-import Network.TLS.Packet-import Network.TLS.Struct-import Network.TLS.State-import Network.TLS.Context.Internal-import Network.TLS.Crypto-import Network.TLS.Handshake.State-import Network.TLS.Handshake.Key-import Network.TLS.Extension-import Data.X509 (CertificateChain(..), Certificate(..), getCertificate)--processHandshake :: Context -> Handshake -> IO ()-processHandshake ctx hs = do- role <- usingState_ ctx isClientContext- case hs of- ClientHello cver ran _ _ _ ex _ -> when (role == ServerRole) $ do- mapM_ (usingState_ ctx . processClientExtension) ex- startHandshake ctx cver ran- Certificates certs -> processCertificates role certs- ClientKeyXchg content -> when (role == ServerRole) $ do- processClientKeyXchg ctx content- HsNextProtocolNegotiation selected_protocol ->- when (role == ServerRole) $ usingState_ ctx $ setNegotiatedProtocol selected_protocol- Finished fdata -> processClientFinished ctx fdata- _ -> return ()- let encoded = encodeHandshake hs- when (certVerifyHandshakeMaterial hs) $ usingHState ctx $ addHandshakeMessage encoded- when (finishHandshakeTypeMaterial $ typeOfHandshake hs) $ usingHState ctx $ updateHandshakeDigest encoded- where -- secure renegotiation- processClientExtension (0xff01, content) = do- v <- getVerifiedData ClientRole- let bs = extensionEncode (SecureRenegotiation v Nothing)- unless (bs `bytesEq` content) $ throwError $ Error_Protocol ("client verified data not matching: " ++ show v ++ ":" ++ show content, True, HandshakeFailure)-- setSecureRenegotiation True- -- unknown extensions- processClientExtension _ = return ()-- processCertificates :: Role -> CertificateChain -> IO ()- processCertificates ServerRole (CertificateChain []) = return ()- processCertificates ClientRole (CertificateChain []) =- throwCore $ Error_Protocol ("server certificate missing", True, HandshakeFailure)- processCertificates _ (CertificateChain (c:_)) =- usingHState ctx $ setPublicKey pubkey- where pubkey = certPubKey $ getCertificate c---- process the client key exchange message. the protocol expects the initial--- client version received in ClientHello, not the negotiated version.--- in case the version mismatch, generate a random master secret-processClientKeyXchg :: Context -> ClientKeyXchgAlgorithmData -> IO ()-processClientKeyXchg ctx (CKX_RSA encryptedPremaster) = do- (rver, role, random) <- usingState_ ctx $ do- (,,) <$> getVersion <*> isClientContext <*> genRandom 48- ePremaster <- decryptRSA ctx encryptedPremaster- usingHState ctx $ do- expectedVer <- gets hstClientVersion- case ePremaster of- Left _ -> setMasterSecretFromPre rver role random- Right premaster -> case decodePreMasterSecret premaster of- Left _ -> setMasterSecretFromPre rver role random- Right (ver, _)- | ver /= expectedVer -> setMasterSecretFromPre rver role random- | otherwise -> setMasterSecretFromPre rver role premaster-processClientKeyXchg ctx (CKX_DH clientDHValue) = do- rver <- usingState_ ctx getVersion- role <- usingState_ ctx isClientContext-- (ServerDHParams dhparams _) <- fromJust "server dh params" <$> usingHState ctx (gets hstServerDHParams)- dhpriv <- fromJust "dh private" <$> usingHState ctx (gets hstDHPrivate)- let premaster = dhGetShared dhparams dhpriv clientDHValue- usingHState ctx $ setMasterSecretFromPre rver role premaster--processClientFinished :: Context -> FinishedData -> IO ()-processClientFinished ctx fdata = do- (cc,ver) <- usingState_ ctx $ (,) <$> isClientContext <*> getVersion- expected <- usingHState ctx $ getHandshakeDigest ver $ invertRole cc- when (expected /= fdata) $ do- throwCore $ Error_Protocol("bad record mac", True, BadRecordMac)- usingState_ ctx $ updateVerifiedData ServerRole fdata- return ()--startHandshake :: Context -> Version -> ClientRandom -> IO ()-startHandshake ctx ver crand = do- -- FIXME check if handshake is already not null- liftIO $ modifyMVar_ (ctxHandshake ctx) $ \hs ->- case hs of- Nothing -> return $ Just $ newEmptyHandshake ver crand- Just _ -> return hs
+ Network/TLS/Handshake/Random.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternGuards #-}++module Network.TLS.Handshake.Random (+ serverRandom,+ serverRandomECH,+ replaceServerRandomECH,+ clientRandom,+ isDowngraded,+) where++import qualified Data.ByteString as B++import Network.TLS.Context.Internal+import Network.TLS.Imports+import Network.TLS.Struct++-- | Generate a server random suitable for the version selected by the server+-- and its supported versions. We use an 8-byte downgrade suffix when the+-- selected version is lowered because of incomplete client support, but also+-- when a version downgrade has been forced with 'debugVersionForced'. This+-- second part allows to test that the client implementation correctly detects+-- downgrades. The suffix is not used when forcing TLS13 to a server not+-- officially supporting TLS13 (this is not a downgrade scenario but only the+-- consequence of our debug API allowing this).+serverRandom :: Context -> Version -> [Version] -> IO ServerRandom+serverRandom ctx chosenVer suppVers+ | TLS13 `elem` suppVers = case chosenVer of+ TLS13 -> ServerRandom <$> getStateRNG ctx 32+ TLS12 -> ServerRandom <$> genServRand suffix12+ _ -> ServerRandom <$> genServRand suffix11+ | TLS12 `elem` suppVers = case chosenVer of+ TLS13 -> ServerRandom <$> getStateRNG ctx 32+ TLS12 -> ServerRandom <$> getStateRNG ctx 32+ _ -> ServerRandom <$> genServRand suffix11+ | otherwise = ServerRandom <$> getStateRNG ctx 32+ where+ genServRand suff = do+ pref <- getStateRNG ctx 24+ return (pref `B.append` suff)++serverRandomECH :: Context -> IO ServerRandom+serverRandomECH ctx = do+ rnd <- getStateRNG ctx 24+ let zeros = "\x00\x00\x00\x00\x00\x00\x00\x00"+ return $ ServerRandom (rnd <> zeros)++replaceServerRandomECH :: ServerRandom -> ByteString -> ServerRandom+replaceServerRandomECH (ServerRandom rnd) bs = ServerRandom (rnd' <> bs)+ where+ rnd' = B.take 24 rnd++-- | Test if the negotiated version was artificially downgraded (that is, for+-- other reason than the versions supported by the client).+isDowngraded :: Version -> [Version] -> ServerRandom -> Bool+isDowngraded ver suppVers (ServerRandom sr)+ | ver <= TLS12+ , TLS13 `elem` suppVers =+ suffix12 `B.isSuffixOf` sr+ || suffix11 `B.isSuffixOf` sr+ | ver <= TLS11+ , TLS12 `elem` suppVers =+ suffix11 `B.isSuffixOf` sr+ | otherwise = False++suffix12 :: ByteString+suffix12 = "\x44\x4F\x57\x4E\x47\x52\x44\x01"++suffix11 :: ByteString+suffix11 = "\x44\x4F\x57\x4E\x47\x52\x44\x00"++clientRandom :: Context -> IO ClientRandom+clientRandom ctx = ClientRandom <$> getStateRNG ctx 32
Network/TLS/Handshake/Server.hs view
@@ -1,44 +1,29 @@-{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}--- |--- Module : Network.TLS.Handshake.Server--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Handshake.Server- ( handshakeServer- , handshakeServerWith- ) where+{-# LANGUAGE OverloadedStrings #-} -import Network.TLS.Parameters-import Network.TLS.Context.Internal-import Network.TLS.Session-import Network.TLS.Struct-import Network.TLS.Cipher-import Network.TLS.Compression-import Network.TLS.Credentials-import Network.TLS.Extension-import Network.TLS.Util (catchException, fromJust)-import Network.TLS.IO-import Network.TLS.Types-import Network.TLS.State hiding (getNegotiatedProtocol)-import Network.TLS.Handshake.State-import Network.TLS.Handshake.Process-import Network.TLS.Handshake.Key-import Network.TLS.Measurement-import Data.Maybe (isJust)-import Data.List (intersect, sortBy)-import qualified Data.ByteString as B-import Data.ByteString.Char8 ()+module Network.TLS.Handshake.Server (+ handshakeServer,+ handshakeServerWith,+ requestCertificateServer,+ keyUpdate,+ updateKey,+ KeyUpdateRequest (..),+) where -import Control.Applicative ((<$>))-import Control.Monad.State+import Control.Monad.State.Strict+import Data.Maybe -import Network.TLS.Handshake.Signature+import Network.TLS.Context.Internal import Network.TLS.Handshake.Common-import Network.TLS.Handshake.Certificate-import Network.TLS.X509+import Network.TLS.Handshake.Common13+import Network.TLS.Handshake.Server.ClientHello+import Network.TLS.Handshake.Server.ClientHello12+import Network.TLS.Handshake.Server.ClientHello13+import Network.TLS.Handshake.Server.ServerHello12+import Network.TLS.Handshake.Server.ServerHello13+import Network.TLS.Handshake.Server.TLS12+import Network.TLS.Handshake.Server.TLS13+import Network.TLS.Imports+import Network.TLS.Struct -- Put the server context in handshake mode. --@@ -46,325 +31,59 @@ -- -- This is just a helper to pop the next message from the recv layer, -- and call handshakeServerWith.-handshakeServer :: MonadIO m => ServerParams -> Context -> m ()+handshakeServer :: ServerParams -> Context -> IO () handshakeServer sparams ctx = liftIO $ do- hss <- recvPacketHandshake ctx- case hss of- [ch] -> handshakeServerWith sparams ctx ch- _ -> fail ("unexpected handshake received, excepting client hello and received " ++ show hss)+ hbs <- recvPacketHandshake ctx+ case hbs of+ chb : _ -> handshake sparams ctx chb+ _ -> unexpected (show $ fst $ unzip hbs) (Just "client hello") +handshakeServerWith+ :: ServerParams -> Context -> HandshakeR -> IO ()+handshakeServerWith = handshake+ -- | 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 protocol (<- receiving, -> sending, [] optional):--- (no session) (session resumption)--- <- client hello <- client hello--- -> server hello -> server hello--- -> [certificate]--- -> [server key xchg]--- -> [cert request]--- -> hello done--- <- [certificate]--- <- client key xchg--- <- [cert verify]--- <- change cipher -> change cipher--- <- [NPN]--- <- finish -> finish--- -> change cipher <- change cipher--- -> finish <- finish----handshakeServerWith :: ServerParams -> Context -> Handshake -> IO ()-handshakeServerWith sparams ctx clientHello@(ClientHello clientVersion _ clientSession ciphers compressions exts _) = do- -- check if policy allow this new handshake to happens- handshakeAuthorized <- withMeasure ctx (onNewHandshake $ serverHooks sparams)- unless handshakeAuthorized (throwCore $ Error_HandshakePolicy "server: handshake denied")- updateMeasure ctx incrementNbHandshakes-- -- Handle Client hello- processHandshake ctx clientHello-- when (clientVersion == SSL2) $ throwCore $ Error_Protocol ("ssl2 is not supported", True, ProtocolVersion)- chosenVersion <- case findHighestVersionFrom clientVersion (supportedVersions $ ctxSupported ctx) of- Nothing -> throwCore $ Error_Protocol ("client version " ++ show clientVersion ++ " is not supported", True, ProtocolVersion)- Just v -> return v-- when (commonCipherIDs == []) $ throwCore $- Error_Protocol ("no cipher in common with the client", True, HandshakeFailure)- when (null commonCompressions) $ throwCore $- Error_Protocol ("no compression in common with the client", True, HandshakeFailure)-- let ciphersFilteredVersion = filter (cipherAllowedForVersion chosenVersion) commonCiphers- usedCipher = (onCipherChoosing $ serverHooks sparams) chosenVersion ciphersFilteredVersion- creds = sharedCredentials $ ctxShared ctx- cred <- case cipherKeyExchange usedCipher of- CipherKeyExchange_RSA -> return $ credentialsFindForDecrypting creds- CipherKeyExchange_DH_Anon -> return $ Nothing- CipherKeyExchange_DHE_RSA -> return $ credentialsFindForSigning SignatureRSA creds- CipherKeyExchange_DHE_DSS -> return $ credentialsFindForSigning SignatureDSS creds- _ -> throwCore $ Error_Protocol ("key exchange algorithm not implemented", True, HandshakeFailure)-- resumeSessionData <- case clientSession of- (Session (Just clientSessionId)) -> liftIO $ sessionResume (sharedSessionManager $ ctxShared ctx) clientSessionId- (Session Nothing) -> return Nothing-- doHandshake sparams cred ctx chosenVersion usedCipher usedCompression clientSession resumeSessionData exts-- where- commonCipherIDs = intersect ciphers (map cipherID $ ctxCiphers ctx)- commonCiphers = filter (flip elem commonCipherIDs . cipherID) (ctxCiphers ctx)- commonCompressions = compressionIntersectID (supportedCompressions $ ctxSupported ctx) compressions- usedCompression = head commonCompressions---handshakeServerWith _ _ _ = throwCore $ Error_Protocol ("unexpected handshake message received in handshakeServerWith", True, HandshakeFailure)--doHandshake :: ServerParams -> Maybe Credential -> Context -> Version -> Cipher- -> Compression -> Session -> Maybe SessionData- -> [(ExtensionID, a)] -> IO ()-doHandshake sparams mcred ctx chosenVersion usedCipher usedCompression clientSession resumeSessionData exts = do- case resumeSessionData of- Nothing -> do- handshakeSendServerData- liftIO $ contextFlush ctx- -- Receive client info until client Finished.- recvClientData sparams ctx- sendChangeCipherAndFinish (return ()) ctx ServerRole- Just sessionData -> do- usingState_ ctx (setSession clientSession True)- serverhello <- makeServerHello clientSession- sendPacket ctx $ Handshake [serverhello]- usingHState ctx $ setMasterSecret chosenVersion ServerRole $ sessionSecret sessionData- sendChangeCipherAndFinish (return ()) ctx ServerRole- recvChangeCipherAndFinish ctx- handshakeTerminate ctx- where- clientRequestedNPN = isJust $ lookup extensionID_NextProtocolNegotiation exts-- ---- -- When the client sends a certificate, check whether- -- it is acceptable for the application.- --- ---- makeServerHello session = do- srand <- getStateRNG ctx 32 >>= return . ServerRandom- case mcred of- Just (_, privkey) -> usingHState ctx $ setPrivateKey privkey- _ -> return () -- return a sensible error-- -- in TLS12, we need to check as well the certificates we are sending if they have in the extension- -- the necessary bits set.- secReneg <- usingState_ ctx getSecureRenegotiation- secRengExt <- if secReneg- then do- vf <- usingState_ ctx $ do- cvf <- getVerifiedData ClientRole- svf <- getVerifiedData ServerRole- return $ extensionEncode (SecureRenegotiation cvf $ Just svf)- return [ (0xff01, vf) ]- else return []- nextProtocols <-- if clientRequestedNPN- then liftIO $ onSuggestNextProtocols $ serverHooks sparams- else return Nothing- npnExt <- case nextProtocols of- Just protos -> do usingState_ ctx $ do setExtensionNPN True- setServerNextProtocolSuggest protos- return [ ( extensionID_NextProtocolNegotiation- , extensionEncode $ NextProtocolNegotiation protos) ]- Nothing -> return []- let extensions = secRengExt ++ npnExt- usingState_ ctx (setVersion chosenVersion)- usingHState ctx $ setServerHelloParameters chosenVersion srand usedCipher usedCompression- return $ ServerHello chosenVersion srand session (cipherID usedCipher)- (compressionID usedCompression) extensions-- handshakeSendServerData = do- serverSession <- newSession ctx- usingState_ ctx (setSession serverSession False)- serverhello <- makeServerHello serverSession- -- send ServerHello & Certificate & ServerKeyXchg & CertReq- let certMsg = case mcred of- Just (srvCerts, _) -> Certificates srvCerts- _ -> Certificates $ CertificateChain []- sendPacket ctx $ Handshake [ serverhello, certMsg ]-- -- send server key exchange if needed- skx <- case cipherKeyExchange usedCipher of- CipherKeyExchange_DH_Anon -> Just <$> generateSKX_DH_Anon- CipherKeyExchange_DHE_RSA -> Just <$> generateSKX_DHE SignatureRSA- CipherKeyExchange_DHE_DSS -> Just <$> generateSKX_DHE SignatureDSS- _ -> return Nothing- maybe (return ()) (sendPacket ctx . Handshake . (:[]) . ServerKeyXchg) skx-- -- FIXME we don't do this on a Anonymous server-- -- When configured, send a certificate request- -- with the DNs of all confgure CA- -- certificates.- --- when (serverWantClientCert sparams) $ do- usedVersion <- usingState_ ctx getVersion- let certTypes = [ CertificateType_RSA_Sign ]- hashSigs = if usedVersion < TLS12- then Nothing- else Just (supportedHashSignatures $ ctxSupported ctx)- creq = CertRequest certTypes hashSigs- (map extractCAname $ serverCACertificates sparams)- usingHState ctx $ setCertReqSent True- sendPacket ctx (Handshake [creq])-- -- Send HelloDone- sendPacket ctx (Handshake [ServerHelloDone])-- extractCAname :: SignedCertificate -> DistinguishedName- extractCAname cert = certSubjectDN $ getCertificate cert-- setup_DHE = do- let dhparams = fromJust "server DHE Params" $ serverDHEParams sparams- (priv, pub) <- generateDHE ctx dhparams-- let serverParams = ServerDHParams dhparams pub-- usingHState ctx $ setServerDHParams serverParams- usingHState ctx $ modify $ \hst -> hst { hstDHPrivate = Just priv }- return (serverParams)-- generateSKX_DHE sigAlg = do- serverParams <- setup_DHE- signatureData <- generateSignedDHParams ctx serverParams-- usedVersion <- usingState_ ctx getVersion- let mhash = case usedVersion of- TLS12 -> case filter ((==) sigAlg . snd) $ supportedHashSignatures $ ctxSupported ctx of- [] -> error ("no hash signature for " ++ show sigAlg)- x:_ -> Just (fst x)- _ -> Nothing- let hashDescr = signatureHashData sigAlg mhash- signed <- signatureCreate ctx (fmap (\h -> (h, sigAlg)) mhash) hashDescr signatureData-- case sigAlg of- SignatureRSA -> return $ SKX_DHE_RSA serverParams signed- SignatureDSS -> return $ SKX_DHE_DSS serverParams signed- _ -> error ("generate skx_dhe unsupported signature type: " ++ show sigAlg)-- generateSKX_DH_Anon = SKX_DH_Anon <$> setup_DHE---- | receive Client data in handshake until the Finished handshake.------ <- [certificate]--- <- client key xchg--- <- [cert verify]--- <- change cipher--- <- [NPN]--- <- finish----recvClientData :: ServerParams -> Context -> IO ()-recvClientData sparams ctx = runRecvState ctx (RecvStateHandshake processClientCertificate)- where processClientCertificate (Certificates certs) = do- -- run certificate recv hook- ctxWithHooks ctx (\hooks -> hookRecvCertificates hooks $ certs)- -- Call application callback to see whether the- -- certificate chain is acceptable.- --- usage <- liftIO $ catchException (onClientCertificate (serverHooks sparams) certs) rejectOnException- case usage of- CertificateUsageAccept -> return ()- CertificateUsageReject reason -> certificateRejected reason-- -- Remember cert chain for later use.- --- usingHState ctx $ setClientCertChain certs-- -- FIXME: We should check whether the certificate- -- matches our request and that we support- -- verifying with that certificate.-- return $ RecvStateHandshake processClientKeyExchange-- processClientCertificate p = processClientKeyExchange 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.- processClientKeyExchange (ClientKeyXchg _) = return $ RecvStateNext processCertificateVerify- processClientKeyExchange p = unexpected (show p) (Just "client key exchange")-- -- Check whether the client correctly signed the handshake.- -- If not, ask the application on how to proceed.- --- processCertificateVerify (Handshake [hs@(CertVerify dsig@(DigitallySigned mbHashSig _))]) = do- processHandshake ctx hs-- checkValidClientCertChain "change cipher message expected"-- usedVersion <- usingState_ ctx getVersion- -- Fetch all handshake messages up to now.- msgs <- usingHState ctx $ B.concat <$> getHandshakeMessages- (hashMethod, toVerify) <- prepareCertificateVerifySignatureData ctx usedVersion mbHashSig msgs-- -- Verify the signature.- verif <- signatureVerifyWithHashDescr ctx SignatureRSA hashMethod toVerify dsig-- case verif of- True -> do- -- When verification succeeds, commit the- -- client certificate chain to the context.- --- Just certs <- usingHState ctx $ getClientCertChain- usingState_ ctx $ setClientCertificateChain certs- return ()-- False -> do- -- Either verification failed because of an- -- invalid format (with an error message), or- -- the signature is wrong. In either case,- -- ask the application if it wants to- -- proceed, we will do that.- res <- liftIO $ onUnverifiedClientCert (serverHooks sparams)- if res- then do- -- When verification fails, but the- -- application callbacks accepts, we- -- also commit the client certificate- -- chain to the context.- Just certs <- usingHState ctx $ getClientCertChain- usingState_ ctx $ setClientCertificateChain certs- else throwCore $ Error_Protocol ("verification failed", True, BadCertificate)- return $ RecvStateNext expectChangeCipher-- processCertificateVerify p = do- chain <- usingHState ctx $ getClientCertChain- case chain of- Just cc | isNullCertificateChain cc -> return ()- | otherwise -> throwCore $ Error_Protocol ("cert verify message missing", True, UnexpectedMessage)- Nothing -> return ()- expectChangeCipher p-- expectChangeCipher ChangeCipherSpec = do- npn <- usingState_ ctx getExtensionNPN- return $ RecvStateHandshake $ if npn then expectNPN else expectFinish- expectChangeCipher p = unexpected (show p) (Just "change cipher")-- expectNPN (HsNextProtocolNegotiation _) = return $ RecvStateHandshake expectFinish- expectNPN p = unexpected (show p) (Just "Handshake NextProtocolNegotiation")-- expectFinish (Finished _) = return RecvStateDone- expectFinish p = unexpected (show p) (Just "Handshake Finished")-- checkValidClientCertChain msg = do- chain <- usingHState ctx $ getClientCertChain- let throwerror = Error_Protocol (msg , True, UnexpectedMessage)- case chain of- Nothing -> throwCore throwerror- Just cc | isNullCertificateChain cc -> throwCore throwerror- | otherwise -> return ()--findHighestVersionFrom :: Version -> [Version] -> Maybe Version-findHighestVersionFrom clientVersion allowedVersions =- case filter (clientVersion >=) $ reverse $ sortBy compare allowedVersions of- [] -> Nothing- v:_ -> Just v+handshake :: ServerParams -> Context -> HandshakeR -> IO ()+handshake sparams ctx chb@(ClientHello ch, bs) = do+ (chosenVersion, chI, mcrnd) <- processClientHello sparams ctx ch bs+ if chosenVersion == TLS13+ then do+ -- fixme: we should check if the client random is the same as+ -- that in the first client hello in the case of hello retry.+ -- r0 :: Cipher, Hash, Bool+ (keyShareResult, r0, r1) <-+ processClientHello13 sparams ctx chI+ case keyShareResult of+ SelectKeyShareNotFound ->+ throwCore $+ Error_Protocol "no group in common with the client for HRR" HandshakeFailure+ SelectKeyShareHRR g -> do+ sendHRR ctx g r0 chI $ isJust mcrnd+ -- Don't reset ctxEstablished since 0-RTT data+ -- would be coming, which should be ignored.+ handshakeServer sparams ctx+ SelectKeyShareFound cliKeyShare -> do+ unless (checkClientKeyShareKeyLength cliKeyShare) $+ throwCore $+ Error_Protocol "broken key_share" IllegalParameter+ -- r2 :: ( SecretTriple ApplicationSecret+ -- , ClientTrafficSecret HandshakeSecret+ -- , Bool -- authenticated+ -- , Bool) -- rtt0OK+ r2 <-+ sendServerHello13 sparams ctx cliKeyShare r0 r1 chI mcrnd+ recvClientSecondFlight13 sparams ctx r2 chI+ else do+ r <-+ processClientHello12 sparams ctx chI+ updateTranscriptHash12 ctx chb+ resumeSessionData <-+ sendServerHello12 sparams ctx r chI+ recvClientSecondFlight12 sparams ctx resumeSessionData+handshake _ _ _ = throwCore $ Error_Protocol "client Hello is expected" HandshakeFailure
+ Network/TLS/Handshake/Server/ClientHello.hs view
@@ -0,0 +1,305 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TupleSections #-}++module Network.TLS.Handshake.Server.ClientHello (+ processClientHello,+) where++import qualified Control.Exception as E+import Crypto.HPKE+import qualified Data.ByteString as BS++import Network.TLS.ECH.Config++import Network.TLS.Compression+import Network.TLS.Context.Internal+import Network.TLS.Extension+import Network.TLS.Handshake.Common+import Network.TLS.Handshake.State+import Network.TLS.Imports+import Network.TLS.Measurement+import Network.TLS.Packet+import Network.TLS.Parameters+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Types++processClientHello+ :: ServerParams+ -> Context+ -> ClientHello+ -> [ByteString]+ -> IO+ ( Version+ , ClientHello+ , Maybe ClientRandom -- Just for ECH to keep the outer one for key log+ )+processClientHello sparams ctx ch@CH{..} b = do+ established <- ctxEstablished ctx+ -- renego is not allowed in TLS 1.3+ when (established /= NotEstablished) $ do+ ver <- usingState_ ctx (getVersionWithDefault TLS12)+ when (ver == TLS13) $+ throwCore $+ Error_Protocol "renegotiation is not allowed in TLS 1.3" UnexpectedMessage+ -- rejecting client initiated renegotiation to prevent DOS.+ eof <- ctxEOF ctx+ let renegotiation = established == Established && not eof+ when+ (renegotiation && not (supportedClientInitiatedRenegotiation $ ctxSupported ctx))+ $ throwCore+ $ Error_Protocol_Warning "renegotiation is not allowed" NoRenegotiation+ -- check if policy allow this new handshake to happens+ handshakeAuthorized <- withMeasure ctx (onNewHandshake $ serverHooks sparams)+ unless+ handshakeAuthorized+ (throwCore $ Error_HandshakePolicy "server: handshake denied")+ updateMeasure ctx incrementNbHandshakes++ when (chVersion /= TLS12) $+ throwCore $+ Error_Protocol (show chVersion ++ " is not supported") ProtocolVersion++ -- Fallback SCSV: RFC7507+ -- TLS_FALLBACK_SCSV: {0x56, 0x00}+ when+ ( supportedFallbackScsv (ctxSupported ctx)+ && (CipherId 0x5600 `elem` chCiphers)+ && chVersion < TLS12+ )+ $ throwCore+ $ Error_Protocol "fallback is not allowed" InappropriateFallback++ -- choosing TLS version+ let extract (SupportedVersionsClientHello vers) = vers -- fixme: vers == []+ extract _ = []+ clientVersions =+ lookupAndDecode EID_SupportedVersions MsgTClientHello chExtensions [] extract+ clientVersion = min TLS12 chVersion+ serverVersions+ | renegotiation = filter (< TLS13) (supportedVersions $ ctxSupported ctx)+ | otherwise = supportedVersions $ ctxSupported ctx+ mVersion = debugVersionForced $ serverDebug sparams+ chosenVersion <- case mVersion of+ Just cver -> return cver+ Nothing ->+ if (TLS13 `elem` serverVersions) && clientVersions /= []+ then case findHighestVersionFrom13 clientVersions serverVersions of+ Nothing ->+ throwCore $+ Error_Protocol+ ("client versions " ++ show clientVersions ++ " is not supported")+ ProtocolVersion+ Just v -> return v+ else case findHighestVersionFrom clientVersion serverVersions of+ Nothing ->+ throwCore $+ Error_Protocol+ ("client version " ++ show clientVersion ++ " is not supported")+ ProtocolVersion+ Just v -> return v++ -- Checking compression+ let nullComp = compressionID nullCompression+ case chosenVersion of+ TLS13 ->+ when (chComps /= [nullComp]) $+ throwCore $+ Error_Protocol "compression is not allowed in TLS 1.3" IllegalParameter+ _ -> case find (== nullComp) chComps of+ Nothing ->+ throwCore $+ Error_Protocol+ "compressions must include nullCompression in TLS 1.2"+ IllegalParameter+ _ -> return ()++ -- Processing encrypted client hello+ (mClientHello', receivedECH) <-+ if chosenVersion == TLS13 && not (null (serverECHKey sparams))+ then do+ lookupAndDecodeAndDo+ EID_EncryptedClientHello+ MsgTClientHello+ chExtensions+ (return (Nothing, False))+ (\bs -> (,True) <$> decryptECH sparams ctx ch bs)+ else return (Nothing, False)+ case mClientHello' of+ Just chI -> do+ -- chI is created from diff.+ -- encodeHandshake is a MUST.+ setupI ctx chI $ [encodeHandshake $ ClientHello chI]+ return (chosenVersion, chI, Just chRandom)+ _ -> do+ setupO ctx ch b+ when (chosenVersion == TLS13) $ do+ let hasECHConf = not (null (sharedECHConfigList (serverShared sparams)))+ when (hasECHConf && not receivedECH) $+ usingHState ctx $+ setECHEE True+ when receivedECH $+ usingHState ctx $+ setECHEE True+ return (chosenVersion, ch, Nothing)++setupI :: Context -> ClientHello -> [ByteString] -> IO ()+setupI ctx chI@CH{..} b = do+ hrr <- usingState_ ctx getTLS13HRR+ unless hrr $ startHandshake ctx TLS13 chRandom+ usingHState ctx $ setClientHello chI b+ let serverName = getServerName chExtensions+ maybe (return ()) (usingState_ ctx . setClientSNI) serverName++setupO :: Context -> ClientHello -> [ByteString] -> IO ()+setupO ctx ch@CH{..} b = do+ hrr <- usingState_ ctx getTLS13HRR+ unless hrr $ startHandshake ctx chVersion chRandom+ usingHState ctx $ setClientHello ch b+ let serverName = getServerName chExtensions+ maybe (return ()) (usingState_ ctx . setClientSNI) serverName++-- SNI (Server Name Indication)+getServerName :: [ExtensionRaw] -> Maybe HostName+getServerName chExts =+ lookupAndDecode+ EID_ServerName+ MsgTClientHello+ chExts+ Nothing+ extractServerName+ where+ extractServerName (ServerName ns) = listToMaybe (mapMaybe toHostName ns)+ toHostName (ServerNameHostName hostName) = Just hostName+ toHostName (ServerNameOther _) = Nothing++findHighestVersionFrom :: Version -> [Version] -> Maybe Version+findHighestVersionFrom clientVersion allowedVersions =+ case filter (clientVersion >=) $ sortOn Down allowedVersions of+ [] -> Nothing+ v : _ -> Just v++findHighestVersionFrom13 :: [Version] -> [Version] -> Maybe Version+findHighestVersionFrom13 clientVersions serverVersions = case svs `intersect` cvs of+ [] -> Nothing+ v : _ -> Just v+ where+ svs = sortOn Down serverVersions+ cvs = sortOn Down $ filter (>= TLS12) clientVersions++decryptECH+ :: ServerParams+ -> Context+ -> ClientHello+ -> EncryptedClientHello+ -> IO (Maybe ClientHello)+decryptECH _ _ _ ECHClientHelloInner = return Nothing+decryptECH sparams ctx chO ech@ECHClientHelloOuter{..} = E.handle hpkeHandler $ do+ mfunc <- getHPKE sparams ctx ech+ case mfunc of+ Nothing -> return Nothing+ Just (func, nenc) -> do+ hrr <- usingState_ ctx getTLS13HRR+ let nenc' = if hrr then 0 else nenc+ let aad = encodeHandshake' $ ClientHello $ fill0ClientHello nenc' chO+ plaintext <- func aad echPayload+ case decodeClientHello' plaintext of+ Right (ClientHello chI) -> do+ case expandClientHello chI chO of+ Nothing -> return Nothing+ Just chI' -> return $ Just chI'+ _ -> return Nothing+ where+ hpkeHandler :: HPKEError -> IO (Maybe ClientHello)+ hpkeHandler _ = return Nothing+decryptECH _ _ _ _ = return Nothing++fill0ClientHello :: Int -> ClientHello -> ClientHello+fill0ClientHello nenc ch@CH{..} =+ ch{chExtensions = fill0Exts nenc chExtensions}++fill0Exts :: Int -> [ExtensionRaw] -> [ExtensionRaw]+fill0Exts nenc xs0 = loop xs0+ where+ loop [] = []+ loop (ExtensionRaw EID_EncryptedClientHello bs : xs) = x' : loop xs+ where+ (prefix, payload) = BS.splitAt (10 + nenc) bs+ bs' = prefix <> BS.replicate (BS.length payload) 0+ x' = ExtensionRaw EID_EncryptedClientHello bs'+ loop (x : xs) = x : loop xs++expandClientHello :: ClientHello -> ClientHello -> Maybe ClientHello+expandClientHello inner outer =+ case expand (chExtensions inner) (chExtensions outer) of+ Nothing -> Nothing+ Just exts ->+ Just $+ inner+ { chSession = chSession outer+ , chExtensions = exts+ }+ where+ expand :: [ExtensionRaw] -> [ExtensionRaw] -> Maybe [ExtensionRaw]+ expand [] _ = Just []+ expand iis [] = chk iis+ expand (i : is) oos = do+ (rs, oos') <- case i of+ ExtensionRaw EID_EchOuterExtensions bs ->+ case extensionDecode MsgTClientHello bs of+ Nothing -> Nothing+ Just (EchOuterExtensions eids) -> expd eids oos+ _ -> Just ([i], oos)+ (rs ++) <$> expand is oos'+ expd+ :: [ExtensionID] -> [ExtensionRaw] -> Maybe ([ExtensionRaw], [ExtensionRaw])+ expd [] oos = Just ([], oos)+ expd _ [] = Nothing+ expd (i : is) oos = case fnd i oos of+ Nothing -> Nothing+ Just (ext, oos') -> do+ (exts, oos'') <- expd is oos'+ Just (ext : exts, oos'')+ fnd :: ExtensionID -> [ExtensionRaw] -> Maybe (ExtensionRaw, [ExtensionRaw])+ fnd _ [] = Nothing+ fnd EID_EncryptedClientHello _ = Nothing+ fnd i (o@(ExtensionRaw eid _) : os)+ | i == eid = Just (o, os)+ | otherwise = fnd i os+ chk :: [ExtensionRaw] -> Maybe [ExtensionRaw]+ chk [] = Just []+ chk (ExtensionRaw EID_EchOuterExtensions _ : _) = Nothing+ chk (i : is) = (i :) <$> chk is++getHPKE+ :: ServerParams+ -> Context+ -> EncryptedClientHello+ -> IO (Maybe (HPKEF, Int))+getHPKE ServerParams{..} ctx ECHClientHelloOuter{..} = do+ mfunc <- getTLS13HPKE ctx+ case mfunc of+ Nothing -> do+ let mconfig = findECHConfigById echConfigId $ sharedECHConfigList serverShared+ mskR = lookup echConfigId serverECHKey+ case (mconfig, mskR) of+ (Just config, Just skR') -> do+ let kemid = KEM_ID $ kem_id $ key_config $ contents config+ skR = EncodedSecretKey skR'+ encodedConfig = encodeECHConfig config+ let info = "tls ech\x00" <> encodedConfig+ (kdfid, aeadid) = echCipherSuite+ ctxR <- setupBaseR kemid kdfid aeadid skR Nothing echEnc info+ let nenc = nEnc kemid+ func = open ctxR+ setTLS13HPKE ctx func nenc+ return $ Just (func, nenc)+ _ -> return Nothing+ _ -> return mfunc+getHPKE _ _ _ = return Nothing++findECHConfigById :: ConfigId -> ECHConfigList -> Maybe ECHConfig+findECHConfigById cnfId echConfigList = find eqCfgId echConfigList+ where+ eqCfgId cnf = config_id (key_config (contents cnf)) == cnfId
+ Network/TLS/Handshake/Server/ClientHello12.hs view
@@ -0,0 +1,235 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Network.TLS.Handshake.Server.ClientHello12 (+ processClientHello12,+) where++import Network.TLS.Cipher+import Network.TLS.Context.Internal+import Network.TLS.Credentials+import Network.TLS.Crypto+import Network.TLS.ErrT+import Network.TLS.Extension+import Network.TLS.Handshake.Server.Common+import Network.TLS.Handshake.Signature+import Network.TLS.Imports+import Network.TLS.Parameters+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Types (CipherId (..), Role (..))++----------------------------------------------------------------++-- serverSupported sparams == ctxSupported ctx++-- TLS 1.2 or earlier+processClientHello12+ :: ServerParams+ -> Context+ -> ClientHello+ -> IO (Cipher, Maybe Credential)+processClientHello12 sparams ctx ch = do+ let secureRenegotiation = supportedSecureRenegotiation $ serverSupported sparams+ when secureRenegotiation $ checkSecureRenegotiation ctx ch+ serverName <- usingState_ ctx getClientSNI+ let hooks = serverHooks sparams+ extraCreds <- onServerNameIndication hooks serverName+ let (creds, signatureCreds, ciphersFilteredVersion) =+ credsTriple sparams ch extraCreds+ -- 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) $+ throwCore $+ Error_Protocol "no cipher in common with the TLS 1.2 client" HandshakeFailure+ let usedCipher = onCipherChoosing hooks TLS12 ciphersFilteredVersion+ mcred <- chooseCreds usedCipher creds signatureCreds+ return (usedCipher, mcred)++checkSecureRenegotiation :: Context -> ClientHello -> IO ()+checkSecureRenegotiation ctx CH{..} = do+ -- RFC 5746: secure renegotiation+ -- TLS_EMPTY_RENEGOTIATION_INFO_SCSV: {0x00, 0xFF}+ when (CipherId 0xff `elem` chCiphers) $+ usingState_ ctx $+ setSecureRenegotiation True+ case extensionLookup EID_SecureRenegotiation chExtensions of+ Just content -> usingState_ ctx $ do+ VerifyData cvd <- getVerifyData ClientRole+ let bs = extensionEncode (SecureRenegotiation cvd "")+ unless (bs == content) $+ throwError $+ Error_Protocol+ ("client verified data not matching: " ++ show cvd ++ ":" ++ show content)+ HandshakeFailure++ setSecureRenegotiation True+ _ -> return ()++----------------------------------------------------------------++credsTriple+ :: ServerParams+ -> ClientHello+ -> Credentials+ -> (Credentials, Credentials, [Cipher])+credsTriple sparams CH{..} extraCreds+ | cipherListCredentialFallback cltCiphers = (allCreds, sigAllCreds, allCiphers)+ | otherwise = (cltCreds, sigCltCreds, cltCiphers)+ where+ ciphers = supportedCiphers $ serverSupported sparams++ commonCiphers creds sigCreds = intersectCiphers chCiphers availableCiphers+ where+ availableCiphers = getCiphers ciphers creds sigCreds++ p = makeCredentialPredicate TLS12 chExtensions+ allCreds =+ filterCredentials (isCredentialAllowed TLS12 p) $+ extraCreds `mappend` sharedCredentials (serverShared sparams)++ -- When selecting a cipher we must ensure that it is allowed for the+ -- TLS version but also that all its key-exchange requirements+ -- will be met.++ -- Some ciphers require a signature and a hash. With TLS 1.2 the hash+ -- algorithm is selected from a combination of server configuration and+ -- the client "supported_signatures" extension. So we cannot pick+ -- such a cipher if no hash is available for it. It's best to skip this+ -- cipher and pick another one (with another key exchange).++ -- 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 evaluated from+ -- the resulting credentials.++ supported = serverSupported sparams+ groups = supportedGroups supported+ possibleGroups = negotiatedGroupsInCommon groups chExtensions+ possibleECGroups = possibleGroups `intersect` availableECGroups+ possibleFFGroups = possibleGroups `intersect` availableFFGroups+ hasCommonGroupForECDHE = not (null possibleECGroups)+ hasCommonGroupForFFDHE = not (null possibleFFGroups)+ hasCustomGroupForFFDHE = isJust (serverDHEParams sparams)+ canFFDHE = hasCustomGroupForFFDHE || hasCommonGroupForFFDHE+ hasCommonGroup cipher =+ case cipherKeyExchange cipher of+ CipherKeyExchange_DH_Anon -> canFFDHE+ CipherKeyExchange_DHE_RSA -> canFFDHE+ CipherKeyExchange_DHE_DSA -> canFFDHE+ CipherKeyExchange_ECDHE_RSA -> hasCommonGroupForECDHE+ CipherKeyExchange_ECDHE_ECDSA -> hasCommonGroupForECDHE+ _ -> True -- group not used++ -- Ciphers are selected according to TLS version, availability of+ -- (EC)DHE group and credential depending on key exchange.+ cipherAllowed cipher = cipherAllowedForVersion TLS12 cipher && hasCommonGroup cipher+ selectCipher credentials signatureCredentials = filter cipherAllowed (commonCiphers credentials signatureCredentials)++ -- Build a list of all hash/signature algorithms in common between+ -- client and server.+ hashAndSignatures = supportedHashSignatures supported+ possibleHashSigAlgs = hashAndSignaturesInCommon hashAndSignatures chExtensions++ -- Check that a candidate signature credential will be compatible with+ -- client & server hash/signature algorithms. This returns Just Int+ -- in order to sort credentials according to server hash/signature+ -- preference. When the certificate has no matching hash/signature in+ -- 'possibleHashSigAlgs' the result is Nothing, and the credential will+ -- not be used to sign. This avoids a failure later in 'decideHashSig'.+ signingRank cred =+ case credentialDigitalSignatureKey cred of+ Just pub -> findIndex (pub `signatureCompatible`) possibleHashSigAlgs+ Nothing -> Nothing++ -- Finally compute credential lists and resulting cipher list.+ --+ -- We try to keep certificates supported by the client, but+ -- fallback to all credentials if this produces no suitable result+ -- (see RFC 5246 section 7.4.2 and RFC 8446 section 4.4.2.2).+ -- The condition is based on resulting (EC)DHE ciphers so that+ -- filtering credentials does not give advantage to a less secure+ -- key exchange like CipherKeyExchange_RSA or CipherKeyExchange_DH_Anon.+ cltCreds = filterCredentialsWithHashSignatures chExtensions allCreds+ sigCltCreds = filterSortCredentials signingRank cltCreds+ sigAllCreds = filterSortCredentials signingRank allCreds+ cltCiphers = selectCipher cltCreds sigCltCreds+ allCiphers = selectCipher allCreds sigAllCreds++chooseCreds :: Cipher -> Credentials -> Credentials -> IO (Maybe Credential)+chooseCreds usedCipher creds signatureCreds = case cipherKeyExchange usedCipher of+ CipherKeyExchange_RSA -> return $ credentialsFindForDecrypting creds+ CipherKeyExchange_DH_Anon -> return Nothing+ CipherKeyExchange_DHE_RSA -> return $ credentialsFindForSigning KX_RSA signatureCreds+ CipherKeyExchange_DHE_DSA -> return $ credentialsFindForSigning KX_DSA signatureCreds+ CipherKeyExchange_ECDHE_RSA -> return $ credentialsFindForSigning KX_RSA signatureCreds+ CipherKeyExchange_ECDHE_ECDSA -> return $ credentialsFindForSigning KX_ECDSA signatureCreds+ _ ->+ throwCore $+ Error_Protocol "key exchange algorithm not implemented" HandshakeFailure++----------------------------------------------------------------++negotiatedGroupsInCommon :: [Group] -> [ExtensionRaw] -> [Group]+negotiatedGroupsInCommon serverGroups exts =+ lookupAndDecode+ EID_SupportedGroups+ MsgTClientHello+ exts+ []+ (\(SupportedGroups clientGroups) -> serverGroups `intersect` clientGroups)++----------------------------------------------------------------++filterSortCredentials+ :: Ord a => (Credential -> Maybe a) -> Credentials -> Credentials+filterSortCredentials rankFun (Credentials creds) =+ let orderedPairs = sortOn fst [(rankFun cred, cred) | cred <- creds]+ in Credentials [cred | (Just _, cred) <- orderedPairs]++-- returns True if certificate filtering with "signature_algorithms_cert" /+-- "signature_algorithms" produced no ephemeral D-H nor TLS13 cipher (so+-- handshake with lower security)+cipherListCredentialFallback :: [Cipher] -> Bool+cipherListCredentialFallback = all nonDH+ where+ nonDH x = case cipherKeyExchange x of+ CipherKeyExchange_DHE_RSA -> False+ CipherKeyExchange_DHE_DSA -> False+ CipherKeyExchange_ECDHE_RSA -> False+ CipherKeyExchange_ECDHE_ECDSA -> False+ CipherKeyExchange_TLS13 -> False+ _ -> True++-- We filter our allowed ciphers here according to dynamic credential lists.+-- Credentials 'creds' come from server parameters but also SNI callback.+-- When the key exchange requires a signature, we use a+-- subset of this list named 'sigCreds'. This list has been filtered in order+-- to remove certificates that are not compatible with hash/signature+-- restrictions (TLS 1.2).+getCiphers :: [Cipher] -> Credentials -> Credentials -> [Cipher]+getCiphers ciphers creds sigCreds = filter authorizedCKE ciphers+ where+ authorizedCKE cipher =+ case cipherKeyExchange cipher of+ CipherKeyExchange_RSA -> canEncryptRSA+ CipherKeyExchange_DH_Anon -> True+ CipherKeyExchange_DHE_RSA -> canSignRSA+ CipherKeyExchange_DHE_DSA -> canSignDSA+ CipherKeyExchange_ECDHE_RSA -> canSignRSA+ CipherKeyExchange_ECDHE_ECDSA -> canSignECDSA+ -- unimplemented: non ephemeral DH & ECDH.+ -- Note, these *should not* be implemented, and have+ -- (for example) been removed in OpenSSL 1.1.0+ --+ CipherKeyExchange_DH_DSA -> False+ CipherKeyExchange_DH_RSA -> False+ CipherKeyExchange_ECDH_ECDSA -> False+ CipherKeyExchange_ECDH_RSA -> False+ CipherKeyExchange_TLS13 -> False -- not reached+ canSignDSA = KX_DSA `elem` signingAlgs+ canSignRSA = KX_RSA `elem` signingAlgs+ canSignECDSA = KX_ECDSA `elem` signingAlgs+ canEncryptRSA = isJust $ credentialsFindForDecrypting creds+ signingAlgs = credentialsListSigningAlgorithms sigCreds
+ Network/TLS/Handshake/Server/ClientHello13.hs view
@@ -0,0 +1,212 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Network.TLS.Handshake.Server.ClientHello13 (+ processClientHello13,+ SelectKeyShareResult (..),+) where++import qualified Data.ByteString as B++import Network.TLS.Cipher+import Network.TLS.Context.Internal+import Network.TLS.Crypto+import Network.TLS.Extension+import Network.TLS.Handshake.Common13+import Network.TLS.Handshake.Signature+import Network.TLS.Handshake.State+import Network.TLS.IO.Encode+import Network.TLS.Imports+import Network.TLS.Parameters+import Network.TLS.Session+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Types++limitSupportedGroups :: Int+limitSupportedGroups = 64++-- TLS 1.3 or later+processClientHello13+ :: ServerParams+ -> Context+ -> ClientHello+ -> IO+ ( SelectKeyShareResult+ , (Cipher, Hash, Bool) -- rtt0+ , (SecretPair EarlySecret, [ExtensionRaw], Bool, Bool) -- authenticated, is0RTTvalid+ )+processClientHello13 sparams ctx ch@CH{..} = do+ when+ (any (\(ExtensionRaw eid _) -> eid == EID_PreSharedKey) $ init chExtensions)+ $ throwCore+ $ Error_Protocol "extension pre_shared_key must be last" IllegalParameter+ -- Deciding cipher.+ -- 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) $+ throwCore $+ Error_Protocol "no cipher in common with the TLS 1.3 client" HandshakeFailure+ let usedCipher = onCipherChoosing (serverHooks sparams) TLS13 ciphersFilteredVersion+ usedHash = cipherHash usedCipher+ rtt0 =+ lookupAndDecode+ EID_EarlyData+ MsgTClientHello+ chExtensions+ False+ (\(EarlyDataIndication _) -> True)+ if rtt0+ then+ -- mark a 0-RTT attempt before a possible HRR, and before updating the+ -- status again if 0-RTT successful+ setEstablished ctx (EarlyDataNotAllowed 3) -- hardcoding+ else+ -- In the case of HRR, EarlyDataNotAllowed is already set.+ -- It should be cleared here.+ setEstablished ctx NotEstablished+ -- Deciding key exchange from key shares+ let require =+ throwCore $+ Error_Protocol+ "key exchange not implemented, expected key_share extension"+ MissingExtension+ extract (KeyShareClientHello kses) = return kses+ extract _ = require+ keyShares <-+ lookupAndDecodeAndDo EID_KeyShare MsgTClientHello chExtensions require extract+ let clientGroups =+ take limitSupportedGroups $+ lookupAndDecode+ EID_SupportedGroups+ MsgTClientHello+ chExtensions+ []+ (\(SupportedGroups gs) -> gs)+ (mgroup, doHRR) <-+ onSelectKeyShare+ (serverHooks sparams)+ serverGroups+ clientGroups+ $ map keyShareEntryGroup keyShares+ keyshareResult <- case mgroup of+ Nothing -> return SelectKeyShareNotFound+ Just g+ | doHRR -> return $ SelectKeyShareHRR g+ | otherwise -> case filter (\e -> keyShareEntryGroup e == g) keyShares of+ [] -> return SelectKeyShareNotFound+ [x] -> return $ SelectKeyShareFound x+ _ -> throwCore $ Error_Protocol "duplicated key_share" IllegalParameter++ let triple = (usedCipher, usedHash, rtt0)+ pskEarlySecret <- pskAndEarlySecret sparams ctx triple ch+ (ich, b) <- fromJust <$> usingHState ctx getClientHello+ updateTranscriptHash12 ctx (ClientHello ich, b)+ return (keyshareResult, triple, pskEarlySecret)+ where+ ciphersFilteredVersion = intersectCiphers chCiphers serverCiphers+ serverCiphers =+ filter+ (cipherAllowedForVersion TLS13)+ (supportedCiphers $ serverSupported sparams)+ serverGroups = supportedGroupsTLS13 $ serverSupported sparams++data SelectKeyShareResult+ = -- | Negotiation failure+ SelectKeyShareNotFound+ | -- | Send a hello retry request with this group+ SelectKeyShareHRR Group+ | -- | Use this key share+ SelectKeyShareFound KeyShareEntry+ deriving (Eq, Show)++pskAndEarlySecret+ :: ServerParams+ -> Context+ -> (Cipher, Hash, Bool) -- rtt0+ -> ClientHello+ -> IO (SecretPair EarlySecret, [ExtensionRaw], Bool, Bool) -- authenticated, is0RTTvalid+pskAndEarlySecret sparams ctx (usedCipher, usedHash, rtt0) CH{..} = do+ (psk, binderInfo, is0RTTvalid) <- choosePSK+ earlyKey <- calculateEarlySecret ctx choice (Left psk)+ let earlySecret = pairBase earlyKey+ authenticated = isJust binderInfo+ preSharedKeyExt <- checkBinder earlySecret binderInfo+ return (earlyKey, preSharedKeyExt, authenticated, is0RTTvalid)+ where+ choice = makeCipherChoice TLS13 usedCipher++ choosePSK =+ lookupAndDecodeAndDo+ EID_PreSharedKey+ MsgTClientHello+ chExtensions+ (return (zero, Nothing, False))+ selectPSK++ selectPSK (PreSharedKeyClientHello (PskIdentity identity obfAge : _) bnds@(bnd : _)) = do+ when (null dhModes) $+ throwCore $+ Error_Protocol "no psk_key_exchange_modes extension" MissingExtension+ if PSK_DHE_KE `elem` dhModes+ then do+ let len = sum (map (\x -> B.length x + 1) bnds) + 2+ mgr = sharedSessionManager $ serverShared sparams+ -- sessionInvalidate is not used for TLS 1.3+ -- because PSK is always changed.+ -- So, identity is not stored in Context.+ msdata <-+ if rtt0+ then sessionResumeOnlyOnce mgr 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)++ checkBinder _ Nothing = return []+ checkBinder earlySecret (Just (binder, n, tlen)) = do+ (_, b) <- fromJust <$> usingHState ctx getClientHello+ let binder' = makePSKBinder earlySecret usedHash tlen $ B.concat b --- xxx+ unless (binder == binder') $+ decryptError "PSK binder validation failed"+ return [toExtensionRaw $ PreSharedKeyServerHello $ fromIntegral n]++ 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+ scid = sessionCipher sdata+ isSameKDF = case findCipher scid ciphers of+ 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+ return (isPSKvalid, is0RTTvalid)++ dhModes =+ lookupAndDecode+ EID_PskKeyExchangeModes+ MsgTClientHello+ chExtensions+ []+ (\(PskKeyExchangeModes ms) -> ms)++ hashSize = hashDigestSize usedHash+ zero = B.replicate hashSize 0
+ Network/TLS/Handshake/Server/Common.hs view
@@ -0,0 +1,207 @@+{-# LANGUAGE OverloadedStrings #-}++module Network.TLS.Handshake.Server.Common (+ applicationProtocol,+ checkValidClientCertChain,+ clientCertificate,+ credentialDigitalSignatureKey,+ filterCredentials,+ filterCredentialsWithHashSignatures,+ makeCredentialPredicate,+ isCredentialAllowed,+ storePrivInfoServer,+ hashAndSignaturesInCommon,+ processRecordSizeLimit,+) where++import Control.Monad.State.Strict+import Data.X509 (ExtKeyUsageFlag (..), ExtKeyUsagePurpose (..))++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.Key+import Network.TLS.Handshake.State+import Network.TLS.Imports+import Network.TLS.Parameters+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Util (catchException)+import Network.TLS.X509++checkValidClientCertChain+ :: MonadIO m => Context -> String -> m CertificateChain+checkValidClientCertChain ctx errmsg = do+ chain <- usingHState ctx getClientCertChain+ let throwerror = Error_Protocol errmsg UnexpectedMessage+ case chain of+ Nothing -> throwCore throwerror+ Just cc+ | isNullCertificateChain cc -> throwCore throwerror+ | otherwise -> return cc++credentialDigitalSignatureKey :: Credential -> Maybe PubKey+credentialDigitalSignatureKey cred+ | isDigitalSignaturePair keys = Just pubkey+ | otherwise = Nothing+ where+ keys@(pubkey, _) = credentialPublicPrivateKeys cred++filterCredentials :: (Credential -> Bool) -> Credentials -> Credentials+filterCredentials p (Credentials l) = Credentials (filter p l)++-- ECDSA keys are tested against supported elliptic curves until TLS12 but+-- not after. With TLS13, the curve is linked to the signature algorithm+-- and client support is tested with signatureCompatible13.+makeCredentialPredicate :: Version -> [ExtensionRaw] -> (Group -> Bool)+makeCredentialPredicate ver exts+ | ver >= TLS13 = const True+ | otherwise =+ lookupAndDecode+ EID_SupportedGroups+ MsgTClientHello+ exts+ (const True)+ (\(SupportedGroups sg) -> (`elem` sg))++isCredentialAllowed :: Version -> (Group -> Bool) -> Credential -> Bool+isCredentialAllowed ver p cred =+ pubkey `versionCompatible` ver && satisfiesEcPredicate p pubkey+ where+ (pubkey, _) = credentialPublicPrivateKeys cred++-- Filters a list of candidate credentials with credentialMatchesHashSignatures.+--+-- Algorithms to filter with are taken from "signature_algorithms_cert"+-- extension when it exists, else from "signature_algorithms" when clients do+-- not implement the new extension (see RFC 8446 section 4.2.3).+--+-- Resulting credential list can be used as input to the hybrid cipher-and-+-- certificate selection for TLS12, or to the direct certificate selection+-- simplified with TLS13. As filtering credential signatures with client-+-- advertised algorithms is not supposed to cause negotiation failure, in case+-- of dead end with the subsequent selection process, this process should always+-- be restarted with the unfiltered credential list as input (see fallback+-- certificate chains, described in same RFC section).+--+-- Calling code should not forget to apply constraints of extension+-- "signature_algorithms" to any signature-based key exchange derived from the+-- output credentials. Respecting client constraints on KX signatures is+-- mandatory but not implemented by this function.+filterCredentialsWithHashSignatures+ :: [ExtensionRaw] -> Credentials -> Credentials+filterCredentialsWithHashSignatures exts =+ lookupAndDecode+ EID_SignatureAlgorithmsCert+ MsgTClientHello+ exts+ lookupSignatureAlgorithms+ (\(SignatureAlgorithmsCert sas) -> withAlgs sas)+ where+ lookupSignatureAlgorithms =+ lookupAndDecode+ EID_SignatureAlgorithms+ MsgTClientHello+ exts+ id+ (\(SignatureAlgorithms sas) -> withAlgs sas)+ withAlgs sas = filterCredentials (credentialMatchesHashSignatures sas)++storePrivInfoServer :: MonadIO m => Context -> Credential -> m ()+storePrivInfoServer ctx (cc, privkey) = void (storePrivInfo ctx cc privkey)++-- ALPN (Application Layer Protocol Negotiation)+applicationProtocol+ :: Context -> [ExtensionRaw] -> ServerParams -> IO (Maybe ExtensionRaw)+applicationProtocol ctx exts sparams = case onALPN of+ Nothing -> return Nothing+ Just io ->+ lookupAndDecodeAndDo+ EID_ApplicationLayerProtocolNegotiation+ MsgTClientHello+ exts+ (return Nothing)+ $ select io+ where+ onALPN = onALPNClientSuggest $ serverHooks sparams+ select io (ApplicationLayerProtocolNegotiation protos) = do+ proto <- io protos+ when (proto == "") $+ throwCore $+ Error_Protocol "no supported application protocols" NoApplicationProtocol+ usingState_ ctx $ do+ setExtensionALPN True+ setNegotiatedProtocol proto+ let alpn = ApplicationLayerProtocolNegotiation [proto]+ return $ Just $ toExtensionRaw alpn++clientCertificate :: ServerParams -> Context -> CertificateChain -> IO ()+clientCertificate sparams ctx certs = do+ -- run certificate recv hook+ ctxWithHooks ctx (`hookRecvCertificates` certs)+ -- Call application callback to see whether the+ -- certificate chain is acceptable.+ --+ usage <-+ liftIO $+ catchException+ (onClientCertificate (serverHooks sparams) certs)+ rejectOnException+ case usage of+ CertificateUsageAccept -> do+ verifyLeafKeyUsage [KeyUsage_digitalSignature] certs+ verifyLeafKeyUsagePurpose KeyUsagePurpose_ClientAuth certs+ CertificateUsageReject reason -> certificateRejected reason++ -- Remember cert chain for later use.+ --+ usingHState ctx $ setClientCertChain certs++----------------------------------------------------------------++-- The values in the "signature_algorithms" extension+-- are in descending order of preference.+-- However here the algorithms are selected according+-- to server preference in 'supportedHashSignatures'.+hashAndSignaturesInCommon+ :: [HashAndSignatureAlgorithm] -> [ExtensionRaw] -> [HashAndSignatureAlgorithm]+hashAndSignaturesInCommon sHashSigs exts = sHashSigs `intersect` cHashSigs+ where+ -- See Section 7.4.1.4.1 of RFC 5246.+ defVal =+ [ (HashSHA1, SignatureECDSA)+ , (HashSHA1, SignatureRSA)+ , (HashSHA1, SignatureDSA)+ ]+ cHashSigs =+ lookupAndDecode+ EID_SignatureAlgorithms+ MsgTClientHello+ exts+ defVal+ (\(SignatureAlgorithms sas) -> sas)++processRecordSizeLimit+ :: Context -> [ExtensionRaw] -> Bool -> IO (Maybe ExtensionRaw)+processRecordSizeLimit ctx chExts tls13 = do+ let mmylim = limitRecordSize $ sharedLimit $ ctxShared ctx+ setMyRecordLimit ctx mmylim+ case mmylim of+ Nothing -> return Nothing+ Just mylim -> do+ lookupAndDecodeAndDo+ EID_RecordSizeLimit+ MsgTClientHello+ chExts+ (return ())+ (setPeerRecordSizeLimit ctx tls13)+ peerSentRSL <- checkPeerRecordLimit ctx+ if peerSentRSL+ then do+ let mysiz = fromIntegral mylim + if tls13 then 1 else 0+ rsl = RecordSizeLimit mysiz+ return $ Just $ toExtensionRaw rsl+ else return Nothing
+ Network/TLS/Handshake/Server/ServerHello12.hs view
@@ -0,0 +1,325 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Network.TLS.Handshake.Server.ServerHello12 (+ sendServerHello12,+) where++import Data.ByteArray (convert)++import Network.TLS.Cipher+import Network.TLS.Compression+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.Key+import Network.TLS.Handshake.Random+import Network.TLS.Handshake.Server.Common+import Network.TLS.Handshake.Signature+import Network.TLS.Handshake.State+import Network.TLS.IO+import Network.TLS.Imports+import Network.TLS.Parameters+import Network.TLS.Session+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Types+import Network.TLS.X509 hiding (Certificate)++sendServerHello12+ :: ServerParams+ -> Context+ -> (Cipher, Maybe Credential)+ -> ClientHello+ -> IO (Maybe SessionData)+sendServerHello12 sparams ctx (usedCipher, mcred) ch@CH{..} = do+ resumeSessionData <- recoverSessionData ctx ch+ case resumeSessionData of+ Nothing -> do+ serverSession <- newSession ctx+ usingState_ ctx $ setSession serverSession+ sh <- makeServerHello sparams ctx usedCipher mcred chExtensions serverSession+ build <- sendServerFirstFlight sparams ctx usedCipher mcred chExtensions+ let ff = ServerHello sh : build [ServerHelloDone]+ sendPacket12 ctx $ Handshake ff []+ contextFlush ctx+ Just sessionData -> do+ usingState_ ctx $ do+ setSession chSession+ setTLS12SessionResuming True+ sh <-+ makeServerHello sparams ctx usedCipher mcred chExtensions chSession+ sendPacket12 ctx $ Handshake [ServerHello sh] []+ let mainSecret = convert $ sessionSecret sessionData+ usingHState ctx $ setMainSecret TLS12 ServerRole mainSecret+ logKey ctx $ MainSecret mainSecret+ sendCCSandFinished ctx ServerRole+ return resumeSessionData++recoverSessionData :: Context -> ClientHello -> IO (Maybe SessionData)+recoverSessionData ctx CH{..} = do+ serverName <- usingState_ ctx getClientSNI+ ems <- processExtendedMainSecret ctx TLS12 MsgTClientHello chExtensions+ let mticket =+ lookupAndDecode+ EID_SessionTicket+ MsgTClientHello+ chExtensions+ Nothing+ (\(SessionTicket ticket) -> Just ticket)+ midentity = ticketOrSessionID12 mticket chSession+ case midentity of+ Nothing -> return Nothing+ Just identity -> do+ sd <- sessionResume (sharedSessionManager $ ctxShared ctx) identity+ validateSession ctx chCiphers serverName ems sd++validateSession+ :: Context+ -> [CipherId]+ -> Maybe HostName+ -> Bool+ -> Maybe SessionData+ -> IO (Maybe SessionData)+validateSession _ _ _ _ Nothing = return Nothing+validateSession ctx ciphers sni ems m@(Just sd)+ -- SessionData parameters are assumed to match the local server configuration+ -- so we need to compare only to ClientHello inputs. Abbreviated handshake+ -- uses the same server_name than full handshake so the same+ -- credentials (and thus ciphers) are available.+ | TLS12 < sessionVersion sd = return Nothing -- fixme+ | CipherId (sessionCipher sd) `notElem` ciphers =+ throwCore $+ Error_Protocol "new cipher is different from the old one" IllegalParameter+ | isJust sni && sessionClientSNI sd /= sni = do+ usingState_ ctx clearClientSNI+ return Nothing+ | ems && not emsSession = return Nothing+ | not ems && emsSession =+ let err = "client resumes an EMS session without EMS"+ in throwCore $ Error_Protocol err HandshakeFailure+ | otherwise = return m+ where+ emsSession = SessionEMS `elem` sessionFlags sd++sendServerFirstFlight+ :: ServerParams+ -> Context+ -> Cipher+ -> Maybe Credential+ -> [ExtensionRaw]+ -> IO ([Handshake] -> [Handshake])+sendServerFirstFlight ServerParams{..} ctx usedCipher mcred chExts = do+ let b0 = id+ let cc = case mcred of+ Just (srvCerts, _) -> srvCerts+ _ -> CertificateChain []+ let b1 = b0 . (Certificate (CertificateChain_ cc) :)+ usingState_ ctx $ setServerCertificateChain cc++ -- send server key exchange if needed+ skx <- case cipherKeyExchange usedCipher of+ CipherKeyExchange_DH_Anon -> Just <$> generateSKX_DH_Anon+ CipherKeyExchange_DHE_RSA -> Just <$> generateSKX_DHE KX_RSA+ CipherKeyExchange_DHE_DSA -> Just <$> generateSKX_DHE KX_DSA+ CipherKeyExchange_ECDHE_RSA -> Just <$> generateSKX_ECDHE KX_RSA+ CipherKeyExchange_ECDHE_ECDSA -> Just <$> generateSKX_ECDHE KX_ECDSA+ _ -> return Nothing+ let b2 = case skx of+ Nothing -> b1+ Just kx -> b1 . (ServerKeyXchg kx :)++ -- FIXME we don't do this on a Anonymous server++ -- When configured, send a certificate request with the DNs of all+ -- configured CA certificates.+ --+ -- Client certificates MUST NOT be accepted if not requested.+ --+ if serverWantClientCert+ then do+ let (certTypes, hashSigs) =+ let as = supportedHashSignatures serverSupported+ in (nub $ mapMaybe hashSigToCertType as, as)+ creq =+ CertRequest+ certTypes+ hashSigs+ (map extractCAname serverCACertificates)+ usingHState ctx $ setCertReqSent True+ return $ b2 . (creq :)+ else return b2+ where+ commonGroups = negotiatedGroupsInCommon (supportedGroups serverSupported) chExts+ commonHashSigs = hashAndSignaturesInCommon (supportedHashSignatures serverSupported) chExts+ setup_DHE = do+ let possibleFFGroups = commonGroups `intersect` availableFFGroups+ (dhparams, priv, pub) <-+ case possibleFFGroups of+ [] ->+ let dhparams = fromJust serverDHEParams+ in case findFiniteFieldGroup dhparams of+ Just g -> do+ usingHState ctx $ setSupportedGroup g+ generateFFDHE ctx g+ Nothing -> do+ (priv, pub) <- generateDHE ctx dhparams+ return (dhparams, priv, pub)+ g : _ -> do+ usingHState ctx $ setSupportedGroup g+ generateFFDHE ctx g++ let serverParams = serverDHParamsFrom dhparams pub++ usingHState ctx $ setServerDHParams serverParams+ usingHState ctx $ setDHPrivate priv+ return serverParams++ -- Choosing a hash algorithm to sign (EC)DHE parameters+ -- in ServerKeyExchange. Hash algorithm is not suggested by+ -- the chosen cipher suite. So, it should be selected based on+ -- the "signature_algorithms" extension in a client hello.+ -- If RSA is also used for key exchange, this function is+ -- not called.+ decideHashSig pubKey = do+ case filter (pubKey `signatureCompatible`) commonHashSigs of+ [] -> error ("no hash signature for " ++ pubkeyType pubKey)+ x : _ -> return x++ generateSKX_DHE kxsAlg = do+ serverParams <- setup_DHE+ pubKey <- getLocalPublicKey ctx+ mhashSig <- decideHashSig pubKey+ signed <- digitallySignDHParams ctx serverParams pubKey mhashSig+ case kxsAlg of+ KX_RSA -> return $ SKX_DHE_RSA serverParams signed+ KX_DSA -> return $ SKX_DHE_DSA serverParams signed+ _ ->+ error ("generate skx_dhe unsupported key exchange signature: " ++ show kxsAlg)++ generateSKX_DH_Anon = SKX_DH_Anon <$> setup_DHE++ setup_ECDHE grp = do+ usingHState ctx $ setSupportedGroup grp+ (srvpri, srvpub) <- generateGroup ctx grp+ let serverParams = ServerECDHParams grp srvpub+ usingHState ctx $ setServerECDHParams serverParams+ usingHState ctx $ setGroupPrivate [(grp, srvpri)]+ return serverParams++ generateSKX_ECDHE kxsAlg = do+ let possibleECGroups = commonGroups `intersect` availableECGroups+ grp <- case possibleECGroups of+ [] -> throwCore $ Error_Protocol "no common group" HandshakeFailure+ g : _ -> return g+ serverParams <- setup_ECDHE grp+ pubKey <- getLocalPublicKey ctx+ mhashSig <- decideHashSig pubKey+ signed <- digitallySignECDHParams ctx serverParams pubKey mhashSig+ case kxsAlg of+ KX_RSA -> return $ SKX_ECDHE_RSA serverParams signed+ KX_ECDSA -> return $ SKX_ECDHE_ECDSA serverParams signed+ _ ->+ error ("generate skx_ecdhe unsupported key exchange signature: " ++ show kxsAlg)++---+-- When the client sends a certificate, check whether+-- it is acceptable for the application.+--+---+makeServerHello+ :: ServerParams+ -> Context+ -> Cipher+ -> Maybe Credential+ -> [ExtensionRaw]+ -> Session+ -> IO ServerHello+makeServerHello sparams ctx usedCipher mcred chExts session = do+ resuming <- usingState_ ctx getTLS12SessionResuming+ case mcred of+ Just cred -> storePrivInfoServer ctx cred+ _ -> return () -- return a sensible error+ sniExt <- do+ if resuming+ then return Nothing+ else do+ msni <- usingState_ ctx getClientSNI+ case msni of+ -- RFC6066: In this event, the server SHALL include+ -- an extension of type "server_name" in the+ -- (extended) server hello. The "extension_data"+ -- field of this extension SHALL be empty.+ Just _ -> return $ Just $ toExtensionRaw $ ServerName []+ Nothing -> return Nothing++ let ecPointExt = case extensionLookup EID_EcPointFormats chExts of+ Nothing -> Nothing+ Just _ -> Just $ toExtensionRaw $ EcPointFormatsSupported [EcPointFormat_Uncompressed]++ alpnExt <- applicationProtocol ctx chExts sparams++ ems <- usingHState ctx getExtendedMainSecret+ let emsExt+ | ems = Just $ toExtensionRaw ExtendedMainSecret+ | otherwise = Nothing++ let useTicket = sessionUseTicket $ sharedSessionManager $ serverShared sparams+ sessionTicketExt+ | not resuming && useTicket = Just $ toExtensionRaw $ SessionTicket ""+ | otherwise = Nothing++ -- in TLS12, we need to check as well the certificates we are sending if they have in the extension+ -- the necessary bits set.+ secReneg <- usingState_ ctx getSecureRenegotiation+ secureRenegExt <-+ if secReneg+ then do+ vd <- usingState_ ctx $ do+ VerifyData cvd <- getVerifyData ClientRole+ VerifyData svd <- getVerifyData ServerRole+ return $ SecureRenegotiation cvd svd+ return $ Just $ toExtensionRaw vd+ else return Nothing++ recodeSizeLimitExt <- processRecordSizeLimit ctx chExts False++ srand <-+ serverRandom ctx TLS12 $ supportedVersions $ serverSupported sparams++ let shExts =+ sharedHelloExtensions (serverShared sparams)+ ++ catMaybes+ [ {- 0x00 -} sniExt+ , {- 0x0b -} ecPointExt+ , {- 0x10 -} alpnExt+ , {- 0x17 -} emsExt+ , {- 0x1c -} recodeSizeLimitExt+ , {- 0x23 -} sessionTicketExt+ , {- 0xff01 -} secureRenegExt+ ]+ usingState_ ctx $ setVersion TLS12+ setServerHelloParameters12 ctx TLS12 srand usedCipher nullCompression+ return $+ SH+ { shVersion = TLS12+ , shRandom = srand+ , shSession = session+ , shCipher = CipherId (cipherID usedCipher)+ , shComp = 0+ , shExtensions = shExts+ }++negotiatedGroupsInCommon :: [Group] -> [ExtensionRaw] -> [Group]+negotiatedGroupsInCommon serverGroups chExts =+ lookupAndDecode+ EID_SupportedGroups+ MsgTClientHello+ chExts+ []+ common+ where+ common (SupportedGroups clientGroups) = serverGroups `intersect` clientGroups
+ Network/TLS/Handshake/Server/ServerHello13.hs view
@@ -0,0 +1,367 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Network.TLS.Handshake.Server.ServerHello13 (+ sendServerHello13,+ sendHRR,+) where++import Control.Monad.State.Strict++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.Common+import Network.TLS.Handshake.Common13+import Network.TLS.Handshake.Control+import Network.TLS.Handshake.Key+import Network.TLS.Handshake.Random+import Network.TLS.Handshake.Server.Common+import Network.TLS.Handshake.Signature+import Network.TLS.Handshake.State+import Network.TLS.Handshake.State13+import Network.TLS.Handshake.TranscriptHash+import Network.TLS.IO+import Network.TLS.Imports+import Network.TLS.Parameters+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Types+import Network.TLS.X509++sendServerHello13+ :: ServerParams+ -> Context+ -> KeyShareEntry+ -> (Cipher, Hash, Bool) -- rtt0+ -> (SecretPair EarlySecret, [ExtensionRaw], Bool, Bool) -- authenticated, is0RTTvalid+ -> ClientHello+ -> Maybe ClientRandom+ -> IO+ ( SecretTriple ApplicationSecret+ , ClientTrafficSecret HandshakeSecret+ , Bool -- authenticated+ , Bool -- rtt0OK+ )+sendServerHello13 sparams ctx clientKeyShare (usedCipher, usedHash, rtt0) (earlyKey, preSharedKeyExt, authenticated, is0RTTvalid) CH{..} mOuterClientRandom = do+ let clientEarlySecret = pairClient earlyKey+ earlySecret = pairBase earlyKey+ -- parse CompressCertificate to check if it is broken here+ let zlib =+ lookupAndDecode+ EID_CompressCertificate+ MsgTClientHello+ chExtensions+ False+ (\(CompressCertificate ccas) -> CCA_Zlib `elem` ccas)++ recodeSizeLimitExt <- processRecordSizeLimit ctx chExtensions True+ enableMyRecordLimit ctx++ newSession ctx >>= \ss -> usingState_ ctx $ do+ setSession ss+ setTLS13ClientSupportsPHA supportsPHA+ usingHState ctx $ do+ setSupportedGroup $ keyShareEntryGroup clientKeyShare+ setOuterClientRandom mOuterClientRandom+ hrr <- usingState_ ctx getTLS13HRR+ alpnExt <- applicationProtocol ctx chExtensions sparams+ setServerParameter+ let rtt0OK = authenticated && not hrr && rtt0 && rtt0accept && is0RTTvalid+ extraCreds <-+ usingState_ ctx getClientSNI >>= onServerNameIndication (serverHooks sparams)+ let p = makeCredentialPredicate TLS13 chExtensions+ allCreds =+ filterCredentials (isCredentialAllowed TLS13 p) $+ extraCreds `mappend` sharedCredentials (ctxShared ctx)+ ----------------------------------------------------------------+ established <- ctxEstablished ctx+ if established /= NotEstablished+ then+ if rtt0OK+ then do+ usingHState ctx $ setTLS13HandshakeMode RTT0+ usingHState ctx $ setTLS13RTT0Status RTT0Accepted+ else do+ usingHState ctx $ setTLS13HandshakeMode PreSharedKey+ usingHState ctx $ setTLS13RTT0Status RTT0Rejected+ else when authenticated $ usingHState ctx $ setTLS13HandshakeMode PreSharedKey+ -- else : FullHandshake or HelloRetryRequest+ mCredInfo <-+ if authenticated then return Nothing else decideCredentialInfo allCreds+ (ecdhe, keyShare) <- makeServerKeyShare ctx clientKeyShare+ ensureRecvComplete ctx+ (clientHandshakeSecret, handSecret) <- runPacketFlight ctx $ do+ sendServerHello keyShare+ sendChangeCipherSpec13 ctx+ ----------------------------------------------------------------+ handKey <- liftIO $ calculateHandshakeSecret ctx choice earlySecret ecdhe+ let serverHandshakeSecret = triServer handKey+ clientHandshakeSecret = triClient handKey+ handSecret = triBase handKey+ liftIO $ do+ if rtt0OK && not (ctxQUICMode ctx)+ then setRxRecordState ctx usedHash usedCipher clientEarlySecret+ else setRxRecordState ctx usedHash usedCipher clientHandshakeSecret+ setTxRecordState ctx usedHash usedCipher serverHandshakeSecret+ let mEarlySecInfo+ | rtt0OK = Just $ EarlySecretInfo usedCipher clientEarlySecret+ | otherwise = Nothing+ handSecInfo = HandshakeSecretInfo usedCipher (clientHandshakeSecret, serverHandshakeSecret)+ contextSync ctx $ SendServerHello chExtensions mEarlySecInfo handSecInfo+ ----------------------------------------------------------------+ liftIO $ enablePeerRecordLimit ctx+ sendExtensions rtt0OK alpnExt recodeSizeLimitExt+ case mCredInfo of+ Nothing -> return ()+ Just (cred, hashSig) -> sendCertAndVerify cred hashSig zlib+ let ServerTrafficSecret shs = serverHandshakeSecret+ rawFinished <- makeFinished ctx usedHash shs+ loadPacket13 ctx $ Handshake13 [rawFinished] []+ return (clientHandshakeSecret, handSecret)+ ----------------------------------------------------------------+ hChSf <- transcriptHash ctx "CH..SF"+ appKey <- calculateApplicationSecret ctx choice handSecret hChSf+ let clientApplicationSecret0 = triClient appKey+ serverApplicationSecret0 = triServer appKey+ setTxRecordState ctx usedHash usedCipher serverApplicationSecret0+ let appSecInfo = ApplicationSecretInfo (clientApplicationSecret0, serverApplicationSecret0)+ contextSync ctx $ SendServerFinished appSecInfo+ ----------------------------------------------------------------+ when rtt0OK $ setEstablished ctx (EarlyDataAllowed rtt0max)+ return (appKey, clientHandshakeSecret, authenticated, rtt0OK)+ where+ choice = makeCipherChoice TLS13 usedCipher++ setServerParameter = do+ usingState_ ctx $ setVersion TLS13+ failOnEitherError $ setServerHelloParameters13 ctx usedCipher False++ supportsPHA =+ lookupAndDecode+ EID_PostHandshakeAuth+ MsgTClientHello+ chExtensions+ False+ (\PostHandshakeAuth -> True)++ rtt0max = safeNonNegative32 $ serverEarlyDataSize sparams+ rtt0accept = serverEarlyDataSize sparams > 0++ decideCredentialInfo allCreds = do+ let err =+ throwCore $ Error_Protocol "broken signature_algorithms extension" DecodeError+ cHashSigs <-+ lookupAndDecodeAndDo+ EID_SignatureAlgorithms+ MsgTClientHello+ chExtensions+ err+ (\(SignatureAlgorithms sas) -> return sas)+ -- When deciding signature algorithm and certificate, we try to keep+ -- certificates supported by the client, but fallback to all credentials+ -- if this produces no suitable result (see RFC 5246 section 7.4.2 and+ -- RFC 8446 section 4.4.2.2).+ let sHashSigs = filter isHashSignatureValid13 $ supportedHashSignatures $ ctxSupported ctx+ hashSigs = sHashSigs `intersect` cHashSigs+ cltCreds = filterCredentialsWithHashSignatures chExtensions allCreds+ case credentialsFindForSigning13 hashSigs cltCreds of+ Nothing ->+ case credentialsFindForSigning13 hashSigs allCreds of+ Nothing -> throwCore $ Error_Protocol "credential not found" HandshakeFailure+ mcs -> return mcs+ mcs -> return mcs++ sendServerHello keyShare = do+ let keyShareExt = toExtensionRaw $ KeyShareServerHello keyShare+ versionExt = toExtensionRaw $ SupportedVersionsServerHello TLS13+ shExts = keyShareExt : versionExt : preSharedKeyExt+ if isJust mOuterClientRandom+ then do+ srand <- liftIO $ serverRandomECH ctx+ let cipherId = CipherId (cipherID usedCipher)+ sh =+ SH+ { shVersion = TLS12+ , shRandom = srand+ , shSession = chSession+ , shCipher = cipherId+ , shComp = 0+ , shExtensions = shExts+ }+ suffix <- computeConfirm ctx usedHash sh "ech accept confirmation"+ let srand' = replaceServerRandomECH srand suffix+ sh' =+ SH+ { shVersion = TLS12+ , shRandom = srand'+ , shSession = chSession+ , shCipher = cipherId+ , shComp = 0+ , shExtensions = shExts+ }+ usingHState ctx $ setECHAccepted True+ loadPacket13 ctx $ Handshake13 [ServerHello13 sh'] []+ else do+ srand <-+ liftIO $+ serverRandom ctx TLS13 $+ supportedVersions $+ serverSupported sparams+ let sh =+ SH+ { shVersion = TLS12+ , shRandom = srand+ , shSession = chSession+ , shCipher = CipherId (cipherID usedCipher)+ , shComp = 0+ , shExtensions = shExts+ }+ loadPacket13 ctx $ Handshake13 [ServerHello13 sh] []++ sendCertAndVerify cred@(certChain, _) hashSig zlib = do+ storePrivInfoServer ctx cred+ when (serverWantClientCert sparams) $ do+ let certReqCtx = "" -- this must be zero length here.+ certReq = makeCertRequest sparams ctx certReqCtx True+ loadPacket13 ctx $ Handshake13 [certReq] []+ usingHState ctx $ setCertReqSent True++ let CertificateChain cs = certChain+ ess = replicate (length cs) []+ let certtag = if zlib then CompressedCertificate13 else Certificate13+ loadPacket13 ctx $+ Handshake13 [certtag "" (CertificateChain_ certChain) ess] []+ liftIO $ usingState_ ctx $ setServerCertificateChain certChain+ hChSc <- transcriptHash ctx "CH..SC"+ pubkey <- getLocalPublicKey ctx+ vrfy <- makeCertVerify ctx pubkey hashSig hChSc+ loadPacket13 ctx $ Handshake13 [vrfy] []++ sendExtensions rtt0OK alpnExt recodeSizeLimitExt = do+ msni <- liftIO $ usingState_ ctx getClientSNI+ let sniExt = case msni of+ -- RFC6066: In this event, the server SHALL include+ -- an extension of type "server_name" in the+ -- (extended) server hello. The "extension_data"+ -- field of this extension SHALL be empty.+ Just _ -> Just $ toExtensionRaw $ ServerName []+ Nothing -> Nothing++ mgroup <- usingHState ctx getSupportedGroup+ let serverGroups = supportedGroups (ctxSupported ctx)+ groupExt = case serverGroups of+ [] -> Nothing+ rg : _ -> case mgroup of+ Nothing -> Nothing+ Just grp+ | grp == rg -> Nothing+ | otherwise -> Just $ toExtensionRaw $ SupportedGroups serverGroups+ let earlyDataExt+ | rtt0OK = Just $ toExtensionRaw $ EarlyDataIndication Nothing+ | otherwise = Nothing++ sendECH <- usingHState ctx getECHEE+ let echExt+ | sendECH =+ Just $+ toExtensionRaw $+ ECHEncryptedExtensions $+ sharedECHConfigList $+ serverShared sparams+ | otherwise = Nothing+ let eeExtensions =+ sharedHelloExtensions (serverShared sparams)+ ++ catMaybes+ [ {- 0x00 -} sniExt+ , {- 0x0a -} groupExt+ , {- 0x10 -} alpnExt+ , {- 0x1c -} recodeSizeLimitExt+ , {- 0x2a -} earlyDataExt+ , {- 0xfe0d -} echExt+ ]+ eeExtensions' <-+ liftIO $ onEncryptedExtensionsCreating (serverHooks sparams) eeExtensions+ loadPacket13 ctx $ Handshake13 [EncryptedExtensions13 eeExtensions'] []++credentialsFindForSigning13+ :: [HashAndSignatureAlgorithm]+ -> Credentials+ -> Maybe (Credential, HashAndSignatureAlgorithm)+credentialsFindForSigning13 hss0 creds = loop hss0+ where+ loop [] = Nothing+ loop (hs : hss) = case credentialsFindForSigning13' hs creds of+ Nothing -> loop hss+ Just cred -> Just (cred, hs)++-- See credentialsFindForSigning.+credentialsFindForSigning13'+ :: HashAndSignatureAlgorithm -> Credentials -> Maybe Credential+credentialsFindForSigning13' sigAlg (Credentials l) = find forSigning l+ where+ forSigning cred = case credentialDigitalSignatureKey cred of+ Nothing -> False+ Just pub -> pub `signatureCompatible13` sigAlg++contextSync :: Context -> ServerState -> IO ()+contextSync ctx ctl = case ctxHandshakeSync ctx of+ HandshakeSync _ sync -> sync ctx ctl++----------------------------------------------------------------++sendHRR :: Context -> Group -> (Cipher, Hash, c) -> ClientHello -> Bool -> IO ()+sendHRR ctx g (usedCipher, usedHash, _) CH{..} isEch = do+ twice <- usingState_ ctx getTLS13HRR+ when twice $+ throwCore $+ Error_Protocol "Hello retry not allowed again" HandshakeFailure+ usingState_ ctx $ setTLS13HRR True+ failOnEitherError $ setServerHelloParameters13 ctx usedCipher True+ hrr <- makeHRR ctx usedCipher usedHash chSession g isEch+ usingHState ctx $ setTLS13HandshakeMode HelloRetryRequest+ runPacketFlight ctx $ do+ loadPacket13 ctx $ Handshake13 [ServerHello13 hrr] []+ sendChangeCipherSpec13 ctx++makeHRR+ :: Context -> Cipher -> Hash -> Session -> Group -> Bool -> IO ServerHello+makeHRR _ usedCipher _ session g False = return hrr+ where+ keyShareExt = toExtensionRaw $ KeyShareHRR g+ versionExt = toExtensionRaw $ SupportedVersionsServerHello TLS13+ extensions = [keyShareExt, versionExt]+ cipherId = CipherId $ cipherID usedCipher+ hrr =+ SH+ { shVersion = TLS12+ , shRandom = hrrRandom+ , shSession = session+ , shCipher = cipherId+ , shComp = 0+ , shExtensions = extensions+ }+makeHRR ctx usedCipher usedHash session g True = do+ suffix <- computeConfirm ctx usedHash hrr "hrr ech accept confirmation"+ let echExt' = toExtensionRaw $ ECHHelloRetryRequest suffix+ extensions' = [keyShareExt, versionExt, echExt']+ hrr' = hrr{shExtensions = extensions'}+ return hrr'+ where+ keyShareExt = toExtensionRaw $ KeyShareHRR g+ versionExt = toExtensionRaw $ SupportedVersionsServerHello TLS13+ echExt = toExtensionRaw $ ECHHelloRetryRequest "\x00\x00\x00\x00\x00\x00\x00\x00"+ extensions = [keyShareExt, versionExt, echExt]+ cipherId = CipherId $ cipherID usedCipher+ hrr =+ SH+ { shVersion = TLS12+ , shRandom = hrrRandom+ , shSession = session+ , shCipher = cipherId+ , shComp = 0+ , shExtensions = extensions+ }
+ Network/TLS/Handshake/Server/TLS12.hs view
@@ -0,0 +1,216 @@+{-# LANGUAGE OverloadedStrings #-}++module Network.TLS.Handshake.Server.TLS12 (+ recvClientSecondFlight12,+) where++import Control.Monad.State.Strict (gets)+import Data.ByteArray (convert)+import qualified Data.ByteString as B++import Network.TLS.Context.Internal+import Network.TLS.Crypto+import Network.TLS.Handshake.Common+import Network.TLS.Handshake.Key+import Network.TLS.Handshake.Server.Common+import Network.TLS.Handshake.Signature+import Network.TLS.Handshake.State+import Network.TLS.IO+import Network.TLS.Imports+import Network.TLS.Packet hiding (getSession)+import Network.TLS.Parameters+import Network.TLS.Session+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Types+import Network.TLS.X509 hiding (Certificate)++----------------------------------------------------------------++recvClientSecondFlight12+ :: ServerParams+ -> Context+ -> Maybe SessionData+ -> IO ()+recvClientSecondFlight12 sparams ctx resumeSessionData = do+ case resumeSessionData of+ Nothing -> do+ recvClientCCC sparams ctx+ mticket <- sessionEstablished ctx+ case mticket of+ Nothing -> return ()+ Just ticket -> do+ let life = adjustLifetime $ serverTicketLifetime sparams+ sendPacket12 ctx $ Handshake [NewSessionTicket life ticket] []+ sendCCSandFinished ctx ServerRole+ Just _ -> do+ _ <- sessionEstablished ctx+ recvCCSandFinished ctx+ finishHandshake12 ctx+ where+ adjustLifetime i+ | i < 0 = 0+ | i > 604800 = 604800+ | otherwise = fromIntegral i++sessionEstablished :: Context -> IO (Maybe Ticket)+sessionEstablished ctx = do+ session <- usingState_ ctx getSession+ -- only callback the session established if we have a session+ case session of+ Session (Just sessionId) -> do+ sessionData <- getSessionData ctx+ let sessionId' = B.copy sessionId+ -- SessionID method: SessionID is used as key to store+ -- SessionData. Nothing is returned.+ --+ -- Session ticket method: SessionID is ignored. SessionData+ -- is encrypted and returned.+ sessionEstablish+ (sharedSessionManager $ ctxShared ctx)+ sessionId'+ (fromJust sessionData)+ _ -> return Nothing -- never reach++----------------------------------------------------------------++-- | receive Client data in handshake until the Finished handshake.+--+-- <- [certificate]+-- <- client key xchg+-- <- [cert verify]+-- <- change cipher+-- <- finish+recvClientCCC :: ServerParams -> Context -> IO ()+recvClientCCC sparams ctx = runRecvState ctx (RecvStateHandshake expectClientCertificate)+ where+ expectClientCertificate (Certificate (CertificateChain_ certs)) = do+ clientCertificate sparams ctx certs+ processCertificate ctx ServerRole certs++ -- FIXME: We should check whether the certificate+ -- matches our request and that we support+ -- verifying with that certificate.++ return $ RecvStateHandshake $ expectClientKeyExchange True+ expectClientCertificate p = 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.+ expectClientKeyExchange followedCertVerify (ClientKeyXchg ckx) = do+ processClientKeyXchg ctx ckx+ if followedCertVerify+ then return $ RecvStateHandshake expectCertificateVerify+ else return $ RecvStatePacket $ expectChangeCipherSpec ctx+ expectClientKeyExchange _ p = unexpected (show p) (Just "client key exchange")++ expectCertificateVerify (CertVerify dsig) = do+ certs <- checkValidClientCertChain ctx "change cipher message expected"++ usedVersion <- usingState_ ctx getVersion+ -- Fetch all handshake messages up to now.+ msgs <- usingHState ctx $ B.concat <$> getHandshakeMessages++ pubKey <- usingHState ctx getRemotePublicKey+ checkDigitalSignatureKey usedVersion pubKey++ verif <- checkCertificateVerify ctx usedVersion pubKey msgs dsig+ processClientCertVerify sparams ctx certs verif+ return $ RecvStatePacket $ expectChangeCipherSpec ctx+ expectCertificateVerify p = unexpected (show p) (Just "client certificate verify")++----------------------------------------------------------------++expectChangeCipherSpec :: Context -> Packet -> IO (RecvState IO)+expectChangeCipherSpec ctx ChangeCipherSpec = do+ enableMyRecordLimit ctx+ return $ RecvStateHandshake $ expectFinished ctx+expectChangeCipherSpec _ p = unexpected (show p) (Just "change cipher")++----------------------------------------------------------------++-- process the client key exchange message. the protocol expects the initial+-- client version received in ClientHello, not the negotiated version.+-- in case the version mismatch, generate a random main secret+processClientKeyXchg :: Context -> ClientKeyXchgAlgorithmData -> IO ()+processClientKeyXchg ctx (CKX_RSA encryptedPreMain) = do+ (rver, role, random) <- usingState_ ctx $ do+ (,,) <$> getVersion <*> getRole <*> genRandom 48+ ePreMain <- decryptRSA ctx encryptedPreMain+ expectedVer <- usingHState ctx $ gets hstClientVersion+ mainSecret <- case ePreMain of+ Left _ ->+ -- BadRecordMac is nonsense but for tlsfuzzer+ throwCore $+ Error_Protocol "invalid client public key" BadRecordMac+ Right preMain -> case decodePreMainSecret $ convert preMain of+ Left _ -> usingHState ctx $ setMainSecretFromPre rver role $ convert random+ Right (ver, _)+ | ver /= expectedVer ->+ usingHState ctx $ setMainSecretFromPre rver role $ convert random+ | otherwise -> usingHState ctx $ setMainSecretFromPre rver role preMain+ logKey ctx (MainSecret mainSecret)+processClientKeyXchg ctx (CKX_DH clientDHValue) = do+ rver <- usingState_ ctx getVersion+ role <- usingState_ ctx getRole++ serverParams <- usingHState ctx getServerDHParams+ let params = serverDHParamsToParams serverParams+ unless (dhValid params $ dhUnwrapPublic clientDHValue) $+ throwCore $+ Error_Protocol "invalid client public key" IllegalParameter++ dhpriv <- usingHState ctx getDHPrivate+ let preMain = dhGetShared params dhpriv clientDHValue+ mainSecret <- usingHState ctx $ setMainSecretFromPre rver role preMain+ logKey ctx (MainSecret mainSecret)+processClientKeyXchg ctx (CKX_ECDH bytes) = do+ ServerECDHParams grp _ <- usingHState ctx getServerECDHParams+ case groupDecodePublicB grp bytes of+ Left _ ->+ throwCore $+ Error_Protocol "client public key cannot be decoded" IllegalParameter+ Right clipub -> do+ grpSpris <- usingHState ctx getGroupPrivate+ case lookup grp grpSpris of+ Nothing -> throwCore err+ Just srvpri -> case groupDecapsulate clipub srvpri of+ Just preMain -> do+ rver <- usingState_ ctx getVersion+ role <- usingState_ ctx getRole+ mainSecret <- usingHState ctx $ setMainSecretFromPre rver role preMain+ logKey ctx (MainSecret mainSecret)+ Nothing -> throwCore err+ where+ err = Error_Protocol "cannot generate a shared secret on ECDH" IllegalParameter++----------------------------------------------------------------++processClientCertVerify+ :: ServerParams -> Context -> CertificateChain -> Bool -> IO ()+processClientCertVerify _sparams ctx certs True = do+ -- When verification succeeds, commit the+ -- client certificate chain to the context.+ --+ usingState_ ctx $ setClientCertificateChain certs+ return ()+processClientCertVerify sparams ctx certs False = do+ -- Either verification failed because of an+ -- invalid format (with an error message), or+ -- the signature is wrong. In either case,+ -- ask the application if it wants to+ -- proceed, we will do that.+ res <- onUnverifiedClientCert (serverHooks sparams)+ if res+ then do+ -- When verification fails, but the+ -- application callbacks accepts, we+ -- also commit the client certificate+ -- chain to the context.+ usingState_ ctx $ setClientCertificateChain certs+ else decryptError "verification failed"++----------------------------------------------------------------++recvCCSandFinished :: Context -> IO ()+recvCCSandFinished ctx = runRecvState ctx $ RecvStatePacket $ expectChangeCipherSpec ctx
+ Network/TLS/Handshake/Server/TLS13.hs view
@@ -0,0 +1,387 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Network.TLS.Handshake.Server.TLS13 (+ recvClientSecondFlight13,+ requestCertificateServer,+ keyUpdate,+ updateKey,+ KeyUpdateRequest (..),+) where++import Control.Exception+import Control.Monad.State.Strict+import Data.IORef++import Network.TLS.Cipher+import Network.TLS.Context.Internal+import Network.TLS.Crypto+import Network.TLS.Extension+import Network.TLS.Handshake.Common hiding (expectFinished)+import Network.TLS.Handshake.Common13+import Network.TLS.Handshake.Key+import Network.TLS.Handshake.Server.Common+import Network.TLS.Handshake.Signature+import Network.TLS.Handshake.State+import Network.TLS.Handshake.State13+import Network.TLS.Handshake.TranscriptHash+import Network.TLS.IO+import Network.TLS.Imports+import Network.TLS.KeySchedule+import Network.TLS.Parameters+import Network.TLS.Session+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Types+import Network.TLS.Util+import Network.TLS.X509++----------------------------------------------------------------++recvClientSecondFlight13+ :: ServerParams+ -> Context+ -> ( SecretTriple ApplicationSecret+ , ClientTrafficSecret HandshakeSecret+ , Bool+ , Bool+ )+ -> ClientHello+ -> IO ()+recvClientSecondFlight13 sparams ctx (appKey, clientHandshakeSecret, authenticated, rtt0OK) CH{..} = do+ sfSentTime <- getCurrentTimeFromBase+ let expectFinished' =+ expectFinished sparams ctx chExtensions appKey clientHandshakeSecret sfSentTime+ if not authenticated && serverWantClientCert sparams+ then runRecvHandshake13 $ do+ -- RFC 8446 Sec 4.4.3: Clients MUST send this message+ -- whenever authenticating via a certificate (i.e., when the+ -- Certificate message is non-empty). When sent, this message MUST+ -- appear immediately after the Certificate message and immediately+ -- prior to the Finished message.+ skip <- recvHandshake13 ctx $ expectCertificate sparams ctx+ unless skip $+ recvHandshake13hash ctx "CertVerify" (expectCertVerify sparams ctx)+ recvHandshake13hash ctx "Finished" expectFinished'+ ensureRecvComplete ctx+ else+ if rtt0OK && not (ctxQUICMode ctx)+ then+ setPendingRecvActions+ ctx+ [ PendingRecvAction True $ expectEndOfEarlyData ctx clientHandshakeSecret+ , PendingRecvActionHash True $+ expectFinished sparams ctx chExtensions appKey clientHandshakeSecret sfSentTime+ ]+ else runRecvHandshake13 $ do+ recvHandshake13hash ctx "Finished" expectFinished'+ ensureRecvComplete ctx++expectFinished+ :: MonadIO m+ => ServerParams+ -> Context+ -> [ExtensionRaw]+ -> SecretTriple ApplicationSecret+ -> ClientTrafficSecret HandshakeSecret+ -> Word64+ -> TranscriptHash+ -> Handshake13+ -> m ()+expectFinished sparams ctx exts appKey clientHandshakeSecret sfSentTime hChBeforeCf (Finished13 verifyData) = liftIO $ do+ modifyTLS13State ctx $ \st -> st{tls13stRecvCF = True}+ (usedHash, usedCipher, _, _) <- getRxRecordState ctx+ let ClientTrafficSecret chs = clientHandshakeSecret+ checkFinished ctx usedHash chs hChBeforeCf verifyData+ finishHandshake13 ctx+ setRxRecordState ctx usedHash usedCipher clientApplicationSecret0+ sendNewSessionTicket sparams ctx usedCipher exts applicationSecret sfSentTime+ where+ applicationSecret = triBase appKey+ clientApplicationSecret0 = triClient appKey+expectFinished _ _ _ _ _ _ _ hs = unexpected (show hs) (Just "finished 13")++expectEndOfEarlyData+ :: Context+ -> ClientTrafficSecret HandshakeSecret+ -> Handshake13+ -> IO ()+expectEndOfEarlyData ctx clientHandshakeSecret EndOfEarlyData13 = do+ (usedHash, usedCipher, _, _) <- getRxRecordState ctx+ setRxRecordState ctx usedHash usedCipher clientHandshakeSecret+expectEndOfEarlyData _ _ hs = unexpected (show hs) (Just "end of early data")++expectCertificate+ :: MonadIO m => ServerParams -> Context -> Handshake13 -> m Bool+expectCertificate sparams ctx (Certificate13 certCtx (CertificateChain_ certs) _ext) = liftIO $ do+ when (certCtx /= "") $+ throwCore $+ Error_Protocol "certificate request context MUST be empty" IllegalParameter+ -- fixme checking _ext+ clientCertificate sparams ctx certs+ return $ isNullCertificateChain certs+expectCertificate sparams ctx (CompressedCertificate13 certCtx (CertificateChain_ certs) _ext) = liftIO $ do+ when (certCtx /= "") $+ throwCore $+ Error_Protocol "certificate request context MUST be empty" IllegalParameter+ -- fixme checking _ext+ clientCertificate sparams ctx certs+ return $ isNullCertificateChain certs+expectCertificate _ _ hs = unexpected (show hs) (Just "certificate 13")++sendNewSessionTicket+ :: ServerParams+ -> Context+ -> Cipher+ -> [ExtensionRaw]+ -> BaseSecret ApplicationSecret+ -> Word64+ -> IO ()+sendNewSessionTicket sparams ctx usedCipher exts applicationSecret sfSentTime = when sendNST $ do+ cfRecvTime <- getCurrentTimeFromBase+ let rtt = cfRecvTime - sfSentTime+ nonce <- TicketNonce <$> getStateRNG ctx 32+ resumptionSecret <- calculateResumptionSecret ctx choice applicationSecret+ let life = adjustLifetime $ serverTicketLifetime sparams+ psk = derivePSK choice resumptionSecret nonce+ (identity, add) <- generateSession life psk rtt0max rtt+ let nst = createNewSessionTicket life add nonce identity rtt0max+ sendPacket13 ctx $ Handshake13 [nst] []+ where+ choice = makeCipherChoice TLS13 usedCipher+ rtt0max = safeNonNegative32 $ serverEarlyDataSize sparams+ sendNST = PSK_DHE_KE `elem` dhModes++ dhModes = case extensionLookup EID_PskKeyExchangeModes exts+ >>= extensionDecode MsgTClientHello of+ Just (PskKeyExchangeModes ms) -> ms+ Nothing -> []++ generateSession life psk maxSize rtt = do+ Session (Just sessionId) <- newSession ctx+ tinfo <- createTLS13TicketInfo life (Left ctx) (Just rtt)+ sdata <- getSessionData13 ctx usedCipher tinfo maxSize psk+ let mgr = sharedSessionManager $ serverShared sparams+ mticket <- sessionEstablish mgr sessionId sdata+ let identity = SessionIDorTicket_ $ fromMaybe sessionId mticket+ return (identity, ageAdd tinfo)++ createNewSessionTicket life add nonce identity maxSize =+ NewSessionTicket13 life add nonce identity nstExtensions+ where+ nstExtensions+ | maxSize == 0 = []+ | otherwise = [earlyDataExt]+ where+ earlyDataExt = toExtensionRaw $ EarlyDataIndication $ Just $ fromIntegral maxSize+ adjustLifetime i+ | i < 0 = 0+ | i > 604800 = 604800+ | otherwise = fromIntegral i++expectCertVerify+ :: MonadIO m+ => ServerParams -> Context -> TranscriptHash -> Handshake13 -> m ()+expectCertVerify sparams ctx (TranscriptHash hChCc) (CertVerify13 (DigitallySigned sigAlg sig)) = liftIO $ do+ certs@(CertificateChain cc) <-+ checkValidClientCertChain ctx "invalid client certificate chain"+ pubkey <- case cc of+ [] -> throwCore $ Error_Protocol "client certificate missing" HandshakeFailure+ c : _ -> return $ certPubKey $ getCertificate c+ ver <- usingState_ ctx getVersion+ checkDigitalSignatureKey ver pubkey+ usingHState ctx $ setPublicKey pubkey+ verif <- checkCertVerify ctx pubkey sigAlg sig hChCc+ clientCertVerify sparams ctx certs verif+expectCertVerify _ _ _ hs = unexpected (show hs) (Just "certificate verify 13")++clientCertVerify :: ServerParams -> Context -> CertificateChain -> Bool -> IO ()+clientCertVerify sparams ctx certs verif = do+ if verif+ then do+ -- When verification succeeds, commit the+ -- client certificate chain to the context.+ --+ usingState_ ctx $ setClientCertificateChain certs+ return ()+ else do+ -- Either verification failed because of an+ -- invalid format (with an error message), or+ -- the signature is wrong. In either case,+ -- ask the application if it wants to+ -- proceed, we will do that.+ res <- liftIO $ onUnverifiedClientCert (serverHooks sparams)+ if res+ then do+ -- When verification fails, but the+ -- application callbacks accepts, we+ -- also commit the client certificate+ -- chain to the context.+ usingState_ ctx $ setClientCertificateChain certs+ else decryptError "verification failed"++----------------------------------------------------------------++newCertReqContext :: Context -> IO CertReqContext+newCertReqContext ctx = getStateRNG ctx 32++requestCertificateServer :: ServerParams -> Context -> IO Bool+requestCertificateServer sparams ctx = handleEx ctx $ do+ tls13 <- tls13orLater ctx+ supportsPHA <- usingState_ ctx getTLS13ClientSupportsPHA+ let ok = tls13 && supportsPHA+ if ok+ then newIORef [] >>= sendCertReqAndRecv+ else return ok+ where+ sendCertReqAndRecv ref = do+ origCertReqCtx <- newCertReqContext ctx+ let certReq13 = makeCertRequest sparams ctx origCertReqCtx False+ _ <- withWriteLock ctx $ do+ bracket (saveHState ctx) (restoreHState ctx) $ \_ -> do+ sendPacket13 ctx $ Handshake13 [certReq13] []+ withReadLock ctx $ do+ (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+ (certVerify13, bCertVerify13) <- getHandshake ctx ref+ expectCertVerify sparams ctx th certVerify13+ updateTranscriptHash13 ctx (certVerify13, bCertVerify13)+ (finished13, _bFinished13) <- getHandshake ctx ref+ expectClientFinished ctx finished13+ void $ restoreHState ctx baseHState -- fixme+ return True++-- saving appdata and key update?+-- error handling+getHandshake+ :: Context -> IORef [Handshake13R] -> IO Handshake13R+getHandshake ctx ref = do+ hhs <- readIORef ref+ if null hhs+ then do+ ex <- recvPacket13 ctx+ either (terminate ctx) process ex+ else chk hhs+ where+ process (Handshake13 hss bss) = chk $ zip hss bss+ process _ =+ terminate ctx $+ Error_Protocol "post handshake authenticated" UnexpectedMessage+ chk [] = getHandshake ctx ref+ chk ((KeyUpdate13 mode, _) : hbs) = do+ 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+ -- updated.+ when (mode == UpdateRequested) $ withWriteLock ctx $ do+ sendPacket13 ctx $ Handshake13 [KeyUpdate13 UpdateNotRequested] []+ keyUpdate ctx getTxRecordState setTxRecordState+ chk hbs+ chk (hb : hbs) = do+ writeIORef ref hbs+ return hb++expectClientCertificate+ :: ServerParams -> Context -> CertReqContext -> Handshake13 -> IO Bool+expectClientCertificate sparams ctx origCertReqCtx (Certificate13 certReqCtx (CertificateChain_ certs) _ext) = do+ expectClientCertificate' sparams ctx origCertReqCtx certReqCtx certs+ return $ isNullCertificateChain certs+expectClientCertificate sparams ctx origCertReqCtx (CompressedCertificate13 certReqCtx (CertificateChain_ certs) _ext) = do+ expectClientCertificate' sparams ctx origCertReqCtx certReqCtx certs+ return $ isNullCertificateChain certs+expectClientCertificate _ _ _ h = unexpected "Certificate" $ Just $ show h++expectClientCertificate'+ :: ServerParams+ -> Context+ -> CertReqContext+ -> CertReqContext+ -> CertificateChain+ -> IO ()+expectClientCertificate' sparams ctx origCertReqCtx certReqCtx certs = do+ when (origCertReqCtx /= certReqCtx) $+ throwCore $+ Error_Protocol "certificate context is wrong" IllegalParameter+ void $ clientCertificate sparams ctx certs++expectClientFinished :: Context -> Handshake13 -> IO ()+expectClientFinished ctx (Finished13 verifyData) = do+ (usedHash, _, level, applicationSecretN) <- getRxRecordState ctx+ unless (level == CryptApplicationSecret) $+ throwCore $+ Error_Protocol+ "tried post-handshake authentication without application traffic secret"+ InternalError+ hChBeforeCf <- transcriptHash ctx "CH..<CF"+ checkFinished ctx usedHash applicationSecretN hChBeforeCf verifyData+expectClientFinished _ h = unexpected "Finished" $ Just $ show h++terminate :: Context -> TLSError -> IO a+terminate ctx err = do+ let (level, desc) = errorToAlert err+ reason = errorToAlertMessage err+ send = sendPacket13 ctx . Alert13+ catchException (send [(level, desc)]) (\_ -> return ())+ setEOF ctx+ 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+ Just e | Uncontextualized e' <- e -> e'+ _ -> Error_Misc (show exception)+ sendPacket13 ctx $ Alert13 [errorToAlert tlserror]+ void $ throwIO $ PostHandshake tlserror+ return False++----------------------------------------------------------------++keyUpdate+ :: Context+ -> (Context -> IO (Hash, Cipher, CryptLevel, Secret))+ -> (Context -> Hash -> Cipher -> AnyTrafficSecret ApplicationSecret -> IO ())+ -> IO ()+keyUpdate ctx getState setState = do+ (usedHash, usedCipher, level, applicationSecretN) <- getState ctx+ unless (level == CryptApplicationSecret) $+ throwCore $+ Error_Protocol+ "tried key update without application traffic secret"+ InternalError+ let applicationSecretN1 =+ hkdfExpandLabel usedHash applicationSecretN "traffic upd" "" $+ hashDigestSize usedHash+ setState ctx usedHash usedCipher (AnyTrafficSecret applicationSecretN1)++-- | How to update keys in TLS 1.3+data KeyUpdateRequest+ = -- | Unidirectional key update+ OneWay+ | -- | Bidirectional key update (normal case)+ TwoWay+ deriving (Eq, Show)++-- | 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+updateKey ctx way = liftIO $ do+ tls13 <- tls13orLater ctx+ when tls13 $ do+ let req = case way of+ OneWay -> UpdateNotRequested+ TwoWay -> UpdateRequested+ -- Write lock wraps both actions because we don't want another packet to+ -- be sent by another thread before the Tx state is updated.+ withWriteLock ctx $ do+ sendPacket13 ctx $ Handshake13 [KeyUpdate13 req] []+ keyUpdate ctx getTxRecordState setTxRecordState+ return tls13
Network/TLS/Handshake/Signature.hs view
@@ -1,109 +1,305 @@ {-# LANGUAGE OverloadedStrings #-}--- |--- Module : Network.TLS.Handshake.Signature--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Handshake.Signature- ( getHashAndASN1- , prepareCertificateVerifySignatureData- , signatureHashData- , signatureCreate- , signatureVerify- , signatureVerifyWithHashDescr- , generateSignedDHParams- ) where -import Crypto.PubKey.HashDescr-import Network.TLS.Crypto+module Network.TLS.Handshake.Signature (+ createCertificateVerify,+ checkCertificateVerify,+ digitallySignDHParams,+ digitallySignECDHParams,+ digitallySignDHParamsVerify,+ digitallySignECDHParamsVerify,+ checkSupportedHashSignature,+ certificateCompatible,+ signatureCompatible,+ signatureCompatible13,+ hashSigToCertType,+ signatureParams,+ decryptError,+) where++import Control.Monad.State.Strict+ import Network.TLS.Context.Internal-import Network.TLS.Struct-import Network.TLS.Packet (generateCertificateVerify_SSL, encodeSignedDHParams)-import Network.TLS.State-import Network.TLS.Handshake.State+import Network.TLS.Crypto import Network.TLS.Handshake.Key-import Network.TLS.Util+import Network.TLS.Handshake.State+import Network.TLS.Imports+import Network.TLS.Packet (+ encodeSignedDHParams,+ encodeSignedECDHParams,+ )+import Network.TLS.Parameters+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.X509 -import Control.Applicative-import Control.Monad.State+decryptError :: MonadIO m => String -> m a+decryptError msg = throwCore $ Error_Protocol msg DecryptError -getHashAndASN1 :: MonadIO m => (HashAlgorithm, SignatureAlgorithm) -> m HashDescr-getHashAndASN1 hashSig = case hashSig of- (HashSHA1, SignatureRSA) -> return hashDescrSHA1- (HashSHA224, SignatureRSA) -> return hashDescrSHA224- (HashSHA256, SignatureRSA) -> return hashDescrSHA256- (HashSHA384, SignatureRSA) -> return hashDescrSHA384- (HashSHA512, SignatureRSA) -> return hashDescrSHA512- _ -> throwCore $ Error_Misc "unsupported hash/sig algorithm"+-- | Check that the key is compatible with a list of 'CertificateType' values.+-- Ed25519 and Ed448 have no assigned code point and are checked with extension+-- "signature_algorithms" only.+certificateCompatible :: PubKey -> [CertificateType] -> Bool+certificateCompatible (PubKeyRSA _) cTypes = CertificateType_RSA_Sign `elem` cTypes+certificateCompatible (PubKeyDSA _) cTypes = CertificateType_DSA_Sign `elem` cTypes+certificateCompatible (PubKeyEC _) cTypes = CertificateType_ECDSA_Sign `elem` cTypes+certificateCompatible (PubKeyEd25519 _) _ = True+certificateCompatible (PubKeyEd448 _) _ = True+certificateCompatible _ _ = False -prepareCertificateVerifySignatureData :: Context- -> Version- -> Maybe HashAndSignatureAlgorithm- -> Bytes- -> IO (HashDescr, Bytes)-prepareCertificateVerifySignatureData ctx usedVersion malg msgs- | usedVersion == SSL3 = do- Just masterSecret <- usingHState ctx $ gets hstMasterSecret- let digest = generateCertificateVerify_SSL masterSecret (hashUpdate (hashInit hashMD5SHA1) msgs)- hsh = HashDescr id id- return (hsh, digest)- | usedVersion == TLS10 || usedVersion == TLS11 = do- let hashf bs = hashFinal (hashUpdate (hashInit hashMD5SHA1) bs)- hsh = HashDescr hashf id- return (hsh, msgs)- | otherwise = do- let Just hashSig = malg- hsh <- getHashAndASN1 hashSig- return (hsh, msgs)+signatureCompatible :: PubKey -> HashAndSignatureAlgorithm -> Bool+signatureCompatible (PubKeyRSA pk) (HashSHA1, SignatureRSA) = kxCanUseRSApkcs1 pk SHA1+signatureCompatible (PubKeyRSA pk) (HashSHA256, SignatureRSA) = kxCanUseRSApkcs1 pk SHA256+signatureCompatible (PubKeyRSA pk) (HashSHA384, SignatureRSA) = kxCanUseRSApkcs1 pk SHA384+signatureCompatible (PubKeyRSA pk) (HashSHA512, SignatureRSA) = kxCanUseRSApkcs1 pk SHA512+signatureCompatible (PubKeyRSA pk) (_, SignatureRSApssRSAeSHA256) = kxCanUseRSApss pk SHA256+signatureCompatible (PubKeyRSA pk) (_, SignatureRSApssRSAeSHA384) = kxCanUseRSApss pk SHA384+signatureCompatible (PubKeyRSA pk) (_, SignatureRSApssRSAeSHA512) = kxCanUseRSApss pk SHA512+signatureCompatible (PubKeyDSA _) (_, SignatureDSA) = True+signatureCompatible (PubKeyEC _) (_, SignatureECDSA) = True+signatureCompatible (PubKeyEd25519 _) (_, SignatureEd25519) = True+signatureCompatible (PubKeyEd448 _) (_, SignatureEd448) = True+signatureCompatible _ (_, _) = False -signatureHashData :: SignatureAlgorithm -> Maybe HashAlgorithm -> HashDescr-signatureHashData SignatureRSA mhash =- case mhash of- Just HashSHA512 -> hashDescrSHA512- Just HashSHA256 -> hashDescrSHA256- Just HashSHA1 -> hashDescrSHA1- Nothing -> HashDescr (hashFinal . hashUpdate (hashInit hashMD5SHA1)) id- _ -> error ("unimplemented signature hash type")-signatureHashData SignatureDSS mhash =- case mhash of- Nothing -> hashDescrSHA1- Just HashSHA1 -> hashDescrSHA1- Just _ -> error "invalid DSA hash choice, only SHA1 allowed"-signatureHashData sig _ = error ("unimplemented signature type: " ++ show sig)+-- 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+signatureCompatible13 (PubKeyEC ecPub) (h, SignatureECDSA) =+ maybe False (\g -> findEllipticCurveGroup ecPub == Just g) (hashCurve h)+ where+ hashCurve HashSHA256 = Just P256+ hashCurve HashSHA384 = Just P384+ hashCurve HashSHA512 = Just P521+ hashCurve _ = Nothing+signatureCompatible13 pub hs = signatureCompatible pub hs -signatureCreate :: Context -> Maybe HashAndSignatureAlgorithm -> HashDescr -> Bytes -> IO DigitallySigned-signatureCreate ctx malg hashMethod toSign = do- cc <- usingState_ ctx $ isClientContext- DigitallySigned malg <$> signRSA ctx cc hashMethod toSign+-- | Translate a 'HashAndSignatureAlgorithm' to an acceptable 'CertificateType'.+-- Perhaps this needs to take supported groups into account, so that, for+-- example, if we don't support any shared ECDSA groups with the server, we+-- return 'Nothing' rather than 'CertificateType_ECDSA_Sign'.+--+-- Therefore, this interface is preliminary. It gets us moving in the right+-- direction. The interplay between all the various TLS extensions and+-- certificate selection is rather complex.+--+-- The goal is to ensure that the client certificate request callback only sees+-- 'CertificateType' values that are supported by the library and also+-- compatible with the server signature algorithms extension.+--+-- Since we don't yet support ECDSA private keys, the caller will use+-- 'lastSupportedCertificateType' to filter those out for now, leaving just+-- @RSA@ as the only supported client certificate algorithm for TLS 1.3.+--+-- FIXME: Add RSA_PSS_PSS signatures when supported.+hashSigToCertType :: HashAndSignatureAlgorithm -> Maybe CertificateType+--+hashSigToCertType (_, SignatureRSA) = Just CertificateType_RSA_Sign+--+hashSigToCertType (_, SignatureDSA) = Just CertificateType_DSA_Sign+--+hashSigToCertType (_, SignatureECDSA) = Just CertificateType_ECDSA_Sign+--+hashSigToCertType (HashIntrinsic, SignatureRSApssRSAeSHA256) =+ Just CertificateType_RSA_Sign+hashSigToCertType (HashIntrinsic, SignatureRSApssRSAeSHA384) =+ Just CertificateType_RSA_Sign+hashSigToCertType (HashIntrinsic, SignatureRSApssRSAeSHA512) =+ Just CertificateType_RSA_Sign+hashSigToCertType (HashIntrinsic, SignatureEd25519) =+ Just CertificateType_Ed25519_Sign+hashSigToCertType (HashIntrinsic, SignatureEd448) =+ Just CertificateType_Ed448_Sign+--+hashSigToCertType _ = Nothing -signatureVerify :: Context -> SignatureAlgorithm -> Bytes -> DigitallySigned -> IO Bool-signatureVerify ctx sigAlgExpected toVerify digSig@(DigitallySigned hashSigAlg _) = do+checkCertificateVerify+ :: Context+ -> Version+ -> PubKey+ -> ByteString+ -> DigitallySigned+ -> IO Bool+checkCertificateVerify ctx usedVersion pubKey msgs digSig@(DigitallySigned hashSigAlg _)+ | pubKey `signatureCompatible` hashSigAlg = doVerify+ | otherwise = return False+ where+ doVerify =+ prepareCertificateVerifySignatureData ctx usedVersion pubKey hashSigAlg msgs+ >>= signatureVerifyWithCertVerifyData ctx digSig++createCertificateVerify+ :: Context+ -> Version+ -> PubKey+ -> HashAndSignatureAlgorithm -- TLS12 only+ -> ByteString+ -> IO DigitallySigned+createCertificateVerify ctx usedVersion pubKey hashSigAlg msgs =+ prepareCertificateVerifySignatureData ctx usedVersion pubKey hashSigAlg msgs+ >>= signatureCreateWithCertVerifyData ctx hashSigAlg++type CertVerifyData = (SignatureParams, ByteString)++-- in the case of TLS < 1.2, RSA signing, then the data need to be hashed first, as+-- the SHA1_MD5 algorithm expect an already digested data+buildVerifyData :: SignatureParams -> ByteString -> CertVerifyData+buildVerifyData (RSAParams SHA1_MD5 enc) bs = (RSAParams SHA1_MD5 enc, hashFinal $ hashUpdate (hashInit SHA1_MD5) bs)+buildVerifyData sigParam bs = (sigParam, bs)++prepareCertificateVerifySignatureData+ :: Context+ -> Version+ -> PubKey+ -> HashAndSignatureAlgorithm -- TLS12 only+ -> ByteString+ -> IO CertVerifyData+prepareCertificateVerifySignatureData _ctx _usedVersion pubKey hashSigAlg msgs =+ return (signatureParams pubKey hashSigAlg, msgs)++signatureParams :: PubKey -> HashAndSignatureAlgorithm -> SignatureParams+signatureParams (PubKeyRSA _) hashSigAlg =+ case hashSigAlg of+ (HashSHA512, SignatureRSA) -> RSAParams SHA512 RSApkcs1+ (HashSHA384, SignatureRSA) -> RSAParams SHA384 RSApkcs1+ (HashSHA256, SignatureRSA) -> RSAParams SHA256 RSApkcs1+ (HashSHA1, SignatureRSA) -> RSAParams SHA1 RSApkcs1+ (HashIntrinsic, SignatureRSApssRSAeSHA512) -> RSAParams SHA512 RSApss+ (HashIntrinsic, SignatureRSApssRSAeSHA384) -> RSAParams SHA384 RSApss+ (HashIntrinsic, SignatureRSApssRSAeSHA256) -> RSAParams SHA256 RSApss+ (hsh, SignatureRSA) -> error ("unimplemented RSA signature hash type: " ++ show hsh)+ (_, sigAlg) ->+ error ("signature algorithm is incompatible with RSA: " ++ show sigAlg)+signatureParams (PubKeyDSA _) hashSigAlg =+ case hashSigAlg of+ (HashSHA1, SignatureDSA) -> DSAParams+ (_, SignatureDSA) -> error "invalid DSA hash choice, only SHA1 allowed"+ (_, sigAlg) ->+ error ("signature algorithm is incompatible with DSA: " ++ show sigAlg)+signatureParams (PubKeyEC _) hashSigAlg =+ case hashSigAlg of+ (HashSHA512, SignatureECDSA) -> ECDSAParams SHA512+ (HashSHA384, SignatureECDSA) -> ECDSAParams SHA384+ (HashSHA256, SignatureECDSA) -> ECDSAParams SHA256+ (HashSHA1, SignatureECDSA) -> ECDSAParams SHA1+ (hsh, SignatureECDSA) -> error ("unimplemented ECDSA signature hash type: " ++ show hsh)+ (_, sigAlg) ->+ error ("signature algorithm is incompatible with ECDSA: " ++ show sigAlg)+signatureParams (PubKeyEd25519 _) hashSigAlg =+ case hashSigAlg of+ (HashIntrinsic, SignatureEd25519) -> Ed25519Params+ (hsh, SignatureEd25519) -> error ("unimplemented Ed25519 signature hash type: " ++ show hsh)+ (_, sigAlg) ->+ error ("signature algorithm is incompatible with Ed25519: " ++ show sigAlg)+signatureParams (PubKeyEd448 _) hashSigAlg =+ case hashSigAlg of+ (HashIntrinsic, SignatureEd448) -> Ed448Params+ (hsh, SignatureEd448) -> error ("unimplemented Ed448 signature hash type: " ++ show hsh)+ (_, sigAlg) ->+ error ("signature algorithm is incompatible with Ed448: " ++ show sigAlg)+signatureParams pk _ = error ("signatureParams: " ++ pubkeyType pk ++ " is not supported")++signatureCreateWithCertVerifyData+ :: Context+ -> HashAndSignatureAlgorithm+ -> CertVerifyData+ -> IO DigitallySigned+signatureCreateWithCertVerifyData ctx malg (sigParam, toSign) = do+ role <- usingState_ ctx getRole+ DigitallySigned malg <$> signPrivate ctx role sigParam toSign++signatureVerify :: Context -> DigitallySigned -> PubKey -> ByteString -> IO Bool+signatureVerify ctx digSig@(DigitallySigned hashSigAlg _) pubKey toVerifyData = do usedVersion <- usingState_ ctx getVersion- let hashDescr = case (usedVersion, hashSigAlg) of- (TLS12, Nothing) -> error "expecting hash and signature algorithm in a TLS12 digitally signed structure"- (TLS12, Just (h,s)) | s == sigAlgExpected -> signatureHashData sigAlgExpected (Just h)- | otherwise -> error "expecting different signature algorithm"- (_, Nothing) -> signatureHashData sigAlgExpected Nothing- (_, Just _) -> error "not expecting hash and signature algorithm in a < TLS12 digitially signed structure"- signatureVerifyWithHashDescr ctx sigAlgExpected hashDescr toVerify digSig+ 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) -signatureVerifyWithHashDescr :: Context- -> SignatureAlgorithm- -> HashDescr- -> Bytes- -> DigitallySigned- -> IO Bool-signatureVerifyWithHashDescr ctx sigAlgExpected hashDescr toVerify (DigitallySigned _ bs) = do- cc <- usingState_ ctx $ isClientContext- case sigAlgExpected of- SignatureRSA -> verifyRSA ctx cc hashDescr toVerify bs- SignatureDSS -> verifyRSA ctx cc hashDescr toVerify bs- _ -> error "not implemented yet"+signatureVerifyWithCertVerifyData+ :: Context+ -> DigitallySigned+ -> CertVerifyData+ -> IO Bool+signatureVerifyWithCertVerifyData ctx (DigitallySigned hs bs) (sigParam, toVerify) = do+ checkSupportedHashSignature ctx hs+ verifyPublic ctx sigParam toVerify bs -generateSignedDHParams :: Context -> ServerDHParams -> IO Bytes-generateSignedDHParams ctx serverParams = do- (cran, sran) <- usingHState ctx $ do- (,) <$> gets hstClientRandom <*> (fromJust "server random" <$> gets hstServerRandom)- return $ encodeSignedDHParams cran sran serverParams+digitallySignParams+ :: Context+ -> ByteString+ -> PubKey+ -> HashAndSignatureAlgorithm+ -> IO DigitallySigned+digitallySignParams ctx signatureData pubKey hashSigAlg =+ let sigParam = signatureParams pubKey hashSigAlg+ in signatureCreateWithCertVerifyData+ ctx+ hashSigAlg+ (buildVerifyData sigParam signatureData)++digitallySignDHParams+ :: Context+ -> ServerDHParams+ -> PubKey+ -> HashAndSignatureAlgorithm -- TLS12 only+ -> IO DigitallySigned+digitallySignDHParams ctx serverParams pubKey mhash = do+ dhParamsData <-+ withClientAndServerRandom ctx $ encodeSignedDHParams serverParams+ digitallySignParams ctx dhParamsData pubKey mhash++digitallySignECDHParams+ :: Context+ -> ServerECDHParams+ -> PubKey+ -> HashAndSignatureAlgorithm -- TLS12 only+ -> IO DigitallySigned+digitallySignECDHParams ctx serverParams pubKey mhash = do+ ecdhParamsData <-+ withClientAndServerRandom ctx $ encodeSignedECDHParams serverParams+ digitallySignParams ctx ecdhParamsData pubKey mhash++digitallySignDHParamsVerify+ :: Context+ -> ServerDHParams+ -> PubKey+ -> DigitallySigned+ -> IO Bool+digitallySignDHParamsVerify ctx dhparams pubKey signature = do+ expectedData <- withClientAndServerRandom ctx $ encodeSignedDHParams dhparams+ signatureVerify ctx signature pubKey expectedData++digitallySignECDHParamsVerify+ :: Context+ -> ServerECDHParams+ -> PubKey+ -> DigitallySigned+ -> IO Bool+digitallySignECDHParamsVerify ctx dhparams pubKey signature = do+ expectedData <- withClientAndServerRandom ctx $ encodeSignedECDHParams dhparams+ signatureVerify ctx signature pubKey expectedData++withClientAndServerRandom+ :: Context -> (ClientRandom -> ServerRandom -> b) -> IO b+withClientAndServerRandom ctx f = do+ (cran, sran) <-+ usingHState ctx $+ (,)+ <$> gets hstClientRandom+ <*> (fromJust <$> gets hstServerRandom)+ return $ f cran sran++-- verify that the hash and signature selected by the peer is supported in+-- the local configuration+checkSupportedHashSignature+ :: Context -> HashAndSignatureAlgorithm -> IO ()+checkSupportedHashSignature ctx hs =+ unless (hs `elem` supportedHashSignatures (ctxSupported ctx)) $+ let msg = "unsupported hash and signature algorithm: " ++ show hs+ in throwCore $ Error_Protocol msg IllegalParameter
Network/TLS/Handshake/State.hs view
@@ -1,262 +1,569 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE CPP #-}--- |--- Module : Network.TLS.Handshake.State--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Handshake.State- ( HandshakeState(..)- , ClientCertRequestData- , HandshakeM- , newEmptyHandshake- , runHandshake+{-# LANGUAGE OverloadedStrings #-}++module Network.TLS.Handshake.State (+ HandshakeState (..),+ TransHashState (..),+ HandshakeMode13 (..),+ RTT0Status (..),+ CertReqCBdata,+ HandshakeM,+ newEmptyHandshake,+ runHandshake,+ -- * key accessors- , setPublicKey- , setPrivateKey- , getLocalPrivateKey- , getRemotePublicKey- , setServerDHParams+ setPublicKey,+ setPublicPrivateKeys,+ getLocalPublicPrivateKeys,+ getRemotePublicKey,+ setServerDHParams,+ getServerDHParams,+ setServerECDHParams,+ getServerECDHParams,+ setDHPrivate,+ getDHPrivate,+ setGroupPrivate,+ getGroupPrivate,+ -- * cert accessors- , setClientCertSent- , getClientCertSent- , setCertReqSent- , getCertReqSent- , setClientCertChain- , getClientCertChain- , setClientCertRequest- , getClientCertRequest+ setClientRandom,+ getClientRandom,+ setOuterClientRandom,+ getOuterClientRandom,+ setClientHello,+ getClientHello,+ setECHEE,+ getECHEE,+ setECHAccepted,+ getECHAccepted,+ setClientCertSent,+ getClientCertSent,+ setCertReqSent,+ getCertReqSent,+ setClientCertChain,+ getClientCertChain,+ setCertReqToken,+ getCertReqToken,+ setCertReqCBdata,+ getCertReqCBdata,+ setCertReqSigAlgsCert,+ getCertReqSigAlgsCert,+ -- * digest accessors- , addHandshakeMessage- , updateHandshakeDigest- , getHandshakeMessages- , getHandshakeDigest- -- * master secret- , setMasterSecret- , setMasterSecretFromPre+ addHandshakeMessage,+ getHandshakeMessages,++ -- * main secret+ setMainSecret,+ setMainSecretFromPre,+ -- * misc accessor- , getPendingCipher- , setServerHelloParameters- ) where+ getPendingCipher,+ setExtendedMainSecret,+ getExtendedMainSecret,+ setSupportedGroup,+ getSupportedGroup,+ setTLS13HandshakeMode,+ getTLS13HandshakeMode,+ setTLS13RTT0Status,+ getTLS13RTT0Status,+ setTLS13EarlySecret,+ getTLS13EarlySecret,+ setTLS13ResumptionSecret,+ getTLS13ResumptionSecret,+ setTLS13CertComp,+ getTLS13CertComp,+ setCCS13Sent,+ getCCS13Sent,+ setCCS13Recv,+ getCCS13Recv,+) where -import Network.TLS.Util-import Network.TLS.Struct-import Network.TLS.Record.State-import Network.TLS.Packet-import Network.TLS.Crypto+import Control.Monad.State.Strict+import Data.ByteArray (convert)+import Data.X509 (CertificateChain)+ import Network.TLS.Cipher import Network.TLS.Compression+import Network.TLS.Crypto+import Network.TLS.Imports+import Network.TLS.Packet+import Network.TLS.Record.State+import Network.TLS.Struct import Network.TLS.Types-import Control.Applicative (Applicative, (<$>))-import Control.Monad.State-import Data.X509 (CertificateChain)+import Network.TLS.Util data HandshakeKeyState = HandshakeKeyState- { hksRemotePublicKey :: !(Maybe PubKey)- , hksLocalPrivateKey :: !(Maybe PrivKey)- } deriving (Show)+ { hksRemotePublicKey :: Maybe PubKey+ , hksLocalPublicPrivateKeys :: Maybe (PubKey, PrivKey)+ }+ deriving (Show) +data TransHashState+ = -- | Initial state+ TransHashState0+ | -- | A raw CH is stored since hash algo is not chosen yet.+ TransHashState1 [ByteString]+ | -- | Hashed+ TransHashState2 HashCtx++{- FOURMOLU_DISABLE -}+instance Show TransHashState where+ show TransHashState0 = "State0 "+ show (TransHashState1 _) = "State1 CH"+ show (TransHashState2 hctx) = showBytesHex $ hashFinal hctx+{- FOURMOLU_ENABLE -}+ data HandshakeState = HandshakeState- { hstClientVersion :: !(Version)- , hstClientRandom :: !ClientRandom- , hstServerRandom :: !(Maybe ServerRandom)- , hstMasterSecret :: !(Maybe Bytes)- , hstKeyState :: !HandshakeKeyState- , hstServerDHParams :: !(Maybe ServerDHParams)- , hstDHPrivate :: !(Maybe DHPrivate)- , hstHandshakeDigest :: !(Either [Bytes] HashCtx)- , hstHandshakeMessages :: [Bytes]- , hstClientCertRequest :: !(Maybe ClientCertRequestData) -- ^ Set to Just-value when certificate request was received- , hstClientCertSent :: !Bool -- ^ Set to true when a client certificate chain was sent- , hstCertReqSent :: !Bool -- ^ Set to true when a certificate request was sent- , hstClientCertChain :: !(Maybe CertificateChain)- , hstPendingTxState :: Maybe RecordState- , hstPendingRxState :: Maybe RecordState- , hstPendingCipher :: Maybe Cipher- , hstPendingCompression :: Compression- } deriving (Show)+ { hstClientVersion :: Version+ , hstClientRandom :: ClientRandom+ -- ^ For ECH, inner client random.+ , hstServerRandom :: Maybe ServerRandom+ , hstMainSecret :: Maybe Secret+ , hstKeyState :: HandshakeKeyState+ , hstServerDHParams :: Maybe ServerDHParams+ , hstDHPrivate :: Maybe DHPrivate+ , hstServerECDHParams :: Maybe ServerECDHParams+ , hstGroupPrivate :: [(Group, GroupPrivate)]+ , hstTransHashState :: TransHashState+ , hstTransHashStateI :: TransHashState -- Inner CH for client ECH+ , hstHandshakeMessages :: [ByteString]+ -- ^ To create certificate verify for TLS 1.2.+ -- This should be removed when TLS 1.2 is dropped.+ , hstCertReqToken :: Maybe ByteString+ -- ^ Set to Just-value when a TLS13 certificate request is received+ , hstCertReqCBdata :: Maybe CertReqCBdata+ -- ^ Set to Just-value when a certificate request is received+ , hstCertReqSigAlgsCert :: Maybe [HashAndSignatureAlgorithm]+ -- ^ In TLS 1.3, these are separate from the certificate+ -- issuer signature algorithm hints in the callback data.+ -- In TLS 1.2 the same list is overloaded for both purposes.+ -- Not present in TLS 1.1 and earlier+ , hstClientCertSent :: Bool+ -- ^ Set to true when a client certificate chain was sent+ , hstCertReqSent :: Bool+ -- ^ Set to true when a certificate request was sent. This applies+ -- only to requests sent during handshake (not post-handshake).+ , hstClientCertChain :: Maybe CertificateChain+ , hstPendingTxState :: Maybe RecordState+ , hstPendingRxState :: Maybe RecordState+ , hstPendingCipher :: Maybe Cipher+ , hstPendingCompression :: Compression+ , hstExtendedMainSecret :: Bool+ , hstSupportedGroup :: Maybe Group+ , hstTLS13HandshakeMode :: HandshakeMode13+ , hstTLS13RTT0Status :: RTT0Status+ , hstTLS13EarlySecret :: Maybe (BaseSecret EarlySecret) -- xxx+ , hstTLS13ResumptionSecret :: Maybe (BaseSecret ResumptionSecret)+ , hstTLS13CertComp :: Bool+ , hstCCS13Sent :: Bool+ , hstCCS13Recv :: Bool+ , hstTLS13OuterClientRandom :: Maybe ClientRandom+ -- ^ Used for key logging in the case of ECH.+ , hstTLS13ClientHello :: Maybe (ClientHello, [ByteString])+ -- ^ Inner client hello in the case of ECH.+ , hstTLS13ECHAccepted :: Bool+ , hstTLS13ECHEE :: Bool+ }+ deriving (Show) -type ClientCertRequestData = ([CertificateType],- Maybe [(HashAlgorithm, SignatureAlgorithm)],- [DistinguishedName])+-- | When we receive a CertificateRequest from a server, a just-in-time+-- callback is issued to the application to obtain a suitable certificate.+-- Somewhat unfortunately, the callback parameters don't abstract away the+-- details of the TLS 1.2 Certificate Request message, which combines the+-- legacy @certificate_types@ and new @supported_signature_algorithms@+-- parameters is a rather subtle way.+--+-- TLS 1.2 also (again unfortunately, in the opinion of the author of this+-- comment) overloads the signature algorithms parameter to constrain not only+-- the algorithms used in TLS, but also the algorithms used by issuing CAs in+-- the X.509 chain. Best practice is to NOT treat such that restriction as a+-- MUST, but rather take it as merely a preference, when a choice exists. If+-- the best chain available does not match the provided signature algorithm+-- list, go ahead and use it anyway, it will probably work, and the server may+-- not even care about the issuer CAs at all, it may be doing DANE or have+-- explicit mappings for the client's public key, ...+--+-- The TLS 1.3 @CertificateRequest@ message, drops @certificate_types@ and no+-- longer overloads @supported_signature_algorithms@ to cover X.509. It also+-- includes a new opaque context token that the client must echo back, which+-- makes certain client authentication replay attacks more difficult. We will+-- store that context separately, it does not need to be presented in the user+-- callback. The certificate signature algorithms preferred by the peer are+-- now in the separate @signature_algorithms_cert@ extension, but we cannot+-- report these to the application callback without an API change. The good+-- news is that filtering the X.509 signature types is generally unnecessary,+-- unwise and difficult. So we just ignore this extension.+--+-- As a result, the information we provide to the callback is no longer a+-- verbatim copy of the certificate request payload. In the case of TLS 1.3+-- The 'CertificateType' list is synthetically generated from the server's+-- @signature_algorithms@ extension, and the @signature_algorithms_certs@+-- extension is ignored.+--+-- Since the original TLS 1.2 'CertificateType' has no provision for the newer+-- certificate types that have appeared in TLS 1.3 we're adding some synthetic+-- values that have no equivalent values in the TLS 1.2 'CertificateType' as+-- defined in the IANA+-- <https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-2+-- TLS ClientCertificateType Identifiers> registry. These values are inferred+-- from the TLS 1.3 @signature_algorithms@ extension, and will allow clients to+-- present Ed25519 and Ed448 certificates when these become supported.+type CertReqCBdata =+ ( [CertificateType]+ , Maybe [HashAndSignatureAlgorithm]+ , [DistinguishedName]+ ) -newtype HandshakeM a = HandshakeM { runHandshakeM :: State HandshakeState a }+newtype HandshakeM a = HandshakeM {runHandshakeM :: State HandshakeState a} deriving (Functor, Applicative, Monad) instance MonadState HandshakeState HandshakeM where put x = HandshakeM (put x)- get = HandshakeM (get)-#if MIN_VERSION_mtl(2,1,0)+ get = HandshakeM get state f = HandshakeM (state f)-#endif -- create a new empty handshake state newEmptyHandshake :: Version -> ClientRandom -> HandshakeState-newEmptyHandshake ver crand = HandshakeState- { hstClientVersion = ver- , hstClientRandom = crand- , hstServerRandom = Nothing- , hstMasterSecret = Nothing- , hstKeyState = HandshakeKeyState Nothing Nothing- , hstServerDHParams = Nothing- , hstDHPrivate = Nothing- , hstHandshakeDigest = Left []- , hstHandshakeMessages = []- , hstClientCertRequest = Nothing- , hstClientCertSent = False- , hstCertReqSent = False- , hstClientCertChain = Nothing- , hstPendingTxState = Nothing- , hstPendingRxState = Nothing- , hstPendingCipher = Nothing- , hstPendingCompression = nullCompression- }+newEmptyHandshake ver crand =+ HandshakeState+ { hstClientVersion = ver+ , hstClientRandom = crand+ , hstServerRandom = Nothing+ , hstMainSecret = Nothing+ , hstKeyState = HandshakeKeyState Nothing Nothing+ , hstServerDHParams = Nothing+ , hstDHPrivate = Nothing+ , hstServerECDHParams = Nothing+ , hstGroupPrivate = []+ , hstTransHashState = TransHashState0+ , hstTransHashStateI = TransHashState0+ , hstHandshakeMessages = []+ , hstCertReqToken = Nothing+ , hstCertReqCBdata = Nothing+ , hstCertReqSigAlgsCert = Nothing+ , hstClientCertSent = False+ , hstCertReqSent = False+ , hstClientCertChain = Nothing+ , hstPendingTxState = Nothing+ , hstPendingRxState = Nothing+ , hstPendingCipher = Nothing+ , hstPendingCompression = nullCompression+ , hstExtendedMainSecret = False+ , hstSupportedGroup = Nothing+ , hstTLS13HandshakeMode = FullHandshake+ , hstTLS13RTT0Status = RTT0None+ , hstTLS13EarlySecret = Nothing+ , hstTLS13ResumptionSecret = Nothing+ , hstTLS13CertComp = False+ , hstCCS13Sent = False+ , hstCCS13Recv = False+ , hstTLS13OuterClientRandom = Nothing+ , hstTLS13ClientHello = Nothing+ , hstTLS13ECHAccepted = False+ , hstTLS13ECHEE = False+ } runHandshake :: HandshakeState -> HandshakeM a -> (a, HandshakeState) runHandshake hst f = runState (runHandshakeM f) hst setPublicKey :: PubKey -> HandshakeM ()-setPublicKey pk = modify (\hst -> hst { hstKeyState = setPK (hstKeyState hst) })- where setPK hks = hks { hksRemotePublicKey = Just pk }+setPublicKey pk = modify' (\hst -> hst{hstKeyState = setPK (hstKeyState hst)})+ where+ setPK hks = hks{hksRemotePublicKey = Just pk} -setPrivateKey :: PrivKey -> HandshakeM ()-setPrivateKey pk = modify (\hst -> hst { hstKeyState = setPK (hstKeyState hst) })- where setPK hks = hks { hksLocalPrivateKey = Just pk }+setPublicPrivateKeys :: (PubKey, PrivKey) -> HandshakeM ()+setPublicPrivateKeys keys = modify' (\hst -> hst{hstKeyState = setKeys (hstKeyState hst)})+ where+ setKeys hks = hks{hksLocalPublicPrivateKeys = Just keys} getRemotePublicKey :: HandshakeM PubKey-getRemotePublicKey = fromJust "remote public key" <$> gets (hksRemotePublicKey . hstKeyState)+getRemotePublicKey = fromJust <$> gets (hksRemotePublicKey . hstKeyState) -getLocalPrivateKey :: HandshakeM PrivKey-getLocalPrivateKey = fromJust "local private key" <$> gets (hksLocalPrivateKey . hstKeyState)+getLocalPublicPrivateKeys :: HandshakeM (PubKey, PrivKey)+getLocalPublicPrivateKeys =+ fromJust <$> gets (hksLocalPublicPrivateKeys . hstKeyState) setServerDHParams :: ServerDHParams -> HandshakeM ()-setServerDHParams shp = modify (\hst -> hst { hstServerDHParams = Just shp })+setServerDHParams shp = modify' (\hst -> hst{hstServerDHParams = Just shp}) +getServerDHParams :: HandshakeM ServerDHParams+getServerDHParams = fromJust <$> gets hstServerDHParams++setServerECDHParams :: ServerECDHParams -> HandshakeM ()+setServerECDHParams shp = modify' (\hst -> hst{hstServerECDHParams = Just shp})++getServerECDHParams :: HandshakeM ServerECDHParams+getServerECDHParams = fromJust <$> gets hstServerECDHParams++setDHPrivate :: DHPrivate -> HandshakeM ()+setDHPrivate shp = modify' (\hst -> hst{hstDHPrivate = Just shp})++getDHPrivate :: HandshakeM DHPrivate+getDHPrivate = fromJust <$> gets hstDHPrivate++getGroupPrivate :: HandshakeM [(Group, GroupPrivate)]+getGroupPrivate = gets hstGroupPrivate++setGroupPrivate :: [(Group, GroupPrivate)] -> HandshakeM ()+setGroupPrivate shp = modify' (\hst -> hst{hstGroupPrivate = shp})++setExtendedMainSecret :: Bool -> HandshakeM ()+setExtendedMainSecret b = modify' (\hst -> hst{hstExtendedMainSecret = b})++getExtendedMainSecret :: HandshakeM Bool+getExtendedMainSecret = gets hstExtendedMainSecret++setSupportedGroup :: Group -> HandshakeM ()+setSupportedGroup g = modify' (\hst -> hst{hstSupportedGroup = Just g})++getSupportedGroup :: HandshakeM (Maybe Group)+getSupportedGroup = gets hstSupportedGroup++-- | Type to show which handshake mode is used in TLS 1.3.+data HandshakeMode13+ = -- | Full handshake is used.+ FullHandshake+ | -- | Full handshake is used with hello retry request.+ HelloRetryRequest+ | -- | Server authentication is skipped.+ PreSharedKey+ | -- | Server authentication is skipped and early data is sent.+ RTT0+ deriving (Show, Eq)++setTLS13HandshakeMode :: HandshakeMode13 -> HandshakeM ()+setTLS13HandshakeMode s = modify' (\hst -> hst{hstTLS13HandshakeMode = s})++getTLS13HandshakeMode :: HandshakeM HandshakeMode13+getTLS13HandshakeMode = gets hstTLS13HandshakeMode++data RTT0Status+ = RTT0None+ | RTT0Sent+ | RTT0Accepted+ | RTT0Rejected+ deriving (Show, Eq)++setTLS13RTT0Status :: RTT0Status -> HandshakeM ()+setTLS13RTT0Status s = modify' (\hst -> hst{hstTLS13RTT0Status = s})++getTLS13RTT0Status :: HandshakeM RTT0Status+getTLS13RTT0Status = gets hstTLS13RTT0Status++setTLS13EarlySecret :: BaseSecret EarlySecret -> HandshakeM ()+setTLS13EarlySecret secret = modify' (\hst -> hst{hstTLS13EarlySecret = Just secret})++getTLS13EarlySecret :: HandshakeM (Maybe (BaseSecret EarlySecret))+getTLS13EarlySecret = gets hstTLS13EarlySecret++setTLS13ResumptionSecret :: BaseSecret ResumptionSecret -> HandshakeM ()+setTLS13ResumptionSecret secret = modify' (\hst -> hst{hstTLS13ResumptionSecret = Just secret})++getTLS13ResumptionSecret :: HandshakeM (Maybe (BaseSecret ResumptionSecret))+getTLS13ResumptionSecret = gets hstTLS13ResumptionSecret++setTLS13CertComp :: Bool -> HandshakeM ()+setTLS13CertComp comp = modify' (\hst -> hst{hstTLS13CertComp = comp})++getTLS13CertComp :: HandshakeM Bool+getTLS13CertComp = gets hstTLS13CertComp++setCCS13Sent :: Bool -> HandshakeM ()+setCCS13Sent sent = modify' (\hst -> hst{hstCCS13Sent = sent})++getCCS13Sent :: HandshakeM Bool+getCCS13Sent = gets hstCCS13Sent++setCCS13Recv :: Bool -> HandshakeM ()+setCCS13Recv sent = modify' (\hst -> hst{hstCCS13Recv = sent})++getCCS13Recv :: HandshakeM Bool+getCCS13Recv = gets hstCCS13Recv+ setCertReqSent :: Bool -> HandshakeM ()-setCertReqSent b = modify (\hst -> hst { hstCertReqSent = b })+setCertReqSent b = modify' (\hst -> hst{hstCertReqSent = b}) getCertReqSent :: HandshakeM Bool getCertReqSent = gets hstCertReqSent setClientCertSent :: Bool -> HandshakeM ()-setClientCertSent b = modify (\hst -> hst { hstClientCertSent = b })+setClientCertSent b = modify' (\hst -> hst{hstClientCertSent = b}) +getClientRandom :: HandshakeM ClientRandom+getClientRandom = gets hstClientRandom++setClientRandom :: ClientRandom -> HandshakeM ()+setClientRandom cr = modify' $ \hst -> hst{hstClientRandom = cr}++getOuterClientRandom :: HandshakeM (Maybe ClientRandom)+getOuterClientRandom = gets hstTLS13OuterClientRandom++setOuterClientRandom :: Maybe ClientRandom -> HandshakeM ()+setOuterClientRandom mcr = modify' (\hst -> hst{hstTLS13OuterClientRandom = mcr})++getClientHello :: HandshakeM (Maybe (ClientHello, [ByteString]))+getClientHello = gets hstTLS13ClientHello++setClientHello :: ClientHello -> [ByteString] -> HandshakeM ()+setClientHello ch b = modify' $ \hst -> hst{hstTLS13ClientHello = Just (ch, b)}++getECHAccepted :: HandshakeM Bool+getECHAccepted = gets hstTLS13ECHAccepted++setECHAccepted :: Bool -> HandshakeM ()+setECHAccepted b = modify' $ \hst -> hst{hstTLS13ECHAccepted = b}++getECHEE :: HandshakeM Bool+getECHEE = gets hstTLS13ECHEE++setECHEE :: Bool -> HandshakeM ()+setECHEE b = modify' $ \hst -> hst{hstTLS13ECHEE = b}+ getClientCertSent :: HandshakeM Bool getClientCertSent = gets hstClientCertSent setClientCertChain :: CertificateChain -> HandshakeM ()-setClientCertChain b = modify (\hst -> hst { hstClientCertChain = Just b })+setClientCertChain b = modify' (\hst -> hst{hstClientCertChain = Just b}) getClientCertChain :: HandshakeM (Maybe CertificateChain) getClientCertChain = gets hstClientCertChain -setClientCertRequest :: ClientCertRequestData -> HandshakeM ()-setClientCertRequest d = modify (\hst -> hst { hstClientCertRequest = Just d })+--+setCertReqToken :: Maybe ByteString -> HandshakeM ()+setCertReqToken token = modify' $ \hst -> hst{hstCertReqToken = token} -getClientCertRequest :: HandshakeM (Maybe ClientCertRequestData)-getClientCertRequest = gets hstClientCertRequest+getCertReqToken :: HandshakeM (Maybe ByteString)+getCertReqToken = gets hstCertReqToken +--+setCertReqCBdata :: Maybe CertReqCBdata -> HandshakeM ()+setCertReqCBdata d = modify' (\hst -> hst{hstCertReqCBdata = d})++getCertReqCBdata :: HandshakeM (Maybe CertReqCBdata)+getCertReqCBdata = gets hstCertReqCBdata++-- Dead code, until we find some use for the extension+setCertReqSigAlgsCert :: Maybe [HashAndSignatureAlgorithm] -> HandshakeM ()+setCertReqSigAlgsCert as = modify' $ \hst -> hst{hstCertReqSigAlgsCert = as}++getCertReqSigAlgsCert :: HandshakeM (Maybe [HashAndSignatureAlgorithm])+getCertReqSigAlgsCert = gets hstCertReqSigAlgsCert++-- getPendingCipher :: HandshakeM Cipher-getPendingCipher = fromJust "pending cipher" <$> gets hstPendingCipher+getPendingCipher = fromJust <$> gets hstPendingCipher -addHandshakeMessage :: Bytes -> HandshakeM ()-addHandshakeMessage content = modify $ \hs -> hs { hstHandshakeMessages = content : hstHandshakeMessages hs}+addHandshakeMessage :: ByteString -> HandshakeM ()+addHandshakeMessage content = modify' $ \hs -> hs{hstHandshakeMessages = content : hstHandshakeMessages hs} -getHandshakeMessages :: HandshakeM [Bytes]+getHandshakeMessages :: HandshakeM [ByteString] getHandshakeMessages = gets (reverse . hstHandshakeMessages) -updateHandshakeDigest :: Bytes -> HandshakeM ()-updateHandshakeDigest content = modify $ \hs -> hs- { hstHandshakeDigest = case hstHandshakeDigest hs of- Left bytes -> Left (content:bytes)- Right hashCtx -> Right $ hashUpdate hashCtx content }--getHandshakeDigest :: Version -> Role -> HandshakeM Bytes-getHandshakeDigest ver role = gets gen- where gen hst = case hstHandshakeDigest hst of- Right hashCtx ->- let msecret = fromJust "master secret" $ hstMasterSecret hst- in generateFinish ver msecret hashCtx- Left _ ->- error "un-initialized handshake digest"- generateFinish | role == ClientRole = generateClientFinished- | otherwise = generateServerFinished+-- | Generate the main secret from the pre-main secret.+setMainSecretFromPre+ :: Version+ -- ^ chosen transmission version+ -> Role+ -- ^ the role (Client or Server) of the generating side+ -> Secret+ -- ^ the pre-main secret+ -> HandshakeM Secret+setMainSecretFromPre ver role preMainSecret = do+ ems <- getExtendedMainSecret+ secret <- if ems then get >>= genExtendedSecret else genSecret <$> get+ setMainSecret ver role secret+ return secret+ where+ genSecret hst =+ generateMainSecret+ ver+ (fromJust $ hstPendingCipher hst)+ preMainSecret+ (hstClientRandom hst)+ (fromJust $ hstServerRandom hst)+ genExtendedSecret hst =+ generateExtendedMainSecret+ ver+ (fromJust $ hstPendingCipher hst)+ preMainSecret+ <$> getSessionHash --- | Generate the master secret from the pre master secret.-setMasterSecretFromPre :: Version -- ^ chosen transmission version- -> Role -- ^ the role (Client or Server) of the generating side- -> Bytes -- ^ the pre master secret- -> HandshakeM ()-setMasterSecretFromPre ver role premasterSecret = do- secret <- genSecret <$> get- setMasterSecret ver role secret- where genSecret hst = generateMasterSecret ver- premasterSecret- (hstClientRandom hst)- (fromJust "server random" $ hstServerRandom hst)+getSessionHash :: HandshakeM ByteString+getSessionHash = gets $ \hst ->+ case hstTransHashState hst of+ TransHashState2 hashCtx -> hashFinal hashCtx+ _ -> error "un-initialized session hash" --- | Set master secret and as a side effect generate the key block+-- | Set main secret and as a side effect generate the key block -- with all the right parameters, and setup the pending tx/rx state.-setMasterSecret :: Version -> Role -> Bytes -> HandshakeM ()-setMasterSecret ver role masterSecret = modify $ \hst ->- let (pendingTx, pendingRx) = computeKeyBlock hst masterSecret ver role- in hst { hstMasterSecret = Just masterSecret+setMainSecret :: Version -> Role -> Secret -> HandshakeM ()+setMainSecret ver role mainSecret = modify' $ \hst ->+ let (pendingTx, pendingRx) = computeKeyBlock hst mainSecret ver role+ in hst+ { hstMainSecret = Just mainSecret , hstPendingTxState = Just pendingTx- , hstPendingRxState = Just pendingRx }+ , hstPendingRxState = Just pendingRx+ } -computeKeyBlock :: HandshakeState -> Bytes -> Version -> Role -> (RecordState, RecordState)-computeKeyBlock hst masterSecret ver cc = (pendingTx, pendingRx)- where cipher = fromJust "cipher" $ hstPendingCipher hst- keyblockSize = cipherKeyBlockSize cipher+computeKeyBlock+ :: HandshakeState -> Secret -> Version -> Role -> (RecordState, RecordState)+computeKeyBlock hst mainSecret ver cc = (pendingTx, pendingRx)+ where+ cipher = fromJust $ hstPendingCipher hst+ keyblockSize = cipherKeyBlockSize cipher - bulk = cipherBulk cipher- digestSize = hashSize $ cipherHash cipher- keySize = bulkKeySize bulk- ivSize = bulkIVSize bulk- kb = generateKeyBlock ver (hstClientRandom hst)- (fromJust "server random" $ hstServerRandom hst)- masterSecret keyblockSize+ bulk = cipherBulk cipher+ digestSize =+ if hasMAC (bulkF bulk)+ then hashDigestSize (cipherHash cipher)+ else 0+ keySize = bulkKeySize bulk+ ivSize = bulkIVSize bulk+ kb =+ generateKeyBlock+ ver+ cipher+ (hstClientRandom hst)+ (fromJust $ hstServerRandom hst)+ mainSecret+ keyblockSize - (cMACSecret, sMACSecret, cWriteKey, sWriteKey, cWriteIV, sWriteIV) =- fromJust "p6" $ partition6 kb (digestSize, digestSize, keySize, keySize, ivSize, ivSize)+ (cMACSecret, sMACSecret, cWriteKey, sWriteKey, cWriteIV, sWriteIV) =+ fromJust $+ partition6 kb (digestSize, digestSize, keySize, keySize, ivSize, ivSize) - cstClient = CryptState { cstKey = cWriteKey- , cstIV = cWriteIV- , cstMacSecret = cMACSecret }- cstServer = CryptState { cstKey = sWriteKey- , cstIV = sWriteIV- , cstMacSecret = sMACSecret }- msClient = MacState { msSequence = 0 }- msServer = MacState { msSequence = 0 }+ cstClient =+ CryptState+ { cstKey = bulkInit bulk (BulkEncrypt `orOnServer` BulkDecrypt) cWriteKey+ , cstIV = convert $ cWriteIV+ , cstMacSecret = cMACSecret+ }+ cstServer =+ CryptState+ { cstKey = bulkInit bulk (BulkDecrypt `orOnServer` BulkEncrypt) sWriteKey+ , cstIV = convert $ sWriteIV+ , cstMacSecret = sMACSecret+ }+ msClient = MacState{msSequence = 0}+ msServer = MacState{msSequence = 0} - pendingTx = RecordState- { stCryptState = if cc == ClientRole then cstClient else cstServer- , stMacState = if cc == ClientRole then msClient else msServer- , stCipher = Just cipher- , stCompression = hstPendingCompression hst- }- pendingRx = RecordState- { stCryptState = if cc == ClientRole then cstServer else cstClient- , stMacState = if cc == ClientRole then msServer else msClient- , stCipher = Just cipher- , stCompression = hstPendingCompression hst- }+ pendingTx =+ RecordState+ { stCryptState = if cc == ClientRole then cstClient else cstServer+ , stMacState = if cc == ClientRole then msClient else msServer+ , stCryptLevel = CryptMainSecret+ , stCipher = Just cipher+ , stCompression = hstPendingCompression hst+ }+ pendingRx =+ RecordState+ { stCryptState = if cc == ClientRole then cstServer else cstClient+ , stMacState = if cc == ClientRole then msServer else msClient+ , stCryptLevel = CryptMainSecret+ , stCipher = Just cipher+ , stCompression = hstPendingCompression hst+ } -setServerHelloParameters :: Version -- ^ chosen version- -> ServerRandom- -> Cipher- -> Compression- -> HandshakeM ()-setServerHelloParameters ver sran cipher compression = do- modify $ \hst -> hst- { hstServerRandom = Just sran- , hstPendingCipher = Just cipher- , hstPendingCompression = compression- , hstHandshakeDigest = updateDigest $ hstHandshakeDigest hst- }- where initCtx = if ver < TLS12 then hashMD5SHA1 else hashSHA256- updateDigest (Left bytes) = Right $ foldl hashUpdate initCtx $ reverse bytes- updateDigest (Right _) = error "cannot initialize digest with another digest"+ orOnServer f g = if cc == ClientRole then f else g
+ Network/TLS/Handshake/State13.hs view
@@ -0,0 +1,150 @@+{-# LANGUAGE OverloadedStrings #-}++module Network.TLS.Handshake.State13 (+ CryptLevel (+ CryptEarlySecret,+ CryptHandshakeSecret,+ CryptApplicationSecret+ ),+ TrafficSecret,+ getTxRecordState,+ getRxRecordState,+ setTxRecordState,+ setRxRecordState,+ getTxLevel,+ getRxLevel,+ clearTxRecordState,+ clearRxRecordState,+ PendingRecvAction (..),+ setPendingRecvActions,+ popPendingRecvAction,+) where++import Control.Concurrent.MVar+import Data.IORef++import Network.TLS.Cipher+import Network.TLS.Compression+import Network.TLS.Context.Internal+import Network.TLS.Imports+import Network.TLS.KeySchedule (hkdfExpandLabel)+import Network.TLS.Record.State+import Network.TLS.Types++getTxRecordState :: Context -> IO (Hash, Cipher, CryptLevel, Secret)+getTxRecordState ctx = getXState ctx ctxTxRecordState++getRxRecordState :: Context -> IO (Hash, Cipher, CryptLevel, Secret)+getRxRecordState ctx = getXState ctx ctxRxRecordState++getXState+ :: Context+ -> (Context -> MVar RecordState)+ -> IO (Hash, Cipher, CryptLevel, Secret)+getXState ctx func = do+ tx <- readMVar (func ctx)+ let usedCipher = fromJust $ stCipher tx+ usedHash = cipherHash usedCipher+ level = stCryptLevel tx+ secret = cstMacSecret $ stCryptState tx+ return (usedHash, usedCipher, level, secret)++-- In the case of QUIC, stCipher is Nothing.+-- So, fromJust causes an error.+getTxLevel :: Context -> IO CryptLevel+getTxLevel ctx = getXLevel ctx ctxTxRecordState++getRxLevel :: Context -> IO CryptLevel+getRxLevel ctx = getXLevel ctx ctxRxRecordState++getXLevel+ :: Context+ -> (Context -> MVar RecordState)+ -> IO CryptLevel+getXLevel ctx func = do+ tx <- readMVar (func ctx)+ return $ stCryptLevel tx++class TrafficSecret ty where+ fromTrafficSecret :: ty -> (CryptLevel, Secret)++instance HasCryptLevel a => TrafficSecret (AnyTrafficSecret a) where+ fromTrafficSecret prx@(AnyTrafficSecret s) = (getCryptLevel prx, s)++instance HasCryptLevel a => TrafficSecret (ClientTrafficSecret a) where+ fromTrafficSecret prx@(ClientTrafficSecret s) = (getCryptLevel prx, s)++instance HasCryptLevel a => TrafficSecret (ServerTrafficSecret a) where+ fromTrafficSecret prx@(ServerTrafficSecret s) = (getCryptLevel prx, s)++setTxRecordState :: TrafficSecret ty => Context -> Hash -> Cipher -> ty -> IO ()+setTxRecordState = setXState ctxTxRecordState BulkEncrypt++setRxRecordState :: TrafficSecret ty => Context -> Hash -> Cipher -> ty -> IO ()+setRxRecordState = setXState ctxRxRecordState BulkDecrypt++setXState+ :: TrafficSecret ty+ => (Context -> MVar RecordState)+ -> BulkDirection+ -> Context+ -> Hash+ -> Cipher+ -> ty+ -> IO ()+setXState func encOrDec ctx h cipher ts =+ let (lvl, secret) = fromTrafficSecret ts+ in setXState' func encOrDec ctx h cipher lvl secret++setXState'+ :: (Context -> MVar RecordState)+ -> BulkDirection+ -> Context+ -> Hash+ -> Cipher+ -> CryptLevel+ -> Secret+ -> IO ()+setXState' func encOrDec ctx h cipher lvl secret =+ modifyMVar_ (func ctx) (\_ -> return rt)+ where+ bulk = cipherBulk cipher+ keySize = bulkKeySize bulk+ ivSize = max 8 (bulkIVSize bulk + bulkExplicitIV bulk)+ key = hkdfExpandLabel h secret "key" "" keySize+ iv = hkdfExpandLabel h secret "iv" "" ivSize+ cst =+ CryptState+ { cstKey = bulkInit bulk encOrDec key+ , cstIV = iv+ , cstMacSecret = secret+ }+ rt =+ RecordState+ { stCryptState = cst+ , stMacState = MacState{msSequence = 0}+ , stCryptLevel = lvl+ , stCipher = Just cipher+ , stCompression = nullCompression+ }++clearTxRecordState :: Context -> IO ()+clearTxRecordState = clearXState ctxTxRecordState++clearRxRecordState :: Context -> IO ()+clearRxRecordState = clearXState ctxRxRecordState++clearXState :: (Context -> MVar RecordState) -> Context -> IO ()+clearXState func ctx =+ modifyMVar_ (func ctx) (\rt -> return rt{stCipher = Nothing})++setPendingRecvActions :: Context -> [PendingRecvAction] -> IO ()+setPendingRecvActions ctx = writeIORef (ctxPendingRecvActions ctx)++popPendingRecvAction :: Context -> IO (Maybe PendingRecvAction)+popPendingRecvAction ctx = do+ let ref = ctxPendingRecvActions ctx+ actions <- readIORef ref+ case actions of+ bs : bss -> writeIORef ref bss >> return (Just bs)+ [] -> return Nothing
+ Network/TLS/Handshake/TranscriptHash.hs view
@@ -0,0 +1,131 @@+{-# LANGUAGE OverloadedStrings #-}++module Network.TLS.Handshake.TranscriptHash (+ transcriptHash,+ transcriptHashWith,+ transitTranscriptHashI,+ updateTranscriptHash,+ updateTranscriptHashI,+ transitTranscriptHash,+ copyTranscriptHash,+ TranscriptHash (..),+) where++import Control.Monad.State+import qualified Data.ByteString as B++import Network.TLS.Cipher+import Network.TLS.Context.Internal+import Network.TLS.Crypto+import Network.TLS.Handshake.State+import Network.TLS.Imports+import Network.TLS.Parameters+import Network.TLS.State+import Network.TLS.Types++----------------------------------------------------------------++transitTranscriptHash :: Context -> String -> Hash -> Bool -> IO ()+transitTranscriptHash ctx label hashAlg isHRR = do+ usingHState ctx $ modify' $ \hst ->+ hst{hstTransHashState = transit label hashAlg isHRR $ hstTransHashState hst}+ traceTranscriptHash ctx label hstTransHashState++transitTranscriptHashI :: Context -> String -> Hash -> Bool -> IO ()+transitTranscriptHashI ctx label hashAlg isHRR = do+ usingHState ctx $ modify' $ \hst ->+ hst{hstTransHashStateI = transit label hashAlg isHRR $ hstTransHashStateI hst}+ traceTranscriptHash ctx label hstTransHashStateI++transit :: String -> Hash -> Bool -> TransHashState -> TransHashState+transit label _ _ st0@TransHashState0 = error $ "transitTranscriptHash " ++ label ++ " " ++ show st0+transit _ _ _ st2@(TransHashState2 _) = st2+transit _ hashAlg isHRR (TransHashState1 chs)+ | isHRR = TransHashState2 $ hashUpdate (hashInit hashAlg) hsMsg+ | otherwise = TransHashState2 $ hashUpdates (hashInit hashAlg) ch+ where+ ch = reverse chs+ hsMsg =+ -- Handshake message:+ -- typ <-len-> body+ -- 254 0 0 len hash(CH1)+ B.concat+ [ "\254\0\0"+ , B.singleton len+ , hashedCH+ ]+ where+ hashedCH = hashChunks hashAlg ch+ len = fromIntegral $ B.length hashedCH++----------------------------------------------------------------++updateTranscriptHash :: Context -> String -> ByteString -> IO ()+updateTranscriptHash ctx label eh = do+ usingHState ctx $ modify' $ \hst ->+ hst{hstTransHashState = update eh label $ hstTransHashState hst}+ traceTranscriptHash ctx label hstTransHashState++updateTranscriptHashI :: Context -> String -> ByteString -> IO ()+updateTranscriptHashI ctx label eh = do+ usingHState ctx $ modify' $ \hst ->+ hst{hstTransHashStateI = update eh label $ hstTransHashStateI hst}+ traceTranscriptHash ctx label hstTransHashStateI++update :: ByteString -> String -> TransHashState -> TransHashState+update eh _ TransHashState0 = TransHashState1 [eh]+update eh _ (TransHashState1 bss) = TransHashState1 (eh : bss)+update eh _ (TransHashState2 hctx) = TransHashState2 $ hashUpdate hctx eh++----------------------------------------------------------------++transcriptHash :: MonadIO m => Context -> String -> m TranscriptHash+transcriptHash ctx label = do+ hst <- fromJust <$> getHState ctx+ let th = calc label $ hstTransHashState hst+ liftIO $ debugTraceKey (ctxDebug ctx) $ adjustLabel label ++ showBytesHex th+ return $ TranscriptHash th++calc :: String -> TransHashState -> ByteString+calc _ (TransHashState2 hashCtx) = hashFinal hashCtx+calc label st = error $ "transcriptHash " ++ label ++ " " ++ show st++----------------------------------------------------------------++transcriptHashWith+ :: MonadIO m => Context -> String -> ByteString -> m TranscriptHash+transcriptHashWith ctx label bs = do+ role <- liftIO $ usingState_ ctx getRole+ let isClient = role == ClientRole+ hst <- fromJust <$> getHState ctx+ let st+ | isClient = hstTransHashStateI hst+ | otherwise = hstTransHashState hst+ let th = calcWith bs label st+ liftIO $ debugTraceKey (ctxDebug ctx) $ adjustLabel label ++ showBytesHex th+ return $ TranscriptHash th++calcWith :: ByteString -> String -> TransHashState -> ByteString+calcWith bs _ (TransHashState2 hashCtx) = hashFinal $ hashUpdate hashCtx bs+calcWith _ label st = error $ "transcriptHashWith " ++ label ++ " " ++ show st++----------------------------------------------------------------++copyTranscriptHash :: Context -> String -> IO ()+copyTranscriptHash ctx label = do+ usingHState ctx $ modify' $ \hst ->+ hst+ { hstTransHashState = hstTransHashStateI hst+ }+ traceTranscriptHash ctx label hstTransHashState++----------------------------------------------------------------++traceTranscriptHash+ :: Context -> String -> (HandshakeState -> TransHashState) -> IO ()+traceTranscriptHash ctx label getField = do+ hst <- fromJust <$> getHState ctx+ debugTraceKey (ctxDebug ctx) $ adjustLabel label ++ show (getField hst)++adjustLabel :: String -> String+adjustLabel label = take 24 (label ++ " ")
+ Network/TLS/HashAndSignature.hs view
@@ -0,0 +1,181 @@+{-# LANGUAGE PatternSynonyms #-}++module Network.TLS.HashAndSignature (+ HashAlgorithm (+ ..,+ HashNone,+ HashMD5,+ HashSHA1,+ HashSHA224,+ HashSHA256,+ HashSHA384,+ HashSHA512,+ HashIntrinsic+ ),+ SignatureAlgorithm (+ ..,+ SignatureAnonymous,+ SignatureRSA,+ SignatureDSA,+ SignatureECDSA,+ SignatureRSApssRSAeSHA256,+ SignatureRSApssRSAeSHA384,+ SignatureRSApssRSAeSHA512,+ SignatureEd25519,+ SignatureEd448,+ SignatureRSApsspssSHA256,+ SignatureRSApsspssSHA384,+ SignatureRSApsspssSHA512,+ SignatureBrainpoolP256,+ SignatureBrainpoolP384,+ SignatureBrainpoolP512+ ),+ HashAndSignatureAlgorithm,+ supportedSignatureSchemes,+ signatureSchemesForTLS13,+) where++import Network.TLS.Imports++------------------------------------------------------------++newtype HashAlgorithm = HashAlgorithm {fromHashAlgorithm :: Word8}+ deriving (Eq)++{- FOURMOLU_DISABLE -}+pattern HashNone :: HashAlgorithm+pattern HashNone = HashAlgorithm 0+pattern HashMD5 :: HashAlgorithm+pattern HashMD5 = HashAlgorithm 1+pattern HashSHA1 :: HashAlgorithm+pattern HashSHA1 = HashAlgorithm 2+pattern HashSHA224 :: HashAlgorithm+pattern HashSHA224 = HashAlgorithm 3+pattern HashSHA256 :: HashAlgorithm+pattern HashSHA256 = HashAlgorithm 4+pattern HashSHA384 :: HashAlgorithm+pattern HashSHA384 = HashAlgorithm 5+pattern HashSHA512 :: HashAlgorithm+pattern HashSHA512 = HashAlgorithm 6+pattern HashIntrinsic :: HashAlgorithm+pattern HashIntrinsic = HashAlgorithm 8++instance Show HashAlgorithm where+ show HashNone = "None"+ show HashMD5 = "MD5"+ show HashSHA1 = "SHA1"+ show HashSHA224 = "SHA224"+ show HashSHA256 = "SHA256"+ show HashSHA384 = "SHA384"+ show HashSHA512 = "SHA512"+ show HashIntrinsic = "TLS13"+ show (HashAlgorithm x) = "Hash " ++ show x+{- FOURMOLU_ENABLE -}++------------------------------------------------------------++newtype SignatureAlgorithm = SignatureAlgorithm {fromSignatureAlgorithm :: Word8}+ deriving (Eq)++{- FOURMOLU_DISABLE -}+pattern SignatureAnonymous :: SignatureAlgorithm+pattern SignatureAnonymous = SignatureAlgorithm 0+pattern SignatureRSA :: SignatureAlgorithm+pattern SignatureRSA = SignatureAlgorithm 1+pattern SignatureDSA :: SignatureAlgorithm+pattern SignatureDSA = SignatureAlgorithm 2+pattern SignatureECDSA :: SignatureAlgorithm+pattern SignatureECDSA = SignatureAlgorithm 3+-- TLS 1.3 from here+pattern SignatureRSApssRSAeSHA256 :: SignatureAlgorithm+pattern SignatureRSApssRSAeSHA256 = SignatureAlgorithm 4+pattern SignatureRSApssRSAeSHA384 :: SignatureAlgorithm+pattern SignatureRSApssRSAeSHA384 = SignatureAlgorithm 5+pattern SignatureRSApssRSAeSHA512 :: SignatureAlgorithm+pattern SignatureRSApssRSAeSHA512 = SignatureAlgorithm 6+pattern SignatureEd25519 :: SignatureAlgorithm+pattern SignatureEd25519 = SignatureAlgorithm 7+pattern SignatureEd448 :: SignatureAlgorithm+pattern SignatureEd448 = SignatureAlgorithm 8+pattern SignatureRSApsspssSHA256 :: SignatureAlgorithm+pattern SignatureRSApsspssSHA256 = SignatureAlgorithm 9+pattern SignatureRSApsspssSHA384 :: SignatureAlgorithm+pattern SignatureRSApsspssSHA384 = SignatureAlgorithm 10+pattern SignatureRSApsspssSHA512 :: SignatureAlgorithm+pattern SignatureRSApsspssSHA512 = SignatureAlgorithm 11+pattern SignatureBrainpoolP256 :: SignatureAlgorithm -- RFC8734+pattern SignatureBrainpoolP256 = SignatureAlgorithm 26+pattern SignatureBrainpoolP384 :: SignatureAlgorithm+pattern SignatureBrainpoolP384 = SignatureAlgorithm 27+pattern SignatureBrainpoolP512 :: SignatureAlgorithm+pattern SignatureBrainpoolP512 = SignatureAlgorithm 28++instance Show SignatureAlgorithm where+ show SignatureAnonymous = "Anonymous"+ show SignatureRSA = "RSA"+ show SignatureDSA = "DSA"+ show SignatureECDSA = "ECDSA"+ show SignatureRSApssRSAeSHA256 = "RSApssRSAeSHA256"+ show SignatureRSApssRSAeSHA384 = "RSApssRSAeSHA384"+ show SignatureRSApssRSAeSHA512 = "RSApssRSAeSHA512"+ show SignatureEd25519 = "Ed25519"+ show SignatureEd448 = "Ed448"+ show SignatureRSApsspssSHA256 = "RSApsspssSHA256"+ show SignatureRSApsspssSHA384 = "RSApsspssSHA384"+ show SignatureRSApsspssSHA512 = "RSApsspssSHA512"+ show SignatureBrainpoolP256 = "BrainpoolP256"+ show SignatureBrainpoolP384 = "BrainpoolP384"+ show SignatureBrainpoolP512 = "BrainpoolP512"+ show (SignatureAlgorithm x) = "Signature " ++ show x+{- FOURMOLU_ENABLE -}++------------------------------------------------------------++type HashAndSignatureAlgorithm = (HashAlgorithm, SignatureAlgorithm)++{- FOURMOLU_DISABLE -}+supportedSignatureSchemes :: [HashAndSignatureAlgorithm]+supportedSignatureSchemes =+ -- EdDSA algorithms+ [ (HashIntrinsic, SignatureEd448) -- ed448 (0x0808)+ , (HashIntrinsic, SignatureEd25519) -- ed25519(0x0807)+ -- ECDSA algorithms+ , (HashSHA512, SignatureECDSA) -- ecdsa_secp512r1_sha512(0x0603)+ , (HashSHA384, SignatureECDSA) -- ecdsa_secp384r1_sha384(0x0503)+ , (HashSHA256, SignatureECDSA) -- ecdsa_secp256r1_sha256(0x0403)+ -- RSASSA-PSS RSAE algorithms+ , (HashIntrinsic, SignatureRSApssRSAeSHA512) -- rsa_pss_rsae_sha512(0x0806)+ , (HashIntrinsic, SignatureRSApssRSAeSHA384) -- rsa_pss_rsae_sha384(0x0805)+ , (HashIntrinsic, SignatureRSApssRSAeSHA256) -- rsa_pss_rsae_sha256(0x0804)+ -- RSASSA-PSS PSS algorithms with+ , (HashIntrinsic, SignatureRSApsspssSHA512) -- rsa_pss_pss_sha512(0x080b)+ , (HashIntrinsic, SignatureRSApsspssSHA384) -- rsa_pss_pss_sha384(0x080a)+ , (HashIntrinsic, SignatureRSApsspssSHA256) -- rsa_pss_pss_sha256(0x0809)+ -- RSASSA-PKCS1-v1_5 algorithms+ , (HashSHA512, SignatureRSA) -- rsa_pkcs1_sha512(0x0601)+ , (HashSHA384, SignatureRSA) -- rsa_pkcs1_sha384(0x0501)+ , (HashSHA256, SignatureRSA) -- rsa_pkcs1_sha256(0x0401)+ -- Legacy algorithms+ , (HashSHA1, SignatureRSA) -- rsa_pkcs1_sha1 (0x0201)+ , (HashSHA1, SignatureECDSA) -- ecdsa_sha1 (0x0203)+ ]++signatureSchemesForTLS13 :: [(HashAlgorithm, SignatureAlgorithm)]+signatureSchemesForTLS13 =+ -- EdDSA algorithms+ [ (HashIntrinsic, SignatureEd448) -- ed448 (0x0808)+ , (HashIntrinsic, SignatureEd25519) -- ed25519(0x0807)+ -- ECDSA algorithms+ , (HashSHA512, SignatureECDSA) -- ecdsa_secp512r1_sha512(0x0603)+ , (HashSHA384, SignatureECDSA) -- ecdsa_secp384r1_sha384(0x0503)+ , (HashSHA256, SignatureECDSA) -- ecdsa_secp256r1_sha256(0x0403)+ -- RSASSA-PSS RSAE algorithms+ , (HashIntrinsic, SignatureRSApssRSAeSHA512) -- rsa_pss_rsae_sha512(0x0806)+ , (HashIntrinsic, SignatureRSApssRSAeSHA384) -- rsa_pss_rsae_sha384(0x0805)+ , (HashIntrinsic, SignatureRSApssRSAeSHA256) -- rsa_pss_rsae_sha256(0x0804)+ -- RSASSA-PSS PSS algorithms with+ , (HashIntrinsic, SignatureRSApsspssSHA512) -- rsa_pss_pss_sha512(0x080b)+ , (HashIntrinsic, SignatureRSApsspssSHA384) -- rsa_pss_pss_sha384(0x080a)+ , (HashIntrinsic, SignatureRSApsspssSHA256) -- rsa_pss_pss_sha256(0x0809)+ ]+{- FOURMOLU_ENABLE -}
Network/TLS/Hooks.hs view
@@ -1,20 +1,16 @@--- |--- Module : Network.TLS.Context--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Hooks- ( Logging(..)- , Hooks(..)- , defaultHooks- ) where+module Network.TLS.Hooks (+ Logging (..),+ defaultLogging,+ Hooks (..),+ defaultHooks,+) where -import qualified Data.ByteString as B-import Network.TLS.Struct (Header, Handshake(..))+import Data.Default (Default (def))++import Network.TLS.Imports+import Network.TLS.Struct (Handshake, Header)+import Network.TLS.Struct13 (Handshake13) import Network.TLS.X509 (CertificateChain)-import Data.Default.Class -- | Hooks for logging --@@ -22,37 +18,42 @@ data Logging = Logging { loggingPacketSent :: String -> IO () , loggingPacketRecv :: String -> IO ()- , loggingIOSent :: B.ByteString -> IO ()- , loggingIORecv :: Header -> B.ByteString -> IO ()+ , loggingIOSent :: ByteString -> IO ()+ , loggingIORecv :: Header -> ByteString -> IO () } defaultLogging :: Logging-defaultLogging = Logging- { loggingPacketSent = (\_ -> return ())- , loggingPacketRecv = (\_ -> return ())- , loggingIOSent = (\_ -> return ())- , loggingIORecv = (\_ _ -> return ())- }+defaultLogging =+ Logging+ { loggingPacketSent = \_ -> return ()+ , loggingPacketRecv = \_ -> return ()+ , loggingIOSent = \_ -> return ()+ , loggingIORecv = \_ _ -> return ()+ } instance Default Logging where def = defaultLogging -- | A collection of hooks actions. data Hooks = Hooks- { -- | called at each handshake message received- hookRecvHandshake :: Handshake -> IO Handshake- -- | called at each certificate chain message received+ { hookRecvHandshake :: Handshake -> IO Handshake+ -- ^ called at each handshake message received+ , hookRecvHandshake13 :: Handshake13 -> IO Handshake13+ -- ^ called at each handshake message received for TLS 1.3 , hookRecvCertificates :: CertificateChain -> IO ()- -- | hooks on IO and packets, receiving and sending.- , hookLogging :: Logging+ -- ^ called at each certificate chain message received+ , hookLogging :: Logging+ -- ^ hooks on IO and packets, receiving and sending. } defaultHooks :: Hooks-defaultHooks = Hooks- { hookRecvHandshake = \hs -> return hs- , hookRecvCertificates = return . const ()- , hookLogging = def- }+defaultHooks =+ Hooks+ { hookRecvHandshake = return+ , hookRecvHandshake13 = return+ , hookRecvCertificates = return . const ()+ , hookLogging = def+ } instance Default Hooks where def = defaultHooks
Network/TLS/IO.hs view
@@ -1,122 +1,240 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE CPP #-}--- |--- Module : Network.TLS.IO--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.IO- ( checkValid- , sendPacket- , recvPacket- ) where+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RankNTypes #-} +module Network.TLS.IO (+ sendPacket12,+ sendPacket13,+ recvPacket12,+ recvPacket13,+ --+ isRecvComplete,+ checkValid,++ -- * Grouping multiple packets in the same flight+ PacketFlightM,+ runPacketFlight,+ loadPacket13,+) where++import Control.Exception (finally, throwIO)+import Control.Monad.Reader+import Control.Monad.State.Strict+import qualified Data.ByteString as B+import Data.IORef+ import Network.TLS.Context.Internal-import Network.TLS.Struct-import Network.TLS.Record-import Network.TLS.Packet import Network.TLS.Hooks-import Network.TLS.Sending-import Network.TLS.Receiving-import qualified Data.ByteString as B-import Data.ByteString.Char8 ()+import Network.TLS.IO.Decode+import Network.TLS.IO.Encode+import Network.TLS.Imports+import Network.TLS.Parameters+import Network.TLS.Record+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Struct13 -import Data.IORef-import Control.Monad.State-import Control.Exception (throwIO)-import System.IO.Error (mkIOError, eofErrorType)+---------------------------------------------------------------- +-- | Send one packet to the context+sendPacket12 :: Context -> Packet -> IO ()+sendPacket12 ctx@Context{ctxRecordLayer = recordLayer} pkt = do+ -- in ver <= TLS1.0, block ciphers using CBC are using CBC residue+ -- as IV, which can be guessed by an attacker. Hence, an empty+ -- packet is sent before a normal data packet, to prevent+ -- guessability.+ when (isNonNullAppData pkt) $ do+ withEmptyPacket <- readIORef $ ctxNeedEmptyPacket ctx+ when withEmptyPacket $+ writePacketBytes12 ctx recordLayer (AppData B.empty)+ >>= recordSendBytes recordLayer ctx++ writePacketBytes12 ctx recordLayer pkt >>= recordSendBytes recordLayer ctx+ where+ isNonNullAppData (AppData b) = not $ B.null b+ isNonNullAppData _ = False++writePacketBytes12+ :: Monoid bytes+ => Context+ -> RecordLayer bytes+ -> Packet+ -> IO bytes+writePacketBytes12 ctx recordLayer pkt = do+ withLog ctx $ \logging -> loggingPacketSent logging (show pkt)+ edataToSend <- encodePacket12 ctx recordLayer pkt+ either throwCore return edataToSend++----------------------------------------------------------------++sendPacket13 :: Context -> Packet13 -> IO ()+sendPacket13 ctx@Context{ctxRecordLayer = recordLayer} pkt =+ writePacketBytes13 ctx recordLayer pkt >>= recordSendBytes recordLayer ctx++writePacketBytes13+ :: Monoid bytes+ => Context+ -> RecordLayer bytes+ -> Packet13+ -> IO bytes+writePacketBytes13 ctx recordLayer pkt = do+ withLog ctx $ \logging -> loggingPacketSent logging (show pkt)+ edataToSend <- encodePacket13 ctx recordLayer pkt+ either throwCore return edataToSend++----------------------------------------------------------------++-- | receive one packet from the context that contains 1 or+-- many messages (many only in case of handshake). if will returns a+-- TLSError if the packet is unexpected or malformed+recvPacket12 :: Context -> IO (Either TLSError Packet)+recvPacket12 ctx@Context{ctxRecordLayer = recordLayer} = loop 0+ where+ lim = limitHandshakeFragment $ sharedLimit $ ctxShared ctx+ loop count+ | count > lim = do+ let err = Error_Packet "too many handshake fragment"+ logPacket ctx $ show err+ return $ Left err+ loop count = do+ hrr <- usingState_ ctx getTLS13HRR+ erecord <- recordRecv12 recordLayer ctx+ case erecord of+ Left err -> do+ logPacket ctx $ show err+ return $ Left err+ Right record+ | hrr && isCCS record -> loop (count + 1)+ | otherwise -> do+ pktRecv <- decodePacket12 ctx record+ if isEmptyHandshake pktRecv+ then do+ logPacket ctx "Handshake fragment"+ -- When a handshake record is fragmented+ -- we continue receiving in order to feed+ -- stHandshakeRecordCont+ loop (count + 1)+ else case pktRecv of+ Right (Handshake hss bss) -> do+ pktRecv'@(Right pkt) <- ctxWithHooks ctx $ \hooks -> do+ hss' <- mapM (hookRecvHandshake hooks) hss+ return $ Right $ Handshake hss' bss+ logPacket ctx $ show pkt+ return pktRecv'+ Right pkt -> do+ logPacket ctx $ show pkt+ return pktRecv+ Left err -> do+ logPacket ctx $ show err+ return pktRecv++isCCS :: Record a -> Bool+isCCS (Record ProtocolType_ChangeCipherSpec _ _) = True+isCCS _ = False++isEmptyHandshake :: Either TLSError Packet -> Bool+isEmptyHandshake (Right (Handshake [] _)) = True+isEmptyHandshake _ = False++logPacket :: Context -> String -> IO ()+logPacket ctx msg = withLog ctx $ \logging -> loggingPacketRecv logging msg++----------------------------------------------------------------++recvPacket13 :: Context -> IO (Either TLSError Packet13)+recvPacket13 ctx@Context{ctxRecordLayer = recordLayer} = loop 0+ where+ lim = limitHandshakeFragment $ sharedLimit $ ctxShared ctx+ loop count+ | count > lim =+ return $ Left $ Error_Packet "too many handshake fragment"+ loop count = do+ erecord <- recordRecv13 recordLayer ctx+ case erecord of+ Left err@(Error_Protocol _ BadRecordMac) -> do+ -- If the server decides to reject RTT0 data but accepts RTT1+ -- data, the server should skip all records for RTT0 data.+ logPacket ctx $ show err+ established <- ctxEstablished ctx+ case established of+ EarlyDataNotAllowed n+ | n > 0 -> do+ setEstablished ctx $ EarlyDataNotAllowed (n - 1)+ loop (count + 1)+ _ -> return $ Left err+ Left err -> do+ logPacket ctx $ show err+ return $ Left err+ Right record -> do+ pktRecv <- decodePacket13 ctx record+ if isEmptyHandshake13 pktRecv+ then do+ logPacket ctx "Handshake fragment"+ -- When a handshake record is fragmented we+ -- continue receiving in order to feed+ -- stHandshakeRecordCont13+ loop (count + 1)+ else do+ case pktRecv of+ Right (Handshake13 hss bss) -> do+ pktRecv'@(Right pkt) <- ctxWithHooks ctx $ \hooks -> do+ hss' <- mapM (hookRecvHandshake13 hooks) hss+ return $ Right $ Handshake13 hss' bss+ logPacket ctx $ show pkt+ return pktRecv'+ Right pkt -> do+ logPacket ctx $ show pkt+ return pktRecv+ Left err -> do+ logPacket ctx $ show err+ return pktRecv++isEmptyHandshake13 :: Either TLSError Packet13 -> Bool+isEmptyHandshake13 (Right (Handshake13 [] _)) = True+isEmptyHandshake13 _ = False++----------------------------------------------------------------++isRecvComplete :: Context -> IO Bool+isRecvComplete ctx = usingState_ ctx $ do+ cont12 <- gets stHandshakeRecordCont12+ cont13 <- gets stHandshakeRecordCont13+ return $ isNothing (fst cont12) && isNothing (fst cont13)+ checkValid :: Context -> IO () checkValid ctx = do established <- ctxEstablished ctx- unless established $ liftIO $ throwIO ConnectionNotEstablished+ when (established == NotEstablished) $ throwIO ConnectionNotEstablished eofed <- ctxEOF ctx- when eofed $ liftIO $ throwIO $ mkIOError eofErrorType "data" Nothing Nothing+ when eofed $ throwIO $ PostHandshake Error_EOF -readExact :: Context -> Int -> IO Bytes-readExact ctx sz = do- hdrbs <- liftIO $ contextRecv ctx sz- when (B.length hdrbs < sz) $ do- setEOF ctx- if B.null hdrbs- then throwCore Error_EOF- else throwCore (Error_Packet ("partial packet: expecting " ++ show sz ++ " bytes, got: " ++ (show $B.length hdrbs)))- return hdrbs+---------------------------------------------------------------- --- | recvRecord receive a full TLS record (header + data), from the other side.------ The record is disengaged from the record layer-recvRecord :: Bool -- ^ flag to enable SSLv2 compat ClientHello reception- -> Context -- ^ TLS context- -> IO (Either TLSError (Record Plaintext))-recvRecord compatSSLv2 ctx-#ifdef SSLV2_COMPATIBLE- | compatSSLv2 = do- header <- readExact ctx 2- if B.head header < 0x80- then readExact ctx 3 >>= either (return . Left) recvLength . decodeHeader . B.append header- else either (return . Left) recvDeprecatedLength $ decodeDeprecatedHeaderLength header-#endif- | otherwise = readExact ctx 5 >>= either (return . Left) recvLength . decodeHeader- where recvLength header@(Header _ _ readlen)- | readlen > 16384 + 2048 = return $ Left maximumSizeExceeded- | otherwise = readExact ctx (fromIntegral readlen) >>= getRecord header-#ifdef SSLV2_COMPATIBLE- recvDeprecatedLength readlen- | readlen > 1024 * 4 = return $ Left maximumSizeExceeded- | otherwise = do- content <- readExact ctx (fromIntegral readlen)- case decodeDeprecatedHeader readlen content of- Left err -> return $ Left err- Right header -> getRecord header content-#endif- maximumSizeExceeded = Error_Protocol ("record exceeding maximum size", True, RecordOverflow)- getRecord :: Header -> Bytes -> IO (Either TLSError (Record Plaintext))- getRecord header content = do- liftIO $ withLog ctx $ \logging -> loggingIORecv logging header content- runRxState ctx $ disengageRecord $ rawToRecord header (fragmentCiphertext content)+type Builder b = [b] -> [b] +-- | State monad used to group several packets together and send them on wire as+-- single flight. When packets are loaded in the monad, they are logged+-- immediately, update the context digest and transcript, but actual sending is+-- deferred. Packets are sent all at once when the monadic computation ends+-- (normal termination but also if interrupted by an exception).+newtype PacketFlightM b a+ = PacketFlightM (ReaderT (RecordLayer b, IORef (Builder b)) IO a)+ deriving (Functor, Applicative, Monad, MonadFail, MonadIO) --- | receive one packet from the context that contains 1 or--- many messages (many only in case of handshake). if will returns a--- TLSError if the packet is unexpected or malformed-recvPacket :: MonadIO m => Context -> m (Either TLSError Packet)-recvPacket ctx = liftIO $ do- compatSSLv2 <- ctxHasSSLv2ClientHello ctx- erecord <- recvRecord compatSSLv2 ctx- case erecord of- Left err -> return $ Left err- Right record -> do- pktRecv <- processPacket ctx record- pkt <- case pktRecv of- Right (Handshake hss) ->- ctxWithHooks ctx $ \hooks ->- (mapM (hookRecvHandshake hooks) hss) >>= return . Right . Handshake- _ -> return pktRecv- case pkt of- Right p -> withLog ctx $ \logging -> loggingPacketRecv logging $ show p- _ -> return ()- when compatSSLv2 $ ctxDisableSSLv2ClientHello ctx- return pkt+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 --- | Send one packet to the context-sendPacket :: MonadIO m => Context -> Packet -> m ()-sendPacket ctx pkt = do- -- in ver <= TLS1.0, block ciphers using CBC are using CBC residue as IV, which can be guessed- -- by an attacker. Hence, an empty packet is sent before a normal data packet, to- -- prevent guessability.- withEmptyPacket <- liftIO $ readIORef $ ctxNeedEmptyPacket ctx- when (isNonNullAppData pkt && withEmptyPacket) $ sendPacket ctx $ AppData B.empty+sendPendingFlight+ :: Monoid b => Context -> RecordLayer b -> IORef (Builder b) -> IO ()+sendPendingFlight ctx recordLayer ref = do+ build <- readIORef ref+ let bss = build []+ unless (null bss) $ recordSendBytes recordLayer ctx $ mconcat bss - edataToSend <- liftIO $ do- withLog ctx $ \logging -> loggingPacketSent logging (show pkt)- writePacket ctx pkt- case edataToSend of- Left err -> throwCore err- Right dataToSend -> liftIO $ do- withLog ctx $ \logging -> loggingIOSent logging dataToSend- contextSend ctx dataToSend- where isNonNullAppData (AppData b) = not $ B.null b- isNonNullAppData _ = False+loadPacket13 :: Monoid b => Context -> Packet13 -> PacketFlightM b ()+loadPacket13 ctx pkt = PacketFlightM $ do+ (recordLayer, ref) <- ask+ liftIO $ do+ bs <- writePacketBytes13 ctx recordLayer pkt+ modifyIORef' ref (. (bs :))
+ Network/TLS/IO/Decode.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE FlexibleContexts #-}++module Network.TLS.IO.Decode (+ decodePacket12,+ decodePacket13,+) where++import Control.Concurrent.MVar+import Control.Monad.State.Strict+import qualified Data.ByteString as BS++import Network.TLS.Cipher+import Network.TLS.Context.Internal+import Network.TLS.ErrT+import Network.TLS.Handshake.State+import Network.TLS.Imports+import Network.TLS.Packet+import Network.TLS.Packet13+import Network.TLS.Record+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Util+import Network.TLS.Wire++decodePacket12 :: Context -> Record Plaintext -> IO (Either TLSError Packet)+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+ Left err -> return $ Left err+ Right _ -> do+ switchRxEncryption ctx+ return $ Right ChangeCipherSpec+decodePacket12 ctx (Record ProtocolType_Handshake ver fragment) = do+ keyxchg <-+ getHState ctx >>= \hs -> return (hs >>= hstPendingCipher >>= Just . cipherKeyExchange)+ usingState ctx $ do+ let currentParams =+ CurrentParams+ { cParamsVersion = ver+ , cParamsKeyXchgType = keyxchg+ }+ -- 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)+ return $ Handshake hss bss+ where+ parseMany currentParams 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+ Left err -> throwError err+ Right h -> return [(h, reverse (bs : wirebytes))]+ GotSuccessRemaining (ty, content) left ->+ case decodeHandshake currentParams ty content of+ Left err -> throwError err+ Right h -> do+ hbs <- parseMany currentParams 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)++----------------------------------------------------------------++decodePacket13 :: Context -> Record Plaintext -> IO (Either TLSError Packet13)+decodePacket13 _ (Record ProtocolType_ChangeCipherSpec _ fragment) =+ case decodeChangeCipherSpec $ fragmentGetBytes fragment of+ Left err -> return $ Left err+ Right _ -> return $ Right ChangeCipherSpec13+decodePacket13 _ (Record ProtocolType_AppData _ fragment) = return $ Right $ AppData13 $ fragmentGetBytes fragment+decodePacket13 _ (Record ProtocolType_Alert _ fragment) = return (Alert13 `fmapEither` decodeAlerts (fragmentGetBytes fragment))+decodePacket13 ctx (Record ProtocolType_Handshake _ fragment) = usingState ctx $ do+ (mCont, wirebytes) <- gets stHandshakeRecordCont13+ modify' (\st -> st{stHandshakeRecordCont13 = (Nothing, [])})+ (hss, bss) <- unzip <$> parseMany mCont wirebytes (fragmentGetBytes fragment)+ return $ Handshake13 hss bss+ where+ parseMany mCont wirebytes bs =+ case fromMaybe decodeHandshakeRecord13 mCont bs of+ GotError err -> throwError err+ GotPartial cont -> do+ modify' (\st -> st{stHandshakeRecordCont13 = (Just cont, bs : wirebytes)})+ return []+ GotSuccess (ty, content) ->+ case decodeHandshake13 ty content of+ Left err -> throwError err+ Right h -> return [(h, reverse (bs : wirebytes))]+ GotSuccessRemaining (ty, content) left ->+ case decodeHandshake13 ty content of+ Left err -> throwError err+ Right h -> do+ hbs <- parseMany Nothing [] left+ 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")
+ Network/TLS/IO/Encode.hs view
@@ -0,0 +1,148 @@+module Network.TLS.IO.Encode (+ encodePacket12,+ encodePacket13,+ updateTranscriptHash12,+ encodeUpdateTranscriptHash12,+ updateTranscriptHash13,+ encodeUpdateTranscriptHash13,+) where++import Control.Concurrent.MVar+import Control.Monad.State.Strict+import qualified Data.ByteString as B+import Data.IORef++import Network.TLS.Cipher+import Network.TLS.Context.Internal+import Network.TLS.Handshake.State+import Network.TLS.Handshake.TranscriptHash+import Network.TLS.Imports+import Network.TLS.Packet+import Network.TLS.Packet13+import Network.TLS.Parameters+import Network.TLS.Record+import Network.TLS.State+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Types (Role (..))+import Network.TLS.Util++-- | encodePacket transform a packet into marshalled data related to current state+-- and updating state on the go+encodePacket12+ :: Monoid bytes+ => Context+ -> RecordLayer bytes+ -> Packet+ -> IO (Either TLSError bytes)+encodePacket12 ctx recordLayer pkt = do+ (ver, _) <- decideRecordVersion ctx+ let pt = packetType pkt+ mkRecord bs = Record pt ver (fragmentPlaintext bs)+ mlen <- getPeerRecordLimit ctx+ records <- map mkRecord <$> packetToFragments12 ctx mlen pkt+ bs <- fmap mconcat <$> forEitherM records (recordEncode12 recordLayer ctx)+ when (pkt == ChangeCipherSpec) $ switchTxEncryption ctx+ return bs++-- Decompose handshake packets into fragments of the specified length. AppData+-- packets are not fragmented here but by callers of sendPacket, so that the+-- empty-packet countermeasure may be applied to each fragment independently.+packetToFragments12 :: Context -> Maybe Int -> Packet -> IO [ByteString]+packetToFragments12 ctx mlen (Handshake hss _) =+ getChunks mlen . B.concat <$> mapM (encodeUpdateTranscriptHash12 ctx) hss+packetToFragments12 _ _ (Alert a) = return [encodeAlerts a]+packetToFragments12 _ _ ChangeCipherSpec = return [encodeChangeCipherSpec]+packetToFragments12 _ _ (AppData x) = return [x]++switchTxEncryption :: Context -> IO ()+switchTxEncryption ctx = do+ tx <- usingHState ctx (fromJust <$> gets hstPendingTxState)+ (ver, role) <- usingState_ ctx $ do+ v <- getVersion+ r <- getRole+ return (v, r)+ liftIO $ modifyMVar_ (ctxTxRecordState ctx) (\_ -> return tx)+ -- set empty packet counter measure if condition are met+ when+ ( ver <= TLS10+ && role == ClientRole+ && isCBC tx+ && supportedEmptyPacket (ctxSupported ctx)+ )+ $ liftIO+ $ writeIORef (ctxNeedEmptyPacket ctx) True+ where+ isCBC tx = maybe False (\c -> bulkBlockSize (cipherBulk c) > 0) (stCipher tx)++encodeUpdateTranscriptHash12 :: Context -> Handshake -> IO ByteString+encodeUpdateTranscriptHash12 ctx hs = do+ when (certVerifyHandshakeMaterial hs) $+ usingHState ctx $+ addHandshakeMessage encoded+ let label = show $ typeOfHandshake hs+ when (finishedHandshakeMaterial hs) $ updateTranscriptHash ctx label encoded+ when (isClientHello hs) $ do+ usingHState ctx $ do+ (ch, b) <- fromJust <$> getClientHello+ when (null b) $ setClientHello ch [encoded]+ return encoded+ where+ encoded = encodeHandshake hs+ isClientHello (ClientHello _) = True+ isClientHello _ = False++updateTranscriptHash12 :: Context -> HandshakeR -> IO ()+updateTranscriptHash12 ctx (hs, bss) = do+ when (certVerifyHandshakeMaterial hs) $+ usingHState ctx $+ mapM_ addHandshakeMessage bss+ let label = show $ typeOfHandshake hs+ when (finishedHandshakeMaterial hs) $ do+ mapM_ (updateTranscriptHash ctx label) bss++----------------------------------------------------------------++encodePacket13+ :: Monoid bytes+ => Context+ -> RecordLayer bytes+ -> Packet13+ -> IO (Either TLSError bytes)+encodePacket13 ctx recordLayer pkt = do+ let pt = contentType pkt+ mkRecord bs = Record pt TLS12 (fragmentPlaintext bs)+ mlen <- getPeerRecordLimit ctx+ records <- map mkRecord <$> packetToFragments13 ctx mlen pkt+ fmap mconcat <$> forEitherM records (recordEncode13 recordLayer ctx)++packetToFragments13 :: Context -> Maybe Int -> Packet13 -> IO [ByteString]+packetToFragments13 ctx mlen (Handshake13 hss _) =+ getChunks mlen . B.concat <$> mapM (encodeUpdateTranscriptHash13 ctx) hss+packetToFragments13 _ _ (Alert13 a) = return [encodeAlerts a]+packetToFragments13 _ _ (AppData13 x) = return [x]+packetToFragments13 _ _ ChangeCipherSpec13 = return [encodeChangeCipherSpec]++encodeUpdateTranscriptHash13 :: Context -> Handshake13 -> IO ByteString+encodeUpdateTranscriptHash13 ctx hs+ | isIgnored hs = return encoded+ | otherwise = do+ let label = show $ typeOfHandshake13 hs+ updateTranscriptHash ctx label encoded+ usingHState ctx $ addHandshakeMessage encoded+ return encoded+ where+ encoded = encodeHandshake13 hs++updateTranscriptHash13 :: Context -> Handshake13R -> IO ()+updateTranscriptHash13 ctx (hs, bss)+ | isIgnored hs = return ()+ | otherwise = do+ let label = show $ typeOfHandshake13 hs+ mapM_ (updateTranscriptHash ctx label) bss+ usingHState ctx $ mapM_ addHandshakeMessage bss++isIgnored :: Handshake13 -> Bool+isIgnored NewSessionTicket13{} = True+isIgnored KeyUpdate13{} = True+isIgnored _ = False
+ Network/TLS/Imports.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE NoImplicitPrelude #-}++module Network.TLS.Imports (+ -- generic exports+ ByteString,+ (<&>),+ module Control.Applicative,+ module Control.Monad,+ module Data.Bits,+ module Data.List,+ module Data.Maybe,+ module Data.Semigroup,+ module Data.Ord,+ module Data.Word,+ -- project definition+ showBytesHex,+) where++import Control.Applicative+import Control.Monad+import Data.Bits+import Data.ByteArray.Encoding as B+import Data.ByteString (ByteString)+import Data.ByteString.Char8 ()+import Data.Functor+import Data.List+import Data.Maybe+import Data.Ord+import Data.Semigroup+import Data.Word+import qualified Prelude as P++showBytesHex :: ByteString -> P.String+showBytesHex bs = P.show (B.convertToBase B.Base16 bs :: ByteString)
Network/TLS/Internal.hs view
@@ -1,24 +1,37 @@ {-# OPTIONS_HADDOCK hide #-}--- |--- Module : Network.TLS.Internal--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Internal- ( module Network.TLS.Struct- , module Network.TLS.Packet- , module Network.TLS.Receiving- , module Network.TLS.Sending- , module Network.TLS.Wire- , sendPacket- , recvPacket- ) where -import Network.TLS.Struct+module Network.TLS.Internal (+ module Network.TLS.Extension,+ module Network.TLS.IO.Decode,+ module Network.TLS.IO.Encode,+ module Network.TLS.Packet,+ module Network.TLS.Packet13,+ module Network.TLS.Struct,+ module Network.TLS.Struct13,+ module Network.TLS.Types,+ module Network.TLS.Wire,+ module Network.TLS.X509,+ sendPacket12,+ recvPacket12,+ makeCipherShowPretty,+) where++import Data.IORef++import Network.TLS.Core (recvPacket12, sendPacket12)+import Network.TLS.Extension+import Network.TLS.Extra.Cipher+import Network.TLS.IO.Decode+import Network.TLS.IO.Encode import Network.TLS.Packet-import Network.TLS.Receiving-import Network.TLS.Sending+import Network.TLS.Packet13+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Types import Network.TLS.Wire-import Network.TLS.Core (sendPacket, recvPacket)+import Network.TLS.X509 hiding (Certificate)++----------------------------------------------------------------++makeCipherShowPretty :: IO ()+makeCipherShowPretty = writeIORef globalCipherDict ciphersuite_all
+ Network/TLS/KeySchedule.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE OverloadedStrings #-}++module Network.TLS.KeySchedule (+ hkdfExtract,+ hkdfExpandLabel,+ deriveSecret,+) where++import qualified Crypto.Hash as H+import Crypto.KDF.HKDF+import Data.ByteArray (ByteArray, ByteArrayAccess, convert)+import qualified Data.ByteString as BS++import Network.TLS.Crypto+import Network.TLS.Imports+import Network.TLS.Types+import Network.TLS.Wire++----------------------------------------------------------------++-- | @HKDF-Extract@ function. Returns the pseudorandom key (PRK) from salt and+-- input keying material (IKM).+hkdfExtract+ :: (ByteArray ba, ByteArrayAccess ba) => Hash -> ba -> ba -> ba+hkdfExtract SHA1 salt ikm = convert (extract salt ikm :: PRK H.SHA1)+hkdfExtract SHA256 salt ikm = convert (extract salt ikm :: PRK H.SHA256)+hkdfExtract SHA384 salt ikm = convert (extract salt ikm :: PRK H.SHA384)+hkdfExtract SHA512 salt ikm = convert (extract salt ikm :: PRK H.SHA512)+hkdfExtract _ _ _ = error "hkdfExtract: unsupported hash"++----------------------------------------------------------------++deriveSecret :: Hash -> Secret -> ByteString -> TranscriptHash -> Secret+deriveSecret h secret label (TranscriptHash hashedMsgs) =+ hkdfExpandLabel h secret label hashedMsgs outlen+ where+ outlen = hashDigestSize h++----------------------------------------------------------------++-- | @HKDF-Expand-Label@ function. Returns output keying material of the+-- specified length from the PRK, customized for a TLS label and context.+hkdfExpandLabel+ :: (ByteArray ba, ByteArrayAccess ba)+ => Hash+ -> Secret+ -> ByteString+ -> ByteString+ -> Int+ -> ba+hkdfExpandLabel h secret label ctx outlen = expand' h secret hkdfLabel outlen+ where+ hkdfLabel = runPut $ do+ putWord16 $ fromIntegral outlen+ putOpaque8 ("tls13 " `BS.append` label)+ putOpaque8 ctx++expand'+ :: (ByteArray ba, ByteArrayAccess ba) => Hash -> Secret -> ByteString -> Int -> ba+expand' SHA1 secret label len = expand (extractSkip secret :: PRK H.SHA1) label len+expand' SHA256 secret label len = expand (extractSkip secret :: PRK H.SHA256) label len+expand' SHA384 secret label len = expand (extractSkip secret :: PRK H.SHA384) label len+expand' SHA512 secret label len = expand (extractSkip secret :: PRK H.SHA512) label len+expand' _ _ _ _ = error "expand'"++----------------------------------------------------------------
Network/TLS/MAC.hs view
@@ -1,77 +1,83 @@--- |--- Module : Network.TLS.MAC--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.MAC- ( hmacMD5- , hmacSHA1- , hmacSHA256- , macSSL- , hmac- , prf_MD5- , prf_SHA1- , prf_SHA256- , prf_MD5SHA1- ) where--import qualified Crypto.Hash.MD5 as MD5-import qualified Crypto.Hash.SHA1 as SHA1-import qualified Crypto.Hash.SHA256 as SHA256-import qualified Data.ByteString as B-import Data.ByteString (ByteString)-import Data.Bits (xor)+module Network.TLS.MAC (+ macSSL,+ hmac,+ prf_MD5,+ prf_SHA1,+ prf_SHA256,+ prf_TLS,+ prf_MD5SHA1,+ PRF,+) where -type HMAC = ByteString -> ByteString -> ByteString+import Data.ByteArray (ByteArray, ByteArrayAccess)+import qualified Data.ByteArray as BA -macSSL :: (ByteString -> ByteString) -> HMAC-macSSL f secret msg = f $! B.concat [ secret, B.replicate padlen 0x5c,- f $! B.concat [ secret, B.replicate padlen 0x36, msg ] ]- where -- get the type of algorithm out of the digest length by using the hash fct.- padlen = if (B.length $ f B.empty) == 16 then 48 else 40+import Network.TLS.Crypto+import Network.TLS.Imports+import Network.TLS.Types -hmac :: (ByteString -> ByteString) -> Int -> HMAC-hmac f bl secret msg =- f $! B.append opad (f $! B.append ipad msg)- where opad = B.map (xor 0x5c) k'- ipad = B.map (xor 0x36) k'+type HMAC = Secret -> ByteString -> Secret - k' = B.append kt pad- where kt = if B.length secret > fromIntegral bl then f secret else secret- pad = B.replicate (fromIntegral bl - B.length kt) 0+macSSL :: Hash -> HMAC+macSSL alg secret msg =+ f $+ BA.concat+ [ secret+ , BA.replicate padLen 0x5c+ , f $ BA.concat [secret, BA.replicate padLen 0x36, BA.convert msg]+ ]+ where+ padLen = case alg of+ MD5 -> 48+ SHA1 -> 40+ _ -> error ("internal error: macSSL called with " ++ show alg)+ f = hash alg -hmacMD5 :: HMAC-hmacMD5 secret msg = hmac MD5.hash 64 secret msg+hmac :: (ByteArray ba, ByteArrayAccess ba) => Hash -> ba -> ByteString -> ba+hmac alg secret msg = f $ BA.append opad (f $ BA.append ipad $ BA.convert msg)+ where+ opad = BA.map (0x5c `xor`) k'+ ipad = BA.map (0x36 `xor`) k' -hmacSHA1 :: HMAC-hmacSHA1 secret msg = hmac SHA1.hash 64 secret msg+ f = hash alg+ bl = hashBlockSize alg -hmacSHA256 :: HMAC-hmacSHA256 secret msg = hmac SHA256.hash 64 secret msg+ k' = BA.append kt pad+ where+ kt = if BA.length secret > fromIntegral bl then f secret else secret+ pad = BA.replicate (fromIntegral bl - BA.length kt) 0 -hmacIter :: HMAC -> ByteString -> ByteString -> ByteString -> Int -> [ByteString]+hmacIter+ :: HMAC -> Secret -> ByteString -> ByteString -> Int -> [Secret] hmacIter f secret seed aprev len =- let an = f secret aprev in- let out = f secret (B.concat [an, seed]) in- let digestsize = fromIntegral $ B.length out in- if digestsize >= len- then [ B.take (fromIntegral len) out ]- else out : hmacIter f secret seed an (len - digestsize)+ let an = f secret aprev+ in let out = f secret (BA.concat [an, BA.convert seed])+ in let digestsize = BA.length out+ in if digestsize >= len+ then [BA.take (fromIntegral len) out]+ else out : hmacIter f secret seed (BA.convert an) (len - digestsize) -prf_SHA1 :: ByteString -> ByteString -> Int -> ByteString-prf_SHA1 secret seed len = B.concat $ hmacIter hmacSHA1 secret seed seed len+type PRF = Secret -> ByteString -> Int -> Secret -prf_MD5 :: ByteString -> ByteString -> Int -> ByteString-prf_MD5 secret seed len = B.concat $ hmacIter hmacMD5 secret seed seed len+prf_SHA1 :: PRF+prf_SHA1 secret seed len = BA.concat $ hmacIter (hmac SHA1) secret seed seed len -prf_MD5SHA1 :: ByteString -> ByteString -> Int -> ByteString+prf_MD5 :: PRF+prf_MD5 secret seed len = BA.concat $ hmacIter (hmac MD5) secret seed seed len++prf_MD5SHA1 :: PRF prf_MD5SHA1 secret seed len =- B.pack $ B.zipWith xor (prf_MD5 s1 seed len) (prf_SHA1 s2 seed len)- where slen = B.length secret- s1 = B.take (slen `div` 2 + slen `mod` 2) secret- s2 = B.drop (slen `div` 2) secret+ BA.xor (prf_MD5 s1 seed len) (prf_SHA1 s2 seed len)+ where+ slen = BA.length secret+ s1 = BA.take (slen `div` 2 + slen `mod` 2) secret+ s2 = BA.drop (slen `div` 2) secret -prf_SHA256 :: ByteString -> ByteString -> Int -> ByteString-prf_SHA256 secret seed len = B.concat $ hmacIter hmacSHA256 secret seed seed len+prf_SHA256 :: PRF+prf_SHA256 secret seed len = BA.concat $ hmacIter (hmac SHA256) secret seed seed len++-- | For now we ignore the version, but perhaps some day the PRF will depend+-- not only on the cipher PRF algorithm, but also on the protocol version.+prf_TLS :: Version -> Hash -> PRF+prf_TLS _ halg secret seed len =+ BA.concat $ hmacIter (hmac halg) secret seed seed len
Network/TLS/Measurement.hs view
@@ -1,46 +1,44 @@--- |--- Module : Network.TLS.Measurement--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Measurement- ( Measurement(..)- , newMeasurement- , addBytesReceived- , addBytesSent- , resetBytesCounters- , incrementNbHandshakes- ) where+module Network.TLS.Measurement (+ Measurement (..),+ newMeasurement,+ addBytesReceived,+ addBytesSent,+ resetBytesCounters,+ incrementNbHandshakes,+) where -import Data.Word+import Network.TLS.Imports -- | record some data about this connection. data Measurement = Measurement- { nbHandshakes :: !Word32 -- ^ number of handshakes on this context- , bytesReceived :: !Word32 -- ^ bytes received since last handshake- , bytesSent :: !Word32 -- ^ bytes sent since last handshake- } deriving (Show,Eq)+ { nbHandshakes :: Word32+ -- ^ number of handshakes on this context+ , bytesReceived :: Word32+ -- ^ bytes received since last handshake+ , bytesSent :: Word32+ -- ^ bytes sent since last handshake+ }+ deriving (Show, Eq) newMeasurement :: Measurement-newMeasurement = Measurement- { nbHandshakes = 0+newMeasurement =+ Measurement+ { nbHandshakes = 0 , bytesReceived = 0- , bytesSent = 0+ , bytesSent = 0 } addBytesReceived :: Int -> Measurement -> Measurement addBytesReceived sz measure =- measure { bytesReceived = bytesReceived measure + fromIntegral sz }+ measure{bytesReceived = bytesReceived measure + fromIntegral sz} addBytesSent :: Int -> Measurement -> Measurement addBytesSent sz measure =- measure { bytesSent = bytesSent measure + fromIntegral sz }+ measure{bytesSent = bytesSent measure + fromIntegral sz} resetBytesCounters :: Measurement -> Measurement-resetBytesCounters measure = measure { bytesReceived = 0, bytesSent = 0 }+resetBytesCounters measure = measure{bytesReceived = 0, bytesSent = 0} incrementNbHandshakes :: Measurement -> Measurement incrementNbHandshakes measure =- measure { nbHandshakes = nbHandshakes measure + 1 }+ measure{nbHandshakes = nbHandshakes measure + 1}
Network/TLS/Packet.hs view
@@ -1,407 +1,441 @@ {-# LANGUAGE OverloadedStrings #-}--- |--- Module : Network.TLS.Packet--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ the Packet module contains everything necessary to serialize and deserialize things--- with only explicit parameters, no TLS state is involved here.----module Network.TLS.Packet- (+{-# LANGUAGE RecordWildCards #-}++-- | The Packet module contains everything necessary to serialize and+-- deserialize things with only explicit parameters, no TLS state is+-- involved here.+module Network.TLS.Packet ( -- * params for encoding and decoding- CurrentParams(..)+ CurrentParams (..),+ -- * marshall functions for header messages- , decodeHeader- , decodeDeprecatedHeaderLength- , decodeDeprecatedHeader- , encodeHeader- , encodeHeaderNoVer -- use for SSL3+ decodeHeader,+ encodeHeader, -- * marshall functions for alert messages- , decodeAlert- , decodeAlerts- , encodeAlerts+ decodeAlert,+ decodeAlerts,+ encodeAlerts, -- * marshall functions for handshake messages- , decodeHandshakeRecord- , decodeHandshake- , decodeDeprecatedHandshake- , encodeHandshake- , encodeHandshakes- , encodeHandshakeHeader- , encodeHandshakeContent+ decodeHandshakeRecord,+ decodeHandshake,+ encodeHandshake,+ encodeHandshake',+ encodeCertificate,+ decodeClientHello', -- * marshall functions for change cipher spec message- , decodeChangeCipherSpec- , encodeChangeCipherSpec+ decodeChangeCipherSpec,+ encodeChangeCipherSpec,+ decodePreMainSecret,+ encodePreMainSecret,+ encodeSignedDHParams,+ encodeSignedECDHParams,+ decodeReallyServerKeyXchgAlgorithmData, - , decodePreMasterSecret- , encodePreMasterSecret- , encodeSignedDHParams+ -- * generate things for packet content+ generateMainSecret,+ generateExtendedMainSecret,+ generateKeyBlock, - , decodeReallyServerKeyXchgAlgorithmData+ -- * for extensions parsing+ getSignatureHashAlgorithm,+ putSignatureHashAlgorithm,+ getBinaryVersion,+ putBinaryVersion,+ getClientRandom32,+ putClientRandom32,+ getServerRandom32,+ putServerRandom32,+ getExtensions,+ putExtension,+ getSession,+ putSession,+ putDNames,+ getDNames,+ getHandshakeType, - -- * generate things for packet content- , generateMasterSecret- , generateKeyBlock- , generateClientFinished- , generateServerFinished+ -- * PRF+ PRF,+ getPRF,+) where - , generateCertificateVerify_SSL- ) where+import Data.ByteArray (ByteArrayAccess, convert)+import qualified Data.ByteString as B+import Data.X509 (+ CertificateChain,+ CertificateChainRaw (..),+ decodeCertificateChain,+ encodeCertificateChain,+ ) -import Network.TLS.Struct-import Network.TLS.Wire-import Network.TLS.Cap-import Data.Maybe (fromJust)-import Data.Word-import Control.Applicative ((<$>), (<*>))-import Control.Monad-import Data.ASN1.Types (fromASN1, toASN1)-import Data.ASN1.Encoding (decodeASN1', encodeASN1')-import Data.ASN1.BinaryEncoding (DER(..))-import Data.X509 (CertificateChainRaw(..), encodeCertificateChain, decodeCertificateChain) import Network.TLS.Crypto+import Network.TLS.Imports import Network.TLS.MAC-import Network.TLS.Cipher (CipherKeyExchangeType(..))-import Data.ByteString (ByteString)-import qualified Data.ByteString as B-import qualified Data.ByteString.Char8 as BC+import Network.TLS.Struct+import Network.TLS.Types+import Network.TLS.Util.ASN1+import Network.TLS.Wire -import qualified Crypto.Hash.SHA1 as SHA1-import qualified Crypto.Hash.MD5 as MD5+----------------------------------------------------------------+-- Header data CurrentParams = CurrentParams- { cParamsVersion :: Version -- ^ current protocol version- , cParamsKeyXchgType :: Maybe CipherKeyExchangeType -- ^ current key exchange type- , cParamsSupportNPN :: Bool -- ^ support Next Protocol Negotiation extension- } deriving (Show,Eq)+ { cParamsVersion :: Version+ -- ^ current protocol version+ , cParamsKeyXchgType :: Maybe CipherKeyExchangeType+ -- ^ current key exchange type+ }+ deriving (Show, Eq) -{- marshall helpers -}-getVersion :: Get Version-getVersion = do- major <- getWord8- minor <- getWord8- case verOfNum (major, minor) of- Nothing -> fail ("invalid version : " ++ show major ++ "," ++ show minor)- Just v -> return v+-- marshall helpers+getBinaryVersion :: Get Version+getBinaryVersion = Version <$> getWord16 -putVersion :: Version -> Put-putVersion ver = putWord8 major >> putWord8 minor- where (major, minor) = numericalVer ver+putBinaryVersion :: Version -> Put+putBinaryVersion (Version ver) = putWord16 ver getHeaderType :: Get ProtocolType-getHeaderType = do- ty <- getWord8- case valToType ty of- Nothing -> fail ("invalid header type: " ++ show ty)- Just t -> return t+getHeaderType = ProtocolType <$> getWord8 putHeaderType :: ProtocolType -> Put-putHeaderType = putWord8 . valOfType+putHeaderType (ProtocolType pt) = putWord8 pt getHandshakeType :: Get HandshakeType-getHandshakeType = do- ty <- getWord8- case valToType ty of- Nothing -> fail ("invalid handshake type: " ++ show ty)- Just t -> return t+getHandshakeType = HandshakeType <$> getWord8 -{-- - decode and encode headers- -}+-- decode and encode headers decodeHeader :: ByteString -> Either TLSError Header-decodeHeader = runGetErr "header" $ liftM3 Header getHeaderType getVersion getWord16+decodeHeader =+ runGetErr "header" $ Header <$> getHeaderType <*> getBinaryVersion <*> getWord16 -decodeDeprecatedHeaderLength :: ByteString -> Either TLSError Word16-decodeDeprecatedHeaderLength = runGetErr "deprecatedheaderlength" $ subtract 0x8000 <$> getWord16+encodeHeader :: Header -> ByteString+encodeHeader (Header pt ver len) = runPut (putHeaderType pt >> putBinaryVersion ver >> putWord16 len) -decodeDeprecatedHeader :: Word16 -> ByteString -> Either TLSError Header-decodeDeprecatedHeader size =- runGetErr "deprecatedheader" $ do- 1 <- getWord8- version <- getVersion- return $ Header ProtocolType_DeprecatedHandshake version size+-- FIXME check len <= 2^14 -encodeHeader :: Header -> ByteString-encodeHeader (Header pt ver len) = runPut (putHeaderType pt >> putVersion ver >> putWord16 len)- {- FIXME check len <= 2^14 -}+------------------------------------------------------------+-- CCS -encodeHeaderNoVer :: Header -> ByteString-encodeHeaderNoVer (Header pt _ len) = runPut (putHeaderType pt >> putWord16 len)- {- FIXME check len <= 2^14 -}+decodeChangeCipherSpec :: ByteString -> Either TLSError ()+decodeChangeCipherSpec = runGetErr "changecipherspec" $ do+ x <- getWord8+ when (x /= 1) $ fail "unknown change cipher spec content"+ len <- remaining+ when (len /= 0) $ fail "the length of CSS must be 1" -{-- - decode and encode ALERT- -}+encodeChangeCipherSpec :: ByteString+encodeChangeCipherSpec = runPut (putWord8 1)++----------------------------------------------------------------+-- Alert+ decodeAlert :: Get (AlertLevel, AlertDescription) decodeAlert = do- al <- getWord8- ad <- getWord8- case (valToType al, valToType ad) of- (Just a, Just d) -> return (a, d)- (Nothing, _) -> fail "cannot decode alert level"- (_, Nothing) -> fail "cannot decode alert description"+ al <- AlertLevel <$> getWord8+ ad <- AlertDescription <$> getWord8+ return (al, ad) decodeAlerts :: ByteString -> Either TLSError [(AlertLevel, AlertDescription)]-decodeAlerts = runGetErr "alerts" $ loop- where loop = do- r <- remaining- if r == 0- then return []- else liftM2 (:) decodeAlert loop+decodeAlerts = runGetErr "alerts" loop+ where+ loop = do+ r <- remaining+ if r == 0+ then return []+ else (:) <$> decodeAlert <*> loop encodeAlerts :: [(AlertLevel, AlertDescription)] -> ByteString encodeAlerts l = runPut $ mapM_ encodeAlert l- where encodeAlert (al, ad) = putWord8 (valOfType al) >> putWord8 (valOfType ad)+ where+ encodeAlert (al, ad) = putWord8 (fromAlertLevel al) >> putWord8 (fromAlertDescription ad) -{- decode and encode HANDSHAKE -}-decodeHandshakeRecord :: ByteString -> GetResult (HandshakeType, Bytes)+----------------------------------------------------------------+-- decode HANDSHAKE++decodeHandshakeRecord :: ByteString -> GetResult (HandshakeType, ByteString) decodeHandshakeRecord = runGet "handshake-record" $ do- ty <- getHandshakeType+ ty <- getHandshakeType content <- getOpaque24 return (ty, content) -decodeHandshake :: CurrentParams -> HandshakeType -> ByteString -> Either TLSError Handshake+{- FOURMOLU_DISABLE -}+decodeHandshake+ :: CurrentParams -> HandshakeType -> ByteString -> Either TLSError Handshake decodeHandshake cp ty = runGetErr ("handshake[" ++ show ty ++ "]") $ case ty of- HandshakeType_HelloRequest -> decodeHelloRequest- HandshakeType_ClientHello -> decodeClientHello- HandshakeType_ServerHello -> decodeServerHello- HandshakeType_Certificate -> decodeCertificates- HandshakeType_ServerKeyXchg -> decodeServerKeyXchg cp- HandshakeType_CertRequest -> decodeCertRequest cp- HandshakeType_ServerHelloDone -> decodeServerHelloDone- HandshakeType_CertVerify -> decodeCertVerify cp- HandshakeType_ClientKeyXchg -> decodeClientKeyXchg cp- HandshakeType_Finished -> decodeFinished- HandshakeType_NPN -> do- unless (cParamsSupportNPN cp) $ fail "unsupported handshake type"- decodeNextProtocolNegotiation--decodeDeprecatedHandshake :: ByteString -> Either TLSError Handshake-decodeDeprecatedHandshake b = runGetErr "deprecatedhandshake" getDeprecated b- where getDeprecated = do- 1 <- getWord8- ver <- getVersion- cipherSpecLen <- fromEnum <$> getWord16- sessionIdLen <- fromEnum <$> getWord16- challengeLen <- fromEnum <$> getWord16- ciphers <- getCipherSpec cipherSpecLen- session <- getSessionId sessionIdLen- random <- getChallenge challengeLen- let compressions = [0]- return $ ClientHello ver random session ciphers compressions [] (Just b)- getCipherSpec len | len < 3 = return []- getCipherSpec len = do- [c0,c1,c2] <- map fromEnum <$> replicateM 3 getWord8- ([ toEnum $ c1 * 0x100 + c2 | c0 == 0 ] ++) <$> getCipherSpec (len - 3)- getSessionId 0 = return $ Session Nothing- getSessionId len = Session . Just <$> getBytes len- getChallenge len | 32 < len = getBytes (len - 32) >> getChallenge 32- getChallenge len = ClientRandom . B.append (B.replicate (32 - len) 0) <$> getBytes len+ HandshakeType_HelloRequest -> decodeHelloRequest+ HandshakeType_ClientHello -> decodeClientHello False+ HandshakeType_ServerHello -> decodeServerHello+ HandshakeType_NewSessionTicket -> decodeNewSessionTicket+ HandshakeType_Certificate -> decodeCertificate+ HandshakeType_ServerKeyXchg -> decodeServerKeyXchg cp+ HandshakeType_CertRequest -> decodeCertRequest cp+ HandshakeType_ServerHelloDone -> decodeServerHelloDone+ HandshakeType_CertVerify -> decodeCertVerify cp+ HandshakeType_ClientKeyXchg -> decodeClientKeyXchg cp+ HandshakeType_Finished -> decodeFinished+ x -> fail $ "Unsupported HandshakeType " ++ show x+{- FOURMOLU_ENABLE -} decodeHelloRequest :: Get Handshake decodeHelloRequest = return HelloRequest -decodeClientHello :: Get Handshake-decodeClientHello = do- ver <- getVersion- random <- getClientRandom32- session <- getSession- ciphers <- getWords16+decodeClientHello' :: ByteString -> Either TLSError Handshake+decodeClientHello' = runGetErr "decodeClientHello'" $ decodeClientHello True++decodeClientHello :: Bool -> Get Handshake+decodeClientHello inner = do+ ver <- getBinaryVersion+ random <- getClientRandom32+ session <- getSession+ ciphers <- map CipherId <$> getWords16 compressions <- getWords8- r <- remaining- exts <- if hasHelloExtensions ver && r > 0- then fmap fromIntegral getWord16 >>= getExtensions+ r <- remaining+ exts <-+ if r > 0+ then getWord16 >>= getExtensions . fromIntegral else return []- return $ ClientHello ver random session ciphers compressions exts Nothing+ r1 <- remaining+ if inner+ then+ checkAndSkip r1+ else when (r1 /= 0) $ fail "Client hello has garbage"+ return $+ ClientHello $+ CH+ { chVersion = ver+ , chRandom = random+ , chSession = session+ , chCiphers = ciphers+ , chComps = compressions+ , chExtensions = exts+ }+ where+ checkAndSkip 0 = return ()+ checkAndSkip r = do+ zero <- getWord8+ when (zero /= 0) $ fail "Inner client hello has garbage"+ checkAndSkip (r - 1) decodeServerHello :: Get Handshake decodeServerHello = do- ver <- getVersion- random <- getServerRandom32- session <- getSession- cipherid <- getWord16+ ver <- getBinaryVersion+ random <- getServerRandom32+ session <- getSession+ cipherid <- CipherId <$> getWord16 compressionid <- getWord8- r <- remaining- exts <- if hasHelloExtensions ver && r > 0- then fmap fromIntegral getWord16 >>= getExtensions+ r <- remaining+ exts <-+ if r > 0+ then getWord16 >>= getExtensions . fromIntegral else return []- return $ ServerHello ver random session cipherid compressionid exts+ return $+ ServerHello $+ SH+ { shVersion = ver+ , shRandom = random+ , shSession = session+ , shCipher = cipherid+ , shComp = compressionid+ , shExtensions = exts+ } -decodeServerHelloDone :: Get Handshake-decodeServerHelloDone = return ServerHelloDone+decodeNewSessionTicket :: Get Handshake+decodeNewSessionTicket = NewSessionTicket <$> getWord32 <*> getOpaque16 -decodeCertificates :: Get Handshake-decodeCertificates = do- certsRaw <- CertificateChainRaw <$> (getWord24 >>= \len -> getList (fromIntegral len) getCertRaw)+decodeCertificate :: Get Handshake+decodeCertificate = do+ certsRaw <-+ CertificateChainRaw+ <$> (getWord24 >>= \len -> getList (fromIntegral len) getCertRaw) case decodeCertificateChain certsRaw of Left (i, s) -> fail ("error certificate parsing " ++ show i ++ ":" ++ s)- Right cc -> return $ Certificates cc- where getCertRaw = getOpaque24 >>= \cert -> return (3 + B.length cert, cert)+ Right cc -> return $ Certificate $ CertificateChain_ cc+ where+ getCertRaw = getOpaque24 >>= \cert -> return (3 + B.length cert, cert) -decodeFinished :: Get Handshake-decodeFinished = Finished <$> (remaining >>= getBytes)+---- -decodeNextProtocolNegotiation :: Get Handshake-decodeNextProtocolNegotiation = do- opaque <- getOpaque8- _ <- getOpaque8 -- ignore padding- return $ HsNextProtocolNegotiation opaque+decodeServerKeyXchg :: CurrentParams -> Get Handshake+decodeServerKeyXchg cp =+ case cParamsKeyXchgType cp of+ Just cke -> ServerKeyXchg <$> decodeServerKeyXchgAlgorithmData (cParamsVersion cp) cke+ Nothing -> ServerKeyXchg . SKX_Unparsed <$> (remaining >>= getBytes) +decodeServerKeyXchgAlgorithmData+ :: Version+ -> CipherKeyExchangeType+ -> Get ServerKeyXchgAlgorithmData+decodeServerKeyXchgAlgorithmData ver cke = toCKE+ where+ toCKE = case cke of+ CipherKeyExchange_RSA -> SKX_RSA . Just <$> decodeServerKeyXchg_RSA+ CipherKeyExchange_DH_Anon -> SKX_DH_Anon <$> decodeServerKeyXchg_DH+ CipherKeyExchange_DHE_RSA -> do+ dhparams <- getServerDHParams+ signature <- getDigitallySigned ver+ return $ SKX_DHE_RSA dhparams signature+ CipherKeyExchange_DHE_DSA -> do+ dhparams <- getServerDHParams+ signature <- getDigitallySigned ver+ return $ SKX_DHE_DSA dhparams signature+ CipherKeyExchange_ECDHE_RSA -> do+ ecdhparams <- getServerECDHParams+ signature <- getDigitallySigned ver+ return $ SKX_ECDHE_RSA ecdhparams signature+ CipherKeyExchange_ECDHE_ECDSA -> do+ ecdhparams <- getServerECDHParams+ signature <- getDigitallySigned ver+ return $ SKX_ECDHE_ECDSA ecdhparams signature+ _ -> do+ bs <- remaining >>= getBytes+ return $ SKX_Unknown bs++decodeServerKeyXchg_DH :: Get ServerDHParams+decodeServerKeyXchg_DH = getServerDHParams++-- We don't support ECDH_Anon at this moment+-- decodeServerKeyXchg_ECDH :: Get ServerECDHParams++decodeServerKeyXchg_RSA :: Get ServerRSAParams+decodeServerKeyXchg_RSA =+ ServerRSAParams+ <$> getInteger16 -- modulus+ <*> getInteger16 -- exponent++----+ decodeCertRequest :: CurrentParams -> Get Handshake-decodeCertRequest cp = do- certTypes <- map (fromJust . valToType . fromIntegral) <$> getWords8+decodeCertRequest _cp = do+ certTypes <- map CertificateType <$> getWords8+ sigHashAlgs <- getWord16 >>= getSignatureHashAlgorithms+ CertRequest certTypes sigHashAlgs <$> getDNames+ where+ getSignatureHashAlgorithms len =+ getList (fromIntegral len) (getSignatureHashAlgorithm >>= \sh -> return (2, sh)) - sigHashAlgs <- if cParamsVersion cp >= TLS12- then Just <$> (getWord16 >>= getSignatureHashAlgorithms)- else return Nothing- dNameLen <- getWord16- -- FIXME: Decide whether to remove this check completely or to make it an option.- -- when (cParamsVersion cp < TLS12 && dNameLen < 3) $ fail "certrequest distinguishname not of the correct size"- dNames <- getList (fromIntegral dNameLen) getDName- return $ CertRequest certTypes sigHashAlgs dNames- where getSignatureHashAlgorithms len = getList (fromIntegral len) (getSignatureHashAlgorithm >>= \sh -> return (2, sh))- getDName = do- dName <- getOpaque16- when (B.length dName == 0) $ fail "certrequest: invalid DN length"- dn <- case decodeASN1' DER dName of- Left e -> fail ("cert request decoding DistinguishedName ASN1 failed: " ++ show e)- Right asn1s -> case fromASN1 asn1s of- Left e -> fail ("cert request parsing DistinguishedName ASN1 failed: " ++ show e)- Right (d,_) -> return d- return (2 + B.length dName, dn)+---- +decodeServerHelloDone :: Get Handshake+decodeServerHelloDone = return ServerHelloDone+ decodeCertVerify :: CurrentParams -> Get Handshake decodeCertVerify cp = CertVerify <$> getDigitallySigned (cParamsVersion cp) decodeClientKeyXchg :: CurrentParams -> Get Handshake-decodeClientKeyXchg cp = -- case ClientKeyXchg <$> (remaining >>= getBytes)+decodeClientKeyXchg cp =+ -- case ClientKeyXchg <$> (remaining >>= getBytes) case cParamsKeyXchgType cp of- Nothing -> error "no client key exchange type"+ Nothing -> fail "no client key exchange type" Just cke -> ClientKeyXchg <$> parseCKE cke- where parseCKE CipherKeyExchange_RSA = CKX_RSA <$> (remaining >>= getBytes)- parseCKE CipherKeyExchange_DHE_RSA = parseClientDHPublic- parseCKE CipherKeyExchange_DHE_DSS = parseClientDHPublic- parseCKE CipherKeyExchange_DH_Anon = parseClientDHPublic- parseCKE _ = error "unsupported client key exchange type"- parseClientDHPublic = CKX_DH . dhPublic <$> getInteger16--decodeServerKeyXchg_DH :: Get ServerDHParams-decodeServerKeyXchg_DH = getServerDHParams--decodeServerKeyXchg_RSA :: Get ServerRSAParams-decodeServerKeyXchg_RSA = ServerRSAParams <$> getInteger16 -- modulus- <*> getInteger16 -- exponent+ where+ parseCKE CipherKeyExchange_RSA = CKX_RSA <$> (remaining >>= getBytes)+ parseCKE CipherKeyExchange_DHE_RSA = parseClientDHPublic+ parseCKE CipherKeyExchange_DHE_DSA = parseClientDHPublic+ parseCKE CipherKeyExchange_DH_Anon = parseClientDHPublic+ 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 -decodeServerKeyXchgAlgorithmData :: Version- -> CipherKeyExchangeType- -> Get ServerKeyXchgAlgorithmData-decodeServerKeyXchgAlgorithmData ver cke = toCKE- where toCKE = case cke of- CipherKeyExchange_RSA -> SKX_RSA . Just <$> decodeServerKeyXchg_RSA- CipherKeyExchange_DH_Anon -> SKX_DH_Anon <$> decodeServerKeyXchg_DH- CipherKeyExchange_DHE_RSA -> do- dhparams <- getServerDHParams- signature <- getDigitallySigned ver- return $ SKX_DHE_RSA dhparams signature- CipherKeyExchange_DHE_DSS -> do- dhparams <- getServerDHParams- signature <- getDigitallySigned ver- return $ SKX_DHE_DSS dhparams signature- _ -> do- bs <- remaining >>= getBytes- return $ SKX_Unknown bs+decodeFinished :: Get Handshake+decodeFinished = Finished . VerifyData <$> (remaining >>= getBytes) -decodeServerKeyXchg :: CurrentParams -> Get Handshake-decodeServerKeyXchg cp =- case cParamsKeyXchgType cp of- Just cke -> ServerKeyXchg <$> decodeServerKeyXchgAlgorithmData (cParamsVersion cp) cke- Nothing -> ServerKeyXchg . SKX_Unparsed <$> (remaining >>= getBytes)+----------------------------------------------------------------+-- encode HANDSHAKE encodeHandshake :: Handshake -> ByteString encodeHandshake o =- let content = runPut $ encodeHandshakeContent o in- let len = fromIntegral $ B.length content in- let header = case o of- ClientHello _ _ _ _ _ _ (Just _) -> "" -- SSLv2 ClientHello message- _ -> runPut $ encodeHandshakeHeader (typeOfHandshake o) len in- B.concat [ header, content ]--encodeHandshakes :: [Handshake] -> ByteString-encodeHandshakes hss = B.concat $ map encodeHandshake hss+ let content = encodeHandshake' o+ in let len = B.length content+ in let header = runPut $ encodeHandshakeHeader (typeOfHandshake o) len+ in B.concat [header, content] encodeHandshakeHeader :: HandshakeType -> Int -> Put-encodeHandshakeHeader ty len = putWord8 (valOfType ty) >> putWord24 len--encodeHandshakeContent :: Handshake -> Put--encodeHandshakeContent (ClientHello _ _ _ _ _ _ (Just deprecated)) = do- putBytes deprecated-encodeHandshakeContent (ClientHello version random session cipherIDs compressionIDs exts Nothing) = do- putVersion version- putClientRandom32 random- putSession session- putWords16 cipherIDs- putWords8 compressionIDs- putExtensions exts- return ()--encodeHandshakeContent (ServerHello version random session cipherID compressionID exts) =- putVersion version >> putServerRandom32 random >> putSession session- >> putWord16 cipherID >> putWord8 compressionID- >> putExtensions exts >> return ()--encodeHandshakeContent (Certificates cc) = putOpaque24 (runPut $ mapM_ putOpaque24 certs)- where (CertificateChainRaw certs) = encodeCertificateChain cc--encodeHandshakeContent (ClientKeyXchg ckx) = do- case ckx of- CKX_RSA encryptedPreMaster -> putBytes encryptedPreMaster- CKX_DH clientDHPublic -> putInteger16 $ dhUnwrapPublic clientDHPublic+encodeHandshakeHeader ty len = putWord8 (fromHandshakeType ty) >> putWord24 len -encodeHandshakeContent (ServerKeyXchg skg) =+encodeHandshake' :: Handshake -> ByteString+encodeHandshake' HelloRequest = ""+encodeHandshake' (ClientHello CH{..}) = runPut $ do+ putBinaryVersion chVersion+ putClientRandom32 chRandom+ putSession chSession+ putWords16 $ map fromCipherId chCiphers+ putWords8 chComps+ putExtensions chExtensions+encodeHandshake' (ServerHello SH{..}) = runPut $ do+ putBinaryVersion shVersion+ putServerRandom32 shRandom+ putSession shSession+ putWord16 $ fromCipherId shCipher+ putWord8 shComp+ putExtensions shExtensions+encodeHandshake' (NewSessionTicket life ticket) = runPut $ do+ putWord32 life+ putOpaque16 ticket+encodeHandshake' (Certificate (CertificateChain_ cc)) = encodeCertificate cc+encodeHandshake' (ServerKeyXchg skg) = runPut $ case skg of- SKX_RSA _ -> error "encodeHandshakeContent SKX_RSA not implemented"- SKX_DH_Anon params -> putServerDHParams params+ SKX_RSA _ -> error "encodeHandshake' SKX_RSA not implemented"+ SKX_DH_Anon params -> putServerDHParams params SKX_DHE_RSA params sig -> putServerDHParams params >> putDigitallySigned sig- SKX_DHE_DSS params sig -> putServerDHParams params >> putDigitallySigned sig- SKX_Unparsed bytes -> putBytes bytes- _ -> error ("encodeHandshakeContent: cannot handle: " ++ show skg)--encodeHandshakeContent (HelloRequest) = return ()-encodeHandshakeContent (ServerHelloDone) = return ()--encodeHandshakeContent (CertRequest certTypes sigAlgs certAuthorities) = do- putWords8 (map valOfType certTypes)- case sigAlgs of- Nothing -> return ()- Just l -> putWords16 $ map (\(x,y) -> (fromIntegral $ valOfType x) * 256 + (fromIntegral $ valOfType y)) l- encodeCertAuthorities certAuthorities- where -- Convert a distinguished name to its DER encoding.- encodeCA dn = return $ encodeASN1' DER (toASN1 dn []) --B.concat $ L.toChunks $ encodeDN dn+ SKX_DHE_DSA params sig -> putServerDHParams params >> putDigitallySigned sig+ SKX_ECDHE_RSA params sig -> putServerECDHParams params >> putDigitallySigned sig+ SKX_ECDHE_ECDSA params sig -> putServerECDHParams params >> putDigitallySigned sig+ SKX_Unparsed bytes -> putBytes bytes+ _ -> error ("encodeHandshake': cannot handle: " ++ show skg)+encodeHandshake' (CertRequest certTypes sigAlgs certAuthorities) = runPut $ do+ putWords8 (map fromCertificateType certTypes)+ putWords16 $+ map+ ( \(HashAlgorithm x, SignatureAlgorithm y) -> fromIntegral x * 256 + fromIntegral y+ )+ sigAlgs+ putDNames certAuthorities+encodeHandshake' ServerHelloDone = ""+encodeHandshake' (CertVerify digitallySigned) = runPut $ putDigitallySigned digitallySigned+encodeHandshake' (ClientKeyXchg ckx) = runPut $ do+ case ckx of+ CKX_RSA encryptedPreMain -> putBytes encryptedPreMain+ CKX_DH clientDHPublic -> putInteger16 $ dhUnwrapPublic clientDHPublic+ CKX_ECDH bytes -> putOpaque8 bytes+encodeHandshake' (Finished (VerifyData opaque)) = runPut $ putBytes opaque - -- Encode a list of distinguished names.- encodeCertAuthorities certAuths = do- enc <- mapM encodeCA certAuths- let totLength = sum $ map (((+) 2) . B.length) enc- putWord16 (fromIntegral totLength)- mapM_ (\ b -> putWord16 (fromIntegral (B.length b)) >> putBytes b) enc+------------------------------------------------------------+-- CA distinguished names -encodeHandshakeContent (CertVerify digitallySigned) = putDigitallySigned digitallySigned+-- | Decode a list CA distinguished names+getDNames :: Get [DistinguishedName]+getDNames = do+ dNameLen <- getWord16+ -- FIXME: Decide whether to remove this check completely or to make it an option.+ -- when (cParamsVersion cp < TLS12 && dNameLen < 3) $ fail "certrequest distinguishname not of the correct size"+ getList (fromIntegral dNameLen) getDName+ where+ getDName = do+ dName <- getOpaque16+ when (B.length dName == 0) $ fail "certrequest: invalid DN length"+ dn <-+ either fail return $ decodeASN1Object "cert request DistinguishedName" dName+ return (2 + B.length dName, dn) -encodeHandshakeContent (Finished opaque) = putBytes opaque+-- | Encode a list of distinguished names.+putDNames :: [DistinguishedName] -> Put+putDNames dnames = do+ enc <- mapM encodeCA dnames+ let totLength = sum $ map ((+) 2 . B.length) enc+ putWord16 (fromIntegral totLength)+ mapM_ (\b -> putWord16 (fromIntegral (B.length b)) >> putBytes b) enc+ where+ -- Convert a distinguished name to its DER encoding.+ encodeCA dn = return $ encodeASN1Object dn -encodeHandshakeContent (HsNextProtocolNegotiation protocol) = do- putOpaque8 protocol- putOpaque8 $ B.replicate paddingLen 0- where paddingLen = 32 - ((B.length protocol + 2) `mod` 32)+------------------------------------------------------------ {- FIXME make sure it return error if not 32 available -}-getRandom32 :: Get Bytes+getRandom32 :: Get ByteString getRandom32 = getBytes 32 getServerRandom32 :: Get ServerRandom@@ -410,7 +444,7 @@ getClientRandom32 :: Get ClientRandom getClientRandom32 = ClientRandom <$> getRandom32 -putRandom32 :: Bytes -> Put+putRandom32 :: ByteString -> Put putRandom32 = putBytes putClientRandom32 :: ClientRandom -> Put@@ -419,166 +453,199 @@ putServerRandom32 :: ServerRandom -> Put putServerRandom32 (ServerRandom r) = putRandom32 r +------------------------------------------------------------+ getSession :: Get Session getSession = do len8 <- getWord8 case fromIntegral len8 of- 0 -> return $ Session Nothing- len -> Session . Just <$> getBytes len+ 0 -> return $ Session Nothing+ len+ | len > 32 -> fail "the length of session id must be <= 32"+ | otherwise -> Session . Just <$> getBytes len putSession :: Session -> Put-putSession (Session Nothing) = putWord8 0+putSession (Session Nothing) = putWord8 0 putSession (Session (Just s)) = putOpaque8 s +------------------------------------------------------------+ getExtensions :: Int -> Get [ExtensionRaw]-getExtensions 0 = return []+getExtensions 0 = return [] getExtensions len = do- extty <- getWord16+ extty <- ExtensionID <$> getWord16 extdatalen <- getWord16 extdata <- getBytes $ fromIntegral extdatalen extxs <- getExtensions (len - fromIntegral extdatalen - 4)- return $ (extty, extdata) : extxs+ return $ ExtensionRaw extty extdata : extxs putExtension :: ExtensionRaw -> Put-putExtension (ty, l) = putWord16 ty >> putOpaque16 l+putExtension (ExtensionRaw (ExtensionID ty) l) = putWord16 ty >> putOpaque16 l putExtensions :: [ExtensionRaw] -> Put putExtensions [] = return () putExtensions es = putOpaque16 (runPut $ mapM_ putExtension es) +------------------------------------------------------------+ getSignatureHashAlgorithm :: Get HashAndSignatureAlgorithm getSignatureHashAlgorithm = do- h <- fromJust . valToType <$> getWord8- s <- fromJust . valToType <$> getWord8- return (h,s)+ h <- HashAlgorithm <$> getWord8+ s <- SignatureAlgorithm <$> getWord8+ return (h, s) putSignatureHashAlgorithm :: HashAndSignatureAlgorithm -> Put-putSignatureHashAlgorithm (h,s) =- putWord8 (valOfType h) >> putWord8 (valOfType s)+putSignatureHashAlgorithm (HashAlgorithm h, SignatureAlgorithm s) =+ putWord8 h >> putWord8 s +------------------------------------------------------------+ getServerDHParams :: Get ServerDHParams-getServerDHParams = ServerDHParams <$> getDHParams <*> getDHPublic- where getDHParams = dhParams <$> getInteger16 -- p- <*> getInteger16 -- g- getDHPublic = dhPublic <$> getInteger16 -- y(server)+getServerDHParams = ServerDHParams <$> getBigNum16 <*> getBigNum16 <*> getBigNum16 putServerDHParams :: ServerDHParams -> Put-putServerDHParams (ServerDHParams dhparams dhpub) =- mapM_ putInteger16 $ dhUnwrap dhparams dhpub--getDigitallySigned :: Version -> Get DigitallySigned-getDigitallySigned ver- | ver >= TLS12 = DigitallySigned <$> (Just <$> getSignatureHashAlgorithm)- <*> getOpaque16- | otherwise = DigitallySigned Nothing <$> getOpaque16+putServerDHParams (ServerDHParams p g y) = mapM_ putBigNum16 [p, g, y] -putDigitallySigned :: DigitallySigned -> Put-putDigitallySigned (DigitallySigned mhash sig) =- maybe (return ()) putSignatureHashAlgorithm mhash >> putOpaque16 sig+------------------------------------------------------------ -{-- - decode and encode ALERT- -}+-- RFC 4492 Section 5.4 Server Key Exchange+getServerECDHParams :: Get ServerECDHParams+getServerECDHParams = do+ curveType <- getWord8+ case curveType of+ 3 -> do+ -- ECParameters ECCurveType: curve name type+ grp <- Group <$> getWord16 -- ECParameters NamedCurve+ mxy <- getOpaque8 -- ECPoint+ case groupDecodePublicA grp mxy of+ Left e -> fail $ "getServerECDHParams: " ++ show e+ Right grppub -> return $ ServerECDHParams grp grppub+ _ -> fail "getServerECDHParams: unknown type for ECDH Params" -decodeChangeCipherSpec :: ByteString -> Either TLSError ()-decodeChangeCipherSpec = runGetErr "changecipherspec" $ do- x <- getWord8- when (x /= 1) (fail "unknown change cipher spec content")+-- RFC 4492 Section 5.4 Server Key Exchange+putServerECDHParams :: ServerECDHParams -> Put+putServerECDHParams (ServerECDHParams (Group grp) grppub) = do+ putWord8 3 -- ECParameters ECCurveType+ putWord16 grp -- ECParameters NamedCurve+ putOpaque8 $ groupEncodePublicA grppub -- ECPoint -encodeChangeCipherSpec :: ByteString-encodeChangeCipherSpec = runPut (putWord8 1)+------------------------------------------------------------ --- rsa pre master secret-decodePreMasterSecret :: Bytes -> Either TLSError (Version, Bytes)-decodePreMasterSecret = runGetErr "pre-master-secret" $ do- liftM2 (,) getVersion (getBytes 46)+getDigitallySigned :: Version -> Get DigitallySigned+getDigitallySigned _ver =+ DigitallySigned+ <$> getSignatureHashAlgorithm+ <*> getOpaque16 -encodePreMasterSecret :: Version -> Bytes -> Bytes-encodePreMasterSecret version bytes = runPut (putVersion version >> putBytes bytes)+putDigitallySigned :: DigitallySigned -> Put+putDigitallySigned (DigitallySigned h sig) =+ putSignatureHashAlgorithm h >> putOpaque16 sig --- | in certain cases, we haven't manage to decode ServerKeyExchange properly,--- because the decoding was too eager and the cipher wasn't been set yet.--- we keep the Server Key Exchange in it unparsed format, and this function is--- able to really decode the server key xchange if it's unparsed.-decodeReallyServerKeyXchgAlgorithmData :: Version- -> CipherKeyExchangeType- -> Bytes- -> Either TLSError ServerKeyXchgAlgorithmData-decodeReallyServerKeyXchgAlgorithmData ver cke =- runGetErr "server-key-xchg-algorithm-data" (decodeServerKeyXchgAlgorithmData ver cke)+------------------------------------------------------------ +-- RSA pre-main secret+decodePreMainSecret :: ByteString -> Either TLSError (Version, ByteString)+decodePreMainSecret =+ runGetErr "pre-main-secret" $+ (,) <$> getBinaryVersion <*> getBytes 46 -{-- - generate things for packet content- -}-type PRF = Bytes -> Bytes -> Int -> Bytes+encodePreMainSecret :: Version -> ByteString -> ByteString+encodePreMainSecret version bytes = runPut (putBinaryVersion version >> putBytes bytes) -generateMasterSecret_SSL :: Bytes -> ClientRandom -> ServerRandom -> Bytes-generateMasterSecret_SSL premasterSecret (ClientRandom c) (ServerRandom s) =- B.concat $ map (computeMD5) ["A","BB","CCC"]- where computeMD5 label = MD5.hash $ B.concat [ premasterSecret, computeSHA1 label ]- computeSHA1 label = SHA1.hash $ B.concat [ label, premasterSecret, c, s ]+------------------------------------------------------------+-- generate things for packet content -generateMasterSecret_TLS :: PRF -> Bytes -> ClientRandom -> ServerRandom -> Bytes-generateMasterSecret_TLS prf premasterSecret (ClientRandom c) (ServerRandom s) =- prf premasterSecret seed 48- where seed = B.concat [ "master secret", c, s ]+-- | The TLS12 PRF is cipher specific, and some TLS12 algorithms use SHA384+-- instead of the default SHA256.+getPRF :: Version -> Cipher -> PRF+getPRF ver ciph+ | ver < TLS12 = prf_MD5SHA1+ | maybe True (< TLS12) (cipherMinVer ciph) = prf_SHA256+ | otherwise = prf_TLS ver $ fromMaybe SHA256 $ cipherPRFHash ciph -generateMasterSecret :: Version -> Bytes -> ClientRandom -> ServerRandom -> Bytes-generateMasterSecret SSL2 = generateMasterSecret_SSL-generateMasterSecret SSL3 = generateMasterSecret_SSL-generateMasterSecret TLS10 = generateMasterSecret_TLS prf_MD5SHA1-generateMasterSecret TLS11 = generateMasterSecret_TLS prf_MD5SHA1-generateMasterSecret TLS12 = generateMasterSecret_TLS prf_SHA256+generateMainSecret_TLS+ :: PRF+ -> Secret+ -> ClientRandom+ -> ServerRandom+ -> Secret+generateMainSecret_TLS prf preMainSecret (ClientRandom c) (ServerRandom s) =+ prf preMainSecret seed 48+ where+ seed = B.concat ["master secret", c, s] -generateKeyBlock_TLS :: PRF -> ClientRandom -> ServerRandom -> Bytes -> Int -> Bytes-generateKeyBlock_TLS prf (ClientRandom c) (ServerRandom s) mastersecret kbsize =- prf mastersecret seed kbsize where seed = B.concat [ "key expansion", s, c ]+generateMainSecret+ :: Version+ -> Cipher+ -> Secret+ -> ClientRandom+ -> ServerRandom+ -> Secret+generateMainSecret v c = generateMainSecret_TLS $ getPRF v c -generateKeyBlock_SSL :: ClientRandom -> ServerRandom -> Bytes -> Int -> Bytes-generateKeyBlock_SSL (ClientRandom c) (ServerRandom s) mastersecret kbsize =- B.concat $ map computeMD5 $ take ((kbsize `div` 16) + 1) labels- where labels = [ uncurry BC.replicate x | x <- zip [1..] ['A'..'Z'] ]- computeMD5 label = MD5.hash $ B.concat [ mastersecret, computeSHA1 label ]- computeSHA1 label = SHA1.hash $ B.concat [ label, mastersecret, s, c ]+generateExtendedMainSecret+ :: ByteArrayAccess preMain+ => Version+ -> Cipher+ -> preMain+ -> ByteString+ -> Secret+generateExtendedMainSecret v c preMainSecret sessionHash =+ getPRF v c (convert preMainSecret) seed 48+ where+ seed = B.append "extended master secret" sessionHash -generateKeyBlock :: Version -> ClientRandom -> ServerRandom -> Bytes -> Int -> Bytes-generateKeyBlock SSL2 = generateKeyBlock_SSL-generateKeyBlock SSL3 = generateKeyBlock_SSL-generateKeyBlock TLS10 = generateKeyBlock_TLS prf_MD5SHA1-generateKeyBlock TLS11 = generateKeyBlock_TLS prf_MD5SHA1-generateKeyBlock TLS12 = generateKeyBlock_TLS prf_SHA256+generateKeyBlock_TLS+ :: PRF -> ClientRandom -> ServerRandom -> Secret -> Int -> Secret+generateKeyBlock_TLS prf (ClientRandom c) (ServerRandom s) mainSecret kbsize =+ prf mainSecret seed kbsize+ where+ seed = B.concat ["key expansion", s, c] -generateFinished_TLS :: PRF -> Bytes -> Bytes -> HashCtx -> Bytes-generateFinished_TLS prf label mastersecret hashctx = prf mastersecret seed 12- where seed = B.concat [ label, hashFinal hashctx ]+generateKeyBlock+ :: Version+ -> Cipher+ -> ClientRandom+ -> ServerRandom+ -> Secret+ -> Int+ -> Secret+generateKeyBlock v c = generateKeyBlock_TLS $ getPRF v c -generateFinished_SSL :: Bytes -> Bytes -> HashCtx -> Bytes-generateFinished_SSL sender mastersecret hashctx = B.concat [md5hash, sha1hash]- where md5hash = MD5.hash $ B.concat [ mastersecret, pad2, md5left ]- sha1hash = SHA1.hash $ B.concat [ mastersecret, B.take 40 pad2, sha1left ]+------------------------------------------------------------ - lefthash = hashFinal $ flip hashUpdateSSL (pad1, B.take 40 pad1)- $ foldl hashUpdate hashctx [sender,mastersecret]- (md5left,sha1left) = B.splitAt 16 lefthash- pad2 = B.replicate 48 0x5c- pad1 = B.replicate 48 0x36+encodeSignedDHParams+ :: ServerDHParams -> ClientRandom -> ServerRandom -> ByteString+encodeSignedDHParams dhparams cran sran =+ runPut $+ putClientRandom32 cran >> putServerRandom32 sran >> putServerDHParams dhparams -generateClientFinished :: Version -> Bytes -> HashCtx -> Bytes-generateClientFinished ver- | ver < TLS10 = generateFinished_SSL "CLNT"- | ver < TLS12 = generateFinished_TLS prf_MD5SHA1 "client finished"- | otherwise = generateFinished_TLS prf_SHA256 "client finished"+-- Combination of RFC 5246 and 4492 is ambiguous.+-- Let's assume ecdhe_rsa and ecdhe_dss are identical to+-- dhe_rsa and dhe_dss.+encodeSignedECDHParams+ :: ServerECDHParams -> ClientRandom -> ServerRandom -> ByteString+encodeSignedECDHParams dhparams cran sran =+ runPut $+ putClientRandom32 cran >> putServerRandom32 sran >> putServerECDHParams dhparams -generateServerFinished :: Version -> Bytes -> HashCtx -> Bytes-generateServerFinished ver- | ver < TLS10 = generateFinished_SSL "SRVR"- | ver < TLS12 = generateFinished_TLS prf_MD5SHA1 "server finished"- | otherwise = generateFinished_TLS prf_SHA256 "server finished"+encodeCertificate :: CertificateChain -> ByteString+encodeCertificate cc = runPut $ putOpaque24 (runPut $ mapM_ putOpaque24 certs)+ where+ (CertificateChainRaw certs) = encodeCertificateChain cc -generateCertificateVerify_SSL :: Bytes -> HashCtx -> Bytes-generateCertificateVerify_SSL = generateFinished_SSL ""+------------------------------------------------------------ -encodeSignedDHParams :: ClientRandom -> ServerRandom -> ServerDHParams -> Bytes-encodeSignedDHParams cran sran dhparams = runPut $- putClientRandom32 cran >> putServerRandom32 sran >> putServerDHParams dhparams+-- | in certain cases, we haven't manage to decode ServerKeyExchange properly,+-- because the decoding was too eager and the cipher wasn't been set yet.+-- we keep the Server Key Exchange in it unparsed format, and this function is+-- able to really decode the server key xchange if it's unparsed.+decodeReallyServerKeyXchgAlgorithmData+ :: Version+ -> CipherKeyExchangeType+ -> ByteString+ -> Either TLSError ServerKeyXchgAlgorithmData+decodeReallyServerKeyXchgAlgorithmData ver cke =+ runGetErr+ "server-key-xchg-algorithm-data"+ (decodeServerKeyXchgAlgorithmData ver cke)
+ Network/TLS/Packet13.hs view
@@ -0,0 +1,233 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Network.TLS.Packet13 (+ encodeHandshake13,+ decodeHandshakeRecord13,+ decodeHandshake13,+ decodeHandshakes13,+ encodeCertificate13,+) where++import Codec.Compression.Zlib+import qualified Control.Exception as E+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import Data.X509 (+ CertificateChain,+ CertificateChainRaw (..),+ decodeCertificateChain,+ encodeCertificateChain,+ )+import System.IO.Unsafe++import Network.TLS.ErrT+import Network.TLS.Imports+import Network.TLS.Packet+import Network.TLS.Struct+import Network.TLS.Struct13+import Network.TLS.Types+import Network.TLS.Wire++----------------------------------------------------------------++encodeHandshake13 :: Handshake13 -> ByteString+encodeHandshake13 hdsk = pkt+ where+ tp = typeOfHandshake13 hdsk+ content = encodeHandshake13' hdsk+ len = B.length content+ header = encodeHandshakeHeader13 tp len+ pkt = B.concat [header, content]++-- TLS 1.3 does not use "select (extensions_present)".+putExtensions :: [ExtensionRaw] -> Put+putExtensions es = putOpaque16 (runPut $ mapM_ putExtension es)++encodeHandshake13' :: Handshake13 -> ByteString+encodeHandshake13' (ServerHello13 SH{..}) = runPut $ do+ putBinaryVersion shVersion+ putServerRandom32 shRandom+ putSession shSession+ putWord16 $ fromCipherId shCipher+ putWord8 shComp+ putExtensions shExtensions+encodeHandshake13'+ ( NewSessionTicket13+ life+ ageadd+ (TicketNonce nonce)+ (SessionIDorTicket_ label)+ exts+ ) = runPut $ do+ putWord32 life+ putWord32 ageadd+ putOpaque8 nonce+ putOpaque16 label+ putExtensions exts+encodeHandshake13' EndOfEarlyData13 = ""+encodeHandshake13' (EncryptedExtensions13 exts) = runPut $ putExtensions exts+encodeHandshake13' (Certificate13 reqctx (CertificateChain_ cc) ess) = encodeCertificate13 reqctx cc ess+encodeHandshake13' (CertRequest13 reqctx exts) = runPut $ do+ putOpaque8 reqctx+ putExtensions exts+encodeHandshake13' (CertVerify13 (DigitallySigned hs sig)) = runPut $ do+ putSignatureHashAlgorithm hs+ putOpaque16 sig+encodeHandshake13' (Finished13 (VerifyData dat)) = runPut $ putBytes dat+encodeHandshake13' (KeyUpdate13 UpdateNotRequested) = runPut $ putWord8 0+encodeHandshake13' (KeyUpdate13 UpdateRequested) = runPut $ putWord8 1+encodeHandshake13' (CompressedCertificate13 reqctx (CertificateChain_ cc) ess) = runPut $ do+ putWord16 1 -- zlib: fixme+ let bs = encodeCertificate13 reqctx cc ess+ putWord24 $ fromIntegral $ B.length bs+ putOpaque24 $ BL.toStrict $ compress $ BL.fromStrict bs++encodeHandshakeHeader13 :: HandshakeType -> Int -> ByteString+encodeHandshakeHeader13 ty len = runPut $ do+ putWord8 (fromHandshakeType ty)+ putWord24 len++encodeCertificate13+ :: CertReqContext -> CertificateChain -> [[ExtensionRaw]] -> ByteString+encodeCertificate13 reqctx cc ess = runPut $ do+ putOpaque8 reqctx+ putOpaque24 (runPut $ mapM_ putCert $ zip certs ess)+ where+ CertificateChainRaw certs = encodeCertificateChain cc+ putCert (certRaw, exts) = do+ putOpaque24 certRaw+ putExtensions exts++----------------------------------------------------------------++decodeHandshakes13 :: MonadError TLSError m => ByteString -> m [Handshake13]+decodeHandshakes13 bs = case decodeHandshakeRecord13 bs of+ GotError err -> throwError err+ GotPartial _cont -> error "decodeHandshakes13"+ GotSuccess (ty, content) -> case decodeHandshake13 ty content of+ Left e -> throwError e+ Right h -> return [h]+ GotSuccessRemaining (ty, content) left -> case decodeHandshake13 ty content of+ Left e -> throwError e+ Right h -> (h :) <$> decodeHandshakes13 left++decodeHandshakeRecord13 :: ByteString -> GetResult (HandshakeType, ByteString)+decodeHandshakeRecord13 = runGet "handshake-record" $ do+ ty <- getHandshakeType+ content <- getOpaque24+ return (ty, content)++{- FOURMOLU_DISABLE -}+decodeHandshake13+ :: HandshakeType -> ByteString -> Either TLSError Handshake13+decodeHandshake13 ty = runGetErr ("handshake[" ++ show ty ++ "]") $ case ty of+ HandshakeType_ServerHello -> decodeServerHello13+ HandshakeType_NewSessionTicket -> decodeNewSessionTicket13+ HandshakeType_EndOfEarlyData -> return EndOfEarlyData13+ HandshakeType_EncryptedExtensions -> decodeEncryptedExtensions13+ HandshakeType_Certificate -> decodeCertificate13+ HandshakeType_CertRequest -> decodeCertRequest13+ HandshakeType_CertVerify -> decodeCertVerify13+ HandshakeType_Finished -> decodeFinished13+ HandshakeType_KeyUpdate -> decodeKeyUpdate13+ HandshakeType_CompressedCertificate -> decodeCompressedCertificate13+ (HandshakeType x) -> fail $ "Unsupported HandshakeType " ++ show x+{- FOURMOLU_ENABLE -}++decodeServerHello13 :: Get Handshake13+decodeServerHello13 = do+ ver <- getBinaryVersion+ random <- getServerRandom32+ session <- getSession+ cipherid <- CipherId <$> getWord16+ comp <- getWord8+ exts <- getWord16 >>= getExtensions . fromIntegral+ return $+ ServerHello13 $+ SH+ { shVersion = ver+ , shRandom = random+ , shSession = session+ , shCipher = cipherid+ , shComp = comp+ , shExtensions = exts+ }++decodeNewSessionTicket13 :: Get Handshake13+decodeNewSessionTicket13 = do+ life <- getWord32+ ageadd <- getWord32+ nonce <- TicketNonce <$> getOpaque8+ label <- SessionIDorTicket_ <$> getOpaque16+ len <- fromIntegral <$> getWord16+ exts <- getExtensions len+ return $ NewSessionTicket13 life ageadd nonce label exts++decodeEncryptedExtensions13 :: Get Handshake13+decodeEncryptedExtensions13 =+ EncryptedExtensions13 <$> do+ len <- fromIntegral <$> getWord16+ getExtensions len++decodeCertificate13 :: Get Handshake13+decodeCertificate13 = do+ reqctx <- getOpaque8+ len <- fromIntegral <$> getWord24+ (certRaws, ess) <- unzip <$> getList len getCert+ case decodeCertificateChain $ CertificateChainRaw certRaws of+ Left (i, s) -> fail ("error certificate parsing " ++ show i ++ ":" ++ s)+ Right cc -> return $ Certificate13 reqctx (CertificateChain_ cc) ess+ where+ getCert = do+ l <- fromIntegral <$> getWord24+ cert <- getBytes l+ len <- fromIntegral <$> getWord16+ exts <- getExtensions len+ return (3 + l + 2 + len, (cert, exts))++decodeCertRequest13 :: Get Handshake13+decodeCertRequest13 = do+ reqctx <- getOpaque8+ len <- fromIntegral <$> getWord16+ exts <- getExtensions len+ return $ CertRequest13 reqctx exts++decodeCertVerify13 :: Get Handshake13+decodeCertVerify13 =+ CertVerify13 <$> (DigitallySigned <$> getSignatureHashAlgorithm <*> getOpaque16)++decodeFinished13 :: Get Handshake13+decodeFinished13 = Finished13 . VerifyData <$> (remaining >>= getBytes)++decodeKeyUpdate13 :: Get Handshake13+decodeKeyUpdate13 = do+ ru <- getWord8+ case ru of+ 0 -> return $ KeyUpdate13 UpdateNotRequested+ 1 -> return $ KeyUpdate13 UpdateRequested+ x -> fail $ "Unknown request_update: " ++ show x++decodeCompressedCertificate13 :: Get Handshake13+decodeCompressedCertificate13 = do+ algo <- getWord16+ when (algo /= 1) $ fail "comp algo is not supported" -- fixme+ len <- getWord24+ bs <- getOpaque24+ if bs == ""+ then fail "empty compressed certificate"+ else case decompressIt bs of+ Left e -> fail (show e)+ Right bs' -> do+ when (B.length bs' /= len) $ fail "plain length is wrong"+ 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))+ where+ handler e = return $ Left (e :: DecompressError)
Network/TLS/Parameters.hs view
@@ -1,243 +1,899 @@--- |--- Module : Network.TLS.Parameters--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Parameters- (- ClientParams(..)- , ServerParams(..)- , CommonParams- , ClientHooks(..)- , ServerHooks(..)- , Supported(..)- , Shared(..)- -- * special default- , defaultParamsClient- -- * Parameters- , MaxFragmentEnum(..)- , CertificateUsage(..)- , CertificateRejectReason(..)- ) where--import Network.BSD (HostName)--import Network.TLS.Extension-import Network.TLS.Struct-import qualified Network.TLS.Struct as Struct-import Network.TLS.Session-import Network.TLS.Cipher-import Network.TLS.Measurement-import Network.TLS.Compression-import Network.TLS.Crypto-import Network.TLS.Credentials-import Network.TLS.X509-import Data.Monoid-import Data.Default.Class-import qualified Data.ByteString as B--type CommonParams = (Supported, Shared)--data ClientParams = ClientParams- { clientUseMaxFragmentLength :: Maybe MaxFragmentEnum- -- | Define the name of the server, along with an extra service identification blob.- -- this is important that the hostname part is properly filled for security reason,- -- as it allow to properly associate the remote side with the given certificate- -- during a handshake.- --- -- The extra blob is useful to differentiate services running on the same host, but that- -- might have different certificates given. It's only used as part of the X509 validation- -- infrastructure.- , clientServerIdentification :: (HostName, Bytes)- -- | Allow the use of the Server Name Indication TLS extension during handshake, which allow- -- the client to specify which host name, it's trying to access. This is useful to distinguish- -- CNAME aliasing (e.g. web virtual host).- , clientUseServerNameIndication :: Bool- -- | try to establish a connection using this session.- , clientWantSessionResume :: Maybe (SessionID, SessionData)- , clientShared :: Shared- , clientHooks :: ClientHooks- , clientSupported :: Supported- } deriving (Show)--defaultParamsClient :: HostName -> Bytes -> ClientParams-defaultParamsClient serverName serverId = ClientParams- { clientWantSessionResume = Nothing- , clientUseMaxFragmentLength = Nothing- , clientServerIdentification = (serverName, serverId)- , clientUseServerNameIndication = True- , clientShared = def- , clientHooks = def- , clientSupported = def- }--data ServerParams = ServerParams- { -- | request a certificate from client.- serverWantClientCert :: Bool-- -- | This is a list of certificates from which the- -- disinguished names are sent in certificate request- -- messages. For TLS1.0, it should not be empty.- , serverCACertificates :: [SignedCertificate]-- -- | Server Optional Diffie Hellman parameters. If this value is not- -- properly set, no Diffie Hellman key exchange will take place.- , serverDHEParams :: Maybe DHParams-- , serverShared :: Shared- , serverHooks :: ServerHooks- , serverSupported :: Supported- } deriving (Show)--defaultParamsServer :: ServerParams-defaultParamsServer = ServerParams- { serverWantClientCert = False- , serverCACertificates = []- , serverDHEParams = Nothing- , serverHooks = def- , serverShared = def- , serverSupported = def- }--instance Default ServerParams where- def = defaultParamsServer---- | List all the supported algorithms, versions, ciphers, etc supported.-data Supported = Supported- {- -- | Supported Versions by this context- -- On the client side, the highest version will be used to establish the connection.- -- On the server side, the highest version that is less or equal than the client version will be chosed.- supportedVersions :: [Version]- -- | Supported cipher methods- , supportedCiphers :: [Cipher]- -- | supported compressions methods- , supportedCompressions :: [Compression]- -- | All supported hash/signature algorithms pair for client- -- certificate verification, ordered by decreasing priority.- , supportedHashSignatures :: [HashAndSignatureAlgorithm]- -- | Set if we support secure renegotiation.- , supportedSecureRenegotiation :: Bool- -- | Set if we support session.- , supportedSession :: Bool- } deriving (Show,Eq)--defaultSupported :: Supported-defaultSupported = Supported- { supportedVersions = [TLS12,TLS11,TLS10,SSL3]- , supportedCiphers = []- , supportedCompressions = [nullCompression]- , supportedHashSignatures = [ (Struct.HashSHA512, SignatureRSA)- , (Struct.HashSHA384, SignatureRSA)- , (Struct.HashSHA256, SignatureRSA)- , (Struct.HashSHA224, SignatureRSA)- , (Struct.HashSHA1, SignatureDSS)- ]- , supportedSecureRenegotiation = True- , supportedSession = True- }--instance Default Supported where- def = defaultSupported--data Shared = Shared- { sharedCredentials :: Credentials- , sharedSessionManager :: SessionManager- , sharedCAStore :: CertificateStore- , sharedValidationCache :: ValidationCache- }--instance Show Shared where- show _ = "Shared"-instance Default Shared where- def = Shared- { sharedCAStore = mempty- , sharedCredentials = mempty- , sharedSessionManager = noSessionManager- , sharedValidationCache = def- }---- | A set of callbacks run by the clients for various corners of TLS establishment-data ClientHooks = ClientHooks- { -- | This action is called when the server sends a- -- certificate request. The parameter is the information- -- from the request. The action should select a certificate- -- chain of one of the given certificate types where the- -- last certificate in the chain should be signed by one of- -- the given distinguished names. Each certificate should- -- be signed by the following one, except for the last. At- -- least the first of the certificates in the chain must- -- have a corresponding private key, because that is used- -- for signing the certificate verify message.- --- -- Note that is is the responsibility of this action to- -- select a certificate matching one of the requested- -- certificate types. Returning a non-matching one will- -- lead to handshake failure later.- --- -- Returning a certificate chain not matching the- -- distinguished names may lead to problems or not,- -- depending whether the server accepts it.- onCertificateRequest :: ([CertificateType],- Maybe [HashAndSignatureAlgorithm],- [DistinguishedName]) -> IO (Maybe (CertificateChain, PrivKey))- , onNPNServerSuggest :: Maybe ([B.ByteString] -> IO B.ByteString)- , onServerCertificate :: CertificateStore -> ValidationCache -> ServiceID -> CertificateChain -> IO [FailedReason]- }--defaultClientHooks :: ClientHooks-defaultClientHooks = ClientHooks- { onCertificateRequest = \ _ -> return Nothing- , onNPNServerSuggest = Nothing- , onServerCertificate = validateDefault- }--instance Show ClientHooks where- show _ = "ClientHooks"-instance Default ClientHooks where- def = defaultClientHooks---- | A set of callbacks run by the server for various corners of the TLS establishment-data ServerHooks = ServerHooks- {- -- | This action is called when a client certificate chain- -- is received from the client. When it returns a- -- CertificateUsageReject value, the handshake is aborted.- onClientCertificate :: CertificateChain -> IO CertificateUsage-- -- | This action is called when the client certificate- -- cannot be verified. A 'Nothing' argument indicates a- -- wrong signature, a 'Just e' message signals a crypto- -- error.- , onUnverifiedClientCert :: IO Bool-- -- | Allow the server to choose the cipher relative to the- -- the 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 client cipher list cannot be empty.- , onCipherChoosing :: Version -> [Cipher] -> Cipher-- -- | suggested next protocols accoring to the next protocol negotiation extension.- , onSuggestNextProtocols :: IO (Maybe [B.ByteString])- -- | at each new handshake, we call this hook to see if we allow handshake to happens.- , onNewHandshake :: Measurement -> IO Bool- }--defaultServerHooks :: ServerHooks-defaultServerHooks = ServerHooks- { onCipherChoosing = \_ -> head- , onClientCertificate = \_ -> return $ CertificateUsageReject $ CertificateRejectOther "no client certificates expected"- , onUnverifiedClientCert = return False- , onSuggestNextProtocols = return Nothing- , onNewHandshake = \_ -> return True- }--instance Show ServerHooks where- show _ = "ClientHooks"-instance Default ServerHooks where- def = defaultServerHooks+{-# LANGUAGE Strict #-}++module Network.TLS.Parameters (+ ClientParams (..),+ defaultParamsClient,+ ServerParams (..),+ defaultParamsServer,+ CommonParams,+ DebugParams (..),+ defaultDebugParams,+ defaultKeyLogger,+ ClientHooks (..),+ defaultClientHooks,+ OnCertificateRequest,+ OnServerCertificate,+ ServerHooks (..),+ defaultServerHooks,+ Supported (..),+ defaultSupported,+ Shared (..),+ defaultShared,+ Limit (..),+ defaultLimit,++ -- * Parameters+ MaxFragmentEnum (..),+ EMSMode (..),+ GroupUsage (..),+ CertificateUsage (..),+ CertificateRejectReason (..),+ Information (..),+) where++import Control.Concurrent (MVar, newMVar, withMVar)+import Crypto.HPKE+import Data.Default (Default (def))+import System.Environment (lookupEnv)+import System.IO.Unsafe (unsafePerformIO)++import Network.TLS.Cipher+import Network.TLS.Compression+import Network.TLS.Credentials+import Network.TLS.Crypto+import Network.TLS.ECH.Config+import Network.TLS.Extension+import Network.TLS.Extra.Cipher+import Network.TLS.Handshake.State+import Network.TLS.Imports+import Network.TLS.Measurement+import Network.TLS.RNG (Seed)+import Network.TLS.Session+import Network.TLS.Struct+import qualified Network.TLS.Struct as Struct+import Network.TLS.Types (HostName)+import Network.TLS.X509++type CommonParams = (Supported, Shared, DebugParams)++-- | All settings should not be used in production+data DebugParams = DebugParams+ { debugSeed :: Maybe Seed+ -- ^ Disable the true randomness in favor of deterministic seed that will produce+ -- a deterministic random from. This is useful for tests and debugging purpose.+ -- Do not use in production+ --+ -- Default: 'Nothing'+ , debugPrintSeed :: Seed -> IO ()+ -- ^ 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+ , debugVersionForced :: Maybe Version+ -- ^ Force to choose this version in the server side.+ --+ -- Default: 'Nothing'+ , debugKeyLogger :: String -> IO ()+ -- ^ Printing main keys.+ --+ -- Default: no printing+ , debugError :: String -> IO ()+ , debugTraceKey :: String -> IO ()+ }++{-# NOINLINE keyLogLock #-}+keyLogLock :: MVar ()+keyLogLock = unsafePerformIO $ newMVar ()++{-# NOINLINE keyLogFile #-}+keyLogFile :: Maybe FilePath+keyLogFile = unsafePerformIO $ lookupEnv "SSLKEYLOGFILE"++-- | Key logger with the SSLKEYLOGFILE environment variable.+defaultKeyLogger :: String -> IO ()+defaultKeyLogger ~msg = case keyLogFile of+ Nothing -> return ()+ Just file -> withMVar keyLogLock $ \_ -> appendFile file (msg ++ "\n")++-- | Default value for 'DebugParams'+defaultDebugParams :: DebugParams+defaultDebugParams =+ DebugParams+ { debugSeed = Nothing+ , debugPrintSeed = const (return ())+ , debugVersionForced = Nothing+ , debugKeyLogger = defaultKeyLogger+ , debugError = \ ~_ -> return ()+ , debugTraceKey = \ ~_ -> return ()+ }++instance Show DebugParams where+ show _ = "DebugParams"+instance Default DebugParams where+ def = defaultDebugParams++{-# DEPRECATED clientUseMaxFragmentLength "UseMaxFragmentLength is deprecated" #-}++data ClientParams = ClientParams+ { clientUseMaxFragmentLength :: Maybe MaxFragmentEnum+ -- ^+ --+ -- Default: 'Nothing'+ , clientServerIdentification :: (HostName, ByteString)+ -- ^ Define the name of the server, along with an extra service+ -- identification blob. this is important that the hostname part+ -- is properly filled for security reason, as it allow to properly+ -- associate the remote side with the given certificate during a+ -- handshake.+ --+ -- The extra blob is useful to differentiate services running on+ -- the same host, but that might have different certificates+ -- given. It's only used as part of the X509 validation+ -- infrastructure.+ --+ -- This value is typically set by 'defaultParamsClient'.+ , clientUseServerNameIndication :: Bool+ -- ^ Allow the use of the Server Name Indication TLS extension+ -- during handshake, which allow the client to specify which host+ -- name, it's trying to access. This is useful to distinguish+ -- CNAME aliasing (e.g. web virtual host).+ --+ -- Default: 'True'+ , clientWantSessionResume :: Maybe (SessionID, SessionData)+ -- ^ try to establish a connection using this session for TLS+ -- 1.2/TLS 1.3. This can be used for TLS 1.3 but for backward+ -- compatibility purpose only. Use 'clientWantSessionResume13'+ -- instead for TLS 1.3.+ --+ -- Default: 'Nothing'+ , clientWantSessionResumeList :: [(SessionID, SessionData)]+ -- ^ try to establish a connection using one of this sessions+ -- especially for TLS 1.3. This take precedence over+ -- 'clientWantSessionResume'. For convenience, this can be+ -- specified for TLS 1.2 but only the first entry is used.+ --+ -- Default: '[]'+ , 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+ -- ^ See the default value of 'ClientHooks'.+ , clientSupported :: Supported+ -- ^ In this element, you'll need to override the default empty+ -- value of of 'supportedCiphers' with a suitable cipherlist.+ --+ -- See the default value of 'Supported'.+ , clientDebug :: DebugParams+ -- ^ See the default value of 'DebugParams'.+ , clientUseEarlyData :: Bool+ -- ^ Client tries to send early data in TLS 1.3 via 'sendData' if+ -- possible. If not accepted by the server, the early data is+ -- automatically re-sent.+ --+ -- Default: 'False'+ , clientUseECH :: Bool+ -- ^ Enabling Encrypted Client Hello.+ -- If 'sharedECHConfigList' is null, a greasing ECH extension is sent.+ -- Otherwise, a valid ECH is sent.+ -- If the server rejects ECH in Server Hello,+ -- the client sends an alert after negotiation.+ --+ -- Default: 'False'+ --+ -- @since 2.1.9+ }+ deriving (Show)++-- | Default value for 'ClientParams'+defaultParamsClient :: HostName -> ByteString -> ClientParams+defaultParamsClient serverName serverId =+ ClientParams+ { clientUseMaxFragmentLength = Nothing+ , clientServerIdentification = (serverName, serverId)+ , clientUseServerNameIndication = True+ , clientWantSessionResume = Nothing+ , clientWantSessionResumeList = []+ , clientWantTicket = True+ , clientShared = def+ , clientHooks = def+ , clientSupported = def+ , clientDebug = defaultDebugParams+ , clientUseEarlyData = False+ , clientUseECH = False+ }++data ServerParams = ServerParams+ { serverWantClientCert :: Bool+ -- ^ Request a certificate from client.+ --+ -- Default: 'False'+ , serverCACertificates :: [SignedCertificate]+ -- ^ This is a list of certificates from which the disinguished+ -- names are sent in certificate request messages. For TLS1.0, it+ -- should not be empty.+ --+ -- Default: '[]'+ , serverDHEParams :: Maybe DHParams+ -- ^ Server Optional Diffie Hellman parameters. Setting+ -- parameters is necessary for FFDHE key exchange when clients are+ -- not compatible with RFC 7919.+ --+ -- Value can be one of the standardized groups from module+ -- "Network.TLS.Extra.FFDHE" or custom parameters generated with+ -- 'Crypto.PubKey.DH.generateParams'.+ --+ -- Default: 'Nothing'+ , serverHooks :: ServerHooks+ -- ^ See the default value of 'ServerHooks'.+ , serverShared :: Shared+ -- ^ See the default value of 'Shared'.+ , serverSupported :: Supported+ -- ^ See the default value of 'Supported'.+ , serverDebug :: DebugParams+ -- ^ See the default value of 'DebugParams'.+ , serverEarlyDataSize :: Int+ -- ^ Server accepts this size of early data in TLS 1.3. 0 (or+ -- lower) means that the server does not accept early data.+ --+ -- Default: 0+ , serverTicketLifetime :: Int+ -- ^ Lifetime in seconds for session tickets generated by the+ -- server. Acceptable value range is 0 to 604800 (7 days).+ --+ -- Default: 7200 (2 hours)+ , serverECHKey :: [(ConfigId, ByteString)]+ -- ^ ECH secret keys.+ --+ -- Default: '[]'+ --+ -- @since 2.1.9+ }+ deriving (Show)++defaultParamsServer :: ServerParams+defaultParamsServer =+ ServerParams+ { serverWantClientCert = False+ , serverCACertificates = []+ , serverDHEParams = Nothing+ , serverHooks = def+ , serverShared = def+ , serverSupported = def+ , serverDebug = defaultDebugParams+ , serverEarlyDataSize = 0+ , serverTicketLifetime = 7200+ , serverECHKey = []+ }++instance Default ServerParams where+ def = defaultParamsServer++-- | List all the supported algorithms, versions, ciphers, etc supported.+data Supported = Supported+ { supportedVersions :: [Version]+ -- ^ Supported versions by this context. On the client side, the+ -- highest version will be used to establish the connection. On+ -- the server side, the highest version that is less or equal than+ -- the client version will be chosen.+ --+ -- Versions should be listed in preferred order, i.e. higher+ -- versions first.+ --+ -- Default: @[TLS13,TLS12]@+ , supportedCiphers :: [Cipher]+ -- ^ Supported cipher methods. The default is empty, specify a+ -- suitable cipher list.+ -- 'Network.TLS.Extra.Cipher.ciphersuite_default' is often a good+ -- choice.+ --+ -- Default: @[]@+ , supportedCompressions :: [Compression]+ -- ^ Supported compressions methods. By default only the "null"+ -- compression is supported, which means no compression will be+ -- performed. Allowing other compression method is not advised as+ -- it causes a connection failure when TLS 1.3 is negotiated.+ --+ -- Default: @[nullCompression]@+ , supportedHashSignatures :: [HashAndSignatureAlgorithm]+ -- ^ All supported hash/signature algorithms pair for client+ -- certificate verification and server signature in (EC)DHE,+ -- ordered by decreasing priority.+ --+ -- This list is sent to the peer as part of the+ -- "signature_algorithms" extension. It is used to restrict+ -- accepted signatures received from the peer at TLS level (not in+ -- X.509 certificates), but only when the TLS version is 1.2 or+ -- above. In order to disable SHA-1 one must then also disable+ -- earlier protocol versions in 'supportedVersions'.+ --+ -- The list also impacts the selection of possible algorithms when+ -- generating signatures.+ --+ -- Note: with TLS 1.3 some algorithms have been deprecated and+ -- will not be used even when listed in the parameter: MD5, SHA-1,+ -- SHA-224, RSA PKCS#1, DSA.+ --+ -- Default:+ --+ -- @+ -- [ (HashIntrinsic, SignatureEd448)+ -- , (HashIntrinsic, SignatureEd25519)+ -- , (Struct.HashSHA256, SignatureECDSA)+ -- , (Struct.HashSHA384, SignatureECDSA)+ -- , (Struct.HashSHA512, SignatureECDSA)+ -- , (HashIntrinsic, SignatureRSApssRSAeSHA512)+ -- , (HashIntrinsic, SignatureRSApssRSAeSHA384)+ -- , (HashIntrinsic, SignatureRSApssRSAeSHA256)+ -- , (Struct.HashSHA512, SignatureRSA)+ -- , (Struct.HashSHA384, SignatureRSA)+ -- , (Struct.HashSHA256, SignatureRSA)+ -- , (Struct.HashSHA1, SignatureRSA)+ -- , (Struct.HashSHA1, SignatureDSA)+ -- ]+ -- @+ , supportedSecureRenegotiation :: Bool+ -- ^ Secure renegotiation defined in RFC5746. If 'True', clients+ -- send the renegotiation_info extension. If 'True', servers+ -- handle the extension or the renegotiation SCSV then send the+ -- renegotiation_info extension.+ --+ -- Default: 'True'+ , supportedClientInitiatedRenegotiation :: Bool+ -- ^ If 'True', renegotiation is allowed from the client side.+ -- This is vulnerable to DOS attacks. If 'False', renegotiation+ -- is allowed only from the server side via HelloRequest.+ --+ -- Default: 'False'+ , supportedExtendedMainSecret :: EMSMode+ -- ^ The mode regarding extended main secret. Enabling this+ -- extension provides better security for TLS versions 1.2. TLS+ -- 1.3 provides the security properties natively and does not need+ -- the extension.+ --+ -- By default the extension is 'RequireEMS'. So, the handshake+ -- will fail when the peer does not support the extension.+ --+ -- Default: 'RequireEMS'+ , supportedSession :: Bool+ -- ^ Set if we support session.+ --+ -- Default: 'True'+ , supportedFallbackScsv :: Bool+ -- ^ Support for fallback SCSV defined in RFC7507. If 'True',+ -- servers reject handshakes which suggest a lower protocol than+ -- the highest protocol supported.+ --+ -- Default: 'True'+ , supportedEmptyPacket :: Bool+ -- ^ In ver <= TLS1.0, block ciphers using CBC are using CBC+ -- residue as IV, which can be guessed by an attacker. Hence, an+ -- empty packet is normally sent before a normal data packet, to+ -- prevent guessability. Some Microsoft TLS-based protocol+ -- implementations, however, consider these empty packets as a+ -- protocol violation and disconnect. If this parameter is+ -- 'False', empty packets will never be added, which is less+ -- secure, but might help in rare cases.+ --+ -- Default: 'True'+ , supportedHPKE :: [(KEM_ID, KDF_ID, AEAD_ID)]+ -- ^ Client only.+ --+ -- @since 2.1.9+ , supportedGroups :: [Group]+ -- ^ A list of supported elliptic curves and finite-field groups+ -- in preferred order.+ --+ -- * TLS 1.3 client: this list is used as the 1st argument to+ -- 'onSelectKeyShareGroups' to select groups in "key_share".+ -- This list is also used as values of "supported_groups".+ -- * TLS 1.3 server: this list is not used.+ -- * TLS 1.2 client: this list is also used as values of+ -- "supported_groups".+ -- * TLS 1.2 server: this list is used to select a key exchange+ -- mechanism.+ --+ -- The list is sent to the server as part of the+ -- "supported_groups" extension. It is used in both clients and+ -- servers to restrict accepted groups in DH key exchange. Up+ -- until TLS v1.2, it is also used by a client to restrict+ -- accepted elliptic curves in ECDSA signatures.+ --+ -- The default value includes all groups with security strength+ -- of 128 bits or more.+ --+ -- Default: @[X25519,P256,P384,X448,P521,FFDHE3072,FFDHE4096,FFDHE6144,FFDHE8192,X25519MLKEM768,P256MLKEM768,P384MLKEM1024,MLKEM768,MLKEM1024]@+ , supportedGroupsTLS13 :: [[Group]]+ -- ^ 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'.+ --+ -- Default: @[[X25519MLKEM768,P256MLKEM768,P384MLKEM1024],[X25519,P256],[P384,X448,P521],[FFDHE2048,FFDHE3072,FFDHE4096,FFDHE6144,FFDHE8192],[MLKEM768,MLKEM1024]]@+ --+ -- @since 2.2.3+ }+ deriving (Show, Eq)++-- | Client or server policy regarding Extended Main Secret+data EMSMode+ = -- | Extended Main Secret is not used+ NoEMS+ | -- | Extended Main Secret is allowed+ AllowEMS+ | -- | Extended Main Secret is required+ RequireEMS+ deriving (Show, Eq)++defaultHPKE :: [(KEM_ID, KDF_ID, AEAD_ID)]+defaultHPKE =+ [ (DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, AES_128_GCM)+ , (DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, ChaCha20Poly1305)+ , (DHKEM_P256_HKDF_SHA256, HKDF_SHA256, AES_128_GCM)+ , (DHKEM_P256_HKDF_SHA256, HKDF_SHA512, AES_128_GCM)+ , (DHKEM_P256_HKDF_SHA256, HKDF_SHA256, ChaCha20Poly1305)+ , (DHKEM_P521_HKDF_SHA512, HKDF_SHA512, AES_256_GCM)+ ]++defaultSupported :: Supported+defaultSupported =+ Supported+ { supportedVersions = [TLS13, TLS12]+ , supportedCiphers = ciphersuite_default+ , supportedCompressions = [nullCompression]+ , supportedHashSignatures = Struct.supportedSignatureSchemes+ , supportedSecureRenegotiation = True+ , supportedClientInitiatedRenegotiation = False+ , supportedExtendedMainSecret = RequireEMS+ , supportedSession = True+ , supportedFallbackScsv = True+ , supportedEmptyPacket = True+ , supportedHPKE = defaultHPKE+ , supportedGroups = supportedNamedGroups+ , supportedGroupsTLS13 = supportedNamedGroupsTLS13+ }++instance Default Supported where+ def = defaultSupported++-- | Parameters that are common to clients and servers.+data Shared = Shared+ { sharedCredentials :: Credentials+ -- ^ The list of certificates and private keys that a server will+ -- use as part of authentication to clients. Actual credentials+ -- that are used are selected dynamically from this list based on+ -- client capabilities. Additional credentials returned by+ -- 'onServerNameIndication' are also considered.+ --+ -- When credential list is left empty (the default value), no key+ -- exchange can take place.+ --+ -- Default: 'mempty'+ , sharedSessionManager :: SessionManager+ -- ^ Callbacks used by clients and servers in order to resume TLS+ -- sessions. The default implementation never resumes sessions.+ -- Package+ -- <https://hackage.haskell.org/package/tls-session-manager+ -- tls-session-manager> provides an in-memory implementation.+ --+ -- Default: 'noSessionManager'+ , sharedCAStore :: CertificateStore+ -- ^ A collection of trust anchors to be used by a client as part+ -- of validation of server certificates. This is set as first+ -- argument to function 'onServerCertificate'. Package+ -- <https://hackage.haskell.org/package/crypton-x509-system+ -- crypton-x509-system> gives access to a default certificate+ -- store configured in the system.+ --+ -- Default: 'mempty'+ , sharedValidationCache :: ValidationCache+ -- ^ Callbacks that may be used by a client to cache certificate+ -- validation results (positive or negative) and avoid expensive+ -- signature check. The default implementation does not have any+ -- caching.+ --+ -- See the default value of 'ValidationCache'.+ , sharedHelloExtensions :: [ExtensionRaw]+ -- ^ Additional extensions to be sent during the Hello sequence.+ --+ -- For a client this is always included in message ClientHello.+ -- For a server, this is sent in messages ServerHello or+ -- EncryptedExtensions based on the TLS version.+ --+ -- Default: @[]@+ , sharedECHConfigList :: ECHConfigList+ -- ^ ECH configuration.+ --+ -- @since 2.1.9+ , sharedLimit :: Limit+ -- ^ Limitation parameters.+ --+ -- @since 2.1.8+ }++instance Show Shared where+ show _ = "Shared"+instance Default Shared where+ def = defaultShared++defaultShared :: Shared+defaultShared =+ Shared+ { sharedCredentials = mempty+ , sharedSessionManager = noSessionManager+ , sharedCAStore = mempty+ , sharedValidationCache = def+ , sharedHelloExtensions = []+ , sharedECHConfigList = []+ , sharedLimit = defaultLimit+ }++-- | Group usage callback possible return values.+data GroupUsage+ = -- | usage of group accepted+ GroupUsageValid+ | -- | usage of group provides insufficient security+ GroupUsageInsecure+ | -- | usage of group rejected for other reason (specified as string)+ GroupUsageUnsupported String+ | -- | usage of group with an invalid public value+ GroupUsageInvalidPublic+ deriving (Show, Eq)++defaultGroupUsage :: Int -> DHParams -> DHPublic -> IO GroupUsage+defaultGroupUsage minBits params public+ | even $ dhParamsGetP params =+ return $ GroupUsageUnsupported "invalid odd prime"+ | not $ dhValid params (dhParamsGetG params) =+ return $ GroupUsageUnsupported "invalid generator"+ | not $ dhValid params (dhUnwrapPublic public) =+ return GroupUsageInvalidPublic+ -- To prevent Logjam attack+ | dhParamsGetBits params < minBits = return GroupUsageInsecure+ | otherwise = return GroupUsageValid++-- | Type for 'onCertificateRequest'. This type synonym is to make+-- document readable.+type OnCertificateRequest =+ ( [CertificateType]+ , Maybe [HashAndSignatureAlgorithm]+ , [DistinguishedName]+ )+ -> IO (Maybe (CertificateChain, PrivKey))++-- | Type for 'onServerCertificate'. This type synonym is to make+-- document readable.+type OnServerCertificate =+ CertificateStore+ -> ValidationCache+ -> ServiceID+ -> CertificateChain+ -> IO [FailedReason]++-- | A set of callbacks run by the clients for various corners of TLS establishment+data ClientHooks = ClientHooks+ { onCertificateRequest :: OnCertificateRequest+ -- ^ This action is called when the a certificate request is+ -- received from the server. The callback argument is the+ -- information from the request. The server, at its discretion,+ -- may be willing to continue the handshake without a client+ -- certificate. Therefore, the callback is free to return+ -- 'Nothing' to indicate that no client certificate should be+ -- sent, despite the server's request. In some cases it may be+ -- appropriate to get user consent before sending the certificate;+ -- the content of the user's certificate may be sensitive and+ -- intended only for specific servers.+ --+ -- The action should select a certificate chain of one of the+ -- given certificate types and one of the certificates in the+ -- chain should (if possible) be signed by one of the given+ -- distinguished names. Some servers, that don't have a narrow+ -- set of preferred issuer CAs, will send an empty+ -- 'DistinguishedName' list, rather than send all the names from+ -- their trusted CA bundle. If the client does not have a+ -- certificate chaining to a matching CA, it may choose a default+ -- certificate instead.+ --+ -- Each certificate except the last should be signed by the+ -- following one. The returned private key must be for the first+ -- certificates in the chain. This key will be used to signing+ -- the certificate verify message.+ --+ -- The public key in the first certificate, and the matching+ -- returned private key must be compatible with one of the list of+ -- 'HashAndSignatureAlgorithm' value when provided. TLS 1.3+ -- changes the meaning of the list elements, adding explicit code+ -- points for each supported pair of hash and signature (public+ -- key) algorithms, rather than combining separate codes for the+ -- hash and key. For details see+ -- <https://tools.ietf.org/html/rfc8446#section-4.2.3 RFC 8446>+ -- section 4.2.3. When no compatible certificate chain is+ -- available, return 'Nothing' if it is OK to continue without a+ -- client certificate. Returning a non-matching certificate+ -- should result in a handshake failure.+ --+ -- While the TLS version is not provided to the callback, the+ -- content of the @signature_algorithms@ list provides a strong+ -- hint, since TLS 1.3 servers will generally list RSA pairs with+ -- a hash component of 'Intrinsic' (@0x08@).+ --+ -- Note that is is the responsibility of this action to select a+ -- certificate matching one of the requested certificate types+ -- (public key algorithms). Returning a non-matching one will+ -- lead to handshake failure later.+ --+ -- Default: returns 'Nothing' anyway.+ , onServerCertificate :: OnServerCertificate+ -- ^ Used by the client to validate the server certificate. The+ -- default implementation calls 'validateDefault' which validates+ -- according to the default hooks and checks provided by+ -- "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.+ --+ -- Default: 'validateDefault'+ , onSuggestALPN :: IO (Maybe [ByteString])+ -- ^ This action is called when the client sends ClientHello+ -- to determine ALPN values such as '["h2", "http/1.1"]'.+ --+ -- Default: returns 'Nothing'+ , onCustomFFDHEGroup :: DHParams -> DHPublic -> IO GroupUsage+ -- ^ This action is called to validate DHE parameters when the+ -- server selected a finite-field group not part of the+ -- "Supported Groups Registry" or not part of 'supportedGroups'+ -- list.+ --+ -- With TLS 1.3 custom groups have been removed from the+ -- protocol, so this callback is only used when the version+ -- negotiated is 1.2 or below.+ --+ -- The default behavior with (dh_p, dh_g, dh_size) and pub as+ -- follows:+ --+ -- (1) rejecting if dh_p is even+ -- (2) rejecting unless 1 < dh_g && dh_g < dh_p - 1+ -- (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 recommendations.+ , onServerFinished :: Information -> IO ()+ -- ^ When a handshake is done, this hook can check `Information`.+ , onSelectKeyShareGroups :: [Group] -> [Group]+ -- ^ A function to select groups in "key_share" by TLS 1.3 client.+ --+ -- Client's 'supportedGroups' is passed as the 1st argument.+ --+ -- The default function specifies a pair of hybrid (classical ++ -- post quantum) group and classical group to transit from+ -- classical key exchange to hybrid key exchange. With the+ -- default value of 'supportedGroups', X25519MLKEM and X25519+ -- are chosen.+ --+ -- Middleboxes may drop a ClientHello that contains large+ -- X2219MLKEM. In such environment, @take 1@, which selects+ -- X22519 only with the default value, is maybe a good+ -- candidate.+ --+ -- In the case where X22519 is only contained in "key_share", a+ -- wise-server without nasty middleboxes may ask the client to+ -- send X2219MLKEM via HelloRetryRequest as X2219MLKEM is+ -- specified in "supported_groups".+ --+ -- @since 2.2.3+ }++defaultOnSelectKeyShareGroups :: [Group] -> [Group]+defaultOnSelectKeyShareGroups groups = take 1 hs ++ take 1 es+ where+ (hs, es) = partition isHybrid groups++isHybrid :: Group -> Bool+isHybrid X25519MLKEM768 = True+isHybrid P256MLKEM768 = True+isHybrid P384MLKEM1024 = True+isHybrid _ = False++defaultClientHooks :: ClientHooks+defaultClientHooks =+ ClientHooks+ { onCertificateRequest = \_ -> return Nothing+ , onServerCertificate = validateDefault+ , onSuggestALPN = return Nothing+ , onCustomFFDHEGroup = defaultGroupUsage 1024+ , onServerFinished = \_ -> return ()+ , onSelectKeyShareGroups = defaultOnSelectKeyShareGroups+ }++instance Show ClientHooks where+ show _ = "ClientHooks"+instance Default ClientHooks where+ def = defaultClientHooks++-- | A set of callbacks run by the server for various corners of the TLS establishment+data ServerHooks = ServerHooks+ { onClientCertificate :: CertificateChain -> IO CertificateUsage+ -- ^ This action is called when a client certificate chain is+ -- received from the client. When it returns a+ -- CertificateUsageReject value, the handshake is aborted.+ --+ -- The function is not expected to verify the key-usage extension+ -- of the certificate. This verification is performed by the+ -- library internally.+ --+ -- Default: returns the following:+ --+ -- @+ -- CertificateUsageReject (CertificateRejectOther "no client certificates expected")+ -- @+ , onUnverifiedClientCert :: IO Bool+ -- ^ This action is called when the client certificate cannot be+ -- verified. Return 'True' to accept the certificate anyway, or+ -- 'False' to fail verification.+ --+ -- Default: returns 'False'+ , onCipherChoosing :: Version -> [Cipher] -> Cipher+ -- ^ Allow the server to choose the cipher relative to the the+ -- 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 preferred with TLS < 1.1)+ --+ -- The client cipher list cannot be empty.+ --+ -- Default: taking the head of ciphers.+ , onServerNameIndication :: Maybe HostName -> IO Credentials+ -- ^ Allow the server to indicate additional credentials to be+ -- used depending on the host name indicated by the client.+ --+ -- This is most useful for transparent proxies where credentials+ -- must be generated on the fly according to the host the client+ -- is trying to connect to.+ --+ -- Returned credentials may be ignored if a client does not+ -- support the signature algorithms used in the certificate chain.+ --+ -- Default: returns 'mempty'+ , onNewHandshake :: Measurement -> IO Bool+ -- ^ At each new handshake, we call this hook to see if we allow+ -- handshake to happens.+ --+ -- Default: returns 'True'+ , onALPNClientSuggest :: Maybe ([ByteString] -> IO ByteString)+ -- ^ Allow the server to choose an application layer protocol+ -- suggested from the client through the ALPN (Application Layer+ -- Protocol Negotiation) extensions. If the server supports no+ -- protocols that the client advertises an empty 'ByteString'+ -- should be returned.+ --+ -- Default: 'Nothing'+ , onEncryptedExtensionsCreating :: [ExtensionRaw] -> IO [ExtensionRaw]+ -- ^ Allow to modify extensions to be sent in EncryptedExtensions+ -- of TLS 1.3.+ --+ -- Default: 'return'+ , onSelectKeyShare+ :: [[Group]]+ -> [Group]+ -> [Group]+ -> IO (Maybe Group, Bool)+ -- ^ A function to select one key share by TLS 1.3 server.+ --+ -- The 1st argument is server's 'supportedGroupsTLS13'.+ -- The 2nd arguments is client's groups in "supported_groups"+ -- The 3rd arguments is client's groups in "key_share".+ --+ -- 'True' in the result indicates sending a hello retry request+ -- with this group.+ --+ -- The default function targets @[Group]@ in the first argument in+ -- order. If there is a common group among the "key_share"+ -- groups, it will use that group for key exchange.+ -- Alternatively, if there is a common group among the+ -- "supported_groups" groups, it will instruct to send the+ -- HelloRetryRequest using that group. Otherwise, it will check+ -- the next @[Group]@.+ --+ -- @since 2.2.3+ }++-- | Default value for 'ServerHooks'+defaultServerHooks :: ServerHooks+defaultServerHooks =+ ServerHooks+ { onClientCertificate = \_ ->+ return $+ CertificateUsageReject $+ CertificateRejectOther "no client certificates expected"+ , onUnverifiedClientCert = return False+ , onCipherChoosing = \_ ccs -> case ccs of+ [] -> error "onCipherChoosing"+ c : _ -> c+ , onServerNameIndication = \_ -> return mempty+ , onNewHandshake = \_ -> return True+ , onALPNClientSuggest = Nothing+ , onEncryptedExtensionsCreating = return+ , onSelectKeyShare = defaultOnSelectKeyShare+ }++instance Show ServerHooks where+ show _ = "ServerHooks"+instance Default ServerHooks where+ def = defaultServerHooks++defaultOnSelectKeyShare+ :: [[Group]] -- Server groups+ -> [Group] -- Client's groups in "supported_groups"+ -> [Group] -- Client's groups in "key_share"+ -> IO (Maybe Group, Bool)+defaultOnSelectKeyShare serverSupportedLoL clientSupportedGroups clientKeyShareGroups = go serverSupportedLoL+ where+ go [] = return (Nothing, False)+ go (gs : gss) = case gs `intersect` clientKeyShareGroups of+ [] -> case gs `intersect` clientSupportedGroups of+ [] -> go gss+ h : _ -> return (Just h, True)+ g : _ -> return (Just g, False)++-- | Information related to a running context, e.g. current cipher+data Information = Information+ { infoVersion :: Version+ , infoCipher :: Cipher+ , infoCompression :: Compression+ , infoMainSecret :: Maybe ByteString+ , infoExtendedMainSecret :: Bool+ , infoClientRandom :: Maybe ClientRandom+ , infoServerRandom :: Maybe ServerRandom+ , infoSupportedGroup :: Maybe Group+ , infoTLS12Resumption :: Bool+ , infoTLS13HandshakeMode :: Maybe HandshakeMode13+ , infoIsEarlyDataAccepted :: Bool+ , infoIsECHAccepted :: Bool+ }+ deriving (Show, Eq)++-- | Limitations for security.+--+-- @since 2.1.7+data Limit = Limit+ { limitRecordSize :: Maybe Int+ -- ^ Record size limit defined in RFC 8449.+ --+ -- If 'Nothing', the "record_size_limit" extension is not used.+ --+ -- In the case of 'Just': A client sends the "record_size_limit"+ -- extension with this value to the server. A server sends back+ -- this extension with its own value if a client sends the+ -- extension. When negotiated, both my limit and peer's limit are+ -- enabled for protected communication.+ --+ -- Default: Nothing+ , limitHandshakeFragment :: Int+ -- ^ The limit to accept the number of each handshake message.+ -- For instance, a nasty client may send many fragments of client+ -- certificate.+ --+ -- Default: 32+ }+ deriving (Eq, Show)++-- | Default value for 'Limit'.+defaultLimit :: Limit+defaultLimit =+ Limit+ { limitRecordSize = Nothing+ , limitHandshakeFragment = 32+ }
+ Network/TLS/PostHandshake.hs view
@@ -0,0 +1,33 @@+module Network.TLS.PostHandshake (+ requestCertificate,+ requestCertificateServer,+ postHandshakeAuthWith,+ postHandshakeAuthClientWith,+) where++import Network.TLS.Context.Internal+import Network.TLS.Handshake.Client+import Network.TLS.Handshake.Common+import Network.TLS.Handshake.Server+import Network.TLS.IO+import Network.TLS.Struct13++----------------------------------------------------------------++-- | Post-handshake certificate request with TLS 1.3. Returns 'False'+-- if the request was impossible, i.e. the remote client supports+-- post-handshake authentication or the connection is established in+-- TLS 1.2. Returns 'True' if the client authentication succeeds. An+-- exception is thrown if the authentication fails. Server only.+requestCertificate :: Context -> IO Bool+requestCertificate ctx =+ checkValid ctx >> doRequestCertificate_ (ctxRoleParams ctx) ctx++-- | 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 =+ withWriteLock ctx $+ handleException ctx $+ doPostHandshakeAuthWith_ (ctxRoleParams ctx) ctx hs
+ Network/TLS/QUIC.hs view
@@ -0,0 +1,265 @@+{-# LANGUAGE OverloadedStrings #-}++-- |+-- API to run the TLS handshake establishing a QUIC connection.+--+-- On the northbound API:+--+-- * QUIC starts a TLS client or server thread with 'tlsQUICClient' or+-- 'tlsQUICServer'.+--+-- TLS invokes QUIC callbacks to use the QUIC transport+--+-- * TLS uses 'quicSend' and 'quicRecv' to send and receive handshake message+-- fragments.+--+-- * TLS calls 'quicInstallKeys' to provide to QUIC the traffic secrets it+-- should use for encryption/decryption.+--+-- * TLS calls 'quicNotifyExtensions' to notify to QUIC the transport parameters+-- exchanged through the handshake protocol.+--+-- * TLS calls 'quicDone' when the handshake is done.+module Network.TLS.QUIC (+ -- * Handshakers+ tlsQUICClient,+ tlsQUICServer,++ -- * Callback+ QUICCallbacks (..),+ CryptLevel (..),+ KeyScheduleEvent (..),++ -- * Secrets+ EarlySecretInfo (..),+ HandshakeSecretInfo (..),+ ApplicationSecretInfo (..),+ EarlySecret,+ HandshakeSecret,+ ApplicationSecret,+ TrafficSecrets,+ ServerTrafficSecret (..),+ ClientTrafficSecret (..),++ -- * Negotiated parameters+ NegotiatedProtocol,+ HandshakeMode13 (..),++ -- * Extensions+ ExtensionRaw (..),+ ExtensionID (ExtensionID, EID_QuicTransportParameters),++ -- * Errors+ errorTLS,+ errorToAlertDescription,+ errorToAlertMessage,+ fromAlertDescription,+ toAlertDescription,++ -- * Hash+ hkdfExpandLabel,+ hkdfExtract,+ hashDigestSize,++ -- * Constants+ quicMaxEarlyDataSize,++ -- * Supported+ defaultSupported,+) where++import Data.Default (def)++import Network.TLS.Backend+import Network.TLS.Context+import Network.TLS.Context.Internal+import Network.TLS.Core+import Network.TLS.Crypto (hashDigestSize)+import Network.TLS.Crypto.Types+import Network.TLS.Extension+import Network.TLS.Extra.Cipher+import Network.TLS.Handshake.Common+import Network.TLS.Handshake.Control+import Network.TLS.Handshake.State+import Network.TLS.Handshake.State13+import Network.TLS.Imports+import Network.TLS.KeySchedule (hkdfExpandLabel, hkdfExtract)+import Network.TLS.Parameters hiding (defaultSupported)+import Network.TLS.Record.Layer+import Network.TLS.Record.State+import Network.TLS.Struct+import Network.TLS.Types++nullBackend :: Backend+nullBackend =+ Backend+ { backendFlush = return ()+ , backendClose = return ()+ , backendSend = \_ -> return ()+ , backendRecv = \_ -> return ""+ }++-- | Argument given to 'quicInstallKeys' when encryption material is available.+data KeyScheduleEvent+ = -- | Key material and parameters for traffic at 0-RTT level+ InstallEarlyKeys (Maybe EarlySecretInfo)+ | -- | Key material and parameters for traffic at handshake level+ InstallHandshakeKeys HandshakeSecretInfo+ | -- | Key material and parameters for traffic at application level+ InstallApplicationKeys ApplicationSecretInfo++-- | Callbacks implemented by QUIC and to be called by TLS at specific points+-- during the handshake. TLS may invoke them from external threads but calls+-- are not concurrent. Only a single callback function is called at a given+-- point in time.+data QUICCallbacks = QUICCallbacks+ { 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 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+ -- break them into smaller fragments.+ --+ -- The handshake protocol sometimes combines content at two levels in a+ -- single flight. The TLS library does its best to provide this in the+ -- same @quicSend@ call and with a multi-valued argument. QUIC can then+ -- decide how to transmit this optimally.+ , quicRecv :: CryptLevel -> IO (Either TLSError ByteString)+ -- ^ Called by TLS to receive from QUIC the next plaintext handshake+ -- fragment. The argument specifies with which encryption level the+ -- fragment should be decrypted.+ --+ -- QUIC may return partial fragments to TLS. TLS will then call+ -- @quicRecv@ again as long as necessary. Note however that fragments+ -- must be returned in the correct sequence, i.e. the order the TLS peer+ -- emitted them.+ --+ -- The function may return an error to TLS if end of stream is reached or+ -- if a protocol error has been received, believing the handshake cannot+ -- proceed any longer. If the TLS handshake protocol cannot recover from+ -- this error, the failure condition will be reported back to QUIC through+ -- the control interface.+ , quicInstallKeys :: Context -> KeyScheduleEvent -> IO ()+ -- ^ Called by TLS when new encryption material is ready to be used in the+ -- handshake. The next 'quicSend' or 'quicRecv' may now use the+ -- associated encryption level (although the previous level is also+ -- possible: directions Send/Recv do not change at the same time).+ , quicNotifyExtensions :: Context -> [ExtensionRaw] -> IO ()+ -- ^ Called by TLS when QUIC-specific extensions have been received from+ -- the peer.+ , quicDone :: Context -> IO ()+ -- ^ Called when 'handshake' is done. 'tlsQUICServer' is+ -- finished after calling this hook. 'tlsQUICClient' calls+ -- 'recvData' after calling this hook to wait for new session+ -- tickets.+ }++newRecordLayer+ :: QUICCallbacks+ -> RecordLayer [(CryptLevel, ByteString)]+newRecordLayer callbacks = newTransparentRecordLayer get send recv+ where+ get = getTxLevel+ send = quicSend callbacks+ recv ctx = getRxLevel ctx >>= quicRecv callbacks++-- | Start a TLS handshake thread for a QUIC client. The client will use the+-- specified TLS parameters and call the provided callback functions to send and+-- receive handshake data.+tlsQUICClient :: ClientParams -> QUICCallbacks -> IO ()+tlsQUICClient cparams callbacks = do+ ctx0 <- contextNew nullBackend cparams+ mylimref <- newRecordLimitRef Nothing+ peerlimref <- newRecordLimitRef Nothing+ let ctx1 =+ ctx0+ { ctxHandshakeSync = HandshakeSync sync (\_ _ -> return ())+ , ctxMyRecordLimit = mylimref+ , ctxPeerRecordLimit = peerlimref+ , ctxQUICMode = True+ }+ rl = newRecordLayer callbacks+ ctx2 = updateRecordLayer rl ctx1+ handshake ctx2+ quicDone callbacks ctx2+ void $ recvData ctx2 -- waiting for new session tickets+ where+ sync ctx (SendClientHello mEarlySecInfo) =+ quicInstallKeys callbacks ctx (InstallEarlyKeys mEarlySecInfo)+ sync ctx (RecvServerHello handSecInfo) =+ quicInstallKeys callbacks ctx (InstallHandshakeKeys handSecInfo)+ sync ctx (SendClientFinished exts appSecInfo) = do+ let qexts = filterQTP exts+ when (null qexts) $ do+ throwCore $+ Error_Protocol "QUIC transport parameters are missing" MissingExtension+ quicNotifyExtensions callbacks ctx qexts+ quicInstallKeys callbacks ctx (InstallApplicationKeys appSecInfo)++-- | Start a TLS handshake thread for a QUIC server. The server will use the+-- specified TLS parameters and call the provided callback functions to send and+-- receive handshake data.+tlsQUICServer :: ServerParams -> QUICCallbacks -> IO ()+tlsQUICServer sparams callbacks = do+ ctx0 <- contextNew nullBackend sparams+ mylimref <- newRecordLimitRef Nothing+ peerlimref <- newRecordLimitRef Nothing+ let ctx1 =+ ctx0+ { ctxHandshakeSync = HandshakeSync (\_ _ -> return ()) sync+ , ctxMyRecordLimit = mylimref+ , ctxPeerRecordLimit = peerlimref+ , ctxQUICMode = True+ }+ rl = newRecordLayer callbacks+ ctx2 = updateRecordLayer rl ctx1+ handshake ctx2+ quicDone callbacks ctx2+ where+ sync ctx (SendServerHello exts mEarlySecInfo handSecInfo) = do+ let qexts = filterQTP exts+ when (null qexts) $ do+ throwCore $+ Error_Protocol "QUIC transport parameters are missing" MissingExtension+ quicNotifyExtensions callbacks ctx qexts+ quicInstallKeys callbacks ctx (InstallEarlyKeys mEarlySecInfo)+ quicInstallKeys callbacks ctx (InstallHandshakeKeys handSecInfo)+ sync ctx (SendServerFinished appSecInfo) =+ quicInstallKeys callbacks ctx (InstallApplicationKeys appSecInfo)++filterQTP :: [ExtensionRaw] -> [ExtensionRaw]+filterQTP =+ filter+ (\(ExtensionRaw eid _) -> eid == EID_QuicTransportParameters)++-- | Can be used by callbacks to signal an unexpected condition. This will then+-- generate an "internal_error" alert in the TLS stack.+errorTLS :: String -> IO a+errorTLS msg = throwCore $ Error_Protocol msg InternalError++-- | Return the alert that a TLS endpoint would send to the peer for the+-- specified library error.+errorToAlertDescription :: TLSError -> AlertDescription+errorToAlertDescription = snd . errorToAlert++-- | Decode an alert from the assigned value.+toAlertDescription :: Word8 -> AlertDescription+toAlertDescription = AlertDescription++defaultSupported :: Supported+defaultSupported =+ def+ { supportedVersions = [TLS13]+ , supportedCiphers =+ [ cipher13_AES_256_GCM_SHA384+ , cipher13_AES_128_GCM_SHA256+ , cipher13_AES_128_CCM_SHA256+ ]+ , supportedGroups = [X25519, X448, P256, P384, P521]+ }++-- | Max early data size for QUIC.+quicMaxEarlyDataSize :: Int+quicMaxEarlyDataSize = 0xffffffff
Network/TLS/RNG.hs view
@@ -1,17 +1,30 @@-{-# LANGUAGE ExistentialQuantification, RankNTypes #-}-module Network.TLS.RNG- ( StateRNG(..)- , withTLSRNG- ) where+{-# LANGUAGE GeneralizedNewtypeDeriving #-} +module Network.TLS.RNG (+ StateRNG (..),+ Seed,+ seedNew,+ seedToInteger,+ seedFromInteger,+ withTLSRNG,+ newStateRNG,+ MonadRandom,+ getRandomBytes,+) where+ import Crypto.Random -data StateRNG = forall g . CPRG g => StateRNG g+newtype StateRNG = StateRNG ChaChaDRG+ deriving (DRG) instance Show StateRNG where show _ = "rng[..]" -withTLSRNG :: StateRNG -> (forall g . CPRG g => g -> (a,g)) -> (a, StateRNG)-withTLSRNG (StateRNG rng) f = let (a, rng') = f rng- in (a, StateRNG rng')+withTLSRNG+ :: StateRNG+ -> MonadPseudoRandom StateRNG a+ -> (a, StateRNG)+withTLSRNG rng f = withDRG rng f +newStateRNG :: Seed -> StateRNG+newStateRNG seed = StateRNG $ drgNewSeed seed
− Network/TLS/Receiving.hs
@@ -1,76 +0,0 @@--- |--- Module : Network.TLS.Receiving--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ the Receiving module contains calls related to unmarshalling packets according--- to the TLS state----module Network.TLS.Receiving- ( processPacket- ) where--import Control.Monad.State-import Control.Monad.Error-import Control.Concurrent.MVar--import Network.TLS.Context.Internal-import Network.TLS.Struct-import Network.TLS.Record-import Network.TLS.Packet-import Network.TLS.Wire-import Network.TLS.State-import Network.TLS.Handshake.State-import Network.TLS.Cipher-import Network.TLS.Util--import Data.Byteable--processPacket :: Context -> Record Plaintext -> IO (Either TLSError Packet)--processPacket _ (Record ProtocolType_AppData _ fragment) = return $ Right $ AppData $ toBytes fragment--processPacket _ (Record ProtocolType_Alert _ fragment) = return (Alert `fmapEither` (decodeAlerts $ toBytes fragment))--processPacket ctx (Record ProtocolType_ChangeCipherSpec _ fragment) =- case decodeChangeCipherSpec $ toBytes fragment of- Left err -> return $ Left err- Right _ -> do switchRxEncryption ctx- return $ Right ChangeCipherSpec--processPacket ctx (Record ProtocolType_Handshake ver fragment) = do- keyxchg <- getHState ctx >>= \hs -> return $ (hs >>= hstPendingCipher >>= Just . cipherKeyExchange)- usingState ctx $ do- npn <- getExtensionNPN- let currentParams = CurrentParams- { cParamsVersion = ver- , cParamsKeyXchgType = keyxchg- , cParamsSupportNPN = npn- }- -- get back the optional continuation, and parse as many handshake record as possible.- mCont <- gets stHandshakeRecordCont- modify (\st -> st { stHandshakeRecordCont = Nothing })- hss <- parseMany currentParams mCont (toBytes fragment)- return $ Handshake hss- where parseMany currentParams mCont bs =- case maybe decodeHandshakeRecord id mCont $ bs of- GotError err -> throwError err- GotPartial cont -> modify (\st -> st { stHandshakeRecordCont = Just cont }) >> return []- GotSuccess (ty,content) ->- either throwError (return . (:[])) $ decodeHandshake currentParams ty content- GotSuccessRemaining (ty,content) left ->- case decodeHandshake currentParams ty content of- Left err -> throwError err- Right hh -> (hh:) `fmap` parseMany currentParams Nothing left--processPacket _ (Record ProtocolType_DeprecatedHandshake _ fragment) =- case decodeDeprecatedHandshake $ toBytes fragment of- Left err -> return $ Left err- Right hs -> return $ Right $ Handshake [hs]--switchRxEncryption :: Context -> IO ()-switchRxEncryption ctx =- usingHState ctx (gets hstPendingRxState) >>= \rx ->- liftIO $ modifyMVar_ (ctxRxState ctx) (\_ -> return $ fromJust "rx-state" rx)
Network/TLS/Record.hs view
@@ -1,41 +1,36 @@--- |--- Module : Network.TLS.Record--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ The Record Protocol takes messages to be transmitted, fragments the--- data into manageable blocks, optionally compresses the data, applies--- a MAC, encrypts, and transmits the result. Received data is--- decrypted, verified, decompressed, reassembled, and then delivered to--- higher-level clients.----module Network.TLS.Record- ( Record(..)+-- | The Record Protocol takes messages to be transmitted, fragments+-- the data into manageable blocks, optionally compresses the data,+-- applies a MAC, encrypts, and transmits the result. Received data+-- is decrypted, verified, decompressed, reassembled, and then+-- delivered to higher-level clients.+module Network.TLS.Record (+ Record (..),+ -- * Fragment manipulation types- , Fragment- , fragmentPlaintext- , fragmentCiphertext- , recordToRaw- , rawToRecord- , recordToHeader- , Plaintext- , Compressed- , Ciphertext- -- * Engage and disengage from the record layer- , engageRecord- , disengageRecord+ Fragment,+ fragmentGetBytes,+ fragmentPlaintext,+ fragmentCiphertext,+ recordToRaw,+ rawToRecord,+ recordToHeader,+ Plaintext,+ Ciphertext,++ -- * Encrypt and decrypt from the record layer+ encryptRecord,+ decryptRecord,+ -- * State tracking- , RecordM- , runRecordM- , RecordState(..)- , newRecordState- , getRecordVersion- , setRecordIV- ) where+ RecordM,+ runRecordM,+ RecordState (..),+ newRecordState,+ getRecordVersion,+ setRecordIV,+) where -import Network.TLS.Record.Types-import Network.TLS.Record.Engage-import Network.TLS.Record.Disengage+import Network.TLS.Record.Decrypt+import Network.TLS.Record.Encrypt import Network.TLS.Record.State+import Network.TLS.Record.Types
+ Network/TLS/Record/Decrypt.hs view
@@ -0,0 +1,207 @@+{-# LANGUAGE FlexibleContexts #-}++module Network.TLS.Record.Decrypt (+ decryptRecord,+) where++import Control.Monad.State.Strict+import Crypto.Cipher.Types (AuthTag (..))+import Data.ByteArray (convert)+import qualified Data.ByteArray as BA+import qualified Data.ByteString as B++import Network.TLS.Cipher+import Network.TLS.Crypto+import Network.TLS.ErrT+import Network.TLS.Imports+import Network.TLS.Packet+import Network.TLS.Record.State+import Network.TLS.Record.Types+import Network.TLS.Struct+import Network.TLS.Util+import Network.TLS.Wire++decryptRecord :: Record Ciphertext -> Int -> RecordM (Record Plaintext)+decryptRecord record@(Record ct ver fragment) lim = do+ st <- get+ case stCipher st of+ Nothing -> noDecryption+ _ -> do+ recOpts <- getRecordOptions+ let mver = recordVersion recOpts+ if recordTLS13 recOpts+ then decryptData13 mver (fragmentGetBytes fragment) st+ else onRecordFragment record $ fragmentUncipher $ \e ->+ decryptData mver record e st lim+ where+ noDecryption = onRecordFragment record $ fragmentUncipher $ checkPlainLimit lim+ decryptData13 mver e st = case ct of+ ProtocolType_AppData -> do+ inner <- decryptData mver record e st (lim + 1)+ case unInnerPlaintext inner of+ Left message -> throwError $ Error_Protocol message UnexpectedMessage+ Right (ct', d) -> return $ Record ct' ver $ fragmentPlaintext d+ ProtocolType_ChangeCipherSpec -> noDecryption+ ProtocolType_Alert -> noDecryption+ _ ->+ throwError $ Error_Protocol "illegal plain text" UnexpectedMessage++unInnerPlaintext :: ByteString -> Either String (ProtocolType, ByteString)+unInnerPlaintext inner =+ case B.unsnoc dc of+ Nothing -> Left $ unknownContentType13 (0 :: Word8)+ Just (bytes, c)+ | B.null bytes && ProtocolType c `elem` nonEmptyContentTypes ->+ Left ("empty " ++ show (ProtocolType c) ++ " record disallowed")+ | otherwise -> Right (ProtocolType c, bytes)+ where+ (dc, _pad) = B.spanEnd (== 0) inner+ 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 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)++ -- 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++ unless (macValid &&! paddingValid) $+ throwError $+ Error_Protocol "bad record mac Stream/Block" BadRecordMac++ return $ cipherDataContent cdata++checkPlainLimit :: Int -> ByteString -> RecordM ByteString+checkPlainLimit lim plain+ | len > lim =+ throwError $+ Error_Protocol+ ( "plaintext exceeding record size limit: "+ ++ show len+ ++ " > "+ ++ show lim+ )+ RecordOverflow+ | otherwise = return plain+ where+ len = B.length plain++decryptData+ :: Version+ -> Record Ciphertext+ -> ByteString+ -> RecordState+ -> Int+ -> RecordM ByteString+decryptData ver record econtent tst lim =+ decryptOf (cstKey cst) >>= checkPlainLimit lim+ where+ cipher = fromJust $ stCipher tst+ bulk = cipherBulk cipher+ cst = stCryptState tst+ macSize = hashDigestSize $ cipherHash cipher+ blockSize = bulkBlockSize bulk+ econtentLen = B.length econtent++ sanityCheckError =+ throwError+ (Error_Packet "encrypted content too small for encryption parameters")++ decryptOf :: BulkState -> RecordM ByteString+ decryptOf (BulkStateBlock decryptF) = do+ let minContent = bulkIVSize bulk + max (macSize + 1) blockSize++ -- check if we have enough bytes to cover the minimum for this cipher+ when+ ((econtentLen `mod` blockSize) /= 0 || econtentLen < minContent)+ sanityCheckError++ {- update IV -}+ (iv, econtent') <-+ get2o econtent (bulkIVSize bulk, econtentLen - bulkIVSize bulk)+ 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+ (content, mac, padding) <- get3i content' (contentlen, macSize, paddinglength)+ getCipherData+ record+ CipherData+ { cipherDataContent = content+ , cipherDataMAC = Just mac+ , cipherDataPadding = Just (padding, blockSize)+ }+ decryptOf (BulkStateStream (BulkStream decryptF)) = do+ -- check if we have enough bytes to cover the minimum for this cipher+ when (econtentLen < macSize) sanityCheckError++ let (content', bulkStream') = decryptF econtent+ {- update Ctx -}+ let contentlen = B.length content' - macSize+ (content, mac) <- get2i content' (contentlen, macSize)+ modify' $ \txs -> txs{stCryptState = cst{cstKey = BulkStateStream bulkStream'}}+ getCipherData+ record+ CipherData+ { cipherDataContent = content+ , cipherDataMAC = Just mac+ , cipherDataPadding = Nothing+ }+ decryptOf (BulkStateAEAD decryptF) = do+ let authTagLen = bulkAuthTagLen bulk+ nonceExpLen = bulkExplicitIV bulk+ cipherLen = econtentLen - authTagLen - nonceExpLen++ -- check if we have enough bytes to cover the minimum for this cipher+ when (econtentLen < (authTagLen + nonceExpLen)) sanityCheckError++ (enonce, econtent', authTag) <-+ get3o econtent (nonceExpLen, cipherLen, authTagLen)+ let encodedSeq = encodeWord64 $ msSequence $ stMacState tst+ iv = cstIV (stCryptState tst)+ ivlen = B.length iv+ Header typ v _ = recordToHeader record+ hdrLen = if ver >= TLS13 then econtentLen else cipherLen+ hdr = Header typ v $ fromIntegral hdrLen+ ad+ | ver >= TLS13 = encodeHeader hdr+ | otherwise = B.concat [encodedSeq, encodeHeader hdr]+ sqnc = B.replicate (ivlen - 8) 0 `B.append` encodedSeq+ nonce+ | nonceExpLen == 0 = BA.xor iv sqnc+ | otherwise = iv `B.append` enonce+ (content, authTag2) = decryptF nonce econtent' ad++ when (AuthTag (convert authTag) /= authTag2) $+ throwError $+ Error_Protocol "bad record mac on AEAD" BadRecordMac++ modify' incrRecordState+ return content+ decryptOf BulkStateUninitialized =+ throwError $ Error_Protocol "decrypt state uninitialized" InternalError++ -- handling of outer format can report errors with Error_Packet+ get3o s ls =+ maybe (throwError $ Error_Packet "record bad format") 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+ -- externally as integrity failures, i.e. BadRecordMac+ get3i s ls =+ maybe (throwError $ Error_Protocol "record bad format" BadRecordMac) return $+ partition3 s ls+ get2i s (d1, d2) = get3i s (d1, d2, 0) >>= \(r1, r2, _) -> return (r1, r2)
− Network/TLS/Record/Disengage.hs
@@ -1,114 +0,0 @@--- |--- Module : Network.TLS.Record.Disengage--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ Disengage a record from the Record layer.--- The record is decrypted, checked for integrity and then decompressed.----module Network.TLS.Record.Disengage- ( disengageRecord- ) where--import Control.Monad.State-import Control.Monad.Error--import Network.TLS.Struct-import Network.TLS.Cap-import Network.TLS.Record.State-import Network.TLS.Record.Types-import Network.TLS.Cipher-import Network.TLS.Compression-import Network.TLS.Util-import Data.ByteString (ByteString)-import qualified Data.ByteString as B--disengageRecord :: Record Ciphertext -> RecordM (Record Plaintext)-disengageRecord = decryptRecord >=> uncompressRecord--uncompressRecord :: Record Compressed -> RecordM (Record Plaintext)-uncompressRecord record = onRecordFragment record $ fragmentUncompress $ \bytes ->- withCompression $ compressionInflate bytes--decryptRecord :: Record Ciphertext -> RecordM (Record Compressed)-decryptRecord record = onRecordFragment record $ fragmentUncipher $ \e -> do- st <- get- case stCipher st of- Nothing -> return e- _ -> getRecordVersion >>= \ver -> decryptData ver record e st--getCipherData :: Record a -> CipherData -> RecordM ByteString-getCipherData (Record pt ver _) 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 `bytesEq` digest)-- -- check if the padding is filled with the correct pattern if it exists- paddingValid <- case cipherDataPadding cdata of- Nothing -> return True- Just pad -> do- cver <- getRecordVersion- let b = B.length pad - 1- return (if cver < TLS10 then True else B.replicate (B.length pad) (fromIntegral b) `bytesEq` pad)-- unless (macValid &&! paddingValid) $ do- throwError $ Error_Protocol ("bad record mac", True, BadRecordMac)-- return $ cipherDataContent cdata--decryptData :: Version -> Record Ciphertext -> Bytes -> RecordState -> RecordM Bytes-decryptData ver record econtent tst = decryptOf (bulkF bulk)- where cipher = fromJust "cipher" $ stCipher tst- bulk = cipherBulk cipher- cst = stCryptState tst- macSize = hashSize $ cipherHash cipher- writekey = cstKey cst- blockSize = bulkBlockSize bulk- econtentLen = B.length econtent-- explicitIV = hasExplicitBlockIV ver-- sanityCheckError = throwError (Error_Packet "encrypted content too small for encryption parameters")-- decryptOf :: BulkFunctions -> RecordM Bytes- decryptOf (BulkBlockF _ decryptF) = do- let minContent = (if explicitIV then bulkIVSize bulk else 0) + max (macSize + 1) blockSize- when ((econtentLen `mod` blockSize) /= 0 || econtentLen < minContent) $ sanityCheckError- {- update IV -}- (iv, econtent') <- if explicitIV- then get2 econtent (bulkIVSize bulk, econtentLen - bulkIVSize bulk)- else return (cstIV cst, econtent)- let newiv = fromJust "new iv" $ takelast (bulkBlockSize bulk) econtent'- modify $ \txs -> txs { stCryptState = cst { cstIV = newiv } }-- let content' = decryptF writekey iv econtent'- let paddinglength = fromIntegral (B.last content') + 1- let contentlen = B.length content' - paddinglength - macSize- (content, mac, padding) <- get3 content' (contentlen, macSize, paddinglength)- getCipherData record $ CipherData- { cipherDataContent = content- , cipherDataMAC = Just mac- , cipherDataPadding = Just padding- }-- decryptOf (BulkStreamF initF _ decryptF) = do- when (econtentLen < macSize) $ sanityCheckError- let (content', newiv) = decryptF (if cstIV cst /= B.empty then cstIV cst else initF writekey) econtent- {- update Ctx -}- let contentlen = B.length content' - macSize- (content, mac) <- get2 content' (contentlen, macSize)- modify $ \txs -> txs { stCryptState = cst { cstIV = newiv } }- getCipherData record $ CipherData- { cipherDataContent = content- , cipherDataMAC = Just mac- , cipherDataPadding = Nothing- }-- get3 s ls = maybe (throwError $ Error_Packet "record bad format") return $ partition3 s ls- get2 s (d1,d2) = get3 s (d1,d2,0) >>= \(r1,r2,_) -> return (r1,r2)
+ Network/TLS/Record/Encrypt.hs view
@@ -0,0 +1,131 @@+{-# LANGUAGE BangPatterns #-}++-- |+-- Engage a record into the Record layer.+-- The record is compressed, added some integrity field, then encrypted.+--+-- Starting with TLS v1.3, only the "null" compression method is negotiated in+-- the handshake, so the compression step will be a no-op. Integrity and+-- encryption are performed using an AEAD cipher only.+module Network.TLS.Record.Encrypt (+ encryptRecord,+) where++import Control.Monad.State.Strict+import Crypto.Cipher.Types (AuthTag (..))+import Data.ByteArray (convert)+import qualified Data.ByteArray as BA+import qualified Data.ByteString as B++import Network.TLS.Cipher+import Network.TLS.Imports+import Network.TLS.Packet+import Network.TLS.Record.State+import Network.TLS.Record.Types+import Network.TLS.Wire++-- when Tx Encrypted is set, we pass the data through encryptContent, otherwise+-- we just return the compress payload directly as the ciphered one+--+encryptRecord :: Record Plaintext -> RecordM (Record Ciphertext)+encryptRecord record@(Record ct ver fragment) = do+ st <- get+ case stCipher st of+ Nothing -> noEncryption+ _ -> do+ recOpts <- getRecordOptions+ if recordTLS13 recOpts+ then encryptContent13+ else onRecordFragment record $ fragmentCipher (encryptContent False record)+ where+ noEncryption = onRecordFragment record $ fragmentCipher return+ encryptContent13+ | ct == ProtocolType_ChangeCipherSpec = noEncryption+ | otherwise = do+ let bytes = fragmentGetBytes fragment+ fragment' = fragmentPlaintext $ innerPlaintext ct bytes+ record' = Record ProtocolType_AppData ver fragment'+ onRecordFragment record' $ fragmentCipher (encryptContent True record')++innerPlaintext :: ProtocolType -> ByteString -> ByteString+innerPlaintext (ProtocolType c) bytes = runPut $ do+ putBytes bytes+ putWord8 c -- non zero!+ -- fixme: zeros padding++encryptContent :: Bool -> Record Plaintext -> ByteString -> RecordM ByteString+encryptContent tls13 record content = do+ cst <- getCryptState+ bulk <- getBulk+ case cstKey cst of+ BulkStateBlock encryptF -> do+ digest <- makeDigest (recordToHeader record) content+ let content' = B.concat [content, digest]+ encryptBlock encryptF content' bulk+ BulkStateStream encryptF -> do+ digest <- makeDigest (recordToHeader record) content+ let content' = B.concat [content, digest]+ encryptStream encryptF content'+ BulkStateAEAD encryptF ->+ encryptAead tls13 bulk encryptF content record+ BulkStateUninitialized ->+ return content++encryptBlock :: BulkBlock -> ByteString -> Bulk -> RecordM ByteString+encryptBlock encryptF content bulk = do+ cst <- getCryptState+ let blockSize = fromIntegral $ bulkBlockSize bulk+ let msg_len = B.length content+ let padding =+ if blockSize > 0+ then+ let padbyte = blockSize - (msg_len `mod` blockSize)+ in let padbyte' = if padbyte == 0 then blockSize else padbyte+ in B.replicate padbyte' (fromIntegral (padbyte' - 1))+ else B.empty++ let (e, _iv') = encryptF (cstIV cst) $ B.concat [content, padding]++ return $ B.concat [cstIV cst, e]++encryptStream :: BulkStream -> ByteString -> RecordM ByteString+encryptStream (BulkStream encryptF) content = do+ cst <- getCryptState+ let (!e, !newBulkStream) = encryptF content+ modify' $ \tstate -> tstate{stCryptState = cst{cstKey = BulkStateStream newBulkStream}}+ return e++encryptAead+ :: Bool+ -> Bulk+ -> BulkAEAD+ -> ByteString+ -> Record Plaintext+ -> RecordM ByteString+encryptAead tls13 bulk encryptF content record = do+ let authTagLen = bulkAuthTagLen bulk+ nonceExpLen = bulkExplicitIV bulk+ cst <- getCryptState+ encodedSeq <- encodeWord64 <$> getMacSequence++ let iv = cstIV cst+ ivlen = B.length iv+ Header typ v plainLen = recordToHeader record+ hdrLen = if tls13 then plainLen + fromIntegral authTagLen else plainLen+ hdr = Header typ v hdrLen+ ad+ | tls13 = encodeHeader hdr+ | otherwise = B.concat [encodedSeq, encodeHeader hdr]+ sqnc = B.replicate (ivlen - 8) 0 `B.append` encodedSeq+ nonce+ | nonceExpLen == 0 = BA.xor iv sqnc+ | otherwise = B.concat [iv, encodedSeq]+ (e, AuthTag authtag) = encryptF nonce content ad+ econtent+ | nonceExpLen == 0 = e `B.append` convert authtag+ | otherwise = B.concat [encodedSeq, e, convert authtag]+ modify' incrRecordState+ return econtent++getCryptState :: RecordM CryptState+getCryptState = stCryptState <$> get
− Network/TLS/Record/Engage.hs
@@ -1,84 +0,0 @@--- |--- Module : Network.TLS.Record.Engage--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ Engage a record into the Record layer.--- The record is compressed, added some integrity field, then encrypted.----module Network.TLS.Record.Engage- ( engageRecord- ) where--import Control.Monad.State--import Network.TLS.Cap-import Network.TLS.Record.State-import Network.TLS.Record.Types-import Network.TLS.Cipher-import Network.TLS.Compression-import Network.TLS.Util-import Data.ByteString (ByteString)-import qualified Data.ByteString as B--engageRecord :: Record Plaintext -> RecordM (Record Ciphertext)-engageRecord = compressRecord >=> encryptRecord--compressRecord :: Record Plaintext -> RecordM (Record Compressed)-compressRecord record =- onRecordFragment record $ fragmentCompress $ \bytes -> do- withCompression $ compressionDeflate bytes--{-- - when Tx Encrypted is set, we pass the data through encryptContent, otherwise- - we just return the packet- -}-encryptRecord :: Record Compressed -> RecordM (Record Ciphertext)-encryptRecord record = onRecordFragment record $ fragmentCipher $ \bytes -> do- st <- get- case stCipher st of- Nothing -> return bytes- _ -> encryptContent record bytes--encryptContent :: Record Compressed -> ByteString -> RecordM ByteString-encryptContent record content = do- digest <- makeDigest (recordToHeader record) content- encryptData $ B.concat [content, digest]--encryptData :: ByteString -> RecordM ByteString-encryptData content = do- tstate <- get- ver <- getRecordVersion-- let cipher = fromJust "cipher" $ stCipher tstate- let bulk = cipherBulk cipher- let cst = stCryptState tstate-- let writekey = cstKey cst-- case bulkF bulk of- BulkBlockF encrypt _ -> do- let blockSize = fromIntegral $ bulkBlockSize bulk- let msg_len = B.length content- let padding = if blockSize > 0- then- let padbyte = blockSize - (msg_len `mod` blockSize) in- let padbyte' = if padbyte == 0 then blockSize else padbyte in- B.replicate padbyte' (fromIntegral (padbyte' - 1))- else- B.empty-- let e = encrypt writekey (cstIV cst) (B.concat [ content, padding ])- if hasExplicitBlockIV ver- then return $ B.concat [cstIV cst,e]- else do- let newiv = fromJust "new iv" $ takelast (bulkIVSize bulk) e- put $ tstate { stCryptState = cst { cstIV = newiv } }- return e- BulkStreamF initF encryptF _ -> do- let iv = cstIV cst- let (e, newiv) = encryptF (if iv /= B.empty then iv else initF writekey) content- put $ tstate { stCryptState = cst { cstIV = newiv } }- return e
+ Network/TLS/Record/Layer.hs view
@@ -0,0 +1,64 @@+module Network.TLS.Record.Layer (+ RecordLayer (..),+ newTransparentRecordLayer,+) where++import Network.TLS.Context+import Network.TLS.Imports+import Network.TLS.Record+import Network.TLS.Struct++import qualified Data.ByteString as B++newTransparentRecordLayer+ :: Eq ann+ => (Context -> IO ann)+ -> ([(ann, ByteString)] -> IO ())+ -> (Context -> IO (Either TLSError ByteString))+ -> RecordLayer [(ann, ByteString)]+newTransparentRecordLayer get send recv =+ RecordLayer+ { recordEncode12 = transparentEncodeRecord get+ , recordEncode13 = transparentEncodeRecord get+ , recordSendBytes = transparentSendBytes send+ , recordRecv12 = transparentRecvRecord recv+ , recordRecv13 = transparentRecvRecord recv+ }++transparentEncodeRecord+ :: (Context -> IO ann)+ -> Context+ -> Record Plaintext+ -> IO (Either TLSError [(ann, ByteString)])+transparentEncodeRecord _ _ (Record ProtocolType_ChangeCipherSpec _ _) =+ return $ Right []+transparentEncodeRecord _ _ (Record ProtocolType_Alert _ _) =+ -- all alerts are silent and must be transported externally based on+ -- TLS exceptions raised by the library+ return $ Right []+transparentEncodeRecord get ctx (Record _ _ frag) =+ get ctx >>= \a -> return $ Right [(a, fragmentGetBytes frag)]++transparentSendBytes+ :: Eq ann+ => ([(ann, ByteString)] -> IO ())+ -> Context+ -> [(ann, ByteString)]+ -> IO ()+transparentSendBytes send _ input =+ send+ [ (a, bs) | (a, frgs) <- compress input, let bs = B.concat frgs, not (B.null bs)+ ]++transparentRecvRecord+ :: (Context -> IO (Either TLSError ByteString))+ -> Context+ -> IO (Either TLSError (Record Plaintext))+transparentRecvRecord recv ctx =+ fmap (Record ProtocolType_Handshake TLS12 . fragmentPlaintext) <$> recv ctx++compress :: Eq ann => [(ann, val)] -> [(ann, [val])]+compress [] = []+compress ((a, v) : xs) =+ let (ys, zs) = span ((== a) . fst) xs+ in (a, v : map snd ys) : compress zs
+ Network/TLS/Record/Recv.hs view
@@ -0,0 +1,112 @@+-- | TLS record layer in Rx direction+module Network.TLS.Record.Recv (+ recvRecord12,+ recvRecord13,+) where++import qualified Data.ByteString as B++import Network.TLS.Context.Internal+import Network.TLS.Hooks+import Network.TLS.Imports+import Network.TLS.Packet+import Network.TLS.Record+import Network.TLS.Struct+import Network.TLS.Types++----------------------------------------------------------------++getMyPlainLimit :: Context -> IO Int+getMyPlainLimit ctx = do+ msiz <- getMyRecordLimit ctx+ return $ case msiz of+ Nothing -> defaultRecordSizeLimit+ Just siz -> siz++getRecord+ :: Context+ -> Header+ -> ByteString+ -> IO (Either TLSError (Record Plaintext))+getRecord ctx header content = do+ withLog ctx $ \logging -> loggingIORecv logging header content+ lim <- getMyPlainLimit ctx+ runRxRecordState ctx $ do+ let erecord = rawToRecord header $ fragmentCiphertext content+ decryptRecord erecord lim++----------------------------------------------------------------++exceedsTLSCiphertext :: Int -> Word16 -> Bool+exceedsTLSCiphertext overhead actual =+ -- In TLS 1.3, overhead is included one more byte for content type.+ fromIntegral actual > defaultRecordSizeLimit + overhead++-- | recvRecord receive a full TLS record (header + data), from the other side.+--+-- The record is disengaged from the record layer+recvRecord12+ :: Context+ -- ^ TLS context+ -> IO (Either TLSError (Record Plaintext))+recvRecord12 ctx =+ readExactBytes ctx 5 >>= either (return . Left) (recvLengthE . decodeHeader)+ where+ recvLengthE = either (return . Left) recvLength++ recvLength header@(Header _ _ readlen) = do+ -- RFC 5246 Section 7.2.2+ -- A TLSCiphertext record was received that had a length more+ -- than 2^14+2048 bytes, or a record decrypted to a+ -- TLSCompressed record with more than 2^14+1024 bytes. This+ -- message is always fatal and should never be observed in+ -- communication between proper implementations (except when+ -- messages were corrupted in the network).+ if exceedsTLSCiphertext 2048 readlen+ then return $ Left maximumSizeExceeded+ else+ readExactBytes ctx (fromIntegral readlen)+ >>= either (return . Left) (getRecord ctx header)++recvRecord13 :: Context -> IO (Either TLSError (Record Plaintext))+recvRecord13 ctx = readExactBytes ctx 5 >>= either (return . Left) (recvLengthE . decodeHeader)+ where+ recvLengthE = either (return . Left) recvLength+ recvLength header@(Header _ _ readlen) = do+ -- RFC 8446 Section 5.2:+ -- An AEAD algorithm used in TLS 1.3 MUST NOT produce an+ -- expansion greater than 255 octets. An endpoint that+ -- receives a record from its peer with TLSCiphertext.length+ -- larger than 2^14 + 256 octets MUST terminate the connection+ -- with a "record_overflow" alert. This limit is derived from+ -- the maximum TLSInnerPlaintext length of 2^14 octets + 1+ -- octet for ContentType + the maximum AEAD expansion of 255+ -- octets.+ if exceedsTLSCiphertext 256 readlen+ then return $ Left maximumSizeExceeded+ else+ readExactBytes ctx (fromIntegral readlen)+ >>= either (return . Left) (getRecord ctx header)++maximumSizeExceeded :: TLSError+maximumSizeExceeded = Error_Protocol "record exceeding maximum size" RecordOverflow++----------------------------------------------------------------++readExactBytes :: Context -> Int -> IO (Either TLSError ByteString)+readExactBytes ctx sz = do+ hdrbs <- contextRecv ctx sz+ if B.length hdrbs == sz+ then return $ Right hdrbs+ else do+ setEOF ctx+ return . Left $+ if B.null hdrbs+ then Error_EOF+ else+ Error_Packet+ ( "partial packet: expecting "+ ++ show sz+ ++ " bytes, got: "+ ++ show (B.length hdrbs)+ )
+ Network/TLS/Record/Send.hs view
@@ -0,0 +1,61 @@+-- | TLS record layer in Tx direction+module Network.TLS.Record.Send (+ encodeRecord12,+ encodeRecord13,+ sendBytes,+) where++import Control.Concurrent.MVar+import Control.Monad.State.Strict+import qualified Data.ByteString as B++import Network.TLS.Cipher+import Network.TLS.Context.Internal+import Network.TLS.Hooks+import Network.TLS.Imports+import Network.TLS.Packet+import Network.TLS.Record+import Network.TLS.Struct++encodeRecordM :: Record Plaintext -> RecordM ByteString+encodeRecordM record = do+ erecord <- encryptRecord record+ let (hdr, content) = recordToRaw erecord+ return $ B.concat [encodeHeader hdr, content]++----------------------------------------------------------------++encodeRecord12 :: Context -> Record Plaintext -> IO (Either TLSError ByteString)+encodeRecord12 ctx = prepareRecord12 ctx . encodeRecordM++-- before TLS 1.1, the block cipher IV is made of the residual of the previous block,+-- so we use cstIV as is, however in other case we generate an explicit IV+prepareRecord12 :: Context -> RecordM a -> IO (Either TLSError a)+prepareRecord12 ctx f = do+ txState <- readMVar $ ctxTxRecordState ctx+ let sz = case stCipher txState of+ Nothing -> 0+ Just cipher ->+ if hasRecordIV $ bulkF $ cipherBulk cipher+ then bulkIVSize $ cipherBulk cipher+ else 0 -- to not generate IV+ if sz > 0+ then do+ newIV <- getStateRNG ctx sz+ runTxRecordState ctx (modify' (setRecordIV newIV) >> f)+ else runTxRecordState ctx f++----------------------------------------------------------------++encodeRecord13 :: Context -> Record Plaintext -> IO (Either TLSError ByteString)+encodeRecord13 ctx = prepareRecord13 ctx . encodeRecordM++prepareRecord13 :: Context -> RecordM a -> IO (Either TLSError a)+prepareRecord13 = runTxRecordState++----------------------------------------------------------------++sendBytes :: Context -> ByteString -> IO ()+sendBytes ctx dataToSend = do+ withLog ctx $ \logging -> loggingIOSent logging dataToSend+ contextSend ctx dataToSend
Network/TLS/Record/State.hs view
@@ -1,135 +1,177 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE CPP #-}--- |--- Module : Network.TLS.Record.State--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Record.State- ( CryptState(..)- , MacState(..)- , RecordState(..)- , newRecordState- , RecordM- , runRecordM- , getRecordVersion- , setRecordIV- , withCompression- , computeDigest- , makeDigest- ) where -import Data.Word-import Control.Applicative-import Control.Monad.State-import Control.Monad.Error-import Network.TLS.Compression+module Network.TLS.Record.State (+ CryptState (..),+ CryptLevel (..),+ HasCryptLevel (..),+ MacState (..),+ RecordOptions (..),+ RecordState (..),+ newRecordState,+ incrRecordState,+ RecordM,+ runRecordM,+ getRecordOptions,+ getRecordVersion,+ setRecordIV,+ withCompression,+ computeDigest,+ makeDigest,+ getBulk,+ getMacSequence,+) where++import Control.Monad.State.Strict+import qualified Data.ByteArray as BA+import qualified Data.ByteString as B+ import Network.TLS.Cipher+import Network.TLS.Compression+import Network.TLS.ErrT+import Network.TLS.Imports+import Network.TLS.MAC+import Network.TLS.Packet import Network.TLS.Struct+import Network.TLS.Types import Network.TLS.Wire -import Network.TLS.Packet-import Network.TLS.MAC-import Network.TLS.Util--import qualified Data.ByteString as B- data CryptState = CryptState- { cstKey :: !Bytes- , cstIV :: !Bytes- , cstMacSecret :: !Bytes- } deriving (Show)+ { cstKey :: BulkState+ , cstIV :: IV+ , -- In TLS 1.2 or earlier, this holds mac secret.+ -- In TLS 1.3, this holds application traffic secret N.+ cstMacSecret :: Secret+ }+ deriving (Show) newtype MacState = MacState { msSequence :: Word64- } deriving (Show)+ }+ deriving (Show) +data RecordOptions = RecordOptions+ { recordVersion :: Version -- version to use when sending/receiving+ , recordTLS13 :: Bool -- TLS13 record processing+ }++-- | TLS encryption level.+data CryptLevel+ = -- | Unprotected traffic+ CryptInitial+ | -- | Protected with main secret (TLS < 1.3)+ CryptMainSecret+ | -- | Protected with early traffic secret (TLS 1.3)+ CryptEarlySecret+ | -- | Protected with handshake traffic secret (TLS 1.3)+ CryptHandshakeSecret+ | -- | Protected with application traffic secret (TLS 1.3)+ CryptApplicationSecret+ deriving (Eq, Show)++class HasCryptLevel a where getCryptLevel :: proxy a -> CryptLevel+instance HasCryptLevel EarlySecret where getCryptLevel _ = CryptEarlySecret+instance HasCryptLevel HandshakeSecret where+ getCryptLevel _ = CryptHandshakeSecret+instance HasCryptLevel ApplicationSecret where+ getCryptLevel _ = CryptApplicationSecret+ data RecordState = RecordState- { stCipher :: Maybe Cipher+ { stCipher :: Maybe Cipher , stCompression :: Compression- , stCryptState :: !CryptState- , stMacState :: !MacState- } deriving (Show)+ , stCryptLevel :: CryptLevel+ , stCryptState :: CryptState+ , stMacState :: MacState+ }+ deriving (Show) -newtype RecordM a = RecordM { runRecordM :: Version- -> RecordState- -> Either TLSError (a, RecordState) }+newtype RecordM a = RecordM+ { runRecordM+ :: RecordOptions+ -> RecordState+ -> Either TLSError (a, RecordState)+ } instance Applicative RecordM where- pure = return+ pure a = RecordM $ \_ st -> Right (a, st) (<*>) = ap instance Monad RecordM where- return a = RecordM $ \_ st -> Right (a, st)- m1 >>= m2 = RecordM $ \ver st -> do- case runRecordM m1 ver st of- Left err -> Left err- Right (a, st2) -> runRecordM (m2 a) ver st2+ m1 >>= m2 = RecordM $ \opt st ->+ case runRecordM m1 opt st of+ Left err -> Left err+ Right (a, st2) -> runRecordM (m2 a) opt st2 instance Functor RecordM where- fmap f m = RecordM $ \ver st ->- case runRecordM m ver st of- Left err -> Left err- Right (a, st2) -> Right (f a, st2)+ fmap f m = RecordM $ \opt st ->+ case runRecordM m opt st of+ Left err -> Left err+ Right (a, st2) -> Right (f a, st2) +getRecordOptions :: RecordM RecordOptions+getRecordOptions = RecordM $ \opt st -> Right (opt, st)+ getRecordVersion :: RecordM Version-getRecordVersion = RecordM $ \ver st -> Right (ver, st)+getRecordVersion = recordVersion <$> getRecordOptions instance MonadState RecordState RecordM where- put x = RecordM $ \_ _ -> Right ((), x)- get = RecordM $ \_ st -> Right (st, st)-#if MIN_VERSION_mtl(2,1,0)+ put x = RecordM $ \_ _ -> Right ((), x)+ get = RecordM $ \_ st -> Right (st, st) state f = RecordM $ \_ st -> Right (f st)-#endif instance MonadError TLSError RecordM where- throwError e = RecordM $ \_ _ -> Left e- catchError m f = RecordM $ \ver st ->- case runRecordM m ver st of- Left err -> runRecordM (f err) ver st- r -> r+ throwError e = RecordM $ \_ _ -> Left e+ catchError m f = RecordM $ \opt st ->+ case runRecordM m opt st of+ Left err -> runRecordM (f err) opt st+ r -> r newRecordState :: RecordState-newRecordState = RecordState- { stCipher = Nothing- , stCompression = nullCompression- , stCryptState = CryptState B.empty B.empty B.empty- , stMacState = MacState 0- }+newRecordState =+ RecordState+ { stCipher = Nothing+ , stCompression = nullCompression+ , stCryptLevel = CryptInitial+ , stCryptState = CryptState BulkStateUninitialized B.empty BA.empty+ , stMacState = MacState 0+ } incrRecordState :: RecordState -> RecordState-incrRecordState ts = ts { stMacState = MacState (ms + 1) }- where (MacState ms) = stMacState ts+incrRecordState ts = ts{stMacState = MacState (ms + 1)}+ where+ (MacState ms) = stMacState ts -setRecordIV :: Bytes -> RecordState -> RecordState-setRecordIV iv st = st { stCryptState = (stCryptState st) { cstIV = iv } }+setRecordIV :: ByteString -> RecordState -> RecordState+setRecordIV iv st = st{stCryptState = (stCryptState st){cstIV = iv}} withCompression :: (Compression -> (Compression, a)) -> RecordM a withCompression f = do st <- get let (nc, a) = f $ stCompression st- put $ st { stCompression = nc }+ put $ st{stCompression = nc} return a -computeDigest :: Version -> RecordState -> Header -> Bytes -> (Bytes, RecordState)-computeDigest ver tstate hdr content = (digest, incrRecordState tstate)- where digest = macF (cstMacSecret cst) msg- cst = stCryptState tstate- cipher = fromJust "cipher" $ stCipher tstate- hashf = hashF $ cipherHash cipher- encodedSeq = encodeWord64 $ msSequence $ stMacState tstate+computeDigest+ :: Version -> RecordState -> Header -> ByteString -> (ByteString, RecordState)+computeDigest _ver tstate hdr content = (digest, incrRecordState tstate)+ where+ digest = BA.convert $ macF (cstMacSecret cst) msg+ cst = stCryptState tstate+ cipher = fromJust $ stCipher tstate+ hashA = cipherHash cipher+ encodedSeq = encodeWord64 $ msSequence $ stMacState tstate - (macF, msg)- | ver < TLS10 = (macSSL hashf, B.concat [ encodedSeq, encodeHeaderNoVer hdr, content ])- | otherwise = (hmac hashf 64, B.concat [ encodedSeq, encodeHeader hdr, content ])+ (macF, msg) = (hmac hashA, B.concat [encodedSeq, encodeHeader hdr, content]) -makeDigest :: Header -> Bytes -> RecordM Bytes+makeDigest :: Header -> ByteString -> RecordM ByteString makeDigest hdr content = do ver <- getRecordVersion st <- get let (digest, nstate) = computeDigest ver st hdr content put nstate return digest++getBulk :: RecordM Bulk+getBulk = cipherBulk . fromJust . stCipher <$> get++getMacSequence :: RecordM Word64+getMacSequence = msSequence . stMacState <$> get
Network/TLS/Record/Types.hs view
@@ -1,90 +1,81 @@ {-# LANGUAGE EmptyDataDecls #-}--- |--- Module : Network.TLS.Record.Types--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ The Record Protocol takes messages to be transmitted, fragments the--- data into manageable blocks, optionally compresses the data, applies--- a MAC, encrypts, and transmits the result. Received data is--- decrypted, verified, decompressed, reassembled, and then delivered to--- higher-level clients.----module Network.TLS.Record.Types- ( Header(..)- , ProtocolType(..)- , packetType++-- | The Record Protocol takes messages to be transmitted, fragments+-- the data into manageable blocks. applies a MAC, encrypts, and+-- transmits the result. Received data is decrypted, verified,+-- reassembled, and then delivered to higher-level clients.+module Network.TLS.Record.Types (+ Header (..),+ ProtocolType (..),+ packetType,+ -- * TLS Records- , Record(..)+ Record (..),+ -- * TLS Record fragment and constructors- , Fragment- , fragmentPlaintext- , fragmentCiphertext- , Plaintext- , Compressed- , Ciphertext+ Fragment,+ fragmentGetBytes,+ fragmentPlaintext,+ fragmentCiphertext,+ Plaintext,+ Ciphertext,+ -- * manipulate record- , onRecordFragment- , fragmentCompress- , fragmentCipher- , fragmentUncipher- , fragmentUncompress+ onRecordFragment,+ fragmentCipher,+ fragmentUncipher,+ -- * serialize record- , rawToRecord- , recordToRaw- , recordToHeader- ) where+ rawToRecord,+ recordToRaw,+ recordToHeader,+) where -import Network.TLS.Struct-import Network.TLS.Record.State import qualified Data.ByteString as B-import Data.Byteable-import Control.Applicative ((<$>)) +import Network.TLS.Imports+import Network.TLS.Record.State+import Network.TLS.Struct+ -- | Represent a TLS record.-data Record a = Record !ProtocolType !Version !(Fragment a) deriving (Show,Eq)+data Record a = Record ProtocolType Version (Fragment a) deriving (Show, Eq) -newtype Fragment a = Fragment Bytes deriving (Show,Eq)+newtype Fragment a = Fragment {fragmentGetBytes :: ByteString}+ deriving (Show, Eq) data Plaintext-data Compressed data Ciphertext -fragmentPlaintext :: Bytes -> Fragment Plaintext+fragmentPlaintext :: ByteString -> Fragment Plaintext fragmentPlaintext bytes = Fragment bytes -fragmentCiphertext :: Bytes -> Fragment Ciphertext+fragmentCiphertext :: ByteString -> Fragment Ciphertext fragmentCiphertext bytes = Fragment bytes -instance Byteable (Fragment a) where- toBytes (Fragment b) = b--onRecordFragment :: Record a -> (Fragment a -> RecordM (Fragment b)) -> RecordM (Record b)+onRecordFragment+ :: Record a -> (Fragment a -> RecordM (Fragment b)) -> RecordM (Record b) onRecordFragment (Record pt ver frag) f = Record pt ver <$> f frag -fragmentMap :: (Bytes -> RecordM Bytes) -> Fragment a -> RecordM (Fragment b)+fragmentMap+ :: (ByteString -> RecordM ByteString) -> Fragment a -> RecordM (Fragment b) fragmentMap f (Fragment b) = Fragment <$> f b --- | turn a plaintext record into a compressed record using the compression function supplied-fragmentCompress :: (Bytes -> RecordM Bytes) -> Fragment Plaintext -> RecordM (Fragment Compressed)-fragmentCompress f = fragmentMap f- -- | turn a compressed record into a ciphertext record using the cipher function supplied-fragmentCipher :: (Bytes -> RecordM Bytes) -> Fragment Compressed -> RecordM (Fragment Ciphertext)+fragmentCipher+ :: (ByteString -> RecordM ByteString)+ -> Fragment Plaintext+ -> RecordM (Fragment Ciphertext) fragmentCipher f = fragmentMap f --- | turn a ciphertext fragment into a compressed fragment using the cipher function supplied-fragmentUncipher :: (Bytes -> RecordM Bytes) -> Fragment Ciphertext -> RecordM (Fragment Compressed)+-- | turn a ciphertext fragment into a plaintext fragment using the cipher function supplied+fragmentUncipher+ :: (ByteString -> RecordM ByteString)+ -> Fragment Ciphertext+ -> RecordM (Fragment Plaintext) fragmentUncipher f = fragmentMap f --- | turn a compressed fragment into a plaintext fragment using the decompression function supplied-fragmentUncompress :: (Bytes -> RecordM Bytes) -> Fragment Compressed -> RecordM (Fragment Plaintext)-fragmentUncompress f = fragmentMap f- -- | turn a record into an header and bytes-recordToRaw :: Record a -> (Header, Bytes)+recordToRaw :: Record a -> (Header, ByteString) recordToRaw (Record pt ver (Fragment bytes)) = (Header pt ver (fromIntegral $ B.length bytes), bytes) -- | turn a header and a fragment into a record
− Network/TLS/Sending.hs
@@ -1,90 +0,0 @@--- |--- Module : Network.TLS.Sending--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ the Sending module contains calls related to marshalling packets according--- to the TLS state ----module Network.TLS.Sending (writePacket) where--import Control.Applicative-import Control.Monad.State-import Control.Concurrent.MVar-import Data.IORef--import Data.ByteString (ByteString)-import qualified Data.ByteString as B--import Network.TLS.Types (Role(..))-import Network.TLS.Cap-import Network.TLS.Struct-import Network.TLS.Record-import Network.TLS.Packet-import Network.TLS.Context.Internal-import Network.TLS.Parameters-import Network.TLS.State-import Network.TLS.Handshake.State-import Network.TLS.Cipher-import Network.TLS.Util---- | 'makePacketData' create a Header and a content bytestring related to a packet--- this doesn't change any state-makeRecord :: Packet -> RecordM (Record Plaintext)-makeRecord pkt = do- ver <- getRecordVersion- return $ Record (packetType pkt) ver (fragmentPlaintext $ writePacketContent pkt)- where writePacketContent (Handshake hss) = encodeHandshakes hss- writePacketContent (Alert a) = encodeAlerts a- writePacketContent (ChangeCipherSpec) = encodeChangeCipherSpec- writePacketContent (AppData x) = x---- | marshall packet data-encodeRecord :: Record Ciphertext -> RecordM ByteString-encodeRecord record = return $ B.concat [ encodeHeader hdr, content ]- where (hdr, content) = recordToRaw record---- | writePacket transform a packet into marshalled data related to current state--- and updating state on the go-writePacket :: Context -> Packet -> IO (Either TLSError ByteString)-writePacket ctx pkt@(Handshake hss) = do- forM_ hss $ \hs -> do- case hs of- Finished fdata -> usingState_ ctx $ updateVerifiedData ClientRole fdata- _ -> return ()- let encoded = encodeHandshake hs- usingHState ctx $ do- when (certVerifyHandshakeMaterial hs) $ addHandshakeMessage encoded- when (finishHandshakeTypeMaterial $ typeOfHandshake hs) $ updateHandshakeDigest encoded- prepareRecord ctx (makeRecord pkt >>= engageRecord >>= encodeRecord)-writePacket ctx pkt = do- d <- prepareRecord ctx (makeRecord pkt >>= engageRecord >>= encodeRecord)- when (pkt == ChangeCipherSpec) $ switchTxEncryption ctx- return d---- before TLS 1.1, the block cipher IV is made of the residual of the previous block,--- so we use cstIV as is, however in other case we generate an explicit IV-prepareRecord :: Context -> RecordM a -> IO (Either TLSError a)-prepareRecord ctx f = do- ver <- usingState_ ctx (getVersionWithDefault $ maximum $ supportedVersions $ ctxSupported ctx)- txState <- readMVar $ ctxTxState ctx- let sz = case stCipher $ txState of- Nothing -> 0- Just cipher -> bulkIVSize $ cipherBulk cipher- if hasExplicitBlockIV ver && sz > 0- then do newIV <- getStateRNG ctx sz- runTxState ctx (modify (setRecordIV newIV) >> f)- else runTxState ctx f--switchTxEncryption :: Context -> IO ()-switchTxEncryption ctx = do- tx <- usingHState ctx (fromJust "tx-state" <$> gets hstPendingTxState)- (ver, cc) <- usingState_ ctx $ do v <- getVersion- c <- isClientContext- return (v, c)- liftIO $ modifyMVar_ (ctxTxState ctx) (\_ -> return tx)- -- set empty packet counter measure if condition are met- when (ver <= TLS10 && cc == ClientRole && isCBC tx) $ liftIO $ writeIORef (ctxNeedEmptyPacket ctx) True- where isCBC tx = maybe False (\c -> bulkBlockSize (cipherBulk c) > 0) (stCipher tx)
Network/TLS/Session.hs view
@@ -1,30 +1,35 @@--- |--- Module : Network.TLS.Session--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Session- ( SessionManager(..)- , noSessionManager- ) where+module Network.TLS.Session (+ SessionManager (..),+ noSessionManager,+) where import Network.TLS.Types --- | A session manager+-- | A session manager.+-- In the server side, all fields are used.+-- In the client side, only 'sessionEstablish' is used. data SessionManager = SessionManager- { -- | used on server side to decide whether to resume a client session.- sessionResume :: SessionID -> IO (Maybe SessionData)- -- | used when a session is established.- , sessionEstablish :: SessionID -> SessionData -> IO ()- -- | used when a session is invalidated.- , sessionInvalidate :: SessionID -> IO ()+ { sessionResume :: SessionIDorTicket -> IO (Maybe SessionData)+ -- ^ Used on TLS 1.2\/1.3 servers to lookup 'SessionData' with 'SessionID' or to decrypt 'Ticket' to get 'SessionData'.+ , sessionResumeOnlyOnce :: SessionIDorTicket -> IO (Maybe SessionData)+ -- ^ Used for 0RTT on TLS 1.3 servers to lookup 'SessionData' with 'SessionID' or to decrypt 'Ticket' to get 'SessionData'.+ , sessionEstablish :: SessionIDorTicket -> SessionData -> IO (Maybe Ticket)+ -- ^ Used on TLS 1.2\/1.3 servers to store 'SessionData' with 'SessionID' or to encrypt 'SessionData' to get 'Ticket' ignoring 'SessionID'. Used on TLS 1.2\/1.3 clients to store 'SessionData' with 'SessionIDorTicket' and then return 'Nothing'. For clients, only this field should be set with 'noSessionManager'.+ , sessionInvalidate :: SessionIDorTicket -> IO ()+ -- ^ Used TLS 1.2 servers to delete 'SessionData' with 'SessionID' on errors.+ , sessionUseTicket :: Bool+ -- ^ Used on TLS 1.2 servers to decide to use 'SessionID' or 'Ticket'. Note that 'SessionID' and 'Ticket' are integrated as identity in TLS 1.3. } +-- | The session manager to do nothing. noSessionManager :: SessionManager-noSessionManager = SessionManager- { sessionResume = \_ -> return Nothing- , sessionEstablish = \_ _ -> return ()- , sessionInvalidate = \_ -> return ()- }+noSessionManager =+ SessionManager+ { sessionResume = \_ -> return Nothing+ , sessionResumeOnlyOnce = \_ -> return Nothing+ , sessionEstablish = \_ _ -> return Nothing+ , sessionInvalidate = \_ -> return ()+ , -- Don't send NewSessionTicket in TLS 1.2 by default.+ -- Send NewSessionTicket with SessionID in TLS 1.3 by default.+ sessionUseTicket = False+ }
Network/TLS/State.hs view
@@ -1,217 +1,374 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE Rank2Types #-}-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}--- |--- Module : Network.TLS.State--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ the State module contains calls related to state initialization/manipulation--- which is use by the Receiving module and the Sending module.----module Network.TLS.State- ( TLSState(..)- , TLSSt- , runTLSState- , newTLSState- , withTLSRNG- , updateVerifiedData- , finishHandshakeTypeMaterial- , finishHandshakeMaterial- , certVerifyHandshakeTypeMaterial- , certVerifyHandshakeMaterial- , setVersion- , setVersionIfUnset- , getVersion- , getVersionWithDefault- , setSecureRenegotiation- , getSecureRenegotiation- , setExtensionNPN- , getExtensionNPN- , setNegotiatedProtocol- , getNegotiatedProtocol- , setServerNextProtocolSuggest- , getServerNextProtocolSuggest- , getClientCertificateChain- , setClientCertificateChain- , getVerifiedData- , setSession- , getSession- , isSessionResuming- , isClientContext+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Rank2Types #-}++-- | the State module contains calls related to state+-- initialization/manipulation which is use by the Receiving module+-- and the Sending module.+module Network.TLS.State (+ TLSState (..),+ TLSSt,+ runTLSState,+ newTLSState,+ withTLSRNG,+ setVerifyDataForSend,+ setVerifyDataForRecv,+ getVerifyData,+ getMyVerifyData,+ getPeerVerifyData,+ getFirstVerifyData,+ finishedHandshakeTypeMaterial,+ finishedHandshakeMaterial,+ certVerifyHandshakeTypeMaterial,+ certVerifyHandshakeMaterial,+ setVersion,+ setVersionIfUnset,+ getVersion,+ getVersionWithDefault,+ setSecureRenegotiation,+ getSecureRenegotiation,+ setExtensionALPN,+ getExtensionALPN,+ setNegotiatedProtocol,+ getNegotiatedProtocol,+ setClientALPNSuggest,+ getClientALPNSuggest,+ setClientEcPointFormatSuggest,+ getClientEcPointFormatSuggest,+ setClientSNI,+ clearClientSNI,+ getClientSNI,+ getClientCertificateChain,+ setClientCertificateChain,+ getServerCertificateChain,+ setServerCertificateChain,+ setSession,+ getSession,+ getRole,+ --+ setTLS12SessionResuming,+ getTLS12SessionResuming,+ --+ setTLS13ExporterSecret,+ getTLS13ExporterSecret,+ setTLS13KeyShare,+ getTLS13KeyShare,+ setTLS13PreSharedKey,+ getTLS13PreSharedKey,+ setTLS13HRR,+ getTLS13HRR,+ setTLS13Cookie,+ getTLS13Cookie,+ setTLS13ClientSupportsPHA,+ getTLS13ClientSupportsPHA,+ setTLS12SessionTicket,+ getTLS12SessionTicket,+ -- * random- , genRandom- , withRNG- ) where+ genRandom,+ withRNG,+) where -import Control.Applicative-import Network.TLS.Struct-import Network.TLS.RNG-import Network.TLS.Types (Role(..))-import Network.TLS.Wire (GetContinuation)-import qualified Data.ByteString as B-import Control.Monad.State-import Control.Monad.Error+import Control.Monad.State.Strict import Crypto.Random import Data.X509 (CertificateChain) +import Network.TLS.ErrT+import Network.TLS.Extension+import Network.TLS.Imports+import Network.TLS.RNG+import Network.TLS.Struct+import Network.TLS.Types (HostName, Role (..), Secret, Ticket, WireBytes)+import Network.TLS.Wire (GetContinuation)+ data TLSState = TLSState- { stSession :: Session- , stSessionResuming :: Bool- , stSecureRenegotiation :: Bool -- RFC 5746- , stClientVerifiedData :: Bytes -- RFC 5746- , stServerVerifiedData :: Bytes -- RFC 5746- , stExtensionNPN :: Bool -- NPN draft extension- , stHandshakeRecordCont :: Maybe (GetContinuation (HandshakeType, Bytes))- , stNegotiatedProtocol :: Maybe B.ByteString -- NPN protocol- , stServerNextProtocolSuggest :: Maybe [B.ByteString]+ { stSession :: Session+ , -- RFC 5746, Renegotiation Indication Extension+ -- RFC 5929, Channel Bindings for TLS, "tls-unique"+ stSecureRenegotiation :: Bool+ , stClientVerifyData :: Maybe VerifyData+ , stServerVerifyData :: Maybe VerifyData+ , -- RFC 5929, Channel Bindings for TLS, "tls-server-end-point"+ stServerCertificateChain :: Maybe CertificateChain+ , stExtensionALPN :: Bool -- RFC 7301+ , stNegotiatedProtocol :: Maybe ByteString -- ALPN protocol+ , stHandshakeRecordCont12+ :: (Maybe (GetContinuation (HandshakeType, ByteString)), WireBytes)+ , stHandshakeRecordCont13+ :: (Maybe (GetContinuation (HandshakeType, ByteString)), WireBytes)+ , stClientALPNSuggest :: Maybe [ByteString]+ , stClientGroupSuggest :: Maybe [Group]+ , stClientEcPointFormatSuggest :: Maybe [EcPointFormat] , stClientCertificateChain :: Maybe CertificateChain- , stRandomGen :: StateRNG- , stVersion :: Maybe Version- , stClientContext :: Role+ , stClientSNI :: Maybe HostName+ , stRandomGen :: StateRNG+ , stClientContext :: Role+ , stVersion :: Maybe Version+ , --+ stTLS12SessionResuming :: Bool+ , stTLS12SessionTicket :: Maybe Ticket+ , --+ stTLS13KeyShare :: Maybe KeyShare+ , stTLS13PreSharedKey :: Maybe PreSharedKey+ , stTLS13HRR :: Bool+ , stTLS13Cookie :: Maybe Cookie+ , stTLS13ExporterSecret :: Maybe Secret+ , stTLS13ClientSupportsPHA :: Bool -- Post-Handshake Authentication } -newtype TLSSt a = TLSSt { runTLSSt :: ErrorT TLSError (State TLSState) a }+newtype TLSSt a = TLSSt {runTLSSt :: ErrT TLSError (State TLSState) a} deriving (Monad, MonadError TLSError, Functor, Applicative) instance MonadState TLSState TLSSt where put x = TLSSt (lift $ put x)- get = TLSSt (lift get)-#if MIN_VERSION_mtl(2,1,0)+ get = TLSSt (lift get) state f = TLSSt (lift $ state f)-#endif runTLSState :: TLSSt a -> TLSState -> (Either TLSError a, TLSState)-runTLSState f st = runState (runErrorT (runTLSSt f)) st+runTLSState f st = runState (runErrT (runTLSSt f)) st -newTLSState :: CPRG g => g -> Role -> TLSState-newTLSState rng clientContext = TLSState- { stSession = Session Nothing- , stSessionResuming = False- , stSecureRenegotiation = False- , stClientVerifiedData = B.empty- , stServerVerifiedData = B.empty- , stExtensionNPN = False- , stHandshakeRecordCont = Nothing- , stNegotiatedProtocol = Nothing- , stServerNextProtocolSuggest = Nothing- , stClientCertificateChain = Nothing- , stRandomGen = StateRNG rng- , stVersion = Nothing- , stClientContext = clientContext- }+newTLSState :: StateRNG -> Role -> TLSState+newTLSState rng clientContext =+ TLSState+ { stSession = Session Nothing+ , stSecureRenegotiation = False+ , stClientVerifyData = Nothing+ , stServerVerifyData = Nothing+ , stServerCertificateChain = Nothing+ , stExtensionALPN = False+ , stNegotiatedProtocol = Nothing+ , stHandshakeRecordCont12 = (Nothing, [])+ , stHandshakeRecordCont13 = (Nothing, [])+ , stClientALPNSuggest = Nothing+ , stClientGroupSuggest = Nothing+ , stClientEcPointFormatSuggest = Nothing+ , stClientCertificateChain = Nothing+ , stClientSNI = Nothing+ , stRandomGen = rng+ , stClientContext = clientContext+ , stVersion = Nothing+ , stTLS12SessionResuming = False+ , stTLS12SessionTicket = Nothing+ , stTLS13KeyShare = Nothing+ , stTLS13PreSharedKey = Nothing+ , stTLS13HRR = False+ , stTLS13Cookie = Nothing+ , stTLS13ExporterSecret = Nothing+ , stTLS13ClientSupportsPHA = False+ } -updateVerifiedData :: Role -> Bytes -> TLSSt ()-updateVerifiedData sending bs = do- cc <- isClientContext- if cc /= sending- then modify (\st -> st { stServerVerifiedData = bs })- else modify (\st -> st { stClientVerifiedData = bs })+setVerifyDataForSend :: VerifyData -> TLSSt ()+setVerifyDataForSend bs = do+ role <- getRole+ case role of+ ClientRole -> modify' (\st -> st{stClientVerifyData = Just bs})+ ServerRole -> modify' (\st -> st{stServerVerifyData = Just bs}) -finishHandshakeTypeMaterial :: HandshakeType -> Bool-finishHandshakeTypeMaterial HandshakeType_ClientHello = True-finishHandshakeTypeMaterial HandshakeType_ServerHello = True-finishHandshakeTypeMaterial HandshakeType_Certificate = True-finishHandshakeTypeMaterial HandshakeType_HelloRequest = False-finishHandshakeTypeMaterial HandshakeType_ServerHelloDone = True-finishHandshakeTypeMaterial HandshakeType_ClientKeyXchg = True-finishHandshakeTypeMaterial HandshakeType_ServerKeyXchg = True-finishHandshakeTypeMaterial HandshakeType_CertRequest = True-finishHandshakeTypeMaterial HandshakeType_CertVerify = True-finishHandshakeTypeMaterial HandshakeType_Finished = True-finishHandshakeTypeMaterial HandshakeType_NPN = True+setVerifyDataForRecv :: VerifyData -> TLSSt ()+setVerifyDataForRecv bs = do+ role <- getRole+ case role of+ ClientRole -> modify' (\st -> st{stServerVerifyData = Just bs})+ ServerRole -> modify' (\st -> st{stClientVerifyData = Just bs}) -finishHandshakeMaterial :: Handshake -> Bool-finishHandshakeMaterial = finishHandshakeTypeMaterial . typeOfHandshake+finishedHandshakeTypeMaterial :: HandshakeType -> Bool+finishedHandshakeTypeMaterial HandshakeType_ClientHello = True+finishedHandshakeTypeMaterial HandshakeType_ServerHello = True+finishedHandshakeTypeMaterial HandshakeType_Certificate = True+-- finishedHandshakeTypeMaterial HandshakeType_HelloRequest = False+finishedHandshakeTypeMaterial HandshakeType_ServerHelloDone = True+finishedHandshakeTypeMaterial HandshakeType_ClientKeyXchg = True+finishedHandshakeTypeMaterial HandshakeType_ServerKeyXchg = True+finishedHandshakeTypeMaterial HandshakeType_CertRequest = True+finishedHandshakeTypeMaterial HandshakeType_CertVerify = True+finishedHandshakeTypeMaterial HandshakeType_NewSessionTicket = True+finishedHandshakeTypeMaterial HandshakeType_Finished = True+finishedHandshakeTypeMaterial _ = False +finishedHandshakeMaterial :: Handshake -> Bool+finishedHandshakeMaterial = finishedHandshakeTypeMaterial . typeOfHandshake+ certVerifyHandshakeTypeMaterial :: HandshakeType -> Bool-certVerifyHandshakeTypeMaterial HandshakeType_ClientHello = True-certVerifyHandshakeTypeMaterial HandshakeType_ServerHello = True-certVerifyHandshakeTypeMaterial HandshakeType_Certificate = True-certVerifyHandshakeTypeMaterial HandshakeType_HelloRequest = False+certVerifyHandshakeTypeMaterial HandshakeType_ClientHello = True+certVerifyHandshakeTypeMaterial HandshakeType_ServerHello = True+certVerifyHandshakeTypeMaterial HandshakeType_Certificate = True+-- certVerifyHandshakeTypeMaterial HandshakeType_HelloRequest = False certVerifyHandshakeTypeMaterial HandshakeType_ServerHelloDone = True-certVerifyHandshakeTypeMaterial HandshakeType_ClientKeyXchg = True-certVerifyHandshakeTypeMaterial HandshakeType_ServerKeyXchg = True-certVerifyHandshakeTypeMaterial HandshakeType_CertRequest = True-certVerifyHandshakeTypeMaterial HandshakeType_CertVerify = False-certVerifyHandshakeTypeMaterial HandshakeType_Finished = False-certVerifyHandshakeTypeMaterial HandshakeType_NPN = False+certVerifyHandshakeTypeMaterial HandshakeType_ClientKeyXchg = True+certVerifyHandshakeTypeMaterial HandshakeType_ServerKeyXchg = True+certVerifyHandshakeTypeMaterial HandshakeType_CertRequest = True+-- certVerifyHandshakeTypeMaterial HandshakeType_CertVerify = False+-- certVerifyHandshakeTypeMaterial HandshakeType_Finished = False+certVerifyHandshakeTypeMaterial _ = False certVerifyHandshakeMaterial :: Handshake -> Bool certVerifyHandshakeMaterial = certVerifyHandshakeTypeMaterial . typeOfHandshake -setSession :: Session -> Bool -> TLSSt ()-setSession session resuming = modify (\st -> st { stSession = session, stSessionResuming = resuming })+setSession :: Session -> TLSSt ()+setSession session = modify' (\st -> st{stSession = session}) getSession :: TLSSt Session getSession = gets stSession -isSessionResuming :: TLSSt Bool-isSessionResuming = gets stSessionResuming+setTLS12SessionResuming :: Bool -> TLSSt ()+setTLS12SessionResuming b = modify' (\st -> st{stTLS12SessionResuming = b}) +getTLS12SessionResuming :: TLSSt Bool+getTLS12SessionResuming = gets stTLS12SessionResuming+ setVersion :: Version -> TLSSt ()-setVersion ver = modify (\st -> st { stVersion = Just ver })+setVersion ver = modify' (\st -> st{stVersion = Just ver}) setVersionIfUnset :: Version -> TLSSt ()-setVersionIfUnset ver = modify maybeSet- where maybeSet st = case stVersion st of- Nothing -> st { stVersion = Just ver }- Just _ -> st+setVersionIfUnset ver = modify' maybeSet+ where+ maybeSet st = case stVersion st of+ Nothing -> st{stVersion = Just ver}+ Just _ -> st getVersion :: TLSSt Version-getVersion = maybe (error $ "internal error: version hasn't been set yet") id <$> gets stVersion+getVersion =+ fromMaybe (error "internal error: version hasn't been set yet")+ <$> gets stVersion getVersionWithDefault :: Version -> TLSSt Version-getVersionWithDefault defaultVer = maybe defaultVer id <$> gets stVersion+getVersionWithDefault defaultVer = fromMaybe defaultVer <$> gets stVersion setSecureRenegotiation :: Bool -> TLSSt ()-setSecureRenegotiation b = modify (\st -> st { stSecureRenegotiation = b })+setSecureRenegotiation b = modify' (\st -> st{stSecureRenegotiation = b}) getSecureRenegotiation :: TLSSt Bool getSecureRenegotiation = gets stSecureRenegotiation -setExtensionNPN :: Bool -> TLSSt ()-setExtensionNPN b = modify (\st -> st { stExtensionNPN = b })+setExtensionALPN :: Bool -> TLSSt ()+setExtensionALPN b = modify' (\st -> st{stExtensionALPN = b}) -getExtensionNPN :: TLSSt Bool-getExtensionNPN = gets stExtensionNPN+getExtensionALPN :: TLSSt Bool+getExtensionALPN = gets stExtensionALPN -setNegotiatedProtocol :: B.ByteString -> TLSSt ()-setNegotiatedProtocol s = modify (\st -> st { stNegotiatedProtocol = Just s })+setNegotiatedProtocol :: ByteString -> TLSSt ()+setNegotiatedProtocol s = modify' (\st -> st{stNegotiatedProtocol = Just s}) -getNegotiatedProtocol :: TLSSt (Maybe B.ByteString)+getNegotiatedProtocol :: TLSSt (Maybe ByteString) getNegotiatedProtocol = gets stNegotiatedProtocol -setServerNextProtocolSuggest :: [B.ByteString] -> TLSSt ()-setServerNextProtocolSuggest ps = modify (\st -> st { stServerNextProtocolSuggest = Just ps})+setClientALPNSuggest :: [ByteString] -> TLSSt ()+setClientALPNSuggest ps = modify' (\st -> st{stClientALPNSuggest = Just ps}) -getServerNextProtocolSuggest :: TLSSt (Maybe [B.ByteString])-getServerNextProtocolSuggest = gets stServerNextProtocolSuggest+getClientALPNSuggest :: TLSSt (Maybe [ByteString])+getClientALPNSuggest = gets stClientALPNSuggest +setClientEcPointFormatSuggest :: [EcPointFormat] -> TLSSt ()+setClientEcPointFormatSuggest epf = modify' (\st -> st{stClientEcPointFormatSuggest = Just epf})++getClientEcPointFormatSuggest :: TLSSt (Maybe [EcPointFormat])+getClientEcPointFormatSuggest = gets stClientEcPointFormatSuggest+ setClientCertificateChain :: CertificateChain -> TLSSt ()-setClientCertificateChain s = modify (\st -> st { stClientCertificateChain = Just s })+setClientCertificateChain s = modify' (\st -> st{stClientCertificateChain = Just s}) getClientCertificateChain :: TLSSt (Maybe CertificateChain) getClientCertificateChain = gets stClientCertificateChain -getVerifiedData :: Role -> TLSSt Bytes-getVerifiedData client = gets (if client == ClientRole then stClientVerifiedData else stServerVerifiedData)+setServerCertificateChain :: CertificateChain -> TLSSt ()+setServerCertificateChain s = modify' (\st -> st{stServerCertificateChain = Just s}) -isClientContext :: TLSSt Role-isClientContext = gets stClientContext+getServerCertificateChain :: TLSSt (Maybe CertificateChain)+getServerCertificateChain = gets stServerCertificateChain -genRandom :: Int -> TLSSt Bytes+setClientSNI :: HostName -> TLSSt ()+setClientSNI hn = modify' (\st -> st{stClientSNI = Just hn})++clearClientSNI :: TLSSt ()+clearClientSNI = modify' (\st -> st{stClientSNI = Nothing})++getClientSNI :: TLSSt (Maybe HostName)+getClientSNI = gets stClientSNI++getVerifyData :: Role -> TLSSt VerifyData+getVerifyData client = do+ mVerifyData <-+ gets (if client == ClientRole then stClientVerifyData else stServerVerifyData)+ return $ fromMaybe (VerifyData "") mVerifyData++getMyVerifyData :: TLSSt (Maybe VerifyData)+getMyVerifyData = do+ role <- getRole+ if role == ClientRole+ then gets stClientVerifyData+ else gets stServerVerifyData++getPeerVerifyData :: TLSSt (Maybe VerifyData)+getPeerVerifyData = do+ role <- getRole+ if role == ClientRole+ then gets stServerVerifyData+ else gets stClientVerifyData++getFirstVerifyData :: TLSSt (Maybe VerifyData)+getFirstVerifyData = do+ ver <- getVersion+ case ver of+ TLS13 -> gets stServerVerifyData+ _ -> do+ resuming <- getTLS12SessionResuming+ if resuming+ then gets stServerVerifyData+ else gets stClientVerifyData++getRole :: TLSSt Role+getRole = gets stClientContext++genRandom :: Int -> TLSSt ByteString genRandom n = do- st <- get- case withTLSRNG (stRandomGen st) (cprgGenerate n) of- (bytes, rng') -> put (st { stRandomGen = rng' }) >> return bytes+ withRNG (getRandomBytes n) -withRNG :: (forall g . CPRG g => g -> (a, g)) -> TLSSt a+withRNG :: MonadPseudoRandom StateRNG a -> TLSSt a withRNG f = do st <- get- let (a,rng') = withTLSRNG (stRandomGen st) f- put (st { stRandomGen = rng' })+ let (a, rng') = withTLSRNG (stRandomGen st) f+ put (st{stRandomGen = rng'}) return a++setTLS12SessionTicket :: Ticket -> TLSSt ()+setTLS12SessionTicket t = modify' (\st -> st{stTLS12SessionTicket = Just t})++getTLS12SessionTicket :: TLSSt (Maybe Ticket)+getTLS12SessionTicket = gets stTLS12SessionTicket++setTLS13ExporterSecret :: Secret -> TLSSt ()+setTLS13ExporterSecret key = modify' (\st -> st{stTLS13ExporterSecret = Just key})++getTLS13ExporterSecret :: TLSSt (Maybe Secret)+getTLS13ExporterSecret = gets stTLS13ExporterSecret++setTLS13KeyShare :: Maybe KeyShare -> TLSSt ()+setTLS13KeyShare mks = modify' (\st -> st{stTLS13KeyShare = mks})++getTLS13KeyShare :: TLSSt (Maybe KeyShare)+getTLS13KeyShare = gets stTLS13KeyShare++setTLS13PreSharedKey :: Maybe PreSharedKey -> TLSSt ()+setTLS13PreSharedKey mpsk = modify' (\st -> st{stTLS13PreSharedKey = mpsk})++getTLS13PreSharedKey :: TLSSt (Maybe PreSharedKey)+getTLS13PreSharedKey = gets stTLS13PreSharedKey++setTLS13HRR :: Bool -> TLSSt ()+setTLS13HRR b = modify' (\st -> st{stTLS13HRR = b})++getTLS13HRR :: TLSSt Bool+getTLS13HRR = gets stTLS13HRR++setTLS13Cookie :: Maybe Cookie -> TLSSt ()+setTLS13Cookie mcookie = modify' (\st -> st{stTLS13Cookie = mcookie})++getTLS13Cookie :: TLSSt (Maybe Cookie)+getTLS13Cookie = gets stTLS13Cookie++setTLS13ClientSupportsPHA :: Bool -> TLSSt ()+setTLS13ClientSupportsPHA b = modify' (\st -> st{stTLS13ClientSupportsPHA = b})++getTLS13ClientSupportsPHA :: TLSSt Bool+getTLS13ClientSupportsPHA = gets stTLS13ClientSupportsPHA
Network/TLS/Struct.hs view
@@ -1,478 +1,501 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-} {-# OPTIONS_HADDOCK hide #-}-{-# LANGUAGE DeriveDataTypeable #-}--- |--- Module : Network.TLS.Struct--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ the Struct module contains all definitions and values of the TLS protocol----module Network.TLS.Struct- ( Bytes- , Version(..)- , ConnectionEnd(..)- , CipherType(..)- , CipherData(..)- , ExtensionID- , ExtensionRaw- , CertificateType(..)- , HashAlgorithm(..)- , SignatureAlgorithm(..)- , HashAndSignatureAlgorithm- , DigitallySigned(..)- , ProtocolType(..)- , TLSError(..)- , TLSException(..)- , DistinguishedName- , ServerDHParams(..)- , ServerRSAParams(..)- , ServerKeyXchgAlgorithmData(..)- , ClientKeyXchgAlgorithmData(..)- , Packet(..)- , Header(..)- , ServerRandom(..)- , ClientRandom(..)- , serverRandom- , clientRandom- , FinishedData- , SessionID- , Session(..)- , SessionData(..)- , AlertLevel(..)- , AlertDescription(..)- , HandshakeType(..)- , Handshake(..)- , numericalVer- , verOfNum- , TypeValuable, valOfType, valToType- , packetType- , typeOfHandshake- ) where -import Data.ByteString (ByteString)-import qualified Data.ByteString as B (length)-import Data.Word-import Data.X509 (CertificateChain, DistinguishedName)-import Data.Typeable-import Control.Monad.Error (Error(..))-import Control.Exception (Exception(..))-import Network.TLS.Types-import Network.TLS.Crypto.DH+-- | The Struct module contains all definitions and values of the TLS+-- protocol.+module Network.TLS.Struct (+ Version (..),+ CipherData (..),+ CertificateType (+ CertificateType,+ CertificateType_RSA_Sign,+ CertificateType_DSA_Sign,+ CertificateType_ECDSA_Sign,+ CertificateType_Ed25519_Sign,+ CertificateType_Ed448_Sign+ ),+ fromCertificateType,+ lastSupportedCertificateType,+ DigitallySigned (..),+ Signature,+ ProtocolType (+ ..,+ ProtocolType_ChangeCipherSpec,+ ProtocolType_Alert,+ ProtocolType_Handshake,+ ProtocolType_AppData+ ),+ TLSError (..),+ TLSException (..),+ DistinguishedName,+ ServerDHParams (..),+ serverDHParamsToParams,+ serverDHParamsToPublic,+ serverDHParamsFrom,+ ServerECDHParams (..),+ ServerRSAParams (..),+ ServerKeyXchgAlgorithmData (..),+ ClientKeyXchgAlgorithmData (..),+ Packet (..),+ Header (..),+ ServerRandom (..),+ ClientRandom (..),+ FinishedData,+ VerifyData (..),+ SessionID,+ Session (..),+ SessionData (..),+ AlertLevel (+ ..,+ AlertLevel_Warning,+ AlertLevel_Fatal+ ),+ AlertDescription (+ ..,+ CloseNotify,+ UnexpectedMessage,+ BadRecordMac,+ DecryptionFailed,+ RecordOverflow,+ DecompressionFailure,+ HandshakeFailure,+ BadCertificate,+ UnsupportedCertificate,+ CertificateRevoked,+ CertificateExpired,+ CertificateUnknown,+ IllegalParameter,+ UnknownCa,+ AccessDenied,+ DecodeError,+ DecryptError,+ ExportRestriction,+ ProtocolVersion,+ InsufficientSecurity,+ InternalError,+ InappropriateFallback,+ UserCanceled,+ NoRenegotiation,+ MissingExtension,+ UnsupportedExtension,+ CertificateUnobtainable,+ UnrecognizedName,+ BadCertificateStatusResponse,+ BadCertificateHashValue,+ UnknownPskIdentity,+ CertificateRequired,+ NoApplicationProtocol+ ),+ HandshakeType (+ ..,+ HandshakeType_HelloRequest,+ HandshakeType_ClientHello,+ HandshakeType_ServerHello,+ HandshakeType_NewSessionTicket,+ HandshakeType_EndOfEarlyData,+ HandshakeType_EncryptedExtensions,+ HandshakeType_Certificate,+ HandshakeType_ServerKeyXchg,+ HandshakeType_CertRequest,+ HandshakeType_ServerHelloDone,+ HandshakeType_CertVerify,+ HandshakeType_ClientKeyXchg,+ HandshakeType_Finished,+ HandshakeType_KeyUpdate,+ HandshakeType_CompressedCertificate+ ),+ CertificateChain_ (..),+ emptyCertificateChain_,+ Handshake (..),+ packetType,+ typeOfHandshake,+ module Network.TLS.HashAndSignature,+ ExtensionRaw (..),+ ExtensionID (..),+ showCertificateChain,+ isHelloRetryRequest,+ hrrRandom,+ ClientHello (..),+ ServerHello (..),+ HandshakeR,+) where -type Bytes = ByteString+import Data.X509 (+ CertificateChain (..),+ DistinguishedName,+ certSubjectDN,+ getCharacterStringRawData,+ getDistinguishedElements,+ getSigned,+ signedObject,+ ) -data ConnectionEnd = ConnectionServer | ConnectionClient-data CipherType = CipherStream | CipherBlock | CipherAEAD+import Network.TLS.Crypto+import Network.TLS.Error+import {-# SOURCE #-} Network.TLS.Extension+import Network.TLS.HashAndSignature+import Network.TLS.Imports+import Network.TLS.Types +----------------------------------------------------------------+ data CipherData = CipherData- { cipherDataContent :: Bytes- , cipherDataMAC :: Maybe Bytes- , cipherDataPadding :: Maybe Bytes- } deriving (Show,Eq)+ { cipherDataContent :: ByteString+ , cipherDataMAC :: Maybe ByteString+ , cipherDataPadding :: Maybe (ByteString, Int)+ }+ deriving (Show, Eq) -data CertificateType =- CertificateType_RSA_Sign -- TLS10- | CertificateType_DSS_Sign -- TLS10- | CertificateType_RSA_Fixed_DH -- TLS10- | CertificateType_DSS_Fixed_DH -- TLS10- | CertificateType_RSA_Ephemeral_DH -- TLS12- | CertificateType_DSS_Ephemeral_DH -- TLS12- | CertificateType_fortezza_dms -- TLS12- | CertificateType_Unknown Word8- deriving (Show,Eq)+---------------------------------------------------------------- -data HashAlgorithm =- HashNone- | HashMD5- | HashSHA1- | HashSHA224- | HashSHA256- | HashSHA384- | HashSHA512- | HashOther Word8- deriving (Show,Eq)+-- | Some of the IANA registered code points for 'CertificateType' are not+-- currently supported by the library. Nor should they be, they're are either+-- unwise, obsolete or both. There's no point in conveying these to the user+-- in the client certificate request callback. The request callback will be+-- filtered to exclude unsupported values. If the user cannot find a certificate+-- for a supported code point, we'll go ahead without a client certificate and+-- hope for the best, unless the user's callback decides to throw an exception.+newtype CertificateType = CertificateType {fromCertificateType :: Word8}+ deriving (Eq, Ord) -data SignatureAlgorithm =- SignatureAnonymous- | SignatureRSA- | SignatureDSS- | SignatureECDSA- | SignatureOther Word8- deriving (Show,Eq)+{- FOURMOLU_DISABLE -}+-- | TLS10 and up, RFC5246+pattern CertificateType_RSA_Sign :: CertificateType+pattern CertificateType_RSA_Sign = CertificateType 1+-- | TLS10 and up, RFC5246+pattern CertificateType_DSA_Sign :: CertificateType+pattern CertificateType_DSA_Sign = CertificateType 2+-- | TLS10 and up, RFC8422+pattern CertificateType_ECDSA_Sign :: CertificateType+pattern CertificateType_ECDSA_Sign = CertificateType 64+-- \| There are no code points that map to the below synthetic types, these+-- are inferred indirectly from the @signature_algorithms@ extension of the+-- TLS 1.3 @CertificateRequest@ message. the value assignments are there+-- only to avoid partial function warnings.+pattern CertificateType_Ed25519_Sign :: CertificateType+pattern CertificateType_Ed25519_Sign = CertificateType 254 -- fixme: dummy value+pattern CertificateType_Ed448_Sign :: CertificateType+pattern CertificateType_Ed448_Sign = CertificateType 255 -- fixme: dummy value -type HashAndSignatureAlgorithm = (HashAlgorithm, SignatureAlgorithm)+instance Show CertificateType where+ show CertificateType_RSA_Sign = "rsa_sign"+ show CertificateType_DSA_Sign = "dss_sign"+ show CertificateType_ECDSA_Sign = "ecdsa_sign"+ show CertificateType_Ed25519_Sign = "ed25519_sign"+ show CertificateType_Ed448_Sign = "ed448_sign"+ show (CertificateType x) = "CertificateType " ++ show x+{- FOURMOLU_ENABLE -} -type Signature = Bytes+-- | Last supported certificate type, no 'CertificateType that+-- compares greater than this one (based on the 'Ord' instance,+-- not on the wire code point) will be reported to the application+-- via the client certificate request callback.+lastSupportedCertificateType :: CertificateType+lastSupportedCertificateType = CertificateType_ECDSA_Sign -data DigitallySigned = DigitallySigned (Maybe HashAndSignatureAlgorithm) Signature- deriving (Show,Eq)+------------------------------------------------------------ -data ProtocolType =- ProtocolType_ChangeCipherSpec- | ProtocolType_Alert- | ProtocolType_Handshake- | ProtocolType_AppData- | ProtocolType_DeprecatedHandshake- deriving (Eq, Show)+type Signature = ByteString --- | TLSError that might be returned through the TLS stack-data TLSError =- Error_Misc String -- ^ mainly for instance of Error- | Error_Protocol (String, Bool, AlertDescription)- | Error_Certificate String- | Error_HandshakePolicy String -- ^ handshake policy failed.- | Error_EOF- | Error_Packet String- | Error_Packet_unexpected String String- | Error_Packet_Parsing String- deriving (Eq, Show, Typeable)+data DigitallySigned = DigitallySigned HashAndSignatureAlgorithm Signature+ deriving (Eq) -instance Error TLSError where- noMsg = Error_Misc ""- strMsg = Error_Misc+instance Show DigitallySigned where+ show (DigitallySigned hs _sig) = "DigitallySigned " ++ show hs ++ " \"...\"" -instance Exception TLSError+---------------------------------------------------------------- --- | TLS Exceptions related to bad user usage or--- asynchronous errors-data TLSException =- Terminated Bool String TLSError -- ^ Early termination exception with the reason- -- and the error associated- | HandshakeFailed TLSError -- ^ Handshake failed for the reason attached- | ConnectionNotEstablished -- ^ Usage error when the connection has not been established- -- and the user is trying to send or receive data- deriving (Show,Eq,Typeable)+newtype ProtocolType = ProtocolType {fromProtocolType :: Word8} deriving (Eq) -instance Exception TLSException+{- FOURMOLU_DISABLE -}+pattern ProtocolType_ChangeCipherSpec :: ProtocolType+pattern ProtocolType_ChangeCipherSpec = ProtocolType 20 -data Packet =- Handshake [Handshake]+pattern ProtocolType_Alert :: ProtocolType+pattern ProtocolType_Alert = ProtocolType 21++pattern ProtocolType_Handshake :: ProtocolType+pattern ProtocolType_Handshake = ProtocolType 22++pattern ProtocolType_AppData :: ProtocolType+pattern ProtocolType_AppData = ProtocolType 23++instance Show ProtocolType where+ show ProtocolType_ChangeCipherSpec = "ChangeCipherSpec"+ show ProtocolType_Alert = "Alert"+ show ProtocolType_Handshake = "Handshake"+ show ProtocolType_AppData = "AppData"+ show (ProtocolType x) = "ProtocolType " ++ show x+{- FOURMOLU_ENABLE -}++----------------------------------------------------------------++data Packet+ = Handshake [Handshake] [WireBytes] | Alert [(AlertLevel, AlertDescription)] | ChangeCipherSpec | AppData ByteString- deriving (Show,Eq)+ deriving (Eq) -data Header = Header ProtocolType Version Word16 deriving (Show,Eq)+instance Show Packet where+ show (Handshake hs _) = "Handshake " ++ show hs+ show (Alert as) = "Alert " ++ show as+ show ChangeCipherSpec = "ChangeCipherSpec"+ show (AppData bs) = "AppData " ++ showBytesHex bs -newtype ServerRandom = ServerRandom Bytes deriving (Show, Eq)-newtype ClientRandom = ClientRandom Bytes deriving (Show, Eq)-newtype Session = Session (Maybe SessionID) deriving (Show, Eq)+data Header = Header ProtocolType Version Word16 deriving (Show, Eq) -type FinishedData = Bytes-type ExtensionID = Word16-type ExtensionRaw = (ExtensionID, Bytes)+newtype ServerRandom = ServerRandom {unServerRandom :: ByteString}+ deriving (Eq)+instance Show ServerRandom where+ show sr@(ServerRandom bs)+ | isHelloRetryRequest sr = "HelloRetryReqest"+ | otherwise = "ServerRandom " ++ showBytesHex bs -constrRandom32 :: (Bytes -> a) -> Bytes -> Maybe a-constrRandom32 constr l = if B.length l == 32 then Just (constr l) else Nothing+hrrRandom :: ServerRandom+hrrRandom =+ ServerRandom+ "\xCF\x21\xAD\x74\xE5\x9A\x61\x11\xBE\x1D\x8C\x02\x1E\x65\xB8\x91\xC2\xA2\x11\x16\x7A\xBB\x8C\x5E\x07\x9E\x09\xE2\xC8\xA8\x33\x9C" -serverRandom :: Bytes -> Maybe ServerRandom-serverRandom l = constrRandom32 ServerRandom l+isHelloRetryRequest :: ServerRandom -> Bool+isHelloRetryRequest = (== hrrRandom) -clientRandom :: Bytes -> Maybe ClientRandom-clientRandom l = constrRandom32 ClientRandom l+newtype ClientRandom = ClientRandom {unClientRandom :: ByteString}+ deriving (Eq) -data AlertLevel =- AlertLevel_Warning- | AlertLevel_Fatal- deriving (Show,Eq)+instance Show ClientRandom where+ show (ClientRandom bs) = "ClientRandom " ++ showBytesHex bs -data AlertDescription =- CloseNotify- | UnexpectedMessage- | BadRecordMac- | DecryptionFailed -- ^ deprecated alert, should never be sent by compliant implementation- | RecordOverflow- | DecompressionFailure- | HandshakeFailure- | BadCertificate- | UnsupportedCertificate- | CertificateRevoked- | CertificateExpired- | CertificateUnknown- | IllegalParameter- | UnknownCa- | AccessDenied- | DecodeError- | DecryptError- | ExportRestriction- | ProtocolVersion- | InsufficientSecurity- | InternalError- | UserCanceled- | NoRenegotiation- | UnsupportedExtension- | CertificateUnobtainable- | UnrecognizedName- | BadCertificateStatusResponse- | BadCertificateHashValue- deriving (Show,Eq)+newtype Session = Session (Maybe SessionID) deriving (Eq)+instance Show Session where+ show (Session Nothing) = "Session \"\""+ show (Session (Just bs)) = "Session " ++ showBytesHex bs -data HandshakeType =- HandshakeType_HelloRequest- | HandshakeType_ClientHello- | HandshakeType_ServerHello- | HandshakeType_Certificate- | HandshakeType_ServerKeyXchg- | HandshakeType_CertRequest- | HandshakeType_ServerHelloDone- | HandshakeType_CertVerify- | HandshakeType_ClientKeyXchg- | HandshakeType_Finished- | HandshakeType_NPN -- Next Protocol Negotiation extension- deriving (Show,Eq)+{-# DEPRECATED FinishedData "use VerifyData" #-}+type FinishedData = ByteString -data ServerDHParams = ServerDHParams DHParams {- (p,g) -} DHPublic {- y -}- deriving (Show,Eq)+newtype VerifyData = VerifyData ByteString deriving (Eq)+instance Show VerifyData where+ show (VerifyData bs) = showBytesHex bs -data ServerRSAParams = ServerRSAParams- { rsa_modulus :: Integer- , rsa_exponent :: Integer- } deriving (Show,Eq)+---------------------------------------------------------------- -data ServerKeyXchgAlgorithmData =- SKX_DH_Anon ServerDHParams- | SKX_DHE_DSS ServerDHParams DigitallySigned- | SKX_DHE_RSA ServerDHParams DigitallySigned- | SKX_RSA (Maybe ServerRSAParams)- | SKX_DH_DSS (Maybe ServerRSAParams)- | SKX_DH_RSA (Maybe ServerRSAParams)- | SKX_Unparsed Bytes -- if we parse the server key xchg before knowing the actual cipher, we end up with this structure.- | SKX_Unknown Bytes- deriving (Show,Eq)+newtype HandshakeType = HandshakeType {fromHandshakeType :: Word8}+ deriving (Eq) -data ClientKeyXchgAlgorithmData =- CKX_RSA Bytes- | CKX_DH DHPublic- deriving (Show,Eq)+{- FOURMOLU_DISABLE -}+pattern HandshakeType_HelloRequest :: HandshakeType+pattern HandshakeType_HelloRequest = HandshakeType 0+pattern HandshakeType_ClientHello :: HandshakeType+pattern HandshakeType_ClientHello = HandshakeType 1+pattern HandshakeType_ServerHello :: HandshakeType+pattern HandshakeType_ServerHello = HandshakeType 2+pattern HandshakeType_NewSessionTicket :: HandshakeType+pattern HandshakeType_NewSessionTicket = HandshakeType 4+pattern HandshakeType_EndOfEarlyData :: HandshakeType+pattern HandshakeType_EndOfEarlyData = HandshakeType 5+pattern HandshakeType_EncryptedExtensions :: HandshakeType+pattern HandshakeType_EncryptedExtensions = HandshakeType 8+pattern HandshakeType_Certificate :: HandshakeType+pattern HandshakeType_Certificate = HandshakeType 11+pattern HandshakeType_ServerKeyXchg :: HandshakeType+pattern HandshakeType_ServerKeyXchg = HandshakeType 12+pattern HandshakeType_CertRequest :: HandshakeType+pattern HandshakeType_CertRequest = HandshakeType 13+pattern HandshakeType_ServerHelloDone :: HandshakeType+pattern HandshakeType_ServerHelloDone = HandshakeType 14+pattern HandshakeType_CertVerify :: HandshakeType+pattern HandshakeType_CertVerify = HandshakeType 15+pattern HandshakeType_ClientKeyXchg :: HandshakeType+pattern HandshakeType_ClientKeyXchg = HandshakeType 16+pattern HandshakeType_Finished :: HandshakeType+pattern HandshakeType_Finished = HandshakeType 20+pattern HandshakeType_KeyUpdate :: HandshakeType+pattern HandshakeType_KeyUpdate = HandshakeType 24+pattern HandshakeType_CompressedCertificate :: HandshakeType+pattern HandshakeType_CompressedCertificate = HandshakeType 25 -type DeprecatedRecord = ByteString+instance Show HandshakeType where+ show HandshakeType_HelloRequest = "HelloRequest"+ show HandshakeType_ClientHello = "ClientHello"+ show HandshakeType_ServerHello = "ServerHello"+ show HandshakeType_NewSessionTicket = "NewSessionTicket"+ show HandshakeType_EndOfEarlyData = "EndOfEarlyData"+ show HandshakeType_EncryptedExtensions = "EncryptedExtensions"+ show HandshakeType_Certificate = "Certificate"+ show HandshakeType_ServerKeyXchg = "ServerKeyXchg"+ show HandshakeType_CertRequest = "CertRequest"+ show HandshakeType_ServerHelloDone = "ServerHelloDone"+ show HandshakeType_CertVerify = "CertVerify"+ show HandshakeType_ClientKeyXchg = "ClientKeyXchg"+ show HandshakeType_Finished = "Finished"+ show HandshakeType_KeyUpdate = "KeyUpdate"+ show HandshakeType_CompressedCertificate = "CompressedCertificate"+ show (HandshakeType x) = "HandshakeType " ++ show x+{- FOURMOLU_ENABLE -} -data Handshake =- ClientHello !Version !ClientRandom !Session ![CipherID] ![CompressionID] [ExtensionRaw] (Maybe DeprecatedRecord)- | ServerHello !Version !ServerRandom !Session !CipherID !CompressionID [ExtensionRaw]- | Certificates CertificateChain- | HelloRequest- | ServerHelloDone- | ClientKeyXchg ClientKeyXchgAlgorithmData- | ServerKeyXchg ServerKeyXchgAlgorithmData- | CertRequest [CertificateType] (Maybe [HashAndSignatureAlgorithm]) [DistinguishedName]- | CertVerify DigitallySigned- | Finished FinishedData- | HsNextProtocolNegotiation Bytes -- NPN extension- deriving (Show,Eq)+---------------------------------------------------------------- -packetType :: Packet -> ProtocolType-packetType (Handshake _) = ProtocolType_Handshake-packetType (Alert _) = ProtocolType_Alert-packetType ChangeCipherSpec = ProtocolType_ChangeCipherSpec-packetType (AppData _) = ProtocolType_AppData+data ServerDHParams = ServerDHParams+ { serverDHParams_p :: BigNum+ , serverDHParams_g :: BigNum+ , serverDHParams_y :: BigNum+ }+ deriving (Show, Eq) -typeOfHandshake :: Handshake -> HandshakeType-typeOfHandshake (ClientHello {}) = HandshakeType_ClientHello-typeOfHandshake (ServerHello {}) = HandshakeType_ServerHello-typeOfHandshake (Certificates {}) = HandshakeType_Certificate-typeOfHandshake HelloRequest = HandshakeType_HelloRequest-typeOfHandshake (ServerHelloDone) = HandshakeType_ServerHelloDone-typeOfHandshake (ClientKeyXchg {}) = HandshakeType_ClientKeyXchg-typeOfHandshake (ServerKeyXchg {}) = HandshakeType_ServerKeyXchg-typeOfHandshake (CertRequest {}) = HandshakeType_CertRequest-typeOfHandshake (CertVerify {}) = HandshakeType_CertVerify-typeOfHandshake (Finished {}) = HandshakeType_Finished-typeOfHandshake (HsNextProtocolNegotiation {}) = HandshakeType_NPN+serverDHParamsFrom :: DHParams -> DHPublic -> ServerDHParams+serverDHParamsFrom params dhPub =+ ServerDHParams+ (bigNumFromInteger $ dhParamsGetP params)+ (bigNumFromInteger $ dhParamsGetG params)+ (bigNumFromInteger $ dhUnwrapPublic dhPub) -numericalVer :: Version -> (Word8, Word8)-numericalVer SSL2 = (2, 0)-numericalVer SSL3 = (3, 0)-numericalVer TLS10 = (3, 1)-numericalVer TLS11 = (3, 2)-numericalVer TLS12 = (3, 3)+serverDHParamsToParams :: ServerDHParams -> DHParams+serverDHParamsToParams serverParams =+ dhParams+ (bigNumToInteger $ serverDHParams_p serverParams)+ (bigNumToInteger $ serverDHParams_g serverParams) -verOfNum :: (Word8, Word8) -> Maybe Version-verOfNum (2, 0) = Just SSL2-verOfNum (3, 0) = Just SSL3-verOfNum (3, 1) = Just TLS10-verOfNum (3, 2) = Just TLS11-verOfNum (3, 3) = Just TLS12-verOfNum _ = Nothing+serverDHParamsToPublic :: ServerDHParams -> DHPublic+serverDHParamsToPublic serverParams =+ dhPublic (bigNumToInteger $ serverDHParams_y serverParams) -class TypeValuable a where- valOfType :: a -> Word8- valToType :: Word8 -> Maybe a+---------------------------------------------------------------- -instance TypeValuable ConnectionEnd where- valOfType ConnectionServer = 0- valOfType ConnectionClient = 1+data ServerECDHParams = ServerECDHParams Group GroupPublicA+ deriving (Show, Eq) - valToType 0 = Just ConnectionServer- valToType 1 = Just ConnectionClient- valToType _ = Nothing+---------------------------------------------------------------- -instance TypeValuable CipherType where- valOfType CipherStream = 0- valOfType CipherBlock = 1- valOfType CipherAEAD = 2+data ServerRSAParams = ServerRSAParams+ { rsa_modulus :: Integer+ , rsa_exponent :: Integer+ }+ deriving (Show, Eq) - valToType 0 = Just CipherStream- valToType 1 = Just CipherBlock- valToType 2 = Just CipherAEAD- valToType _ = Nothing+---------------------------------------------------------------- -instance TypeValuable ProtocolType where- valOfType ProtocolType_ChangeCipherSpec = 20- valOfType ProtocolType_Alert = 21- valOfType ProtocolType_Handshake = 22- valOfType ProtocolType_AppData = 23- valOfType ProtocolType_DeprecatedHandshake = 128 -- unused+data ServerDSAParams = ServerDSAParams deriving (Show, Eq) - valToType 20 = Just ProtocolType_ChangeCipherSpec- valToType 21 = Just ProtocolType_Alert- valToType 22 = Just ProtocolType_Handshake- valToType 23 = Just ProtocolType_AppData- valToType _ = Nothing+---------------------------------------------------------------- -instance TypeValuable HandshakeType where- valOfType HandshakeType_HelloRequest = 0- valOfType HandshakeType_ClientHello = 1- valOfType HandshakeType_ServerHello = 2- valOfType HandshakeType_Certificate = 11- valOfType HandshakeType_ServerKeyXchg = 12- valOfType HandshakeType_CertRequest = 13- valOfType HandshakeType_ServerHelloDone = 14- valOfType HandshakeType_CertVerify = 15- valOfType HandshakeType_ClientKeyXchg = 16- valOfType HandshakeType_Finished = 20- valOfType HandshakeType_NPN = 67+data ServerKeyXchgAlgorithmData+ = SKX_DH_Anon ServerDHParams+ | SKX_DHE_DSA ServerDHParams DigitallySigned+ | SKX_DHE_RSA ServerDHParams DigitallySigned+ | SKX_ECDHE_RSA ServerECDHParams DigitallySigned+ | SKX_ECDHE_ECDSA ServerECDHParams DigitallySigned+ | SKX_RSA (Maybe ServerRSAParams)+ | SKX_DH_DSA (Maybe ServerDSAParams)+ | SKX_DH_RSA (Maybe ServerRSAParams)+ | SKX_Unparsed ByteString -- if we parse the server key xchg before knowing the actual cipher, we end up with this structure.+ | SKX_Unknown ByteString+ deriving (Eq) - valToType 0 = Just HandshakeType_HelloRequest- valToType 1 = Just HandshakeType_ClientHello- valToType 2 = Just HandshakeType_ServerHello- valToType 11 = Just HandshakeType_Certificate- valToType 12 = Just HandshakeType_ServerKeyXchg- valToType 13 = Just HandshakeType_CertRequest- valToType 14 = Just HandshakeType_ServerHelloDone- valToType 15 = Just HandshakeType_CertVerify- valToType 16 = Just HandshakeType_ClientKeyXchg- valToType 20 = Just HandshakeType_Finished- valToType 67 = Just HandshakeType_NPN- valToType _ = Nothing+{- FOURMOLU_DISABLE -}+instance Show ServerKeyXchgAlgorithmData where+ show (SKX_DH_Anon _) = "SKX_DH_Anon"+ show (SKX_DHE_DSA _ _) = "SKX_DHE_DSA"+ show (SKX_DHE_RSA _ _) = "SKX_DHE_RSA"+ show (SKX_ECDHE_RSA _ _) = "SKX_ECDHE_RSA"+ show (SKX_ECDHE_ECDSA _ _) = "SKX_ECDHE_ECDSA"+ show (SKX_RSA _) = "SKX_RSA"+ show (SKX_DH_DSA _) = "SKX_DH_DSA"+ show (SKX_DH_RSA _) = "SKX_DH_RSA"+ show (SKX_Unparsed _) = "SKX_Unparsed"+ show (SKX_Unknown _) = "SKX_Unknown"+{- FOURMOLU_ENABLE -} -instance TypeValuable AlertLevel where- valOfType AlertLevel_Warning = 1- valOfType AlertLevel_Fatal = 2+---------------------------------------------------------------- - valToType 1 = Just AlertLevel_Warning- valToType 2 = Just AlertLevel_Fatal- valToType _ = Nothing+data ClientKeyXchgAlgorithmData+ = CKX_RSA ByteString+ | CKX_DH DHPublic+ | CKX_ECDH ByteString+ deriving (Eq) -instance TypeValuable AlertDescription where- valOfType CloseNotify = 0- valOfType UnexpectedMessage = 10- valOfType BadRecordMac = 20- valOfType DecryptionFailed = 21- valOfType RecordOverflow = 22- valOfType DecompressionFailure = 30- valOfType HandshakeFailure = 40- valOfType BadCertificate = 42- valOfType UnsupportedCertificate = 43- valOfType CertificateRevoked = 44- valOfType CertificateExpired = 45- valOfType CertificateUnknown = 46- valOfType IllegalParameter = 47- valOfType UnknownCa = 48- valOfType AccessDenied = 49- valOfType DecodeError = 50- valOfType DecryptError = 51- valOfType ExportRestriction = 60- valOfType ProtocolVersion = 70- valOfType InsufficientSecurity = 71- valOfType InternalError = 80- valOfType UserCanceled = 90- valOfType NoRenegotiation = 100- valOfType UnsupportedExtension = 110- valOfType CertificateUnobtainable = 111- valOfType UnrecognizedName = 112- valOfType BadCertificateStatusResponse = 113- valOfType BadCertificateHashValue = 114+instance Show ClientKeyXchgAlgorithmData where+ show (CKX_RSA _bs) = "CKX_RSA \"...\""+ show (CKX_DH pub) = "CKX_DH " ++ show pub+ show (CKX_ECDH _bs) = "CKX_ECDH \"...\"" - valToType 0 = Just CloseNotify- valToType 10 = Just UnexpectedMessage- valToType 20 = Just BadRecordMac- valToType 21 = Just DecryptionFailed- valToType 22 = Just RecordOverflow- valToType 30 = Just DecompressionFailure- valToType 40 = Just HandshakeFailure- valToType 42 = Just BadCertificate- valToType 43 = Just UnsupportedCertificate- valToType 44 = Just CertificateRevoked- valToType 45 = Just CertificateExpired- valToType 46 = Just CertificateUnknown- valToType 47 = Just IllegalParameter- valToType 48 = Just UnknownCa- valToType 49 = Just AccessDenied- valToType 50 = Just DecodeError- valToType 51 = Just DecryptError- valToType 60 = Just ExportRestriction- valToType 70 = Just ProtocolVersion- valToType 71 = Just InsufficientSecurity- valToType 80 = Just InternalError- valToType 90 = Just UserCanceled- valToType 100 = Just NoRenegotiation- valToType 110 = Just UnsupportedExtension- valToType 111 = Just CertificateUnobtainable- valToType 112 = Just UnrecognizedName- valToType 113 = Just BadCertificateStatusResponse- valToType 114 = Just BadCertificateHashValue- valToType _ = Nothing+---------------------------------------------------------------- -instance TypeValuable CertificateType where- valOfType CertificateType_RSA_Sign = 1- valOfType CertificateType_DSS_Sign = 2- valOfType CertificateType_RSA_Fixed_DH = 3- valOfType CertificateType_DSS_Fixed_DH = 4- valOfType CertificateType_RSA_Ephemeral_DH = 5- valOfType CertificateType_DSS_Ephemeral_DH = 6- valOfType CertificateType_fortezza_dms = 20- valOfType (CertificateType_Unknown i) = i+newtype CertificateChain_ = CertificateChain_ CertificateChain deriving (Eq)+instance Show CertificateChain_ where+ show (CertificateChain_ cc) = showCertificateChain cc - valToType 1 = Just CertificateType_RSA_Sign- valToType 2 = Just CertificateType_DSS_Sign- valToType 3 = Just CertificateType_RSA_Fixed_DH- valToType 4 = Just CertificateType_DSS_Fixed_DH- valToType 5 = Just CertificateType_RSA_Ephemeral_DH- valToType 6 = Just CertificateType_DSS_Ephemeral_DH- valToType 20 = Just CertificateType_fortezza_dms- valToType i = Just (CertificateType_Unknown i)+emptyCertificateChain_ :: CertificateChain_+emptyCertificateChain_ = CertificateChain_ (CertificateChain []) -instance TypeValuable HashAlgorithm where- valOfType HashNone = 0- valOfType HashMD5 = 1- valOfType HashSHA1 = 2- valOfType HashSHA224 = 3- valOfType HashSHA256 = 4- valOfType HashSHA384 = 5- valOfType HashSHA512 = 6- valOfType (HashOther i) = i+showCertificateChain :: CertificateChain -> String+showCertificateChain (CertificateChain xs) = show $ map getName xs+ where+ getName =+ maybe "" getCharacterStringRawData+ . lookup [2, 5, 4, 3]+ . getDistinguishedElements+ . certSubjectDN+ . signedObject+ . getSigned - valToType 0 = Just HashNone- valToType 1 = Just HashMD5- valToType 2 = Just HashSHA1- valToType 3 = Just HashSHA224- valToType 4 = Just HashSHA256- valToType 5 = Just HashSHA384- valToType 6 = Just HashSHA512- valToType i = Just (HashOther i)+data ClientHello = CH+ { chVersion :: Version+ , chRandom :: ClientRandom+ , chSession :: Session+ , chCiphers :: [CipherId]+ , chComps :: [CompressionID]+ , chExtensions :: [ExtensionRaw]+ }+ deriving (Eq, Show) -instance TypeValuable SignatureAlgorithm where- valOfType SignatureAnonymous = 0- valOfType SignatureRSA = 1- valOfType SignatureDSS = 2- valOfType SignatureECDSA = 3- valOfType (SignatureOther i) = i+data ServerHello = SH+ { shVersion :: Version+ , shRandom :: ServerRandom+ , shSession :: Session+ , shCipher :: CipherId+ , shComp :: CompressionID+ , shExtensions :: [ExtensionRaw]+ }+ deriving (Eq, Show) - valToType 0 = Just SignatureAnonymous- valToType 1 = Just SignatureRSA- valToType 2 = Just SignatureDSS- valToType 3 = Just SignatureECDSA- valToType i = Just (SignatureOther i)+data Handshake+ = ClientHello ClientHello+ | ServerHello ServerHello+ | Certificate CertificateChain_+ | HelloRequest+ | ServerHelloDone+ | ClientKeyXchg ClientKeyXchgAlgorithmData+ | ServerKeyXchg ServerKeyXchgAlgorithmData+ | CertRequest+ [CertificateType]+ [HashAndSignatureAlgorithm]+ [DistinguishedName]+ | CertVerify DigitallySigned+ | Finished VerifyData+ | NewSessionTicket Second Ticket+ deriving (Show, Eq)++{- FOURMOLU_DISABLE -}+packetType :: Packet -> ProtocolType+packetType (Handshake _ _) = ProtocolType_Handshake+packetType (Alert _) = ProtocolType_Alert+packetType ChangeCipherSpec = ProtocolType_ChangeCipherSpec+packetType (AppData _) = ProtocolType_AppData++typeOfHandshake :: Handshake -> HandshakeType+typeOfHandshake ClientHello{} = HandshakeType_ClientHello+typeOfHandshake ServerHello{} = HandshakeType_ServerHello+typeOfHandshake Certificate{} = HandshakeType_Certificate+typeOfHandshake HelloRequest = HandshakeType_HelloRequest+typeOfHandshake ServerHelloDone = HandshakeType_ServerHelloDone+typeOfHandshake ClientKeyXchg{} = HandshakeType_ClientKeyXchg+typeOfHandshake ServerKeyXchg{} = HandshakeType_ServerKeyXchg+typeOfHandshake CertRequest{} = HandshakeType_CertRequest+typeOfHandshake CertVerify{} = HandshakeType_CertVerify+typeOfHandshake Finished{} = HandshakeType_Finished+typeOfHandshake NewSessionTicket{} = HandshakeType_NewSessionTicket+{- FOURMOLU_ENABLE -}++type HandshakeR = (Handshake, WireBytes)
+ Network/TLS/Struct13.hs view
@@ -0,0 +1,81 @@+module Network.TLS.Struct13 (+ Packet13 (..),+ Handshake13 (..),+ typeOfHandshake13,+ contentType,+ KeyUpdate (..),+ CertReqContext,+ isKeyUpdate13,+ TicketNonce (..),+ SessionIDorTicket_ (..),+ Handshake13R,+) where++import Network.TLS.Imports+import Network.TLS.Struct+import Network.TLS.Types++data Packet13+ = Handshake13 [Handshake13] [WireBytes]+ | Alert13 [(AlertLevel, AlertDescription)]+ | ChangeCipherSpec13+ | AppData13 ByteString+ deriving (Show, Eq)++data KeyUpdate+ = UpdateNotRequested+ | UpdateRequested+ deriving (Show, Eq)++newtype TicketNonce = TicketNonce ByteString deriving (Eq)++instance Show TicketNonce where+ show (TicketNonce bs) = showBytesHex bs++newtype SessionIDorTicket_ = SessionIDorTicket_ ByteString deriving (Eq)++instance Show SessionIDorTicket_ where+ show (SessionIDorTicket_ bs) = showBytesHex bs++-- fixme: convert Word32 to proper data type+data Handshake13+ = ServerHello13 ServerHello+ | NewSessionTicket13 Second Word32 TicketNonce SessionIDorTicket_ [ExtensionRaw]+ | EndOfEarlyData13+ | EncryptedExtensions13 [ExtensionRaw]+ | Certificate13 CertReqContext CertificateChain_ [[ExtensionRaw]]+ | CertRequest13 CertReqContext [ExtensionRaw]+ | CertVerify13 DigitallySigned+ | Finished13 VerifyData+ | KeyUpdate13 KeyUpdate+ | CompressedCertificate13 CertReqContext CertificateChain_ [[ExtensionRaw]]+ deriving (Show, Eq)++-- | Certificate request context for TLS 1.3.+type CertReqContext = ByteString++{- FOURMOLU_DISABLE -}+typeOfHandshake13 :: Handshake13 -> HandshakeType+typeOfHandshake13 ServerHello13{} = HandshakeType_ServerHello+typeOfHandshake13 NewSessionTicket13{} = HandshakeType_NewSessionTicket+typeOfHandshake13 EndOfEarlyData13{} = HandshakeType_EndOfEarlyData+typeOfHandshake13 EncryptedExtensions13{} = HandshakeType_EncryptedExtensions+typeOfHandshake13 Certificate13{} = HandshakeType_Certificate+typeOfHandshake13 CertRequest13{} = HandshakeType_CertRequest+typeOfHandshake13 CertVerify13{} = HandshakeType_CertVerify+typeOfHandshake13 Finished13{} = HandshakeType_Finished+typeOfHandshake13 KeyUpdate13{} = HandshakeType_KeyUpdate+typeOfHandshake13 CompressedCertificate13{} = HandshakeType_CompressedCertificate++contentType :: Packet13 -> ProtocolType+contentType ChangeCipherSpec13 = ProtocolType_ChangeCipherSpec+contentType Handshake13{} = ProtocolType_Handshake+contentType Alert13{} = ProtocolType_Alert+contentType AppData13{} = ProtocolType_AppData+{- FOURMOLU_ENABLE -}++isKeyUpdate13 :: Handshake13 -> Bool+isKeyUpdate13 (KeyUpdate13 _) = True+isKeyUpdate13 _ = False++type Handshake13R = (Handshake13, WireBytes)
Network/TLS/Types.hs view
@@ -1,53 +1,71 @@--- |--- Module : Network.TLS.Types--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown----module Network.TLS.Types- ( Version(..)- , SessionID- , SessionData(..)- , CipherID- , CompressionID- , Role(..)- , invertRole- , Direction(..)- ) where--import Data.ByteString (ByteString)-import Data.Word---- | Versions known to TLS------ SSL2 is just defined, but this version is and will not be supported.-data Version = SSL2 | SSL3 | TLS10 | TLS11 | TLS12 deriving (Show, Eq, Ord)---- | A session ID-type SessionID = ByteString+module Network.TLS.Types (+ module Network.TLS.Types.Cipher,+ module Network.TLS.Types.Secret,+ module Network.TLS.Types.Session,+ module Network.TLS.Types.Version,+ HostName,+ Role (..),+ invertRole,+ Direction (..),+ BigNum (..),+ bigNumToInteger,+ bigNumFromInteger,+ defaultRecordSizeLimit,+ TranscriptHash (..),+ WireBytes,+) where --- | Session data to resume-data SessionData = SessionData- { sessionVersion :: Version- , sessionCipher :: CipherID- , sessionSecret :: ByteString- } deriving (Show,Eq)+import Network.Socket (HostName) --- | Cipher identification-type CipherID = Word16+import Network.TLS.Imports+import Network.TLS.Types.Cipher+import Network.TLS.Types.Secret+import Network.TLS.Types.Session+import Network.TLS.Types.Version+import Network.TLS.Util.Serialization --- | Compression identification-type CompressionID = Word8+---------------------------------------------------------------- -- | Role data Role = ClientRole | ServerRole- deriving (Show,Eq)---- | Direction-data Direction = Tx | Rx- deriving (Show,Eq)+ deriving (Show, Eq) invertRole :: Role -> Role invertRole ClientRole = ServerRole invertRole ServerRole = ClientRole++----------------------------------------------------------------++-- | Direction+data Direction = Tx | Rx+ deriving (Show, Eq)++----------------------------------------------------------------++newtype BigNum = BigNum ByteString+ deriving (Show, Eq)++bigNumToInteger :: BigNum -> Integer+bigNumToInteger (BigNum b) = os2ip b++bigNumFromInteger :: Integer -> BigNum+bigNumFromInteger i = BigNum $ i2osp i++----------------------------------------------------------------++-- For plaintext+-- 2^14 for TLS 1.2+-- 2^14 + 1 for TLS 1.3+defaultRecordSizeLimit :: Int+defaultRecordSizeLimit = 16384++----------------------------------------------------------------++newtype TranscriptHash = TranscriptHash ByteString++instance Show TranscriptHash where+ show (TranscriptHash bs) = showBytesHex bs++----------------------------------------------------------------++type WireBytes = [ByteString]
+ Network/TLS/Types/Cipher.hs view
@@ -0,0 +1,130 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Network.TLS.Types.Cipher where++import Crypto.Cipher.Types (AuthTag)+import Data.ByteArray (ScrubbedBytes)+import Data.IORef+import GHC.Generics+import System.IO.Unsafe (unsafePerformIO)+import Text.Printf++import Network.TLS.Crypto (Hash (..))+import Network.TLS.Imports+import Network.TLS.Types.Version++----------------------------------------------------------------++type PlainText = ByteString+type CipherText = ByteString+type Secret = ScrubbedBytes+type Key = ScrubbedBytes+type IV = ByteString+type Nonce = ByteString -- aka IV+type AddDat = ByteString++----------------------------------------------------------------++-- | Cipher identification+type CipherID = Word16++newtype CipherId = CipherId {fromCipherId :: Word16}+ deriving (Eq, Ord, Enum, Num, Integral, Real, Read, Generic)++instance Show CipherId where+ show (CipherId 0x00FF) = "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"+ show (CipherId n) = case find eqID dict of+ Just c -> cipherName c+ Nothing -> printf "0x%04X" n+ where+ eqID c = cipherID c == n+ dict = unsafePerformIO $ readIORef globalCipherDict++-- "ciphersuite" is designed extensible.+-- So, it's not available from internal modules.+-- This is a compromise to gule "ciphersuite" to Show CipherID.++{-# NOINLINE globalCipherDict #-}+globalCipherDict :: IORef [Cipher]+globalCipherDict = unsafePerformIO $ newIORef []++----------------------------------------------------------------++-- | Cipher algorithm+data Cipher = Cipher+ { cipherID :: CipherID+ , cipherName :: String+ , cipherHash :: Hash+ , cipherBulk :: Bulk+ , cipherKeyExchange :: CipherKeyExchangeType+ , cipherMinVer :: Maybe Version+ , cipherPRFHash :: Maybe Hash+ }++instance Show Cipher where+ show c = cipherName c++instance Eq Cipher where+ (==) c1 c2 = cipherID c1 == cipherID c2++----------------------------------------------------------------++data CipherKeyExchangeType+ = CipherKeyExchange_RSA+ | CipherKeyExchange_DH_Anon+ | CipherKeyExchange_DHE_RSA+ | CipherKeyExchange_ECDHE_RSA+ | CipherKeyExchange_DHE_DSA+ | CipherKeyExchange_DH_DSA+ | CipherKeyExchange_DH_RSA+ | CipherKeyExchange_ECDH_ECDSA+ | CipherKeyExchange_ECDH_RSA+ | CipherKeyExchange_ECDHE_ECDSA+ | CipherKeyExchange_TLS13 -- not expressed in cipher suite+ deriving (Show, Eq)++----------------------------------------------------------------++data Bulk = Bulk+ { bulkName :: String+ , bulkKeySize :: Int+ , bulkIVSize :: Int+ , bulkExplicitIV :: Int -- Explicit size for IV for AEAD Cipher, 0 otherwise+ , bulkAuthTagLen :: Int -- Authentication tag length in bytes for AEAD Cipher, 0 otherwise+ , bulkBlockSize :: Int+ , bulkF :: BulkFunctions+ }++instance Show Bulk where+ show bulk = bulkName bulk+instance Eq Bulk where+ b1 == b2 =+ and+ [ bulkName b1 == bulkName b2+ , bulkKeySize b1 == bulkKeySize b2+ , bulkIVSize b1 == bulkIVSize b2+ , bulkBlockSize b1 == bulkBlockSize b2+ ]++----------------------------------------------------------------++data BulkFunctions+ = BulkBlockF (BulkDirection -> BulkKey -> BulkBlock)+ | BulkStreamF (BulkDirection -> BulkKey -> BulkStream)+ | BulkAeadF (BulkDirection -> BulkKey -> BulkAEAD)++data BulkDirection = BulkEncrypt | BulkDecrypt+ deriving (Show, Eq)++type BulkKey = Secret+type BulkIV = Nonce+type BulkNonce = Nonce+type BulkAdditionalData = ByteString++type BulkBlock = BulkIV -> ByteString -> (ByteString, BulkIV)++newtype BulkStream = BulkStream (ByteString -> (ByteString, BulkStream))++type BulkAEAD =+ BulkNonce -> ByteString -> BulkAdditionalData -> (ByteString, AuthTag)
+ Network/TLS/Types/Secret.hs view
@@ -0,0 +1,62 @@+module Network.TLS.Types.Secret where++import Data.ByteArray (convert)+import Network.TLS.Imports+import Network.TLS.Types.Cipher++-- | Phantom type indicating early traffic secret.+data EarlySecret++-- | Phantom type indicating handshake traffic secrets.+data HandshakeSecret++-- | Phantom type indicating application traffic secrets.+data ApplicationSecret++data ResumptionSecret++newtype BaseSecret a = BaseSecret Secret++instance Show (BaseSecret a) where+ show (BaseSecret bs) = showBytesHex $ convert bs++newtype AnyTrafficSecret a = AnyTrafficSecret Secret++instance Show (AnyTrafficSecret a) where+ show (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++-- | 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++data SecretTriple a = SecretTriple+ { triBase :: BaseSecret a+ , triClient :: ClientTrafficSecret a+ , triServer :: ServerTrafficSecret a+ }+ deriving (Show)++data SecretPair a = SecretPair+ { pairBase :: BaseSecret a+ , pairClient :: ClientTrafficSecret a+ }+ deriving (Show)++-- | Hold both client and server traffic secrets at the same step.+type TrafficSecrets a = (ClientTrafficSecret a, ServerTrafficSecret a)++-- Main secret for TLS 1.2 or earlier.+newtype MainSecret = MainSecret Secret++instance Show MainSecret where+ show (MainSecret bs) = showBytesHex $ convert bs
+ Network/TLS/Types/Session.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE DeriveGeneric #-}++module Network.TLS.Types.Session where++import Codec.Serialise+import qualified Data.ByteString as B+import GHC.Generics+import Network.Socket (HostName)++import Network.TLS.Crypto (Group, Hash (..), hash)+import Network.TLS.Imports+import Network.TLS.Types.Cipher+import Network.TLS.Types.Version++-- | A session ID+type SessionID = ByteString++-- | Identity+type SessionIDorTicket = ByteString++-- | Encrypted session ticket (encrypt(encode 'SessionData')).+type Ticket = ByteString++isTicket :: SessionIDorTicket -> Bool+isTicket x+ | B.length x > 32 = True+ | otherwise = False++toSessionID :: Ticket -> SessionID+toSessionID = hash SHA256++-- | Compression identification+type CompressionID = Word8++-- | Session data to resume+data SessionData = SessionData+ { sessionVersion :: Version+ , sessionCipher :: CipherID+ , sessionCompression :: CompressionID+ , sessionClientSNI :: Maybe HostName+ , -- ScrubbedBytes is not an instance of Generic, sigh.+ sessionSecret :: ByteString+ , sessionGroup :: Maybe Group+ , sessionTicketInfo :: Maybe TLS13TicketInfo+ , sessionALPN :: Maybe ByteString+ , sessionMaxEarlyDataSize :: Int+ , sessionFlags :: [SessionFlag]+ } -- sessionFromTicket :: Bool+ deriving (Show, Eq, Generic)++is0RTTPossible :: SessionData -> Bool+is0RTTPossible sd = sessionMaxEarlyDataSize sd > 0++-- | Some session flags+data SessionFlag+ = -- | Session created with Extended Main Secret+ SessionEMS+ deriving (Show, Eq, Enum, Generic)++type Second = Word32+type Millisecond = Word64++data TLS13TicketInfo = TLS13TicketInfo+ { lifetime :: Second -- NewSessionTicket.ticket_lifetime in seconds+ , ageAdd :: Second -- NewSessionTicket.ticket_age_add+ , txrxTime :: Millisecond -- serverSendTime or clientReceiveTime+ , estimatedRTT :: Maybe Millisecond+ }+ deriving (Show, Eq, Generic)++instance Serialise TLS13TicketInfo+instance Serialise SessionFlag+instance Serialise SessionData
+ Network/TLS/Types/Version.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE PatternSynonyms #-}++module Network.TLS.Types.Version (+ Version (Version, SSL2, SSL3, TLS10, TLS11, TLS12, TLS13),+) where++import Codec.Serialise+import GHC.Generics++import Network.TLS.Imports++-- | Versions known to TLS+newtype Version = Version Word16 deriving (Eq, Ord, Generic)++{- FOURMOLU_DISABLE -}+pattern SSL2 :: Version+pattern SSL2 = Version 0x0002+pattern SSL3 :: Version+pattern SSL3 = Version 0x0300+pattern TLS10 :: Version+pattern TLS10 = Version 0x0301+pattern TLS11 :: Version+pattern TLS11 = Version 0x0302+pattern TLS12 :: Version+pattern TLS12 = Version 0x0303+pattern TLS13 :: Version+pattern TLS13 = Version 0x0304++instance Show Version where+ show SSL2 = "SSL2"+ show SSL3 = "SSL3"+ show TLS10 = "TLS1.0"+ show TLS11 = "TLS1.1"+ show TLS12 = "TLS1.2"+ show TLS13 = "TLS1.3"+ show (Version x) = "Version " ++ show x+{- FOURMOLU_ENABLE -}++instance Serialise Version
Network/TLS/Util.hs view
@@ -1,85 +1,125 @@ {-# LANGUAGE ScopedTypeVariables #-}-module Network.TLS.Util- ( sub- , takelast- , partition3- , partition6- , fromJust- , and'- , (&&!)- , bytesEq- , fmapEither- , catchException- ) where+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} -import Data.List (foldl')-import Network.TLS.Struct (Bytes)-import qualified Data.ByteString as B+module Network.TLS.Util (+ sub,+ takelast,+ partition3,+ partition6,+ (&&!),+ fmapEither,+ catchException,+ forEitherM,+ mapChunks_,+ getChunks,+ Saved,+ saveMVar,+ restoreMVar,+) where -import Control.Exception (fromException)+import Control.Concurrent.MVar+import Control.Exception (SomeAsyncException (..)) import qualified Control.Exception as E+import Data.ByteArray (ScrubbedBytes)+import qualified Data.ByteArray as BA+import qualified Data.ByteString as B -sub :: Bytes -> Int -> Int -> Maybe Bytes+import Network.TLS.Imports++sub :: ByteString -> Int -> Int -> Maybe ByteString sub b offset len | B.length b < offset + len = Nothing- | otherwise = Just $ B.take len $ snd $ B.splitAt offset b+ | otherwise = Just $ B.take len $ snd $ B.splitAt offset b -takelast :: Int -> Bytes -> Maybe Bytes+takelast :: Int -> ByteString -> Maybe ByteString takelast i b | B.length b >= i = sub b (B.length b - i) i- | otherwise = Nothing+ | otherwise = Nothing -partition3 :: Bytes -> (Int,Int,Int) -> Maybe (Bytes, Bytes, Bytes)-partition3 bytes (d1,d2,d3)- | any (< 0) l = Nothing+partition3+ :: ByteString -> (Int, Int, Int) -> Maybe (ByteString, ByteString, ByteString)+partition3 bytes (d1, d2, d3)+ | any (< 0) l = Nothing | sum l /= B.length bytes = Nothing- | otherwise = Just (p1,p2,p3)- where l = [d1,d2,d3]- (p1, r1) = B.splitAt d1 bytes- (p2, r2) = B.splitAt d2 r1- (p3, _) = B.splitAt d3 r2--partition6 :: Bytes -> (Int,Int,Int,Int,Int,Int) -> Maybe (Bytes, Bytes, Bytes, Bytes, Bytes, Bytes)-partition6 bytes (d1,d2,d3,d4,d5,d6) = if B.length bytes < s then Nothing else Just (p1,p2,p3,p4,p5,p6)- where s = sum [d1,d2,d3,d4,d5,d6]- (p1, r1) = B.splitAt d1 bytes- (p2, r2) = B.splitAt d2 r1- (p3, r3) = B.splitAt d3 r2- (p4, r4) = B.splitAt d4 r3- (p5, r5) = B.splitAt d5 r4- (p6, _) = B.splitAt d6 r5--fromJust :: String -> Maybe a -> a-fromJust what Nothing = error ("fromJust " ++ what ++ ": Nothing") -- yuck-fromJust _ (Just x) = x+ | otherwise = Just (p1, p2, p3)+ where+ l = [d1, d2, d3]+ (p1, r1) = B.splitAt d1 bytes+ (p2, r2) = B.splitAt d2 r1+ (p3, _) = B.splitAt d3 r2 --- | This is a strict version of and-and' :: [Bool] -> Bool-and' l = foldl' (&&!) True l+partition6+ :: ScrubbedBytes+ -> (Int, Int, Int, Int, Int, Int)+ -> Maybe+ ( ScrubbedBytes+ , ScrubbedBytes+ , ScrubbedBytes+ , ScrubbedBytes+ , ScrubbedBytes+ , ScrubbedBytes+ )+partition6 bytes (d1, d2, d3, d4, d5, d6) = if BA.length bytes < s then Nothing else Just (p1, p2, p3, p4, p5, p6)+ where+ slice' (beg, len) = BA.unsafeSlice bytes beg len+ lens = [d1, d2, d3, d4, d5, d6]+ s = sum lens+ begs = scanl (+) 0 lens+ ys = zip begs lens+ [p1, p2, p3, p4, p5, p6] = map slice' ys -- | This is a strict version of &&. (&&!) :: Bool -> Bool -> Bool-True &&! True = True-True &&! False = False-False &&! True = False+True &&! True = True+True &&! False = False+False &&! True = False False &&! False = False --- | verify that 2 bytestrings are equals.--- it's a non lazy version, that will compare every bytes.--- arguments with different length will bail out early-bytesEq :: Bytes -> Bytes -> Bool-bytesEq b1 b2- | B.length b1 /= B.length b2 = False- | otherwise = and' $ B.zipWith (==) b1 b2- fmapEither :: (a -> b) -> Either l a -> Either l b-fmapEither f e = case e of- Left l -> Left l- Right r -> Right (f r)+fmapEither f = fmap f catchException :: IO a -> (E.SomeException -> IO a) -> IO a catchException f handler = E.catchJust filterExn f handler- where filterExn :: E.SomeException -> Maybe E.SomeException- filterExn e = case fromException e of- Just (_ :: E.AsyncException) -> Nothing- Nothing -> Just e+ where+ filterExn :: E.SomeException -> Maybe E.SomeException+ filterExn e = case E.fromException (E.toException e) of+ Just (SomeAsyncException _) -> Nothing+ Nothing -> Just e++forEitherM :: Monad m => [a] -> (a -> m (Either l b)) -> m (Either l [b])+forEitherM [] _ = return (pure [])+forEitherM (x : xs) f = f x >>= doTail+ where+ doTail (Right b) = fmap (b :) <$> forEitherM xs f+ doTail (Left e) = return (Left e)++mapChunks_+ :: Monad m+ => Maybe Int+ -> (ByteString -> m a)+ -> ByteString+ -> m ()+mapChunks_ len f = mapM_ f . getChunks len++getChunks :: Maybe Int -> ByteString -> [ByteString]+getChunks Nothing = (: [])+getChunks (Just len) = go+ where+ go bs+ | B.length bs > len =+ let (chunk, remain) = B.splitAt len bs+ in chunk : go remain+ | otherwise = [bs]++-- | An opaque newtype wrapper to prevent from poking inside content that has+-- been saved.+newtype Saved a = Saved a++-- | Save the content of an 'MVar' to restore it later.+saveMVar :: MVar a -> IO (Saved a)+saveMVar ref = Saved <$> readMVar ref++-- | Restore the content of an 'MVar' to a previous saved value and return the+-- content that has just been replaced.+restoreMVar :: MVar a -> Saved a -> IO (Saved a)+restoreMVar ref (Saved val) = Saved <$> swapMVar ref val
Network/TLS/Util/ASN1.hs view
@@ -1,37 +1,32 @@--- |--- Module : Network.TLS.Util.ASN1--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ ASN1 utils for TLS----module Network.TLS.Util.ASN1- ( decodeASN1Object- , encodeASN1Object- ) where+-- | ASN1 utils for TLS+module Network.TLS.Util.ASN1 (+ decodeASN1Object,+ encodeASN1Object,+) where -import Data.ASN1.Types (fromASN1, toASN1, ASN1Object)+import Data.ASN1.BinaryEncoding (DER (..)) import Data.ASN1.Encoding (decodeASN1', encodeASN1')-import Data.ASN1.BinaryEncoding (DER(..))-import Data.ByteString (ByteString)+import Data.ASN1.Types (ASN1Object, fromASN1, toASN1) +import Network.TLS.Imports+ -- | Attempt to decode a bytestring representing -- an DER ASN.1 serialized object into the object.-decodeASN1Object :: ASN1Object a- => String- -> ByteString- -> Either String a+decodeASN1Object+ :: ASN1Object a+ => String+ -> ByteString+ -> Either String a decodeASN1Object name bs = case decodeASN1' DER bs of- Left e -> Left (name ++ ": cannot decode ASN1: " ++ show e)+ Left e -> Left (name ++ ": cannot decode ASN1: " ++ show e) Right asn1 -> case fromASN1 asn1 of- Left e -> Left (name ++ ": cannot parse ASN1: " ++ show e)- Right (d,_) -> Right d+ Left e -> Left (name ++ ": cannot parse ASN1: " ++ show e)+ Right (d, _) -> Right d -- | Encode an ASN.1 Object to the DER serialized bytestring-encodeASN1Object :: ASN1Object a- => a- -> ByteString+encodeASN1Object+ :: ASN1Object a+ => a+ -> ByteString encodeASN1Object obj = encodeASN1' DER $ toASN1 obj []
Network/TLS/Util/Serialization.hs view
@@ -1,6 +1,7 @@-module Network.TLS.Util.Serialization- ( os2ip- , i2osp- ) where+module Network.TLS.Util.Serialization (+ os2ip,+ i2osp,+ i2ospOf_,+) where -import Crypto.Number.Serialize (os2ip, i2osp)+import Crypto.Number.Serialize (i2osp, i2ospOf_, os2ip)
Network/TLS/Wire.hs view
@@ -1,88 +1,89 @@--- |--- Module : Network.TLS.Wire--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ the Wire module is a specialized marshalling/unmarshalling package related to the TLS protocol.--- all multibytes values are written as big endian.----module Network.TLS.Wire- ( Get- , GetResult(..)- , GetContinuation- , runGet- , runGetErr- , runGetMaybe- , tryGet- , remaining- , getWord8- , getWords8- , getWord16- , getWords16- , getWord24- , getBytes- , getOpaque8- , getOpaque16- , getOpaque24- , getInteger16- , getList- , processBytes- , isEmpty- , Put- , runPut- , putWord8- , putWords8- , putWord16- , putWords16- , putWord24- , putBytes- , putOpaque8- , putOpaque16- , putOpaque24- , putInteger16- , encodeWord16- , encodeWord64- ) where+-- | The Wire module is a specialized marshalling/unmarshalling+-- package related to the TLS protocol. All multibytes values are+-- written as big endian.+module Network.TLS.Wire (+ Get,+ GetResult (..),+ GetContinuation,+ runGet,+ runGetErr,+ runGetMaybe,+ tryGet,+ remaining,+ getWord8,+ getWords8,+ getWord16,+ getWords16,+ getWord24,+ getWord32,+ getWord64,+ getBytes,+ getOpaque8,+ getOpaque16,+ getOpaque24,+ getInteger16,+ getBigNum16,+ getList,+ processBytes,+ isEmpty,+ Put,+ runPut,+ putWord8,+ putWords8,+ putWord16,+ putWords16,+ putWord24,+ putWord32,+ putWord64,+ putBytes,+ putOpaque8,+ putOpaque16,+ putOpaque24,+ putInteger16,+ putBigNum16,+ encodeWord16,+ encodeWord32,+ encodeWord64,+) where +import qualified Data.ByteString as B import Data.Serialize.Get hiding (runGet) import qualified Data.Serialize.Get as G import Data.Serialize.Put-import Control.Applicative ((<$>))-import Control.Monad.Error-import qualified Data.ByteString as B-import Data.Word-import Data.Bits-import Network.TLS.Struct++import Network.TLS.Error+import Network.TLS.Imports+import Network.TLS.Types import Network.TLS.Util.Serialization -type GetContinuation a = Bytes -> GetResult a-data GetResult a =- GotError TLSError+type GetContinuation a = ByteString -> GetResult a+data GetResult a+ = GotError TLSError | GotPartial (GetContinuation a) | GotSuccess a- | GotSuccessRemaining a Bytes+ | GotSuccessRemaining a ByteString -runGet :: String -> Get a -> Bytes -> GetResult a+runGet :: String -> Get a -> ByteString -> GetResult a runGet lbl f = toGetResult <$> G.runGetPartial (label lbl f)- where toGetResult (G.Fail err _) = GotError (Error_Packet_Parsing err)- toGetResult (G.Partial cont) = GotPartial (toGetResult <$> cont)- toGetResult (G.Done r bsLeft)- | B.null bsLeft = GotSuccess r- | otherwise = GotSuccessRemaining r bsLeft+ where+ toGetResult (G.Fail err _) = GotError (Error_Packet_Parsing err)+ toGetResult (G.Partial cont) = GotPartial (toGetResult <$> cont)+ toGetResult (G.Done r bsLeft)+ | B.null bsLeft = GotSuccess r+ | otherwise = GotSuccessRemaining r bsLeft -runGetErr :: String -> Get a -> Bytes -> Either TLSError a+runGetErr :: String -> Get a -> ByteString -> Either TLSError a runGetErr lbl getter b = toSimple $ runGet lbl getter b- where toSimple (GotError err) = Left err- toSimple (GotPartial _) = Left (Error_Packet_Parsing (lbl ++ ": parsing error: partial packet"))- toSimple (GotSuccessRemaining _ _) = Left (Error_Packet_Parsing (lbl ++ ": parsing error: remaining bytes"))- toSimple (GotSuccess r) = Right r+ where+ toSimple (GotError err) = Left err+ toSimple (GotPartial _) = Left (Error_Packet_Parsing (lbl ++ ": parsing error: partial packet"))+ toSimple (GotSuccessRemaining _ _) = Left (Error_Packet_Parsing (lbl ++ ": parsing error: remaining bytes"))+ toSimple (GotSuccess r) = Right r -runGetMaybe :: Get a -> Bytes -> Maybe a+runGetMaybe :: Get a -> ByteString -> Maybe a runGetMaybe f = either (const Nothing) Just . G.runGet f -tryGet :: Get a -> Bytes -> Maybe a+tryGet :: Get a -> ByteString -> Maybe a tryGet f = either (const Nothing) Just . G.runGet f getWords8 :: Get [Word8]@@ -92,7 +93,10 @@ getWord16 = getWord16be getWords16 :: Get [Word16]-getWords16 = getWord16 >>= \lenb -> replicateM (fromIntegral lenb `div` 2) getWord16+getWords16 = do+ lenb <- getWord16+ when (odd lenb) $ fail "length for ciphers must be even"+ replicateM (fromIntegral lenb `shiftR` 1) getWord16 getWord24 :: Get Int getWord24 = do@@ -101,24 +105,36 @@ c <- fromIntegral <$> getWord8 return $ (a `shiftL` 16) .|. (b `shiftL` 8) .|. c -getOpaque8 :: Get Bytes+getWord32 :: Get Word32+getWord32 = getWord32be++getWord64 :: Get Word64+getWord64 = getWord64be++getOpaque8 :: Get ByteString getOpaque8 = getWord8 >>= getBytes . fromIntegral -getOpaque16 :: Get Bytes+getOpaque16 :: Get ByteString getOpaque16 = getWord16 >>= getBytes . fromIntegral -getOpaque24 :: Get Bytes+getOpaque24 :: Get ByteString getOpaque24 = getWord24 >>= getBytes getInteger16 :: Get Integer getInteger16 = os2ip <$> getOpaque16 -getList :: Int -> (Get (Int, a)) -> Get [a]+getBigNum16 :: Get BigNum+getBigNum16 = BigNum <$> getOpaque16++getList :: Int -> Get (Int, a) -> Get [a] getList totalLen getElement = isolate totalLen (getElements totalLen)- where getElements len- | len < 0 = error "list consumed too much data. should never happen with isolate."- | len == 0 = return []- | otherwise = getElement >>= \(elementLen, a) -> liftM ((:) a) (getElements (len - elementLen))+ where+ getElements len+ | len < 0 =+ error "list consumed too much data. should never happen with isolate."+ | len == 0 = return []+ | otherwise =+ getElement >>= \(elementLen, a) -> (:) a <$> getElements (len - elementLen) processBytes :: Int -> Get a -> Get a processBytes i f = isolate i f@@ -131,9 +147,15 @@ putWord16 :: Word16 -> Put putWord16 = putWord16be +putWord32 :: Word32 -> Put+putWord32 = putWord32be++putWord64 :: Word64 -> Put+putWord64 = putWord64be+ putWords16 :: [Word16] -> Put putWords16 l = do- putWord16 $ 2 * (fromIntegral $ length l)+ putWord16 $ 2 * fromIntegral (length l) mapM_ putWord16 l putWord24 :: Int -> Put@@ -141,25 +163,31 @@ let a = fromIntegral ((i `shiftR` 16) .&. 0xff) let b = fromIntegral ((i `shiftR` 8) .&. 0xff) let c = fromIntegral (i .&. 0xff)- mapM_ putWord8 [a,b,c]+ mapM_ putWord8 [a, b, c] -putBytes :: Bytes -> Put+putBytes :: ByteString -> Put putBytes = putByteString -putOpaque8 :: Bytes -> Put+putOpaque8 :: ByteString -> Put putOpaque8 b = putWord8 (fromIntegral $ B.length b) >> putBytes b -putOpaque16 :: Bytes -> Put+putOpaque16 :: ByteString -> Put putOpaque16 b = putWord16 (fromIntegral $ B.length b) >> putBytes b -putOpaque24 :: Bytes -> Put+putOpaque24 :: ByteString -> Put putOpaque24 b = putWord24 (B.length b) >> putBytes b putInteger16 :: Integer -> Put putInteger16 = putOpaque16 . i2osp -encodeWord16 :: Word16 -> Bytes+putBigNum16 :: BigNum -> Put+putBigNum16 (BigNum b) = putOpaque16 b++encodeWord16 :: Word16 -> ByteString encodeWord16 = runPut . putWord16 -encodeWord64 :: Word64 -> Bytes+encodeWord32 :: Word32 -> ByteString+encodeWord32 = runPut . putWord32++encodeWord64 :: Word64 -> ByteString encodeWord64 = runPut . putWord64be
Network/TLS/X509.hs view
@@ -1,59 +1,82 @@--- |--- Module : Network.TLS.X509--- License : BSD-style--- Maintainer : Vincent Hanquez <vincent@snarc.org>--- Stability : experimental--- Portability : unknown------ X509 helpers----module Network.TLS.X509- ( CertificateChain(..)- , Certificate(..)- , SignedCertificate- , getCertificate- , isNullCertificateChain- , getCertificateChainLeaf- , CertificateRejectReason(..)- , CertificateUsage(..)- , CertificateStore- , ValidationCache- , exceptionValidationCache- , validateDefault- , FailedReason- , ServiceID- , wrapCertificateChecks- ) where+-- | X509 helpers+module Network.TLS.X509 (+ CertificateChain (..),+ Certificate (..),+ SignedCertificate,+ getCertificate,+ isNullCertificateChain,+ getCertificateChainLeaf,+ CertificateRejectReason (..),+ CertificateUsage (..),+ CertificateStore,+ ValidationCache,+ defaultValidationCache,+ exceptionValidationCache,+ validateDefault,+ FailedReason,+ ServiceID,+ wrapCertificateChecks,+ pubkeyType,+ validateClientCertificate,+) where import Data.X509-import Data.X509.Validation import Data.X509.CertificateStore+import Data.X509.Validation isNullCertificateChain :: CertificateChain -> Bool isNullCertificateChain (CertificateChain l) = null l getCertificateChainLeaf :: CertificateChain -> SignedExact Certificate-getCertificateChainLeaf (CertificateChain []) = error "empty certificate chain"-getCertificateChainLeaf (CertificateChain (x:_)) = x+getCertificateChainLeaf (CertificateChain []) = error "empty certificate chain"+getCertificateChainLeaf (CertificateChain (x : _)) = x -- | Certificate and Chain rejection reason-data CertificateRejectReason =- CertificateRejectExpired- | CertificateRejectRevoked- | CertificateRejectUnknownCA- | CertificateRejectOther String- deriving (Show,Eq)+data CertificateRejectReason+ = CertificateRejectExpired+ | CertificateRejectRevoked+ | CertificateRejectUnknownCA+ | CertificateRejectAbsent+ | CertificateRejectOther String+ deriving (Show, Eq) -- | Certificate Usage callback possible returns values.-data CertificateUsage =- CertificateUsageAccept -- ^ usage of certificate accepted- | CertificateUsageReject CertificateRejectReason -- ^ usage of certificate rejected- deriving (Show,Eq)+data CertificateUsage+ = -- | usage of certificate accepted+ CertificateUsageAccept+ | -- | usage of certificate rejected+ CertificateUsageReject CertificateRejectReason+ deriving (Show, Eq) wrapCertificateChecks :: [FailedReason] -> CertificateUsage wrapCertificateChecks [] = CertificateUsageAccept wrapCertificateChecks l- | Expired `elem` l = CertificateUsageReject $ CertificateRejectExpired- | InFuture `elem` l = CertificateUsageReject $ CertificateRejectExpired- | UnknownCA `elem` l = CertificateUsageReject $ CertificateRejectUnknownCA- | otherwise = CertificateUsageReject $ CertificateRejectOther (show l)+ | Expired `elem` l = CertificateUsageReject CertificateRejectExpired+ | InFuture `elem` l = CertificateUsageReject CertificateRejectExpired+ | UnknownCA `elem` l = CertificateUsageReject CertificateRejectUnknownCA+ | SelfSigned `elem` l = CertificateUsageReject CertificateRejectUnknownCA+ | EmptyChain `elem` l = CertificateUsageReject CertificateRejectAbsent+ | otherwise = CertificateUsageReject $ CertificateRejectOther (show l)++pubkeyType :: PubKey -> String+pubkeyType = show . pubkeyToAlg++-- | A utility function for client authentication which can be used+-- `onClientCertificate`.+--+-- Since: 2.1.7+validateClientCertificate+ :: CertificateStore+ -> ValidationCache+ -> CertificateChain+ -> IO CertificateUsage+validateClientCertificate store cache cc =+ wrapCertificateChecks+ <$> validate+ HashSHA256+ defaultHooks+ defaultChecks{checkFQHN = False}+ store+ cache+ ("", mempty)+ cc
Setup.hs view
@@ -1,2 +1,3 @@ import Distribution.Simple+ main = defaultMain
− Tests/Certificate.hs
@@ -1,86 +0,0 @@-{-# LANGUAGE BangPatterns #-}-module Certificate- ( arbitraryX509- , arbitraryX509WithKey- , simpleCertificate- , simpleX509- ) where--import Test.QuickCheck-import Data.X509-import Data.Time.Calendar (fromGregorian)-import Data.Time.Clock (secondsToDiffTime, UTCTime(..))-import qualified Data.ByteString as B--import PubKey--testExtensionEncode critical ext = ExtensionRaw (extOID ext) critical (extEncode ext)--arbitraryDN = return $ DistinguishedName []--arbitraryTime = do- year <- choose (1951, 2050)- month <- choose (1, 12)- day <- choose (1, 30)- hour <- choose (0, 23)- minute <- choose (0, 59)- second <- choose (0, 59)- --z <- arbitrary- return $ UTCTime (fromGregorian year month day) (secondsToDiffTime (hour * 3600 + minute * 60 + second))--maxSerial = 16777216--arbitraryCertificate pubKey = do- serial <- choose (0,maxSerial)- issuerdn <- arbitraryDN- subjectdn <- arbitraryDN- time1 <- arbitraryTime- time2 <- arbitraryTime- let sigalg = SignatureALG HashSHA1 (pubkeyToAlg pubKey)- return $ Certificate- { certVersion = 3- , certSerial = serial- , certSignatureAlg = sigalg- , certIssuerDN = issuerdn- , certSubjectDN = subjectdn- , certValidity = (time1, time2)- , certPubKey = pubKey- , certExtensions = Extensions $ Just- [ testExtensionEncode True $ ExtKeyUsage [KeyUsage_digitalSignature,KeyUsage_keyEncipherment,KeyUsage_keyCertSign]- ]- }--simpleCertificate pubKey =- Certificate- { certVersion = 3- , certSerial = 0- , certSignatureAlg = SignatureALG HashSHA1 (pubkeyToAlg pubKey)- , certIssuerDN = simpleDN- , certSubjectDN = simpleDN- , certValidity = (time1, time2)- , certPubKey = pubKey- , certExtensions = Extensions $ Just- [ testExtensionEncode True $ ExtKeyUsage [KeyUsage_digitalSignature,KeyUsage_keyEncipherment]- ]- }- where time1 = UTCTime (fromGregorian 1999 1 1) 0- time2 = UTCTime (fromGregorian 2901 1 1) 0- simpleDN = DistinguishedName []--simpleX509 pubKey = do- let cert = simpleCertificate pubKey- sig = replicate 40 1- sigalg = SignatureALG HashSHA1 (pubkeyToAlg pubKey)- (signedExact, ()) = objectToSignedExact (\_ -> (B.pack sig,sigalg,())) cert- in signedExact--arbitraryX509WithKey (pubKey, _) = do- cert <- arbitraryCertificate pubKey- sig <- resize 40 $ listOf1 arbitrary- let sigalg = SignatureALG HashSHA1 (pubkeyToAlg pubKey)- let (signedExact, ()) = objectToSignedExact (\(!(_)) -> (B.pack sig,sigalg,())) cert- return signedExact--arbitraryX509 = do- let (pubKey, privKey) = getGlobalRSAPair- arbitraryX509WithKey (PubKeyRSA pubKey, PrivKeyRSA privKey)
− Tests/Ciphers.hs
@@ -1,38 +0,0 @@-module Ciphers- ( propertyBulkFunctional- ) where--import Control.Applicative ((<$>), (<*>))--import Test.QuickCheck--import qualified Data.ByteString as B-import Network.TLS.Cipher-import Network.TLS.Extra.Cipher--arbitraryKey :: Bulk -> Gen B.ByteString-arbitraryKey bulk = B.pack `fmap` vector (fromIntegral $ bulkKeySize bulk)--arbitraryIV :: Bulk -> Gen B.ByteString-arbitraryIV bulk = B.pack `fmap` vector (fromIntegral $ bulkIVSize bulk)--arbitraryText :: Bulk -> Gen B.ByteString-arbitraryText bulk = B.pack `fmap` vector (fromIntegral $ bulkBlockSize bulk)--data BulkTest = BulkTest Bulk B.ByteString B.ByteString B.ByteString- deriving (Show,Eq)--instance Arbitrary BulkTest where- arbitrary = do- bulk <- cipherBulk `fmap` elements ciphersuite_all- BulkTest bulk <$> arbitraryKey bulk <*> arbitraryIV bulk <*> arbitraryText bulk--propertyBulkFunctional :: BulkTest -> Bool-propertyBulkFunctional (BulkTest bulk key iv t) =- case bulkF bulk of- BulkBlockF enc dec -> block enc dec- BulkStreamF ktoi enc dec -> stream ktoi enc dec- where- block e d = (d key iv . e key iv) t == t- stream ktoi e d = (fst . d siv . fst . e siv) t == t- where siv = ktoi key
− Tests/Connection.hs
@@ -1,166 +0,0 @@-module Connection- ( newPairContext- , arbitraryPairParams- , setPairParamsSessionManager- , setPairParamsSessionResuming- , establishDataPipe- , blockCipher- , streamCipher- ) where--import Test.QuickCheck-import Certificate-import PubKey-import PipeChan-import Network.TLS-import Data.X509-import Data.Default.Class-import Control.Applicative-import Control.Concurrent.Chan-import Control.Concurrent-import qualified Control.Exception as E--import qualified Crypto.Random.AESCtr as RNG-import qualified Data.ByteString as B--debug = False--blockCipher :: Cipher-blockCipher = Cipher- { cipherID = 0xff12- , cipherName = "rsa-id-const"- , cipherBulk = Bulk- { bulkName = "id"- , bulkKeySize = 16- , bulkIVSize = 16- , bulkBlockSize = 16- , bulkF = BulkBlockF (\_ _ m -> m) (\_ _ m -> m)- }- , cipherHash = Hash- { hashName = "const-hash"- , hashSize = 16- , hashF = (\_ -> B.replicate 16 1)- }- , cipherKeyExchange = CipherKeyExchange_RSA- , cipherMinVer = Nothing- }--blockCipherDHE_RSA :: Cipher-blockCipherDHE_RSA = blockCipher- { cipherID = 0xff14- , cipherName = "dhe-rsa-id-const"- , cipherKeyExchange = CipherKeyExchange_DHE_RSA- }--blockCipherDHE_DSS :: Cipher-blockCipherDHE_DSS = blockCipher- { cipherID = 0xff15- , cipherName = "dhe-dss-id-const"- , cipherKeyExchange = CipherKeyExchange_DHE_DSS- }--streamCipher :: Cipher-streamCipher = blockCipher- { cipherID = 0xff13- , cipherBulk = Bulk- { bulkName = "stream"- , bulkKeySize = 16- , bulkIVSize = 0- , bulkBlockSize = 0- , bulkF = BulkStreamF (\k -> k) (\i m -> (m,i)) (\i m -> (m,i))- }- }--knownCiphers :: [Cipher]-knownCiphers = [blockCipher,blockCipherDHE_RSA,blockCipherDHE_DSS,streamCipher]--knownVersions :: [Version]-knownVersions = [SSL3,TLS10,TLS11,TLS12]--arbitraryPairParams = do- (dsaPub, dsaPriv) <- (\(p,r) -> (PubKeyDSA p, PrivKeyDSA r)) <$> arbitraryDSAPair- let (pubKey, privKey) = (\(p, r) -> (PubKeyRSA p, PrivKeyRSA r)) $ getGlobalRSAPair- creds <- mapM (\(pub, priv) -> do- cert <- arbitraryX509WithKey (pub, priv)- return (CertificateChain [cert], priv)- ) [ (pubKey, privKey), (dsaPub, dsaPriv) ]- connectVersion <- elements knownVersions- let allowedVersions = [ v | v <- knownVersions, v <= connectVersion ]- serAllowedVersions <- (:[]) `fmap` elements allowedVersions- serverCiphers <- arbitraryCiphers- clientCiphers <- oneof [arbitraryCiphers] `suchThat` (\cs -> or [x `elem` serverCiphers | x <- cs])- secNeg <- arbitrary--- let serverState = def- { serverSupported = def { supportedCiphers = serverCiphers- , supportedVersions = serAllowedVersions- , supportedSecureRenegotiation = secNeg- }- , serverDHEParams = Just dhParams- , serverShared = def { sharedCredentials = Credentials creds }- }- let clientState = (defaultParamsClient "" B.empty)- { clientSupported = def { supportedCiphers = clientCiphers- , supportedVersions = allowedVersions- , supportedSecureRenegotiation = secNeg- }- , clientShared = def { sharedValidationCache = ValidationCache- { cacheAdd = \_ _ _ -> return ()- , cacheQuery = \_ _ _ -> return ValidationCachePass- }- }- }- return (clientState, serverState)- where- arbitraryCiphers = resize (length knownCiphers + 1) $ listOf1 (elements knownCiphers)--setPairParamsSessionManager :: SessionManager -> (ClientParams, ServerParams) -> (ClientParams, ServerParams)-setPairParamsSessionManager manager (clientState, serverState) = (nc,ns)- where nc = clientState { clientShared = updateSessionManager $ clientShared clientState }- ns = serverState { serverShared = updateSessionManager $ serverShared serverState }- updateSessionManager shared = shared { sharedSessionManager = manager }--setPairParamsSessionResuming sessionStuff (clientState, serverState) =- ( clientState { clientWantSessionResume = Just sessionStuff }- , serverState)--newPairContext pipe (cParams, sParams) = do- let noFlush = return ()- let noClose = return ()-- cRNG <- RNG.makeSystem- sRNG <- RNG.makeSystem-- let cBackend = Backend noFlush noClose (writePipeA pipe) (readPipeA pipe)- let sBackend = Backend noFlush noClose (writePipeB pipe) (readPipeB pipe)- cCtx' <- contextNew cBackend cParams cRNG- sCtx' <- contextNew sBackend sParams sRNG-- contextHookSetLogging cCtx' (logging "client: ")- contextHookSetLogging sCtx' (logging "server: ")-- return (cCtx', sCtx')- where- logging pre =- if debug- then def { loggingPacketSent = putStrLn . ((pre ++ ">> ") ++)- , loggingPacketRecv = putStrLn . ((pre ++ "<< ") ++) }- else def--establishDataPipe params tlsServer tlsClient = do- -- initial setup- pipe <- newPipe- _ <- (runPipe pipe)- startQueue <- newChan- resultQueue <- newChan-- (cCtx, sCtx) <- newPairContext pipe params-- _ <- forkIO $ E.catch (tlsServer sCtx resultQueue) (printAndRaise "server")- _ <- forkIO $ E.catch (tlsClient startQueue cCtx) (printAndRaise "client")-- return (startQueue, resultQueue)- where- printAndRaise :: String -> E.SomeException -> IO ()- printAndRaise s e = putStrLn (s ++ " exception: " ++ show e) >> E.throw e
− Tests/Marshalling.hs
@@ -1,111 +0,0 @@-{-# LANGUAGE CPP #-}-module Marshalling where--import Control.Monad-import Control.Applicative-import Test.QuickCheck-import Network.TLS.Internal-import Network.TLS--import qualified Data.ByteString as B-import Data.Word-import Data.X509-import Certificate--genByteString :: Int -> Gen B.ByteString-genByteString i = B.pack <$> vector i--instance Arbitrary Version where- arbitrary = elements [ SSL2, SSL3, TLS10, TLS11, TLS12 ]--instance Arbitrary ProtocolType where- arbitrary = elements- [ ProtocolType_ChangeCipherSpec- , ProtocolType_Alert- , ProtocolType_Handshake- , ProtocolType_AppData ]--#if MIN_VERSION_QuickCheck(2,3,0)-#else-instance Arbitrary Word8 where- arbitrary = fromIntegral <$> (choose (0,255) :: Gen Int)--instance Arbitrary Word16 where- arbitrary = fromIntegral <$> (choose (0,65535) :: Gen Int)-#endif--instance Arbitrary Header where- arbitrary = Header <$> arbitrary <*> arbitrary <*> arbitrary--instance Arbitrary ClientRandom where- arbitrary = ClientRandom <$> (genByteString 32)--instance Arbitrary ServerRandom where- arbitrary = ServerRandom <$> (genByteString 32)--instance Arbitrary Session where- arbitrary = do- i <- choose (1,2) :: Gen Int- case i of- 2 -> liftM (Session . Just) (genByteString 32)- _ -> return $ Session Nothing--instance Arbitrary DigitallySigned where- arbitrary = DigitallySigned Nothing <$> genByteString 32--arbitraryCiphersIDs :: Gen [Word16]-arbitraryCiphersIDs = choose (0,200) >>= vector--arbitraryCompressionIDs :: Gen [Word8]-arbitraryCompressionIDs = choose (0,200) >>= vector--someWords8 :: Int -> Gen [Word8]-someWords8 i = replicateM i (fromIntegral <$> (choose (0,255) :: Gen Int))--instance Arbitrary CertificateType where- arbitrary = elements- [ CertificateType_RSA_Sign, CertificateType_DSS_Sign- , CertificateType_RSA_Fixed_DH, CertificateType_DSS_Fixed_DH- , CertificateType_RSA_Ephemeral_DH, CertificateType_DSS_Ephemeral_DH- , CertificateType_fortezza_dms ]--instance Arbitrary Handshake where- arbitrary = oneof- [ ClientHello- <$> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitraryCiphersIDs- <*> arbitraryCompressionIDs- <*> (return [])- <*> (return Nothing)- , ServerHello- <$> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> (return [])- , liftM Certificates (CertificateChain <$> (resize 2 $ listOf $ arbitraryX509))- , pure HelloRequest- , pure ServerHelloDone- , ClientKeyXchg . CKX_RSA <$> genByteString 48- --, liftM ServerKeyXchg- , liftM3 CertRequest arbitrary (return Nothing) (return [])- , CertVerify <$> arbitrary- , Finished <$> (genByteString 12)- ]--{- quickcheck property -}--prop_header_marshalling_id :: Header -> Bool-prop_header_marshalling_id x = (decodeHeader $ encodeHeader x) == Right x--prop_handshake_marshalling_id :: Handshake -> Bool-prop_handshake_marshalling_id x = (decodeHs $ encodeHandshake x) == Right x- where decodeHs b = case decodeHandshakeRecord b of- GotPartial _ -> error "got partial"- GotError e -> error ("got error: " ++ show e)- GotSuccessRemaining _ _ -> error "got remaining byte left"- GotSuccess (ty, content) -> decodeHandshake cp ty content- cp = CurrentParams { cParamsVersion = TLS10, cParamsKeyXchgType = Just CipherKeyExchange_RSA, cParamsSupportNPN = True }
− Tests/PipeChan.hs
@@ -1,56 +0,0 @@--- create a similar concept than a unix pipe.-module PipeChan- ( PipeChan(..)- , newPipe- , runPipe- , readPipeA- , readPipeB- , writePipeA- , writePipeB- ) where--import Control.Applicative-import Control.Concurrent.Chan-import Control.Concurrent-import Control.Monad (forever)-import Data.ByteString (ByteString)-import Data.IORef-import qualified Data.ByteString as B---- | represent a unidirectional pipe with a buffered read channel and a write channel-data UniPipeChan = UniPipeChan (Chan ByteString) (Chan ByteString)--newUniPipeChan = UniPipeChan <$> newChan <*> newChan--runUniPipe (UniPipeChan r w) = forkIO $ forever $ readChan r >>= writeChan w--getReadUniPipe (UniPipeChan r _) = r-getWriteUniPipe (UniPipeChan _ w) = w---- | Represent a bidirectional pipe with 2 nodes A and B-data PipeChan = PipeChan (IORef ByteString) (IORef ByteString) UniPipeChan UniPipeChan--newPipe = PipeChan <$> newIORef B.empty <*> newIORef B.empty <*> newUniPipeChan <*> newUniPipeChan--runPipe (PipeChan _ _ cToS sToC) = runUniPipe cToS >> runUniPipe sToC--readPipeA (PipeChan _ b _ s) sz = readBuffered b (getWriteUniPipe s) sz-writePipeA (PipeChan _ _ c _) = writeChan $ getWriteUniPipe c--readPipeB (PipeChan b _ c _) sz = readBuffered b (getWriteUniPipe c) sz-writePipeB (PipeChan _ _ _ s) = writeChan $ getReadUniPipe s---- helper to read buffered data.-readBuffered buf chan sz = do- left <- readIORef buf- if B.length left >= sz- then do- let (ret, nleft) = B.splitAt sz left- writeIORef buf nleft- return ret- else do- let newSize = (sz - B.length left)- newData <- readChan chan- writeIORef buf newData- remain <- readBuffered buf chan newSize- return (left `B.append` remain)
− Tests/PubKey.hs
@@ -1,71 +0,0 @@-module PubKey- ( arbitraryRSAPair- , arbitraryDSAPair- , globalRSAPair- , getGlobalRSAPair- , dhParams- , dsaParams- , rsaParams- ) where--import Test.QuickCheck--import qualified Crypto.PubKey.DH as DH-import Crypto.Random (createTestEntropyPool)-import qualified Crypto.Random.AESCtr as RNG-import qualified Crypto.PubKey.RSA as RSA-import qualified Crypto.PubKey.DSA as DSA--import qualified Data.ByteString as B--import Control.Concurrent.MVar-import System.IO.Unsafe--arbitraryRSAPair :: Gen (RSA.PublicKey, RSA.PrivateKey)-arbitraryRSAPair = do- rng <- (RNG.make . createTestEntropyPool . B.pack) `fmap` vector 1024- arbitraryRSAPairWithRNG rng--arbitraryRSAPairWithRNG rng = return $ fst $ RSA.generate rng 128 0x10001--{-# NOINLINE globalRSAPair #-}-globalRSAPair :: MVar (RSA.PublicKey, RSA.PrivateKey)-globalRSAPair = unsafePerformIO (RNG.makeSystem >>= arbitraryRSAPairWithRNG >>= newMVar)--{-# NOINLINE getGlobalRSAPair #-}-getGlobalRSAPair :: (RSA.PublicKey, RSA.PrivateKey)-getGlobalRSAPair = unsafePerformIO (readMVar globalRSAPair)--rsaParams :: (RSA.PublicKey, RSA.PrivateKey)-rsaParams = (pub, priv)- where priv = RSA.PrivateKey { RSA.private_pub = pub- , RSA.private_d = d- , RSA.private_p = 0- , RSA.private_q = 0- , RSA.private_dP = 0- , RSA.private_dQ = 0- , RSA.private_qinv = 0- }- pub = RSA.PublicKey { RSA.public_size = (1024 `div` 8), RSA.public_n = n, RSA.public_e = e }- n = 0x00c086b4c6db28ae578d73766d6fdd04b913808a85bf9ad7bcfc9a6ff04d13d2ff75f761ce7db9ee8996e29dc433d19a2d3f748e8d368ba099781d58276e1863a324ae3fb1a061874cd9f3510e54e49727c68de0616964335371cfb63f15ebff8ce8df09c74fb8625f8f58548b90f079a3405f522e738e664d0c645b015664f7c7- e = 0x10001- d = 0x3edc3cae28e4717818b1385ba7088d0038c3e176a606d2a5dbfc38cc46fe500824e62ec312fde04a803f61afac13a5b95c5c9c26b346879b54429083df488b4f29bb7b9722d366d6f5d2b512150a2e950eacfe0fd9dd56b87b0322f74ae3c8d8674ace62bc723f7c05e9295561efd70d7a924c6abac2e482880fc0149d5ad481--dhParams :: DH.Params-dhParams = DH.Params- { DH.params_p = 0x00ccaa3884b50789ebea8d39bef8bbc66e20f2a78f537a76f26b4edde5de8b0ff15a8193abf0873cbdc701323a2bf6e860affa6e043fe8300d47e95baf9f6354cb- , DH.params_g = 0x2- }--dsaParams :: DSA.Params-dsaParams = DSA.Params- { DSA.params_p = 0x009f356bbc4750645555b02aa3918e85d5e35bdccd56154bfaa3e1801d5fe0faf65355215148ea866d5732fd27eb2f4d222c975767d2eb573513e460eceae327c8ac5da1f4ce765c49a39cae4c904b4e5cc64554d97148f20a2655027a0cf8f70b2550cc1f0c9861ce3a316520ab0588407ea3189d20c78bd52df97e56cbe0bbeb- , DSA.params_q = 0x00f33a57b47de86ff836f9fe0bb060c54ab293133b- , DSA.params_g = 0x3bb973c4f6eee92d1530f250487735595d778c2e5c8147d67a46ebcba4e6444350d49da8e7da667f9b1dbb22d2108870b9fcfabc353cdfac5218d829f22f69130317cc3b0d724881e34c34b8a2571d411da6458ef4c718df9e826f73e16a035b1dcbc1c62cac7a6604adb3e7930be8257944c6dfdddd655004b98253185775ff- }--arbitraryDSAPair :: Gen (DSA.PublicKey, DSA.PrivateKey)-arbitraryDSAPair = do- priv <- choose (1, DSA.params_q dsaParams)- let pub = DSA.calculatePublic dsaParams priv- return (DSA.PublicKey dsaParams pub, DSA.PrivateKey dsaParams priv)
− Tests/Tests.hs
@@ -1,182 +0,0 @@-{-# LANGUAGE CPP #-}--import Test.QuickCheck-import Test.QuickCheck.Monadic-import Test.Framework (defaultMain, testGroup)-import Test.Framework.Providers.QuickCheck2 (testProperty)--import PipeChan-import Connection-import Marshalling-import Ciphers--import Data.Maybe--import qualified Data.ByteString as B-import qualified Data.ByteString.Char8 as C8-import qualified Data.ByteString.Lazy as L-import Network.TLS-import Control.Applicative-import Control.Concurrent-import Control.Monad--import Data.IORef--prop_pipe_work :: PropertyM IO ()-prop_pipe_work = do- pipe <- run newPipe- _ <- run (runPipe pipe)-- let bSize = 16- n <- pick (choose (1, 32))-- let d1 = B.replicate (bSize * n) 40- let d2 = B.replicate (bSize * n) 45-- d1' <- run (writePipeA pipe d1 >> readPipeB pipe (B.length d1))- d1 `assertEq` d1'-- d2' <- run (writePipeB pipe d2 >> readPipeA pipe (B.length d2))- d2 `assertEq` d2'-- return ()--recvDataNonNull ctx = recvData ctx >>= \l -> if B.null l then recvDataNonNull ctx else return l--runTLSPipe params tlsServer tlsClient = do- (startQueue, resultQueue) <- run (establishDataPipe params tlsServer tlsClient)- -- send some data- d <- B.pack <$> pick (someWords8 256)- run $ writeChan startQueue d- -- receive it- dres <- run $ readChan resultQueue- -- check if it equal- d `assertEq` dres- return ()--prop_handshake_initiate :: PropertyM IO ()-prop_handshake_initiate = do- params <- pick arbitraryPairParams- runTLSPipe params tlsServer tlsClient- where tlsServer ctx queue = do- handshake ctx- d <- recvDataNonNull ctx- writeChan queue d- return ()- tlsClient queue ctx = do- handshake ctx- d <- readChan queue- sendData ctx (L.fromChunks [d])- bye ctx- return ()--prop_handshake_npn_initiate :: PropertyM IO ()-prop_handshake_npn_initiate = do- (clientParam,serverParam) <- pick arbitraryPairParams- let clientParam' = clientParam { clientHooks = (clientHooks clientParam)- { onNPNServerSuggest = Just $ \protos -> return (head protos) }- }- serverParam' = serverParam { serverHooks = (serverHooks serverParam)- { onSuggestNextProtocols = return $ Just [C8.pack "spdy/2", C8.pack "http/1.1"] }- }- params' = (clientParam',serverParam')- runTLSPipe params' tlsServer tlsClient- where tlsServer ctx queue = do- handshake ctx- proto <- getNegotiatedProtocol ctx- Just (C8.pack "spdy/2") `assertEq` proto- d <- recvDataNonNull ctx- writeChan queue d- return ()- tlsClient queue ctx = do- handshake ctx- proto <- getNegotiatedProtocol ctx- Just (C8.pack "spdy/2") `assertEq` proto- d <- readChan queue- sendData ctx (L.fromChunks [d])- bye ctx- return ()--prop_handshake_renegociation :: PropertyM IO ()-prop_handshake_renegociation = do- params <- pick arbitraryPairParams- runTLSPipe params tlsServer tlsClient- where tlsServer ctx queue = do- handshake ctx- d <- recvDataNonNull ctx- writeChan queue d- return ()- tlsClient queue ctx = do- handshake ctx- handshake ctx- d <- readChan queue- sendData ctx (L.fromChunks [d])- bye ctx- return ()---- | simple session manager to store one session id and session data for a single thread.--- a Real concurrent session manager would use an MVar and have multiples items.-oneSessionManager :: IORef (Maybe (SessionID, SessionData)) -> SessionManager-oneSessionManager ref = SessionManager- { sessionResume = \myId -> (>>= maybeResume myId) <$> readIORef ref- , sessionEstablish = \myId dat -> writeIORef ref $ Just (myId, dat)- , sessionInvalidate = \_ -> return ()- }- where- maybeResume myId (sid, sdata)- | sid == myId = Just sdata- | otherwise = Nothing--prop_handshake_session_resumption :: PropertyM IO ()-prop_handshake_session_resumption = do- sessionRef <- run $ newIORef Nothing- let sessionManager = oneSessionManager sessionRef-- plainParams <- pick arbitraryPairParams- let params = setPairParamsSessionManager sessionManager plainParams-- runTLSPipe params tlsServer tlsClient-- -- and resume- sessionParams <- run $ readIORef sessionRef- assert (isJust sessionParams)- let params2 = setPairParamsSessionResuming (fromJust sessionParams) params-- runTLSPipe params2 tlsServer tlsClient- where tlsServer ctx queue = do- handshake ctx- d <- recvDataNonNull ctx- writeChan queue d- return ()- tlsClient queue ctx = do- handshake ctx- d <- readChan queue- sendData ctx (L.fromChunks [d])- bye ctx- return ()--assertEq :: (Show a, Monad m, Eq a) => a -> a -> m ()-assertEq expected got = unless (expected == got) $ error ("got " ++ show got ++ " but was expecting " ++ show expected)--main :: IO ()-main = defaultMain- [ tests_marshalling- , tests_ciphers- , tests_handshake- ]- where -- lowlevel tests to check the packet marshalling.- tests_marshalling = testGroup "Marshalling"- [ testProperty "Header" prop_header_marshalling_id- , testProperty "Handshake" prop_handshake_marshalling_id- ]- tests_ciphers = testGroup "Ciphers"- [ testProperty "Bulk" propertyBulkFunctional ]-- -- high level tests between a client and server with fake ciphers.- tests_handshake = testGroup "Handshakes"- [ testProperty "setup" (monadicIO prop_pipe_work)- , testProperty "initiate" (monadicIO prop_handshake_initiate)- , testProperty "npnInitiate" (monadicIO prop_handshake_npn_initiate)- , testProperty "renegociation" (monadicIO prop_handshake_renegociation)- , testProperty "resumption" (monadicIO prop_handshake_session_resumption)- ]
+ test/API.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}++module API where++import Control.Applicative+import Control.Monad+import Data.ByteString (ByteString)+import Data.Maybe+import Network.TLS+import Test.Hspec++checkCtxFinished :: Context -> IO ()+checkCtxFinished ctx = do+ mUnique <- getTLSUnique ctx+ mExporter <- getTLSExporter ctx+ when (isNothing (mUnique <|> mExporter)) $+ fail "unexpected channel binding"++recvDataAssert :: Context -> ByteString -> IO ()+recvDataAssert ctx expected = do+ got <- recvData ctx+ got `shouldBe` expected
+ test/Arbitrary.hs view
@@ -0,0 +1,452 @@+{-# LANGUAGE FlexibleInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Arbitrary where++import Control.Monad+import qualified Data.ByteString as B+import Data.List+import Data.Word+import Data.X509 (ExtKeyUsageFlag)+import Network.TLS+import Network.TLS.Extra.Cipher+import Network.TLS.Internal+import Test.QuickCheck++import Certificate+import PubKey++----------------------------------------------------------------++instance Arbitrary Version where+ arbitrary = elements [TLS12, TLS13]++instance Arbitrary ProtocolType where+ arbitrary =+ elements+ [ ProtocolType_ChangeCipherSpec+ , ProtocolType_Alert+ , ProtocolType_Handshake+ , ProtocolType_AppData+ ]++instance Arbitrary Header where+ arbitrary = Header <$> arbitrary <*> arbitrary <*> arbitrary++instance Arbitrary ClientRandom where+ arbitrary = ClientRandom <$> genByteString 32++instance Arbitrary ServerRandom where+ arbitrary = ServerRandom <$> genByteString 32++instance Arbitrary Session where+ arbitrary = do+ i <- choose (1, 2) :: Gen Int+ case i of+ 2 -> Session . Just <$> genByteString 32+ _ -> return $ Session Nothing++instance {-# OVERLAPS #-} Arbitrary [HashAndSignatureAlgorithm] where+ arbitrary = shuffle supportedSignatureSchemes++instance Arbitrary DigitallySigned where+ arbitrary = DigitallySigned . unsafeHead <$> arbitrary <*> genByteString 32++instance Arbitrary ExtensionRaw where+ arbitrary =+ let arbitraryContent = choose (0, 40) >>= genByteString+ in ExtensionRaw . ExtensionID <$> arbitrary <*> arbitraryContent++instance Arbitrary CertificateType where+ arbitrary =+ elements+ [ CertificateType_RSA_Sign+ , CertificateType_DSA_Sign+ , CertificateType_ECDSA_Sign+ ]++instance Arbitrary CipherId where+ arbitrary = CipherId <$> arbitrary++instance Arbitrary Handshake where+ arbitrary =+ oneof+ [ arbitrary >>= \ver -> do+ ClientHello+ <$> ( CH ver+ <$> arbitrary+ <*> arbitrary+ <*> arbitraryCiphersIds+ <*> arbitraryCompressionIDs+ <*> arbitraryHelloExtensions ver+ )+ , arbitrary >>= \ver ->+ ServerHello+ <$> ( SH ver+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitraryHelloExtensions ver+ )+ , Certificate . CertificateChain_ . CertificateChain+ <$> resize 2 (listOf arbitraryX509)+ , pure HelloRequest+ , pure ServerHelloDone+ , ClientKeyXchg . CKX_RSA <$> genByteString 48+ , CertRequest <$> arbitrary <*> arbitrary <*> listOf arbitraryDN+ , CertVerify <$> arbitrary+ , Finished . VerifyData <$> genByteString 12+ ]++instance Arbitrary Handshake13 where+ arbitrary =+ oneof+ [ arbitrary >>= \ver ->+ ServerHello13+ <$> ( SH TLS12+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> pure 0+ <*> arbitraryHelloExtensions ver+ )+ , NewSessionTicket13+ <$> arbitrary+ <*> arbitrary+ <*> (TicketNonce <$> genByteString 32) -- nonce+ <*> (SessionIDorTicket_ <$> genByteString 32) -- session ID+ <*> arbitrary+ , pure EndOfEarlyData13+ , EncryptedExtensions13 <$> arbitrary+ , CertRequest13+ <$> arbitraryCertReqContext+ <*> arbitrary+ , resize 2 (listOf arbitraryX509) >>= \certs ->+ Certificate13+ <$> arbitraryCertReqContext+ <*> return (CertificateChain_ (CertificateChain certs))+ <*> replicateM (length certs) arbitrary+ , CertVerify13+ <$> ( DigitallySigned . unsafeHead+ <$> arbitrary+ <*> genByteString 32+ )+ , Finished13 . VerifyData <$> genByteString 12+ , KeyUpdate13 <$> elements [UpdateNotRequested, UpdateRequested]+ ]++----------------------------------------------------------------++arbitraryCiphersIds :: Gen [CipherId]+arbitraryCiphersIds = map CipherId <$> (choose (0, 200) >>= vector)++arbitraryCompressionIDs :: Gen [Word8]+arbitraryCompressionIDs = choose (0, 200) >>= vector++someWords8 :: Int -> Gen [Word8]+someWords8 = vector++arbitraryHelloExtensions :: Version -> Gen [ExtensionRaw]+arbitraryHelloExtensions _ver = arbitrary++arbitraryCertReqContext :: Gen B.ByteString+arbitraryCertReqContext = oneof [return B.empty, genByteString 32]++----------------------------------------------------------------++knownCiphers :: [Cipher]+knownCiphers = ciphersuite_all++instance Arbitrary Cipher where+ arbitrary = elements knownCiphers++knownVersions :: [Version]+knownVersions = [TLS13, TLS12]++arbitraryVersions :: Gen [Version]+arbitraryVersions = sublistOf knownVersions++-- for performance reason P521, FFDHE6144, FFDHE8192 are not tested+knownGroups, knownECGroups, knownFFGroups :: [Group]+knownECGroups = [P256, P384, X25519, X448]+knownFFGroups = [FFDHE2048, FFDHE3072, FFDHE4096]+knownGroups = knownECGroups ++ knownFFGroups++defaultECGroup :: Group+defaultECGroup = P256 -- same as defaultECCurve++otherKnownECGroups :: [Group]+otherKnownECGroups = filter (/= defaultECGroup) knownECGroups++instance Arbitrary Group where+ arbitrary = elements knownGroups++instance {-# OVERLAPS #-} Arbitrary [Group] where+ arbitrary = sublistOf knownGroups++newtype EC = EC [Group] deriving (Show)++instance Arbitrary EC where+ arbitrary = EC <$> shuffle knownECGroups++newtype FFDHE = FFDHE [Group] deriving (Show)++instance Arbitrary FFDHE where+ arbitrary = FFDHE <$> shuffle knownFFGroups++isCredentialDSA :: (CertificateChain, PrivKey) -> Bool+isCredentialDSA (_, PrivKeyDSA _) = True+isCredentialDSA _ = False++----------------------------------------------------------------++arbitraryCredentialsOfEachType :: Gen [(CertificateChain, PrivKey)]+arbitraryCredentialsOfEachType = arbitraryCredentialsOfEachType' >>= shuffle++arbitraryCredentialsOfEachType' :: Gen [(CertificateChain, PrivKey)]+arbitraryCredentialsOfEachType' = do+ let (pubKey, privKey) = getGlobalRSAPair+ curveName = defaultECCurve+ (ecdsaPub, ecdsaPriv) <- arbitraryECDSAPair curveName+ (ed25519Pub, ed25519Priv) <- arbitraryEd25519Pair+ (ed448Pub, ed448Priv) <- arbitraryEd448Pair+ mapM+ ( \(pub, priv) -> do+ cert <- arbitraryX509WithKey (pub, priv)+ return (CertificateChain [cert], priv)+ )+ [ (PubKeyRSA pubKey, PrivKeyRSA privKey)+ , (toPubKeyEC curveName ecdsaPub, toPrivKeyEC curveName ecdsaPriv)+ , (PubKeyEd25519 ed25519Pub, PrivKeyEd25519 ed25519Priv)+ , (PubKeyEd448 ed448Pub, PrivKeyEd448 ed448Priv)+ ]++arbitraryCredentialsOfEachCurve :: Gen [(CertificateChain, PrivKey)]+arbitraryCredentialsOfEachCurve = arbitraryCredentialsOfEachCurve' >>= shuffle++arbitraryCredentialsOfEachCurve' :: Gen [(CertificateChain, PrivKey)]+arbitraryCredentialsOfEachCurve' = do+ ecdsaPairs <-+ mapM+ ( \curveName -> do+ (ecdsaPub, ecdsaPriv) <- arbitraryECDSAPair curveName+ return (toPubKeyEC curveName ecdsaPub, toPrivKeyEC curveName ecdsaPriv)+ )+ knownECCurves+ (ed25519Pub, ed25519Priv) <- arbitraryEd25519Pair+ (ed448Pub, ed448Priv) <- arbitraryEd448Pair+ mapM+ ( \(pub, priv) -> do+ cert <- arbitraryX509WithKey (pub, priv)+ return (CertificateChain [cert], priv)+ )+ $ [ (PubKeyEd25519 ed25519Pub, PrivKeyEd25519 ed25519Priv)+ , (PubKeyEd448 ed448Pub, PrivKeyEd448 ed448Priv)+ ]+ ++ ecdsaPairs++----------------------------------------------------------------++leafPublicKey :: CertificateChain -> Maybe PubKey+leafPublicKey (CertificateChain []) = Nothing+leafPublicKey (CertificateChain (leaf : _)) = Just (certPubKey $ getCertificate leaf)++isLeafRSA :: Maybe CertificateChain -> Bool+isLeafRSA chain = case chain >>= leafPublicKey of+ Just (PubKeyRSA _) -> True+ _ -> False++arbitraryCipherPair :: Version -> Gen ([Cipher], [Cipher])+arbitraryCipherPair connectVersion = do+ serverCiphers <-+ arbitrary+ `suchThat` (\cs -> or [cipherAllowedForVersion connectVersion x | x <- cs])+ clientCiphers <-+ arbitrary+ `suchThat` ( \cs ->+ or+ [ x `elem` serverCiphers+ && cipherAllowedForVersion connectVersion x+ | x <- cs+ ]+ )+ return (clientCiphers, serverCiphers)++----------------------------------------------------------------++instance {-# OVERLAPS #-} Arbitrary (ClientParams, ServerParams) where+ arbitrary = elements knownVersions >>= arbitraryPairParamsAt++----------------------------------------------------------------++data GGP = GGP [Group] [Group] deriving (Show)++instance Arbitrary GGP where+ arbitrary = arbitraryGroupPair++-- Pair of groups so that at least the default EC group P256 and one FF group+-- are in common. This makes DHE and ECDHE ciphers always compatible with+-- extension "Supported Elliptic Curves" / "Supported Groups".+arbitraryGroupPair :: Gen GGP+arbitraryGroupPair = do+ (serverECGroups, clientECGroups) <-+ arbitraryGroupPairWith defaultECGroup otherKnownECGroups+ serverGroups <- shuffle serverECGroups+ clientGroups <- shuffle clientECGroups+ return $ GGP clientGroups serverGroups+ where+ arbitraryGroupPairWith e es = do+ s <- sublistOf es+ c <- sublistOf es+ return (e : s, e : c)++----------------------------------------------------------------++arbitraryPairParams12 :: Gen (ClientParams, ServerParams)+arbitraryPairParams12 = arbitraryPairParamsAt TLS12++arbitraryPairParams13 :: Gen (ClientParams, ServerParams)+arbitraryPairParams13 = arbitraryPairParamsAt TLS13++arbitraryPairParamsAt :: Version -> Gen (ClientParams, ServerParams)+arbitraryPairParamsAt connectVersion = do+ (clientCiphers, serverCiphers) <- arbitraryCipherPair connectVersion+ -- Select version lists containing connectVersion, as well as some other+ -- versions for which we have compatible ciphers. Criteria about cipher+ -- ensure we can test version downgrade.+ let allowedVersions =+ [ v+ | v <- knownVersions+ , or+ [ x `elem` serverCiphers+ && cipherAllowedForVersion v x+ | x <- clientCiphers+ ]+ ]+ allowedVersionsFiltered = filter (<= connectVersion) allowedVersions+ -- Server or client is allowed to have versions > connectVersion, but not+ -- both simultaneously.+ filterSrv <- arbitrary+ let (clientAllowedVersions, serverAllowedVersions)+ | filterSrv = (allowedVersions, allowedVersionsFiltered)+ | otherwise = (allowedVersionsFiltered, allowedVersions)+ -- Generate version lists containing less than 127 elements, otherwise the+ -- "supported_versions" extension cannot be correctly serialized+ clientVersions <- listWithOthers connectVersion 126 clientAllowedVersions+ serverVersions <- listWithOthers connectVersion 126 serverAllowedVersions+ arbitraryPairParamsWithVersionsAndCiphers+ (clientVersions, serverVersions)+ (clientCiphers, serverCiphers)+ where+ listWithOthers :: a -> Int -> [a] -> Gen [a]+ listWithOthers fixedElement maxOthers others+ | maxOthers < 1 = return [fixedElement]+ | otherwise = sized $ \n -> do+ num <- choose (0, min n maxOthers)+ pos <- choose (0, num)+ prefix <- vectorOf pos $ elements others+ suffix <- vectorOf (num - pos) $ elements others+ return $ prefix ++ (fixedElement : suffix)++----------------------------------------------------------------++getConnectVersion :: (ClientParams, ServerParams) -> Version+getConnectVersion (cparams, sparams) = maximum (cver `intersect` sver)+ where+ sver = supportedVersions (serverSupported sparams)+ cver = supportedVersions (clientSupported cparams)++isVersionEnabled :: Version -> (ClientParams, ServerParams) -> Bool+isVersionEnabled ver (cparams, sparams) =+ (ver `elem` supportedVersions (serverSupported sparams))+ && (ver `elem` supportedVersions (clientSupported cparams))++arbitraryPairParamsWithVersionsAndCiphers+ :: ([Version], [Version])+ -> ([Cipher], [Cipher])+ -> Gen (ClientParams, ServerParams)+arbitraryPairParamsWithVersionsAndCiphers (clientVersions, serverVersions) (clientCiphers, serverCiphers) = do+ secNeg <- arbitrary++ creds <- arbitraryCredentialsOfEachType+ GGP clientGroups serverGroups <- arbitraryGroupPair+ clientHashSignatures <- arbitrary+ serverHashSignatures <- arbitrary+ let serverState =+ defaultParamsServer+ { serverSupported =+ defaultSupported+ { supportedCiphers = serverCiphers+ , supportedVersions = serverVersions+ , supportedSecureRenegotiation = secNeg+ , supportedHashSignatures = serverHashSignatures+ , supportedGroups = serverGroups+ , supportedGroupsTLS13 = [serverGroups]+ }+ , serverShared = defaultShared{sharedCredentials = Credentials creds}+ }+ let clientState =+ (defaultParamsClient "" B.empty)+ { clientSupported =+ defaultSupported+ { supportedCiphers = clientCiphers+ , supportedVersions = clientVersions+ , supportedSecureRenegotiation = secNeg+ , supportedGroups = clientGroups+ , supportedHashSignatures = clientHashSignatures+ }+ , clientShared =+ defaultShared+ { sharedValidationCache =+ ValidationCache+ { cacheAdd = \_ _ _ -> return ()+ , cacheQuery = \_ _ _ -> return ValidationCachePass+ }+ }+ }+ return (clientState, serverState)++arbitraryClientCredential :: Version -> Gen Credential+arbitraryClientCredential _ = arbitraryCredentialsOfEachCurve' >>= elements++arbitraryRSACredentialWithUsage+ :: [ExtKeyUsageFlag] -> Gen (CertificateChain, PrivKey)+arbitraryRSACredentialWithUsage usageFlags = do+ let (pubKey, privKey) = getGlobalRSAPair+ cert <- arbitraryX509WithKeyAndUsage usageFlags (PubKeyRSA pubKey, ())+ return (CertificateChain [cert], PrivKeyRSA privKey)++instance {-# OVERLAPS #-} Arbitrary (EMSMode, EMSMode) where+ arbitrary = (,) <$> gen <*> gen+ where+ gen = elements [NoEMS, AllowEMS, RequireEMS]++setEMSMode+ :: (EMSMode, EMSMode)+ -> (ClientParams, ServerParams)+ -> (ClientParams, ServerParams)+setEMSMode (cems, sems) (clientParam, serverParam) = (clientParam', serverParam')+ where+ clientParam' =+ clientParam+ { clientSupported =+ (clientSupported clientParam)+ { supportedExtendedMainSecret = cems+ }+ }+ serverParam' =+ serverParam+ { serverSupported =+ (serverSupported serverParam)+ { supportedExtendedMainSecret = sems+ }+ }++genByteString :: Int -> Gen B.ByteString+genByteString i = B.pack <$> vector i++-- Just for preventing warnings of GHC 9.10+unsafeHead :: [a] -> a+unsafeHead [] = error "unsafeHead"+unsafeHead (x : _) = x
+ test/Certificate.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Certificate (+ arbitraryX509,+ arbitraryX509WithKey,+ arbitraryX509WithKeyAndUsage,+ arbitraryDN,+ simpleCertificate,+ simpleX509,+ getSignatureALG,+ toPubKeyEC,+ toPrivKeyEC,+) where++import Crypto.Number.Serialize (i2ospOf_)+import qualified Crypto.PubKey.ECC.ECDSA as ECDSA+import qualified Crypto.PubKey.ECC.Types as ECC+import Data.ASN1.OID+import qualified Data.ByteString as B+import Data.Hourglass+import Data.X509+import Test.QuickCheck++import PubKey++arbitraryDN :: Gen DistinguishedName+arbitraryDN = return $ DistinguishedName []++instance Arbitrary Date where+ arbitrary = do+ y <- choose (1971, 2035)+ m <- elements [January .. December]+ d <- choose (1, 30)+ return $ normalizeDate $ Date y m d++normalizeDate :: Date -> Date+normalizeDate d = timeConvert (timeConvert d :: Elapsed)++instance Arbitrary TimeOfDay where+ arbitrary = do+ h <- choose (0, 23)+ mi <- choose (0, 59)+ se <- choose (0, 59)+ let nsec = 0+ return $ TimeOfDay (Hours h) (Minutes mi) (Seconds se) nsec++instance Arbitrary DateTime where+ arbitrary = DateTime <$> arbitrary <*> arbitrary++maxSerial :: Integer+maxSerial = 16777216++arbitraryCertificate :: [ExtKeyUsageFlag] -> PubKey -> Gen Certificate+arbitraryCertificate usageFlags pubKey = do+ serial <- choose (0, maxSerial)+ subjectdn <- arbitraryDN+ validity <- (,) <$> arbitrary <*> arbitrary+ let sigalg = getSignatureALG pubKey+ return $+ Certificate+ { certVersion = 3+ , certSerial = serial+ , certSignatureAlg = sigalg+ , certIssuerDN = issuerdn+ , certSubjectDN = subjectdn+ , certValidity = validity+ , certPubKey = pubKey+ , certExtensions =+ Extensions $+ Just+ [ extensionEncode True $ ExtKeyUsage usageFlags+ ]+ }+ where+ issuerdn = DistinguishedName [(getObjectID DnCommonName, "Root CA")]++simpleCertificate :: PubKey -> Certificate+simpleCertificate pubKey =+ Certificate+ { certVersion = 3+ , certSerial = 0+ , certSignatureAlg = getSignatureALG pubKey+ , certIssuerDN = simpleDN+ , certSubjectDN = simpleDN+ , certValidity = (time1, time2)+ , certPubKey = pubKey+ , certExtensions =+ Extensions $+ Just+ [ extensionEncode True $+ ExtKeyUsage [KeyUsage_digitalSignature, KeyUsage_keyEncipherment]+ ]+ }+ where+ time1 = DateTime (Date 1999 January 1) (TimeOfDay 0 0 0 0)+ time2 = DateTime (Date 2049 January 1) (TimeOfDay 0 0 0 0)+ simpleDN = DistinguishedName []++simpleX509 :: PubKey -> SignedCertificate+simpleX509 pubKey =+ let cert = simpleCertificate pubKey+ sig = replicate 40 1+ sigalg = getSignatureALG pubKey+ (signedExact, ()) = objectToSignedExact (\_ -> (B.pack sig, sigalg, ())) cert+ in signedExact++arbitraryX509WithKey :: (PubKey, t) -> Gen SignedCertificate+arbitraryX509WithKey = arbitraryX509WithKeyAndUsage knownKeyUsage++arbitraryX509WithKeyAndUsage+ :: [ExtKeyUsageFlag] -> (PubKey, t) -> Gen SignedCertificate+arbitraryX509WithKeyAndUsage usageFlags (pubKey, _) = do+ cert <- arbitraryCertificate usageFlags pubKey+ sig <- resize 40 $ listOf1 arbitrary+ let sigalg = getSignatureALG pubKey+ let (signedExact, ()) = objectToSignedExact (\_ -> (B.pack sig, sigalg, ())) cert+ return signedExact++arbitraryX509 :: Gen SignedCertificate+arbitraryX509 = do+ let (pubKey, privKey) = getGlobalRSAPair+ arbitraryX509WithKey (PubKeyRSA pubKey, PrivKeyRSA privKey)++instance {-# OVERLAPS #-} Arbitrary [ExtKeyUsageFlag] where+ arbitrary = sublistOf knownKeyUsage++knownKeyUsage :: [ExtKeyUsageFlag]+knownKeyUsage =+ [ KeyUsage_digitalSignature+ , KeyUsage_keyEncipherment+ , KeyUsage_keyAgreement+ ]++getSignatureALG :: PubKey -> SignatureALG+getSignatureALG (PubKeyRSA _) = SignatureALG HashSHA1 PubKeyALG_RSA+getSignatureALG (PubKeyDSA _) = SignatureALG HashSHA1 PubKeyALG_DSA+getSignatureALG (PubKeyEC _) = SignatureALG HashSHA256 PubKeyALG_EC+getSignatureALG (PubKeyEd25519 _) = SignatureALG_IntrinsicHash PubKeyALG_Ed25519+getSignatureALG (PubKeyEd448 _) = SignatureALG_IntrinsicHash PubKeyALG_Ed448+getSignatureALG pubKey =+ error $ "getSignatureALG: unsupported public key: " ++ show pubKey++toPubKeyEC :: ECC.CurveName -> ECDSA.PublicKey -> PubKey+toPubKeyEC curveName key =+ let (x, y) = fromPoint $ ECDSA.public_q key+ pub = SerializedPoint bs+ bs = B.cons 4 (i2ospOf_ bytes x `B.append` i2ospOf_ bytes y)+ bits = ECC.curveSizeBits (ECC.getCurveByName curveName)+ bytes = (bits + 7) `div` 8+ in PubKeyEC (PubKeyEC_Named curveName pub)++toPrivKeyEC :: ECC.CurveName -> ECDSA.PrivateKey -> PrivKey+toPrivKeyEC curveName key =+ let priv = ECDSA.private_d key+ in PrivKeyEC (PrivKeyEC_Named curveName priv)++fromPoint :: ECC.Point -> (Integer, Integer)+fromPoint (ECC.Point x y) = (x, y)+fromPoint _ = error "fromPoint"
+ test/CiphersSpec.hs view
@@ -0,0 +1,76 @@+module CiphersSpec where++import qualified Data.ByteArray as BA+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Network.TLS.Cipher+import Network.TLS.Extra.Cipher+import Test.Hspec+import Test.Hspec.QuickCheck+import Test.QuickCheck++spec :: Spec+spec = do+ describe "ciphers" $ do+ prop "can ecnrypt/decrypt" $ \(BulkTest bulk key iv t additional) -> do+ let enc = bulkInit bulk BulkEncrypt key+ dec = bulkInit bulk BulkDecrypt key+ case (enc, dec) of+ (BulkStateBlock encF, BulkStateBlock decF) -> block encF decF iv t+ (BulkStateAEAD encF, BulkStateAEAD decF) -> aead encF decF iv t additional+ (BulkStateStream (BulkStream encF), BulkStateStream (BulkStream decF)) -> stream encF decF t+ _ -> return ()++block+ :: BulkBlock+ -> BulkBlock+ -> BulkIV+ -> ByteString+ -> IO ()+block e d iv t = do+ let (etxt, e_iv) = e iv t+ (dtxt, d_iv) = d iv etxt+ dtxt `shouldBe` t+ d_iv `shouldBe` e_iv++stream+ :: (ByteString -> (ByteString, BulkStream))+ -> (ByteString -> (ByteString, BulkStream))+ -> ByteString+ -> Expectation+stream e d t = (fst . d . fst . e) t `shouldBe` t++aead+ :: BulkAEAD+ -> BulkAEAD+ -> BulkNonce+ -> ByteString+ -> BulkAdditionalData+ -> Expectation+aead e d iv t additional = do+ let (encrypted, at) = e iv t additional+ (decrypted, at2) = d iv encrypted additional+ decrypted `shouldBe` t+ at `shouldBe` at2++arbitraryKey :: Bulk -> Gen BA.ScrubbedBytes+arbitraryKey bulk = BA.pack `fmap` vector (bulkKeySize bulk)++arbitraryIV :: Bulk -> Gen B.ByteString+arbitraryIV bulk = B.pack `fmap` vector (bulkIVSize bulk + bulkExplicitIV bulk)++arbitraryText :: Bulk -> Gen B.ByteString+arbitraryText bulk = B.pack `fmap` vector (bulkBlockSize bulk)++data BulkTest+ = BulkTest Bulk BA.ScrubbedBytes B.ByteString B.ByteString B.ByteString+ deriving (Show, Eq)++instance Arbitrary BulkTest where+ arbitrary = do+ bulk <- cipherBulk `fmap` elements ciphersuite_all+ BulkTest bulk+ <$> arbitraryKey bulk+ <*> arbitraryIV bulk+ <*> arbitraryText bulk+ <*> arbitraryText bulk
+ test/ECHSpec.hs view
@@ -0,0 +1,446 @@+{-# 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"
+ test/EncodeSpec.hs view
@@ -0,0 +1,39 @@+module EncodeSpec where++import Data.ByteString (ByteString)+import Network.TLS+import Network.TLS.Internal+import Test.Hspec+import Test.Hspec.QuickCheck++import Arbitrary ()++spec :: Spec+spec = do+ describe "encoder/decoder" $ do+ prop "can encode/decode Header" $ \x -> do+ decodeHeader (encodeHeader x) `shouldBe` Right x+ prop "can encode/decode Handshake" $ \x -> do+ decodeHs (encodeHandshake x) `shouldBe` Right x+ prop "can encode/decode Handshake13" $ \x -> do+ decodeHs13 (encodeHandshake13 x) `shouldBe` Right x++decodeHs :: ByteString -> Either TLSError Handshake+decodeHs b = verifyResult (decodeHandshake cp) $ decodeHandshakeRecord b+ where+ cp =+ CurrentParams+ { cParamsVersion = TLS12+ , cParamsKeyXchgType = Just CipherKeyExchange_RSA+ }++decodeHs13 :: ByteString -> Either TLSError Handshake13+decodeHs13 b = verifyResult decodeHandshake13 $ decodeHandshakeRecord13 b++verifyResult :: (f -> r -> a) -> GetResult (f, r) -> a+verifyResult fn result =+ case result of+ GotPartial _ -> error "got partial"+ GotError e -> error ("got error: " ++ show e)+ GotSuccessRemaining _ _ -> error "got remaining byte left"+ GotSuccess (ty, content) -> fn ty content
+ test/HandshakeSpec.hs view
@@ -0,0 +1,1088 @@+{-# LANGUAGE OverloadedStrings #-}++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.Extra.CipherCBC+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 handshake with TLS 1.2 CBC" handshake_cbc+ 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++--------------------------------------------------------------++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_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 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++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"++expectJust :: String -> Maybe a -> Expectation+expectJust tag mx = case mx of+ Nothing -> expectationFailure tag+ Just _ -> return ()
+ test/PipeChan.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE RecordWildCards #-}++-- create a similar concept than a unix pipe.+module PipeChan (+ PipeChan (..),+ newPipe,+ runPipe,+ readPipeC,+ readPipeS,+ writePipeC,+ writePipeS,+) where++import Control.Concurrent+import Control.Monad (forever)+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Data.IORef++----------------------------------------------------------------++-- | represent a unidirectional pipe with a buffered read channel and+-- a write channel+data UniPipeChan = UniPipeChan+ { getReadUniPipe :: Chan ByteString+ , getWriteUniPipe :: Chan ByteString+ }++newUniPipeChan :: IO UniPipeChan+newUniPipeChan = UniPipeChan <$> newChan <*> newChan++runUniPipe :: UniPipeChan -> IO ThreadId+runUniPipe UniPipeChan{..} =+ forkIO $+ forever $+ readChan getReadUniPipe >>= writeChan getWriteUniPipe++----------------------------------------------------------------++-- | Represent a bidirectional pipe with 2 nodes A and B+data PipeChan = PipeChan+ { fromC :: IORef ByteString+ , fromS :: IORef ByteString+ , c2s :: UniPipeChan+ , s2c :: UniPipeChan+ }++newPipe :: IO PipeChan+newPipe =+ PipeChan+ <$> newIORef B.empty+ <*> newIORef B.empty+ <*> newUniPipeChan+ <*> newUniPipeChan++runPipe :: PipeChan -> IO (ThreadId, ThreadId)+runPipe PipeChan{..} = (,) <$> runUniPipe c2s <*> runUniPipe s2c++readPipeC :: PipeChan -> Int -> IO ByteString+readPipeC PipeChan{..} sz = readBuffered fromS (getWriteUniPipe s2c) sz++writePipeC :: PipeChan -> ByteString -> IO ()+writePipeC PipeChan{..} = writeChan $ getWriteUniPipe c2s++readPipeS :: PipeChan -> Int -> IO ByteString+readPipeS PipeChan{..} sz = readBuffered fromC (getWriteUniPipe c2s) sz++writePipeS :: PipeChan -> ByteString -> IO ()+writePipeS PipeChan{..} = writeChan $ getReadUniPipe s2c++-- helper to read buffered data.+readBuffered :: IORef ByteString -> Chan ByteString -> Int -> IO ByteString+readBuffered ref chan sz = do+ left <- readIORef ref+ if B.length left >= sz+ then do+ let (ret, nleft) = B.splitAt sz left+ writeIORef ref nleft+ return ret+ else do+ let newSize = sz - B.length left+ newData <- readChan chan+ writeIORef ref newData+ remain <- readBuffered ref chan newSize+ return (left `B.append` remain)
+ test/PubKey.hs view
@@ -0,0 +1,123 @@+module PubKey (+ arbitraryRSAPair,+ arbitraryDSAPair,+ arbitraryECDSAPair,+ arbitraryEd25519Pair,+ arbitraryEd448Pair,+ globalRSAPair,+ getGlobalRSAPair,+ knownECCurves,+ defaultECCurve,+ dsaParams,+ rsaParams,+) where++import Control.Concurrent.MVar+import Crypto.Error+import qualified Crypto.PubKey.DSA as DSA+import qualified Crypto.PubKey.ECC.ECDSA as ECDSA+import qualified Crypto.PubKey.ECC.Prim as ECC+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.RSA as RSA+import Crypto.Random+import qualified Data.ByteString as B+import System.IO.Unsafe+import Test.QuickCheck++arbitraryRSAPair :: Gen (RSA.PublicKey, RSA.PrivateKey)+arbitraryRSAPair = (rngToRSA . drgNewTest) `fmap` arbitrary+ where+ rngToRSA :: ChaChaDRG -> (RSA.PublicKey, RSA.PrivateKey)+ rngToRSA rng = fst $ withDRG rng arbitraryRSAPairWithRNG++arbitraryRSAPairWithRNG :: MonadRandom m => m (RSA.PublicKey, RSA.PrivateKey)+arbitraryRSAPairWithRNG = RSA.generate 256 0x10001++{-# NOINLINE globalRSAPair #-}+globalRSAPair :: MVar (RSA.PublicKey, RSA.PrivateKey)+globalRSAPair = unsafePerformIO $ do+ drg <- drgNew+ newMVar (fst $ withDRG drg arbitraryRSAPairWithRNG)++{-# NOINLINE getGlobalRSAPair #-}+getGlobalRSAPair :: (RSA.PublicKey, RSA.PrivateKey)+getGlobalRSAPair = unsafePerformIO (readMVar globalRSAPair)++rsaParams :: (RSA.PublicKey, RSA.PrivateKey)+rsaParams = (pub, priv)+ where+ priv =+ RSA.PrivateKey+ { RSA.private_pub = pub+ , RSA.private_d = d+ , RSA.private_p = 0+ , RSA.private_q = 0+ , RSA.private_dP = 0+ , RSA.private_dQ = 0+ , RSA.private_qinv = 0+ }+ pub =+ RSA.PublicKey+ { RSA.public_size = 1024 `div` 8+ , RSA.public_n = n+ , RSA.public_e = e+ }+ n =+ 0x00c086b4c6db28ae578d73766d6fdd04b913808a85bf9ad7bcfc9a6ff04d13d2ff75f761ce7db9ee8996e29dc433d19a2d3f748e8d368ba099781d58276e1863a324ae3fb1a061874cd9f3510e54e49727c68de0616964335371cfb63f15ebff8ce8df09c74fb8625f8f58548b90f079a3405f522e738e664d0c645b015664f7c7+ e = 0x10001+ d =+ 0x3edc3cae28e4717818b1385ba7088d0038c3e176a606d2a5dbfc38cc46fe500824e62ec312fde04a803f61afac13a5b95c5c9c26b346879b54429083df488b4f29bb7b9722d366d6f5d2b512150a2e950eacfe0fd9dd56b87b0322f74ae3c8d8674ace62bc723f7c05e9295561efd70d7a924c6abac2e482880fc0149d5ad481++dsaParams :: DSA.Params+dsaParams =+ DSA.Params+ { DSA.params_p =+ 0x009f356bbc4750645555b02aa3918e85d5e35bdccd56154bfaa3e1801d5fe0faf65355215148ea866d5732fd27eb2f4d222c975767d2eb573513e460eceae327c8ac5da1f4ce765c49a39cae4c904b4e5cc64554d97148f20a2655027a0cf8f70b2550cc1f0c9861ce3a316520ab0588407ea3189d20c78bd52df97e56cbe0bbeb+ , DSA.params_q = 0x00f33a57b47de86ff836f9fe0bb060c54ab293133b+ , DSA.params_g =+ 0x3bb973c4f6eee92d1530f250487735595d778c2e5c8147d67a46ebcba4e6444350d49da8e7da667f9b1dbb22d2108870b9fcfabc353cdfac5218d829f22f69130317cc3b0d724881e34c34b8a2571d411da6458ef4c718df9e826f73e16a035b1dcbc1c62cac7a6604adb3e7930be8257944c6dfdddd655004b98253185775ff+ }++arbitraryDSAPair :: Gen (DSA.PublicKey, DSA.PrivateKey)+arbitraryDSAPair = do+ priv <- choose (1, DSA.params_q dsaParams)+ let pub = DSA.calculatePublic dsaParams priv+ return (DSA.PublicKey dsaParams pub, DSA.PrivateKey dsaParams priv)++-- for performance reason P521 is not tested+knownECCurves :: [ECC.CurveName]+knownECCurves =+ [ ECC.SEC_p256r1+ , ECC.SEC_p384r1+ , ECC.SEC_p521r1+ ]++defaultECCurve :: ECC.CurveName+defaultECCurve = ECC.SEC_p256r1++arbitraryECDSAPair :: ECC.CurveName -> Gen (ECDSA.PublicKey, ECDSA.PrivateKey)+arbitraryECDSAPair curveName = do+ d <- choose (1, n - 1)+ let p = ECC.pointBaseMul curve d+ return (ECDSA.PublicKey curve p, ECDSA.PrivateKey curve d)+ where+ curve = ECC.getCurveByName curveName+ n = ECC.ecc_n . ECC.common_curve $ curve++arbitraryEd25519Pair :: Gen (Ed25519.PublicKey, Ed25519.SecretKey)+arbitraryEd25519Pair = do+ bytes <- vectorOf 32 arbitrary+ let priv = fromCryptoPassed $ Ed25519.secretKey (B.pack bytes)+ return (Ed25519.toPublic priv, priv)++arbitraryEd448Pair :: Gen (Ed448.PublicKey, Ed448.SecretKey)+arbitraryEd448Pair = do+ bytes <- vectorOf 57 arbitrary+ let priv = fromCryptoPassed $ Ed448.secretKey (B.pack bytes)+ return (Ed448.toPublic priv, priv)++fromCryptoPassed :: CryptoFailable a -> a+fromCryptoPassed (CryptoPassed x) = x+fromCryptoPassed _ = error "fromCryptoPassed"
+ test/Run.hs view
@@ -0,0 +1,394 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-orphans #-}++module Run (+ runTLS,+ runTLSSimple,+ runTLSPredicate,+ runTLSSimple13,+ runTLSSimple13ECH,+ runTLS0RTT,+ runTLS0RTTech,+ runTLSSimpleKeyUpdate,+ runTLSCaptureFail,+ runTLSCapture13,+ runTLSSuccess,+ runTLSFailure,+ expectMaybe,+ newPairContext,+ withDataPipe,+ byeBye,+) where++import Control.Concurrent+import Control.Concurrent.Async+import qualified Control.Exception as E+import Control.Monad+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import Data.IORef+import Network.TLS+import System.Timeout+import Test.Hspec+import Test.QuickCheck++import API+import Arbitrary+import PipeChan++type ClinetWithInput = Chan ByteString -> Context -> IO ()+type ServerWithOutput = Context -> Chan [ByteString] -> IO ()++----------------------------------------------------------------++runTLS+ :: (ClientParams, ServerParams)+ -> ClinetWithInput+ -> ServerWithOutput+ -> IO ()+runTLS = runTLSN 1++runTLSN+ :: Int+ -> (ClientParams, ServerParams)+ -> ClinetWithInput+ -> ServerWithOutput+ -> IO ()+runTLSN n params tlsClient tlsServer = do+ inputChan <- newChan+ outputChan <- newChan+ -- generate some data to send+ ds <- replicateM n $ B.pack <$> generate (someWords8 256)+ forM_ ds $ writeChan inputChan+ -- run client and server+ withPairContext params $ \(cCtx, sCtx) ->+ concurrently_ (server sCtx outputChan) (client inputChan cCtx)+ -- read result+ mDs <- timeout 1000000 $ readChan outputChan -- 60 sec+ expectMaybe "timeout" ds mDs+ where+ server sCtx outputChan =+ E.catch+ (tlsServer sCtx outputChan)+ (printAndRaise "S: " (serverSupported $ snd params))+ client inputChan cCtx =+ E.catch+ (tlsClient inputChan cCtx)+ (printAndRaise "C: " (clientSupported $ fst params))+ printAndRaise :: String -> Supported -> E.SomeException -> IO ()+ printAndRaise s supported e = do+ putStrLn $+ s+ ++ " exception: "+ ++ show e+ ++ ", supported: "+ ++ show supported+ E.throwIO e++----------------------------------------------------------------++runTLSSimple :: (ClientParams, ServerParams) -> IO ()+runTLSSimple params = runTLSPredicate params (const True)++runTLSPredicate+ :: (ClientParams, ServerParams) -> (Maybe Information -> Bool) -> IO ()+runTLSPredicate params p = runTLSSuccess params hsClient hsServer+ where+ hsClient ctx = do+ handshake ctx+ checkInfoPredicate ctx+ hsServer ctx = do+ handshake ctx+ checkInfoPredicate ctx+ checkInfoPredicate ctx = do+ minfo <- contextGetInformation ctx+ unless (p minfo) $+ fail ("unexpected information: " ++ show minfo)++----------------------------------------------------------------++runTLSSimple13+ :: (ClientParams, ServerParams)+ -> HandshakeMode13+ -> IO ()+runTLSSimple13 params mode =+ runTLSSuccess params hsClient hsServer+ where+ hsClient ctx = do+ handshake ctx+ mmode <- (>>= infoTLS13HandshakeMode) <$> contextGetInformation ctx+ expectMaybe "C: mode should be Just" mode mmode+ hsServer ctx = do+ handshake ctx+ mmode <- (>>= infoTLS13HandshakeMode) <$> contextGetInformation ctx+ expectMaybe "S: mode should be Just" mode mmode++runTLSSimple13ECH+ :: (ClientParams, ServerParams)+ -> HandshakeMode13+ -> IO ()+runTLSSimple13ECH params mode =+ runTLSSuccess params hsClient hsServer+ where+ hsClient ctx = do+ handshake ctx+ minfo <- contextGetInformation ctx+ let mmode = minfo >>= infoTLS13HandshakeMode+ maccepted = infoIsECHAccepted <$> minfo+ expectMaybe "C: mode should be Just" mode mmode+ expectMaybe "C: TLS accepted should be Just" True maccepted+ hsServer ctx = do+ handshake ctx+ mmode <- (>>= infoTLS13HandshakeMode) <$> contextGetInformation ctx+ expectMaybe "S: mode should be Just" mode mmode++runTLS0RTT+ :: (ClientParams, ServerParams)+ -> HandshakeMode13+ -> ByteString+ -> IO ()+runTLS0RTT params mode earlyData =+ withPairContext params $ \(cCtx, sCtx) ->+ concurrently_ (tlsServer sCtx) (tlsClient cCtx)+ where+ tlsClient ctx = do+ handshake ctx+ sendData ctx $ L.fromStrict earlyData+ _ <- recvData ctx+ bye ctx+ mmode <- (>>= infoTLS13HandshakeMode) <$> contextGetInformation ctx+ expectMaybe "C: mode should be Just" mode mmode+ tlsServer ctx = do+ handshake ctx+ let ls = chunkLengths $ B.length earlyData+ chunks <- replicateM (length ls) $ recvData ctx+ (map B.length chunks, B.concat chunks) `shouldBe` (ls, earlyData)+ sendData ctx $ L.fromStrict earlyData+ bye ctx+ mmode <- (>>= infoTLS13HandshakeMode) <$> contextGetInformation ctx+ expectMaybe "S: mode should be Just" mode mmode+ chunkLengths :: Int -> [Int]+ chunkLengths len+ | len > 16384 = 16384 : chunkLengths (len - 16384)+ | len > 0 = [len]+ | otherwise = []++runTLS0RTTech+ :: (ClientParams, ServerParams)+ -> HandshakeMode13+ -> ByteString+ -> IO ()+runTLS0RTTech params mode earlyData =+ withPairContext params $ \(cCtx, sCtx) ->+ concurrently_ (tlsServer sCtx) (tlsClient cCtx)+ where+ tlsClient ctx = do+ handshake ctx+ sendData ctx $ L.fromStrict earlyData+ _ <- recvData ctx+ bye ctx+ minfo <- contextGetInformation ctx+ let mmode = minfo >>= infoTLS13HandshakeMode+ maccepted = infoIsECHAccepted <$> minfo+ expectMaybe "C: mode should be Just" mode mmode+ expectMaybe "C: TLS accepted should be Just" True maccepted+ tlsServer ctx = do+ handshake ctx+ let ls = chunkLengths $ B.length earlyData+ chunks <- replicateM (length ls) $ recvData ctx+ (map B.length chunks, B.concat chunks) `shouldBe` (ls, earlyData)+ sendData ctx $ L.fromStrict earlyData+ bye ctx+ mmode <- (>>= infoTLS13HandshakeMode) <$> contextGetInformation ctx+ expectMaybe "S: mode should be Just" mode mmode+ chunkLengths :: Int -> [Int]+ chunkLengths len+ | len > 16384 = 16384 : chunkLengths (len - 16384)+ | len > 0 = [len]+ | otherwise = []++expectMaybe :: (Show a, Eq a) => String -> a -> Maybe a -> Expectation+expectMaybe tag e mx = case mx of+ Nothing -> expectationFailure tag+ Just x -> x `shouldBe` e++runTLSCaptureFail+ :: (ClientParams, ServerParams) -> IO ([Handshake], [Handshake])+runTLSCaptureFail params = do+ sRef <- newIORef []+ cRef <- newIORef []+ runTLSFailure params (hsClient cRef) (hsServer sRef)+ sReceived <- readIORef sRef+ cReceived <- readIORef cRef+ return (reverse sReceived, reverse cReceived)+ where+ hsClient ref ctx = do+ installHook ctx ref+ handshake ctx+ sendData ctx "Foo"+ hsServer ref ctx = do+ installHook ctx ref+ handshake ctx+ _ <- recvData ctx+ return ()+ installHook ctx ref =+ let recv hss = modifyIORef ref (hss :) >> return hss+ in contextHookSetHandshakeRecv ctx recv++runTLSCapture13+ :: (ClientParams, ServerParams) -> IO ([Handshake13], [Handshake13])+runTLSCapture13 params = do+ sRef <- newIORef []+ cRef <- newIORef []+ runTLSSuccess params (hsClient cRef) (hsServer sRef)+ sReceived <- readIORef sRef+ cReceived <- readIORef cRef+ return (reverse sReceived, reverse cReceived)+ where+ hsClient ref ctx = do+ installHook ctx ref+ handshake ctx+ hsServer ref ctx = do+ installHook ctx ref+ handshake ctx+ installHook ctx ref =+ let recv hss = modifyIORef ref (hss :) >> return hss+ in contextHookSetHandshake13Recv ctx recv++runTLSSimpleKeyUpdate :: (ClientParams, ServerParams) -> IO ()+runTLSSimpleKeyUpdate params = runTLSN 3 params tlsClient tlsServer+ where+ tlsClient queue ctx = do+ handshake ctx+ d0 <- readChan queue+ sendData ctx (L.fromChunks [d0])+ d1 <- readChan queue+ sendData ctx (L.fromChunks [d1])+ req <- generate $ elements [OneWay, TwoWay]+ _ <- updateKey ctx req+ d2 <- readChan queue+ sendData ctx (L.fromChunks [d2])+ checkCtxFinished ctx+ bye ctx+ tlsServer ctx queue = do+ handshake ctx+ d0 <- recvData ctx+ req <- generate $ elements [OneWay, TwoWay]+ _ <- updateKey ctx req+ d1 <- recvData ctx+ d2 <- recvData ctx+ writeChan queue [d0, d1, d2]+ checkCtxFinished ctx+ bye ctx++----------------------------------------------------------------++runTLSSuccess+ :: (ClientParams, ServerParams)+ -> (Context -> IO ())+ -> (Context -> IO ())+ -> IO ()+runTLSSuccess params hsClient hsServer = runTLS params tlsClient tlsServer+ where+ tlsClient queue ctx = do+ hsClient ctx+ d <- readChan queue+ sendData ctx (L.fromChunks [d])+ checkCtxFinished ctx+ bye ctx+ tlsServer ctx queue = do+ hsServer ctx+ d <- recvData ctx+ writeChan queue [d]+ checkCtxFinished ctx+ bye ctx++runTLSFailure+ :: (ClientParams, ServerParams)+ -> (Context -> IO c)+ -> (Context -> IO s)+ -> IO ()+runTLSFailure params hsClient hsServer =+ withPairContext params $ \(cCtx, sCtx) ->+ concurrently_ (tlsServer sCtx) (tlsClient cCtx)+ where+ tlsClient ctx = hsClient ctx `shouldThrow` anyTLSException+ tlsServer ctx = hsServer ctx `shouldThrow` anyTLSException++anyTLSException :: Selector TLSException+anyTLSException = const True++----------------------------------------------------------------++debug :: Bool+debug = False++withPairContext+ :: (ClientParams, ServerParams) -> ((Context, Context) -> IO ()) -> IO ()+withPairContext params body =+ E.bracket+ (newPairContext params)+ (\((t1, t2), _) -> killThread t1 >> killThread t2)+ (\(_, ctxs) -> body ctxs)++newPairContext+ :: (ClientParams, ServerParams)+ -> IO ((ThreadId, ThreadId), (Context, Context))+newPairContext (cParams, sParams) = do+ pipe <- newPipe+ tids <- runPipe pipe+ let noFlush = return ()+ let noClose = return ()++ 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++ contextHookSetLogging cCtx' (logging "client: ")+ contextHookSetLogging sCtx' (logging "server: ")++ return (tids, (cCtx', sCtx'))+ where+ logging pre =+ if debug+ then+ defaultLogging+ { loggingPacketSent = putStrLn . ((pre ++ ">> ") ++)+ , loggingPacketRecv = putStrLn . ((pre ++ "<< ") ++)+ }+ else defaultLogging+++withDataPipe :: (ClientParams, ServerParams) -> (Context -> Chan result -> IO ()) -> (Chan start -> Context -> IO ()) -> ((start -> IO (), IO result) -> IO a) -> IO a+withDataPipe params tlsServer tlsClient cont = do+ -- initial setup+ startQueue <- newChan+ resultQueue <- newChan++ (cCtx, sCtx) <- snd <$> newPairContext params++ withAsync (E.catch (tlsServer sCtx resultQueue)+ (printAndRaise "server" (serverSupported $ snd params))) $ \sAsync -> withAsync (E.catch (tlsClient startQueue cCtx)+ (printAndRaise "client" (clientSupported $ fst params))) $ \cAsync -> do+ let readResult = waitBoth cAsync sAsync >> readChan resultQueue+ cont (writeChan startQueue, readResult)++ where+ printAndRaise :: String -> Supported -> E.SomeException -> IO ()+ printAndRaise s supported e = do+ putStrLn $ s ++ " exception: " ++ show e +++ ", supported: " ++ show supported+ E.throwIO e++-- Terminate the write direction and wait to receive the peer EOF. This is+-- necessary in situations where we want to confirm the peer status, or to make+-- sure to receive late messages like session tickets. In the test suite this+-- is used each time application code ends the connection without prior call to+-- 'recvData'.+byeBye :: Context -> IO ()+byeBye ctx = do+ bye ctx+ bs <- recvData ctx+ unless (B.null bs) $ fail "byeBye: unexpected application data"
+ test/Session.hs view
@@ -0,0 +1,92 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module Session (+ readClientSessionRef,+ clearClientSessionRef,+ twoSessionRefs,+ twoSessionManagers,+ setPairParamsSessionManagers,+ setPairParamsSessionResuming,+ oneSessionTicket,+) where++import Codec.Serialise+import Control.Monad+import qualified Data.ByteString.Lazy as L+import Data.IORef+import Network.TLS+import Network.TLS.Internal++----------------------------------------------------------------++readClientSessionRef :: (IORef (Maybe c), IORef (Maybe s)) -> IO (Maybe c)+readClientSessionRef refs = readIORef (fst refs)++clearClientSessionRef :: (IORef (Maybe c), IORef (Maybe s)) -> IO ()+clearClientSessionRef refs = writeIORef (fst refs) Nothing++twoSessionRefs :: IO (IORef (Maybe client), IORef (Maybe server))+twoSessionRefs = (,) <$> newIORef Nothing <*> newIORef Nothing++-- | simple session manager to store one session id and session data for a single thread.+-- a Real concurrent session manager would use an MVar and have multiples items.+oneSessionManager :: IORef (Maybe (SessionID, SessionData)) -> SessionManager+oneSessionManager ref =+ noSessionManager+ { sessionResume = \myId -> readIORef ref >>= maybeResume False myId+ , sessionResumeOnlyOnce = \myId -> readIORef ref >>= maybeResume True myId+ , sessionEstablish = \myId dat -> writeIORef ref (Just (myId, dat)) >> return Nothing+ , sessionInvalidate = \_ -> return ()+ , sessionUseTicket = False+ }+ where+ maybeResume onlyOnce myId (Just (sid, sdata))+ | sid == myId = when onlyOnce (writeIORef ref Nothing) >> return (Just sdata)+ maybeResume _ _ _ = return Nothing++twoSessionManagers+ :: (IORef (Maybe (SessionID, SessionData)), IORef (Maybe (SessionID, SessionData)))+ -> (SessionManager, SessionManager)+twoSessionManagers (cRef, sRef) = (oneSessionManager cRef, oneSessionManager sRef)++setPairParamsSessionManagers+ :: (SessionManager, SessionManager)+ -> (ClientParams, ServerParams)+ -> (ClientParams, ServerParams)+setPairParamsSessionManagers (clientManager, serverManager) (clientParams, serverParams) = (nc, ns)+ where+ nc =+ clientParams+ { clientShared = updateSessionManager clientManager $ clientShared clientParams+ }+ ns =+ serverParams+ { serverShared = updateSessionManager serverManager $ serverShared serverParams+ }+ updateSessionManager manager shared = shared{sharedSessionManager = manager}++----------------------------------------------------------------++setPairParamsSessionResuming+ :: (SessionID, SessionData)+ -> (ClientParams, ServerParams)+ -> (ClientParams, ServerParams)+setPairParamsSessionResuming sessionStuff (clientParams, serverParams) =+ ( clientParams{clientWantSessionResume = Just sessionStuff}+ , serverParams+ )++oneSessionTicket :: SessionManager+oneSessionTicket =+ noSessionManager+ { sessionResume = resume+ , sessionResumeOnlyOnce = resume+ , sessionEstablish = \_ dat -> return $ Just $ L.toStrict $ serialise dat+ , sessionInvalidate = \_ -> return ()+ , sessionUseTicket = True+ }++resume :: Ticket -> IO (Maybe SessionData)+resume ticket+ | isTicket ticket = return $ Just $ deserialise $ L.fromStrict ticket+ | otherwise = return Nothing
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/ThreadSpec.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE OverloadedStrings #-}++module ThreadSpec where++import Control.Concurrent+import Control.Concurrent.Async+import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as L+import Data.Foldable (traverse_)+import Network.TLS+import Test.Hspec+import Test.Hspec.QuickCheck++import API+import Arbitrary ()+import Run++spec :: Spec+spec = do+ describe "thread safety" $ do+ prop "can read/write concurrently" $ \params ->+ runTLS params tlsClient tlsServer++tlsClient :: Chan ByteString -> Context -> IO ()+tlsClient queue ctx = do+ handshake ctx+ runReaderWriters ctx "server-value" "client-value"+ d <- readChan queue+ sendData ctx (L.fromChunks [d])+ checkCtxFinished ctx+ bye ctx++tlsServer :: Context -> Chan [ByteString] -> IO ()+tlsServer ctx queue = do+ handshake ctx+ runReaderWriters ctx "client-value" "server-value"+ d <- recvData ctx+ writeChan queue [d]+ checkCtxFinished ctx+ bye ctx++runReaderWriters :: Context -> ByteString -> L.ByteString -> IO ()+runReaderWriters ctx r w =+ -- run concurrently 10 readers and 10 writers on the same context+ let workers = concat $ replicate 10 [recvDataAssert ctx r, sendData ctx w]+ in runConcurrently $ traverse_ Concurrently workers
tls.cabal view
@@ -1,154 +1,280 @@-Name: tls-Version: 1.2.8-Description:- Native Haskell TLS and SSL protocol implementation for server and client.- .- This provides a high-level implementation of a sensitive security protocol,- eliminating a common set of security issues through the use of the advanced- type system, high level constructions and common Haskell features.- .- Currently implement the SSL3.0, TLS1.0, TLS1.1 and TLS1.2 protocol,- and support RSA and Ephemeral Diffie Hellman key exchanges,- and many extensions.- .- Some debug tools linked with tls, are available through the- <http://hackage.haskell.org/package/tls-debug/>.-License: BSD3-License-file: LICENSE-Copyright: Vincent Hanquez <vincent@snarc.org>-Author: Vincent Hanquez <vincent@snarc.org>-Maintainer: Vincent Hanquez <vincent@snarc.org>-Synopsis: TLS/SSL protocol native implementation (Server and Client)-Build-Type: Simple-Category: Network-stability: experimental-Cabal-Version: >=1.8-Homepage: http://github.com/vincenthz/hs-tls-extra-source-files: Tests/*.hs- CHANGELOG.md+cabal-version: >=1.10+name: tls+version: 2.4.3+license: BSD3+license-file: LICENSE+copyright: Vincent Hanquez <vincent@snarc.org>+maintainer: Kazu Yamamoto <kazu@iij.ad.jp>+author: Vincent Hanquez <vincent@snarc.org>+homepage: https://github.com/haskell-tls/hs-tls+synopsis: TLS protocol native implementation+description:+ Native Haskell TLS 1.2/1.3 protocol implementation for servers and clients. -Flag compat- Description: Accept SSLv2 compatible handshake- Default: True+category: Network+build-type: Simple+extra-source-files:+ test/*.hs+ CHANGELOG.md -Library- Build-Depends: base >= 3 && < 5- , mtl- , cereal >= 0.4- , bytestring- , byteable- , network- , data-default-class- -- crypto related- , cryptohash >= 0.6- , crypto-random >= 0.0 && < 0.1- , crypto-numbers- , crypto-cipher-types >= 0.0.8- , crypto-pubkey >= 0.2.4- , crypto-pubkey-types >= 0.4- , cipher-rc4- , cipher-des- , cipher-aes >= 0.2 && < 0.3- -- certificate related- , asn1-types >= 0.2.0- , asn1-encoding- , x509 >= 1.4.3 && < 1.5.0- , x509-store >= 1.4.4- , x509-validation >= 1.5.0 && < 1.6.0- Exposed-modules: Network.TLS- Network.TLS.Cipher- Network.TLS.Compression- Network.TLS.Internal- Network.TLS.Extra- Network.TLS.Extra.Cipher- other-modules: Network.TLS.Cap- Network.TLS.Struct- Network.TLS.Core- Network.TLS.Context- Network.TLS.Context.Internal- Network.TLS.Credentials- Network.TLS.Backend- Network.TLS.Crypto- Network.TLS.Crypto.DH- Network.TLS.Extension- Network.TLS.Handshake- Network.TLS.Handshake.Common- Network.TLS.Handshake.Certificate- Network.TLS.Handshake.Key- Network.TLS.Handshake.Client- Network.TLS.Handshake.Server- Network.TLS.Handshake.Process- Network.TLS.Handshake.Signature- Network.TLS.Handshake.State- Network.TLS.Hooks- Network.TLS.IO- Network.TLS.MAC- Network.TLS.Measurement- Network.TLS.Packet- Network.TLS.Parameters- Network.TLS.Record- Network.TLS.Record.Types- Network.TLS.Record.Engage- Network.TLS.Record.Disengage- Network.TLS.Record.State- Network.TLS.RNG- Network.TLS.State- Network.TLS.Session- Network.TLS.Sending- Network.TLS.Receiving- Network.TLS.Util- Network.TLS.Util.ASN1- Network.TLS.Util.Serialization- Network.TLS.Types- Network.TLS.Wire- Network.TLS.X509- ghc-options: -Wall -fwarn-tabs- if impl(ghc == 7.6.1)- ghc-options: -O0- if flag(compat)- cpp-options: -DSSLV2_COMPATIBLE+source-repository head+ type: git+ location: https://github.com/haskell-tls/hs-tls+ subdir: tls -Test-Suite test-tls- type: exitcode-stdio-1.0- hs-source-dirs: Tests- Main-is: Tests.hs- Build-Depends: base >= 3 && < 5- , mtl- , cereal >= 0.3- , data-default-class- , QuickCheck >= 2- , test-framework- , test-framework-quickcheck2- , cprng-aes >= 0.5- , crypto-pubkey >= 0.2- , bytestring- , x509- , x509-validation- , tls- , time- , crypto-random- , crypto-pubkey- ghc-options: -Wall -fno-warn-orphans -fno-warn-missing-signatures -fwarn-tabs+flag devel+ description: Development commands+ default: False -Benchmark bench-tls- hs-source-dirs: Benchmarks Tests- Main-Is: Benchmarks.hs- type: exitcode-stdio-1.0- Build-depends: base >= 4 && < 5- , tls- , x509- , x509-validation- , data-default-class- , crypto-random- , criterion- , cprng-aes- , mtl- , bytestring- , crypto-pubkey >= 0.2- , time- , QuickCheck >= 2+library+ exposed-modules:+ Network.TLS+ Network.TLS.Cipher+ Network.TLS.Compression+ Network.TLS.Internal+ Network.TLS.Extra+ Network.TLS.Extra.Cipher+ Network.TLS.Extra.CipherCBC+ Network.TLS.Extra.FFDHE+ Network.TLS.QUIC -source-repository head- type: git- location: git://github.com/vincenthz/hs-tls- subdir: core+ other-modules:+ Network.TLS.Struct+ Network.TLS.Struct13+ Network.TLS.Core+ Network.TLS.Context+ Network.TLS.Context.Internal+ Network.TLS.Credentials+ Network.TLS.Backend+ Network.TLS.Crypto+ Network.TLS.Crypto.DH+ Network.TLS.Crypto.IES+ Network.TLS.Crypto.Types+ Network.TLS.ErrT+ Network.TLS.Error+ Network.TLS.Extension+ Network.TLS.Handshake+ Network.TLS.Handshake.Certificate+ Network.TLS.Handshake.Client+ Network.TLS.Handshake.Client.ClientHello+ Network.TLS.Handshake.Client.Common+ Network.TLS.Handshake.Client.ServerHello+ Network.TLS.Handshake.Client.TLS12+ Network.TLS.Handshake.Client.TLS13+ Network.TLS.Handshake.Common+ Network.TLS.Handshake.Common13+ Network.TLS.Handshake.Control+ Network.TLS.Handshake.Key+ Network.TLS.Handshake.Random+ Network.TLS.Handshake.Server+ Network.TLS.Handshake.Server.ClientHello+ Network.TLS.Handshake.Server.ClientHello12+ Network.TLS.Handshake.Server.ClientHello13+ Network.TLS.Handshake.Server.Common+ Network.TLS.Handshake.Server.ServerHello12+ Network.TLS.Handshake.Server.ServerHello13+ Network.TLS.Handshake.Server.TLS12+ Network.TLS.Handshake.Server.TLS13+ Network.TLS.Handshake.Signature+ Network.TLS.Handshake.State+ Network.TLS.Handshake.State13+ Network.TLS.Handshake.TranscriptHash+ Network.TLS.HashAndSignature+ Network.TLS.Hooks+ Network.TLS.IO+ Network.TLS.IO.Decode+ Network.TLS.IO.Encode+ Network.TLS.Imports+ Network.TLS.KeySchedule+ Network.TLS.MAC+ Network.TLS.Measurement+ Network.TLS.Packet+ Network.TLS.Packet13+ Network.TLS.Parameters+ Network.TLS.PostHandshake+ Network.TLS.RNG+ Network.TLS.Record+ Network.TLS.Record.Decrypt+ Network.TLS.Record.Encrypt+ Network.TLS.Record.Layer+ Network.TLS.Record.Recv+ Network.TLS.Record.Send+ Network.TLS.Record.State+ Network.TLS.Record.Types+ Network.TLS.Session+ Network.TLS.State+ Network.TLS.Types+ Network.TLS.Types.Cipher+ Network.TLS.Types.Secret+ Network.TLS.Types.Session+ Network.TLS.Types.Version+ Network.TLS.Util+ Network.TLS.Util.ASN1+ Network.TLS.Util.Serialization+ Network.TLS.Wire+ Network.TLS.X509++ default-language: Haskell2010+ default-extensions: Strict StrictData+ ghc-options: -Wall+ build-depends:+ base >=4.9 && <5,+ base16-bytestring,+ bytestring >=0.10 && <0.13,+ cereal >=0.5.3 && <0.6,+ crypton >=1.1.2 && <1.2,+ 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,+ data-default,+ ech-config,+ hpke >=0.1.0 && <0.2,+ mlkem >= 0.2.0 && <0.3,+ 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.6,+ zlib >=0.7 && <0.8++executable tls-server+ main-is: tls-server.hs+ hs-source-dirs: util+ other-modules:+ Common+ Server+ Imports++ default-language: Haskell2010+ default-extensions: Strict StrictData+ ghc-options: -Wall -threaded -rtsopts+ build-depends:+ base >=4.9 && <5,+ base16-bytestring,+ bytestring,+ containers,+ crypton,+ crypton-x509-store,+ crypton-x509-system,+ ech-config,+ network,+ network-run,+ tls++ if flag(devel)++ else+ buildable: False++executable tls-client+ main-is: tls-client.hs+ hs-source-dirs: util+ other-modules:+ Client+ Common+ Imports++ default-language: Haskell2010+ default-extensions: Strict StrictData+ ghc-options: -Wall -threaded -rtsopts+ build-depends:+ base >=4.9 && <5,+ base16-bytestring,+ bytestring,+ crypton,+ crypton-x509-store,+ crypton-x509-system,+ ech-config,+ network,+ network-run >=0.5,+ tls++ if flag(devel)++ else+ buildable: False++test-suite spec+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ build-tool-depends: hspec-discover:hspec-discover+ hs-source-dirs: test+ other-modules:+ API+ Arbitrary+ Certificate+ CiphersSpec+ ECHSpec+ EncodeSpec+ HandshakeSpec+ PipeChan+ PubKey+ Run+ Session+ ThreadSpec++ default-language: Haskell2010+ default-extensions: Strict StrictData+ ghc-options: -Wall -threaded -rtsopts+ build-depends:+ base >=4.9 && <5,+ QuickCheck,+ async,+ base64-bytestring,+ bytestring,+ crypton,+ crypton-asn1-types,+ crypton-x509,+ crypton-x509-validation,+ ech-config,+ hspec,+ 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
+ util/Client.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Client (+ Aux (..),+ Cli,+ clientHTTP11,+ clientDNS,+) where++import qualified Data.ByteString.Base16 as BS16+import qualified Data.ByteString.Char8 as C8+import qualified Data.ByteString.Lazy.Char8 as CL8+import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as NE+import Network.Socket+import Network.TLS++import Imports++data Aux = Aux+ { auxAuthority :: HostName+ , auxPort :: ServiceName+ , auxDebugPrint :: String -> IO ()+ , auxShow :: ByteString -> IO ()+ , auxReadResumptionData :: IO [(SessionID, SessionData)]+ }++type Cli = Aux -> NonEmpty ByteString -> Context -> IO ()++clientHTTP11 :: Cli+clientHTTP11 aux@Aux{..} paths ctx = do+ sendData ctx $+ CL8.fromStrict $+ "GET "+ <> NE.head paths+ <> " HTTP/1.1\r\n"+ <> "Host: "+ <> C8.pack auxAuthority+ <> "\r\n"+ <> "Connection: close\r\n"+ <> "\r\n"+ consume ctx aux++clientDNS :: Cli+clientDNS Aux{..} _paths ctx = do+ sendData+ ctx+ "\x00\x2c\xdc\xe3\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\x03\x77\x77\x77\x07\x65\x78\x61\x6d\x70\x6c\x65\x03\x63\x6f\x6d\x00\x00\x01\x00\x01\x00\x00\x29\x04\xd0\x00\x00\x00\x00\x00\x00"+ bs <- recvData ctx+ auxShow $ "Reply: " <> BS16.encode bs+ auxShow "\n"++consume :: Context -> Aux -> IO ()+consume ctx Aux{..} = loop+ where+ loop = do+ bs <- recvData ctx+ if bs == ""+ then auxShow "\n"+ else auxShow bs >> loop
+ util/Common.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE CPP #-}+-- Disable this warning so we can still test deprecated functionality.+{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}++module Common (+ printDHParams,+ printGroups,+ readNumber,+ readDHParams,+ readGroups,+ getCertificateStore,+ getLogger,+ namedGroups,+ getInfo,+ printHandshakeInfo,+ showBytesHex,+) where++import qualified Data.ByteString.Base16 as B16+import qualified Data.ByteString.Char8 as C8+import Data.Char (isDigit)+import Data.X509.CertificateStore+import Network.TLS hiding (HostName)+import Network.TLS.Extra.FFDHE+import System.Exit+import System.X509++import Imports++namedDHParams :: [(String, DHParams)]+namedDHParams =+ [ ("ffdhe2048", ffdhe2048)+ , ("ffdhe3072", ffdhe3072)+ , ("ffdhe4096", ffdhe4096)+ , ("ffdhe6144", ffdhe6144)+ , ("ffdhe8192", ffdhe8192)+ ]++{- FOURMOLU_DISABLE -}+namedGroups :: [(String, Group)]+namedGroups =+ [ ("ffdhe2048", FFDHE2048)+ , ("ffdhe3072", FFDHE3072)+ , ("ffdhe4096", FFDHE4096)+ , ("ffdhe6144", FFDHE6144)+ , ("ffdhe8192", FFDHE8192)+ , ("p256", P256)+ , ("p384", P384)+ , ("p521", P521)+ , ("x25519", X25519)+ , ("x448", X448)+ , ("mlkem512", MLKEM512)+ , ("mlkem768", MLKEM768)+ , ("mlkem1024", MLKEM1024)+ , ("x25519mlkem768", X25519MLKEM768)+ , ("p256mlkem768", P256MLKEM768)+ , ("p384mlkem1024", P384MLKEM1024)+ ]+{- FOURMOLU_ENABLE -}++readNumber :: (Num a, Read a) => String -> Maybe a+readNumber s+ | all isDigit s = Just $ read s+ | otherwise = Nothing++readDHParams :: String -> IO (Maybe DHParams)+readDHParams s =+ case lookup s namedDHParams of+ Nothing -> (Just . read) `fmap` readFile s+ mparams -> return mparams++readGroups :: String -> [Group]+readGroups s = fromMaybe [] $ traverse (`lookup` namedGroups) (split ',' s)++printDHParams :: IO ()+printDHParams = do+ putStrLn "DH Parameters"+ putStrLn "====================================="+ forM_ namedDHParams $ \(name, _) -> putStrLn name+ putStrLn "(or /path/to/dhparams)"++printGroups :: IO ()+printGroups = do+ putStrLn "Groups"+ putStrLn "====================================="+ forM_ namedGroups $ \(name, _) -> putStrLn name++split :: Char -> String -> [String]+split _ "" = []+split c s = case break (c ==) s of+ ("", _ : rs) -> split c rs+ (s', "") -> [s']+ (s', _ : rs) -> s' : split c rs++getCertificateStore :: [FilePath] -> IO CertificateStore+getCertificateStore [] = getSystemCertificateStore+getCertificateStore paths = foldM readPathAppend mempty paths+ where+ readPathAppend acc path = do+ mstore <- readCertificateStore path+ case mstore of+ Nothing -> error ("invalid certificate store: " ++ path)+ Just st -> return $! mappend st acc++getLogger :: Maybe FilePath -> (String -> IO ())+getLogger Nothing = \_ -> return ()+getLogger (Just file) = \msg -> appendFile file (msg ++ "\n")++getInfo :: Context -> IO Information+getInfo ctx = do+ minfo <- contextGetInformation ctx+ case minfo of+ Nothing -> do+ putStrLn "Error: information cannot be obtained"+ exitFailure+ Just info -> return info++printHandshakeInfo :: Information -> IO ()+printHandshakeInfo i = do+ putStrLn $ "Version: " ++ show (infoVersion i)+ putStrLn $ "Cipher: " ++ show (infoCipher i)+ putStrLn $ "Compression: " ++ show (infoCompression i)+ putStrLn $ "Groups: " ++ maybe "(none)" show (infoSupportedGroup i)+ when (infoVersion i < TLS13) $ do+ putStrLn $ "Extended master secret: " ++ show (infoExtendedMainSecret i)+ putStrLn $ "Resumption: " ++ show (infoTLS12Resumption i)+ when (infoVersion i == TLS13) $ do+ putStrLn $ "Handshake mode: " ++ show (fromJust (infoTLS13HandshakeMode i))+ putStrLn $ "Early data accepted: " ++ show (infoIsEarlyDataAccepted i)+ putStrLn $ "Encrypted client hello accepted: " ++ show (infoIsECHAccepted i)++showBytesHex :: ByteString -> String+showBytesHex bs = C8.unpack $ B16.encode bs
+ util/Imports.hs view
@@ -0,0 +1,17 @@+module Imports (+ ByteString,+ module Control.Applicative,+ module Control.Monad,+ module Data.List,+ module Data.Maybe,+ module Data.Monoid,+ module Data.Word,+) where++import Control.Applicative+import Control.Monad+import Data.ByteString (ByteString)+import Data.List+import Data.Maybe+import Data.Monoid+import Data.Word
+ util/Server.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE OverloadedStrings #-}++module Server where++import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as C8+import qualified Data.ByteString.Lazy.Char8 as CL8+import Data.IORef+import Network.TLS+import Prelude hiding (getLine)++import Imports++-- "<>" creates *chunks* of lazy ByteString, resulting+-- many TLS fragments.+-- To prevent this, strict ByteString is created first and+-- converted into lazy one.+html :: CL8.ByteString+html =+ CL8.fromStrict $+ "HTTP/1.1 200 OK\r\n"+ <> "Context-Type: text/html\r\n"+ <> "Content-Length: "+ <> C8.pack (show (BS.length body))+ <> "\r\n"+ <> "\r\n"+ <> body+ where+ body = "<html><<body>Hello world!</body></html>"++server :: Context -> Bool -> IO ()+server ctx showRequest = do+ bs <- recvData ctx+ case C8.uncons bs of+ Nothing -> return ()+ Just ('A', _) -> do+ sendData ctx $ CL8.fromStrict bs+ echo ctx+ Just _ -> handleHTML ctx showRequest bs++echo :: Context -> IO ()+echo ctx = loop+ where+ loop = do+ bs <- recvData ctx+ when (bs /= "") $ do+ sendData ctx $ CL8.fromStrict bs+ loop++handleHTML :: Context -> Bool -> ByteString -> IO ()+handleHTML ctx showRequest ini = do+ getLine <- newSource ctx ini+ process getLine+ where+ process getLine = do+ bs <- getLine+ when ("GET /keyupdate" `BS.isPrefixOf` bs) $ do+ r <- updateKey ctx TwoWay+ putStrLn $ "Updating key..." ++ if r then "OK" else "NG"+ when ("GET /secret" `BS.isPrefixOf` bs) $ do+ r <- requestCertificate ctx+ putStrLn $ "Post handshake authentication..." ++ if r then "OK" else "NG"+ when (bs /= "") $ do+ when showRequest $ do+ BS.putStr bs+ BS.putStr "\n"+ consume getLine+ sendData ctx html+ consume getLine = do+ bs <- getLine+ when (bs /= "") $ do+ when showRequest $ do+ BS.putStr bs+ BS.putStr "\n"+ consume getLine++newSource :: Context -> ByteString -> IO (IO ByteString)+newSource ctx ini = do+ ref <- newIORef ini+ return $ getline ref+ where+ getline :: IORef ByteString -> IO ByteString+ getline ref = do+ bs0 <- readIORef ref+ case BS.breakSubstring "\n" bs0 of+ (_, "") -> do+ bs1 <- recvData ctx+ writeIORef ref (bs0 <> bs1)+ getline ref+ (bs1, bs2) -> do+ writeIORef ref $ BS.drop 1 bs2+ return $ BS.dropWhileEnd (== 0x0d) bs1
+ util/tls-client.hs view
@@ -0,0 +1,465 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Main where++import Control.Concurrent+import qualified Control.Exception as E+import qualified Data.ByteString.Base16 as BS16+import qualified Data.ByteString.Char8 as C8+import Data.IORef+import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as NE+import Data.X509.CertificateStore+import Network.Run.TCP+import Network.Socket+import Network.TLS hiding (is0RTTPossible)+import Network.TLS.ECH.Config+import Network.TLS.Internal (makeCipherShowPretty)+import System.Console.GetOpt+import System.Environment+import System.Exit+import System.X509++import Client+import Common+import Imports++data Options = Options+ { optDebugLog :: Bool+ , optShow :: Bool+ , optKeyLogFile :: Maybe FilePath+ , optGroups :: [Group]+ , optValidate :: Bool+ , optVerNego :: Bool+ , optResumption :: Bool+ , opt0RTT :: Bool+ , optRetry :: Bool+ , optVersions :: [Version]+ , optALPN :: String+ , optCertFile :: Maybe FilePath+ , optKeyFile :: Maybe FilePath+ , optECHConfigFile :: Maybe FilePath+ , optTraceKey :: Bool+ , optIPv4Only :: Bool+ , optIPv6Only :: Bool+ , optTrustedAnchor :: Maybe FilePath+ }+ deriving (Show)++defaultOptions :: Options+defaultOptions =+ Options+ { optDebugLog = False+ , optShow = False+ , optKeyLogFile = Nothing+ , optGroups = supportedGroups defaultSupported+ , optValidate = False+ , optVerNego = False+ , optResumption = False+ , opt0RTT = False+ , optRetry = False+ , optVersions = supportedVersions defaultSupported+ , optALPN = "http/1.1"+ , optCertFile = Nothing+ , optKeyFile = Nothing+ , optECHConfigFile = Nothing+ , optTraceKey = False+ , optIPv4Only = False+ , optIPv6Only = False+ , optTrustedAnchor = Nothing+ }++usage :: String+usage = "Usage: tls-client [OPTION] addr port [path]"++options :: [OptDescr (Options -> Options)]+options =+ [ Option+ ['d']+ ["debug"]+ (NoArg (\o -> o{optDebugLog = True}))+ "print debug info"+ , Option+ ['v']+ ["show-content"]+ (NoArg (\o -> o{optShow = True}))+ "print downloaded content"+ , Option+ ['l']+ ["key-log-file"]+ (ReqArg (\file o -> o{optKeyLogFile = Just file}) "<file>")+ "a file to store negotiated secrets"+ , Option+ ['g']+ ["groups"]+ (ReqArg (\gs o -> o{optGroups = readGroups gs}) "<groups>")+ "specify groups"+ , Option+ ['e']+ ["validate"]+ (NoArg (\o -> o{optValidate = True}))+ "validate server's certificate"+ , Option+ ['R']+ ["resumption"]+ (NoArg (\o -> o{optResumption = True}))+ "try session resumption"+ , Option+ ['Z']+ ["0rtt"]+ (NoArg (\o -> o{opt0RTT = True}))+ "try sending early data"+ , Option+ ['S']+ ["hello-retry"]+ (NoArg (\o -> o{optRetry = True}))+ "try client hello retry"+ , Option+ ['2']+ ["tls12"]+ (NoArg (\o -> o{optVersions = [TLS12]}))+ "use TLS 1.2"+ , Option+ ['3']+ ["tls13"]+ (NoArg (\o -> o{optVersions = [TLS13]}))+ "use TLS 1.3"+ , Option+ ['a']+ ["alpn"]+ (ReqArg (\a o -> o{optALPN = a}) "<alpn>")+ "set ALPN"+ , Option+ ['c']+ ["cert"]+ (ReqArg (\fl o -> o{optCertFile = Just fl}) "<file>")+ "certificate file"+ , Option+ ['k']+ ["key"]+ (ReqArg (\fl o -> o{optKeyFile = Just fl}) "<file>")+ "key file"+ , Option+ []+ ["ech-config"]+ (ReqArg (\fl o -> o{optECHConfigFile = Just fl}) "<file>")+ "ECH config file"+ , Option+ []+ ["trace-key"]+ (NoArg (\o -> o{optTraceKey = True}))+ "Trace transcript hash"+ , Option+ ['4']+ []+ (NoArg (\o -> o{optIPv4Only = True, optIPv6Only = False}))+ "IPv4 only"+ , Option+ ['6']+ []+ (NoArg (\o -> o{optIPv6Only = True, optIPv4Only = False}))+ "IPv6 only"+ , Option+ ['t']+ ["trusted-anchor"]+ (ReqArg (\fl o -> o{optTrustedAnchor = Just fl}) "<file>")+ "trusted anchor file"+ ]++showUsageAndExit :: String -> IO a+showUsageAndExit msg = do+ putStrLn msg+ putStrLn $ usageInfo usage options+ putStrLn $ " <groups> = " ++ intercalate "," (map fst namedGroups)+ exitFailure++clientOpts :: [String] -> IO (Options, [String])+clientOpts argv =+ case getOpt Permute options argv of+ (o, n, []) -> return (foldl (flip id) defaultOptions o, n)+ (_, _, errs) -> showUsageAndExit $ concat errs++main :: IO ()+main = do+ args <- getArgs+ (opts@Options{..}, ips) <- clientOpts args+ (host, port, paths) <- case ips of+ [] -> showUsageAndExit usage+ _ : [] -> showUsageAndExit usage+ h : p : [] -> return (h, p, ["/"])+ h : p : ps -> return (h, p, C8.pack <$> NE.fromList ps)+ when (null optGroups) $ do+ putStrLn "Error: unsupported groups"+ exitFailure+ let onCertReq = \_ -> case optCertFile of+ Just certFile -> case optKeyFile of+ Just keyFile -> do+ Right (!cc, !priv) <- credentialLoadX509 certFile keyFile+ return $ Just (cc, priv)+ _ -> return Nothing+ _ -> return Nothing+ ref <- newIORef []+ let debug+ | optDebugLog = putStrLn+ | otherwise = \_ -> return ()+ traceKey+ | optTraceKey = putStrLn+ | otherwise = \_ -> return ()+ showContent+ | optShow = C8.putStr+ | otherwise = \_ -> return ()+ aux =+ Aux+ { auxAuthority = host+ , auxPort = port+ , auxDebugPrint = debug+ , auxShow = showContent+ , auxReadResumptionData = readIORef ref+ }+ mstore <- case optTrustedAnchor of+ Nothing ->+ if optValidate then Just <$> getSystemCertificateStore else return Nothing+ Just file -> do+ mstore' <- readCertificateStore file+ when (isNothing mstore') $ showUsageAndExit "cannot set trusted anchor"+ return mstore'+ echConfList <- case optECHConfigFile of+ Nothing -> return []+ Just ecnff ->+ loadECHConfigList ecnff `E.catch` \(E.SomeException _) -> do+ putStrLn $ ecnff ++ " is broken"+ exitFailure+ let cparams =+ getClientParams+ opts+ host+ port+ (smIORef ref)+ mstore+ onCertReq+ echConfList+ debug+ traceKey+ client+ | optALPN == "dot" = clientDNS+ | otherwise = clientHTTP11+ makeCipherShowPretty+ runClient opts client cparams aux paths++runClient+ :: Options -> Cli -> ClientParams -> Aux -> NonEmpty ByteString -> IO ()+runClient opts@Options{..} client cparams aux@Aux{..} paths = do+ auxDebugPrint "------------------------"+ (info1, msd) <- runTLS opts cparams aux $ \ctx -> do+ i1 <- getInfo ctx+ when optDebugLog $ printHandshakeInfo i1+ client aux paths ctx+ msd' <- auxReadResumptionData+ return (i1, msd')+ if+ | optResumption ->+ if isResumptionPossible msd+ then do+ let cparams2 = modifyClientParams cparams msd False+ info2 <- runClient2 opts client cparams2 aux paths+ if infoVersion info1 == TLS12+ then do+ if infoTLS12Resumption info2+ then do+ putStrLn "Result: (R) TLS resumption ... OK"+ exitSuccess+ else do+ putStrLn "Result: (R) TLS resumption ... NG"+ exitFailure+ else do+ if infoTLS13HandshakeMode info2 == Just PreSharedKey+ then do+ putStrLn "Result: (R) TLS resumption ... OK"+ exitSuccess+ else do+ putStrLn "Result: (R) TLS resumption ... NG"+ exitFailure+ else do+ putStrLn "Result: (R) TLS resumption ... NG"+ exitFailure+ | opt0RTT ->+ if is0RTTPossible info1 msd+ then do+ let cparams2 = modifyClientParams cparams msd True+ info2 <- runClient2 opts client cparams2 aux paths+ if infoTLS13HandshakeMode info2 == Just RTT0+ then do+ putStrLn "Result: (Z) 0-RTT ... OK"+ exitSuccess+ else do+ putStrLn "Result: (Z) 0-RTT ... NG"+ exitFailure+ else do+ putStrLn "Result: (Z) 0-RTT ... NG"+ exitFailure+ | optRetry ->+ if infoTLS13HandshakeMode info1 == Just HelloRetryRequest+ then do+ putStrLn "Result: (S) retry ... OK"+ exitSuccess+ else do+ putStrLn "Result: (S) retry ... NG"+ exitFailure+ | otherwise -> do+ putStrLn "Result: (H) handshake ... OK"+ when (optALPN == "http/1.1") $+ putStrLn "Result: (1) HTTP/1.1 transaction ... OK"+ exitSuccess++runClient2+ :: Options+ -> Cli+ -> ClientParams+ -> Aux+ -> NonEmpty ByteString+ -> IO Information+runClient2 opts@Options{..} client cparams aux@Aux{..} paths = do+ threadDelay 100000+ auxDebugPrint "<<<< next connection >>>>"+ auxDebugPrint "------------------------"+ runTLS opts cparams aux $ \ctx -> do+ if opt0RTT+ then do+ void $ client aux paths ctx+ i <- getInfo ctx+ when optDebugLog $ printHandshakeInfo i+ return i+ else do+ i <- getInfo ctx+ when optDebugLog $ printHandshakeInfo i+ void $ client aux paths ctx+ return i++runTLS+ :: Options+ -> ClientParams+ -> Aux+ -> (Context -> IO a)+ -> IO a+runTLS Options{..} cparams Aux{..} action =+ runTCPClientWithSettings settings auxAuthority auxPort $ \sock -> do+ ctx <- contextNew sock cparams+ when optDebugLog $+ contextHookSetLogging+ ctx+ defaultLogging+ { loggingPacketSent = putStrLn . (">> " ++)+ , loggingPacketRecv = putStrLn . ("<< " ++)+ -- , loggingIOSent = \bs -> putStrLn $ "}} " ++ showBytesHex bs+ -- , loggingIORecv = \hd bs -> putStrLn $ "{{ " ++ show hd ++ " " ++ showBytesHex bs+ }+ handshake ctx+ r <- action ctx+ bye ctx+ return r+ where+ select addrs+ | optIPv4Only = case NE.filter (\ai -> addrFamily ai == AF_INET) addrs of+ [] -> error "IPv4 address is not available"+ ai : _ -> ai+ | optIPv6Only = case NE.filter (\ai -> addrFamily ai == AF_INET6) addrs of+ [] -> error "IPv6 address is not available"+ ai : _ -> ai+ | otherwise = NE.head addrs+ settings =+ defaultSettings+ { settingsSelectAddrInfo = select+ }++modifyClientParams+ :: ClientParams -> [(SessionID, SessionData)] -> Bool -> ClientParams+modifyClientParams cparams ts early =+ cparams+ { clientWantSessionResumeList = ts+ , clientUseEarlyData = early+ }++getClientParams+ :: Options+ -> HostName+ -> ServiceName+ -> SessionManager+ -> Maybe CertificateStore+ -> OnCertificateRequest+ -> ECHConfigList+ -> (String -> IO ())+ -> (String -> IO ())+ -> ClientParams+getClientParams Options{..} serverName port sm mstore onCertReq echConfList printError traceKey =+ (defaultParamsClient serverName (C8.pack port))+ { clientSupported = supported+ , clientUseServerNameIndication = True+ , clientShared = shared+ , clientHooks = hooks+ , clientDebug = debug+ , clientUseECH = not (null echConfList)+ }+ where+ groups+ | optRetry = FFDHE8192 : optGroups+ | otherwise = optGroups+ shared =+ defaultShared+ { sharedSessionManager = sm+ , sharedCAStore = fromMaybe mempty mstore+ , sharedValidationCache = validateCache+ , sharedLimit =+ defaultLimit+ { limitRecordSize = Just 8192+ }+ , sharedECHConfigList = echConfList+ }+ supported =+ defaultSupported+ { supportedVersions = optVersions+ , supportedGroups = groups+ }+ hooks =+ defaultClientHooks+ { onSuggestALPN = return $ Just [C8.pack optALPN]+ , onCertificateRequest = onCertReq+ }+ validateCache+ | isJust mstore = sharedValidationCache defaultShared+ | otherwise =+ ValidationCache+ (\_ _ _ -> return ValidationCachePass)+ (\_ _ _ -> return ())+ debug =+ defaultDebugParams+ { debugKeyLogger = getLogger optKeyLogFile+ , debugError = printError+ , debugTraceKey = traceKey+ }++smIORef :: IORef [(SessionID, SessionData)] -> SessionManager+smIORef ref =+ noSessionManager+ { sessionEstablish = \sid sdata ->+ modifyIORef' ref (\xs -> (sid, sdata) : xs)+ >> printTicket sid sdata+ >> return Nothing+ }++printTicket :: SessionID -> SessionData -> IO ()+printTicket sid sdata = do+ C8.putStr $ "Ticket: " <> C8.take 16 (BS16.encode sid) <> "..., "+ putStrLn $ "0-RTT: " <> if sessionMaxEarlyDataSize sdata > 0 then "OK" else "NG"++isResumptionPossible :: [(SessionID, SessionData)] -> Bool+isResumptionPossible = not . null++is0RTTPossible :: Information -> [(SessionID, SessionData)] -> Bool+is0RTTPossible _ [] = False+is0RTTPossible info xs =+ infoVersion info == TLS13+ && any (\(_, sd) -> sessionMaxEarlyDataSize sd > 0) xs
+ util/tls-server.hs view
@@ -0,0 +1,270 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Main where++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.Internal+import System.Console.GetOpt+import System.Environment (getArgs)+import System.Exit+import System.IO+import System.X509++import Common+import Imports+import Server++data Options = Options+ { optDebugLog :: Bool+ , optClientAuth :: Bool+ , optShow :: Bool+ , optKeyLogFile :: Maybe FilePath+ , optTrustedAnchor :: Maybe FilePath+ , optGroups :: [Group]+ , optCertFile :: FilePath+ , optKeyFile :: FilePath+ , optECHConfigFile :: Maybe FilePath+ , optECHKeyFile :: Maybe FilePath+ , optTraceKey :: Bool+ }+ deriving (Show)++defaultOptions :: Options+defaultOptions =+ Options+ { optDebugLog = False+ , optClientAuth = False+ , optShow = False+ , optKeyLogFile = Nothing+ , optTrustedAnchor = Nothing+ , -- excluding FFDHE8192 for retry+ optGroups = FFDHE8192 `delete` supportedGroups defaultSupported+ , optCertFile = "servercert.pem"+ , optKeyFile = "serverkey.pem"+ , optECHConfigFile = Nothing+ , optECHKeyFile = Nothing+ , optTraceKey = False+ }++options :: [OptDescr (Options -> Options)]+options =+ [ Option+ ['a']+ ["client-auth"]+ (NoArg (\o -> o{optClientAuth = True}))+ "require client authentication"+ , Option+ ['d']+ ["debug"]+ (NoArg (\o -> o{optDebugLog = True}))+ "print debug info"+ , Option+ ['v']+ ["show-content"]+ (NoArg (\o -> o{optShow = True}))+ "print downloaded content"+ , Option+ ['l']+ ["key-log-file"]+ (ReqArg (\file o -> o{optKeyLogFile = Just file}) "<file>")+ "a file to store negotiated secrets"+ , Option+ ['g']+ ["groups"]+ (ReqArg (\gs o -> o{optGroups = readGroups gs}) "<groups>")+ "groups for key exchange"+ , Option+ ['c']+ ["cert"]+ (ReqArg (\fl o -> o{optCertFile = fl}) "<file>")+ "certificate file"+ , Option+ ['k']+ ["key"]+ (ReqArg (\fl o -> o{optKeyFile = fl}) "<file>")+ "key file"+ , Option+ ['t']+ ["trusted-anchor"]+ (ReqArg (\fl o -> o{optTrustedAnchor = Just fl}) "<file>")+ "trusted anchor file"+ , Option+ []+ ["ech-config"]+ (ReqArg (\fl o -> o{optECHConfigFile = Just fl}) "<file>")+ "ECH config file"+ , Option+ []+ ["ech-key"]+ (ReqArg (\fl o -> o{optECHKeyFile = Just fl}) "<file>")+ "ECH key file"+ , Option+ []+ ["trace-key"]+ (NoArg (\o -> o{optTraceKey = True}))+ "Trace transcript hash"+ ]++usage :: String+usage = "Usage: tls-server [OPTION] addr port"++showUsageAndExit :: String -> IO a+showUsageAndExit msg = do+ putStrLn msg+ putStrLn $ usageInfo usage options+ exitFailure++serverOpts :: [String] -> IO (Options, [String])+serverOpts argv =+ case getOpt Permute options argv of+ (o, n, []) -> return (foldl (flip id) defaultOptions o, n)+ (_, _, errs) -> showUsageAndExit $ concat errs++main :: IO ()+main = do+ hSetBuffering stdout NoBuffering+ args <- getArgs+ (Options{..}, ips) <- serverOpts args+ (host, port) <- case ips of+ [h, p] -> return (h, p)+ _ -> showUsageAndExit "cannot recognize <addr> and <port>\n"+ when (null optGroups) $ do+ putStrLn "Error: unsupported groups"+ exitFailure+ smgr <- newSessionManager+ Right cred@(!_cc, !_priv) <- credentialLoadX509 optCertFile optKeyFile+ mstore <- do+ mstore' <- case optTrustedAnchor of+ Nothing -> Just <$> getSystemCertificateStore+ Just file -> readCertificateStore file+ when (isNothing mstore') $ showUsageAndExit "cannot set trusted anchor"+ return mstore'+ ech <- case optECHKeyFile of+ Nothing -> case optECHConfigFile of+ Nothing -> return ([], [])+ Just _ -> showUsageAndExit "must specify ECH key file, too"+ Just ekeyf -> case optECHConfigFile of+ Nothing -> showUsageAndExit "must specify ECH config file, too"+ Just ecnff -> do+ ekey <- loadECHSecretKeys [ekeyf]+ ecnf <- loadECHConfigList ecnff+ return (ekey, ecnf)+ let keyLog = getLogger optKeyLogFile+ printError+ | optDebugLog = putStrLn+ | otherwise = \_ -> return ()+ traceKey+ | optTraceKey = putStrLn+ | otherwise = \_ -> return ()+ creds = Credentials [cred]+ makeCipherShowPretty+ runTCPServer (Just host) port $ \sock -> do+ let sparams =+ getServerParams+ creds+ optGroups+ smgr+ keyLog+ optClientAuth+ mstore+ ech+ printError+ traceKey+ ctx <- contextNew sock sparams+ when optDebugLog $+ contextHookSetLogging+ ctx+ defaultLogging+ { loggingPacketSent = putStrLn . ("<< " ++)+ , loggingPacketRecv = putStrLn . (">> " ++)+ -- , loggingIOSent = \bs -> putStrLn $ "{{ " ++ showBytesHex bs+ -- , loggingIORecv = \hd bs -> putStrLn $ "}} " ++ show hd ++ " " ++ showBytesHex bs+ }+ when (optDebugLog || optShow) $ putStrLn "------------------------"+ handshake ctx+ when optDebugLog $+ getInfo ctx >>= printHandshakeInfo+ server ctx optShow+ bye ctx++getServerParams+ :: Credentials+ -> [Group]+ -> SessionManager+ -> (String -> IO ())+ -> Bool+ -> Maybe CertificateStore+ -> ([(Word8, ByteString)], ECHConfigList)+ -> (String -> IO ())+ -> (String -> IO ())+ -> ServerParams+getServerParams creds groups sm keyLog clientAuth mstore (ekey, ecnf) printError traceKey =+ defaultParamsServer+ { serverSupported = supported+ , serverShared = shared+ , serverHooks = hooks+ , serverDebug = debug+ , serverEarlyDataSize = 2048+ , serverWantClientCert = clientAuth+ , serverECHKey = ekey+ }+ where+ shared =+ defaultShared+ { sharedCredentials = creds+ , sharedSessionManager = sm+ , sharedCAStore = case mstore of+ Just store -> store+ Nothing -> sharedCAStore defaultShared+ , sharedECHConfigList = ecnf+ , sharedLimit =+ defaultLimit+ { limitRecordSize = Just 16384+ }+ }+ supported =+ defaultSupported+ { supportedGroups = groups+ }+ hooks =+ defaultServerHooks+ { onALPNClientSuggest = Just chooseALPN+ , onClientCertificate = case mstore of+ Nothing -> onClientCertificate defaultServerHooks+ Just _ ->+ validateClientCertificate (sharedCAStore shared) (sharedValidationCache shared)+ }+ debug =+ defaultDebugParams+ { debugKeyLogger = keyLog+ , debugError = printError+ , debugTraceKey = traceKey+ }++chooseALPN :: [ByteString] -> IO ByteString+chooseALPN protos+ | "http/1.1" `elem` protos = return "http/1.1"+ | otherwise = return ""++newSessionManager :: IO SessionManager+newSessionManager = do+ ref <- newIORef M.empty+ return $+ noSessionManager+ { sessionResume = \key -> do+ 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)+ , sessionInvalidate = \key -> do+ atomicModifyIORef' ref $ \m -> (M.delete key m, ())+ , sessionUseTicket = False+ }