File: Encrypt.hs

package info (click to toggle)
haskell-tls 2.1.8-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,056 kB
  • sloc: haskell: 15,695; makefile: 3
file content (130 lines) | stat: -rw-r--r-- 4,841 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
{-# 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 qualified Data.ByteArray as B (convert, xor)
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 = B.xor iv sqnc
            | otherwise = B.concat [iv, encodedSeq]
        (e, AuthTag authtag) = encryptF nonce content ad
        econtent
            | nonceExpLen == 0 = e `B.append` B.convert authtag
            | otherwise = B.concat [encodedSeq, e, B.convert authtag]
    modify incrRecordState
    return econtent

getCryptState :: RecordM CryptState
getCryptState = stCryptState <$> get