aboutsummaryrefslogtreecommitdiff
path: root/Tools/Hazel-Networking/Hazel/Dtls/AesGcmRecordProtection.cs
blob: 65df39e6f1708e4f30d86bda136ab6f8fed0490d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
using Hazel.Crypto;
using System;
using System.Diagnostics;

namespace Hazel.Dtls
{
    /// <summary>
    /// *_AES_128_GCM_* cipher suite
    /// </summary>
    public class Aes128GcmRecordProtection: IRecordProtection
    {
        private const int ImplicitNonceSize = 4;
        private const int ExplicitNonceSize = 8;

        private readonly Aes128Gcm serverWriteCipher;
        private readonly Aes128Gcm clientWriteCipher;

        private readonly ByteSpan serverWriteIV;
        private readonly ByteSpan clientWriteIV;

        /// <summary>
        /// Create a new instance of the AES128_GCM record protection
        /// </summary>
        /// <param name="masterSecret">Shared secret</param>
        /// <param name="serverRandom">Server random data</param>
        /// <param name="clientRandom">Client random data</param>
        public Aes128GcmRecordProtection(ByteSpan masterSecret, ByteSpan serverRandom, ByteSpan clientRandom)
        {
            ByteSpan combinedRandom = new byte[serverRandom.Length + clientRandom.Length];
            serverRandom.CopyTo(combinedRandom);
            clientRandom.CopyTo(combinedRandom.Slice(serverRandom.Length));

            // Expand master_secret to encryption keys
            const int ExpandedSize = 0
                + 0 // mac_key_length
                + 0 // mac_key_length
                + Aes128Gcm.KeySize // enc_key_length
                + Aes128Gcm.KeySize // enc_key_length
                + ImplicitNonceSize // fixed_iv_length
                + ImplicitNonceSize // fixed_iv_length
                ;

            ByteSpan expandedKey = new byte[ExpandedSize];
            PrfSha256.ExpandSecret(expandedKey, masterSecret, PrfLabel.KEY_EXPANSION, combinedRandom);

            ByteSpan clientWriteKey = expandedKey.Slice(0, Aes128Gcm.KeySize);
            ByteSpan serverWriteKey = expandedKey.Slice(Aes128Gcm.KeySize, Aes128Gcm.KeySize);
            this.clientWriteIV = expandedKey.Slice(2 * Aes128Gcm.KeySize, ImplicitNonceSize);
            this.serverWriteIV = expandedKey.Slice(2 * Aes128Gcm.KeySize + ImplicitNonceSize, ImplicitNonceSize);

            this.serverWriteCipher = new Aes128Gcm(serverWriteKey);
            this.clientWriteCipher = new Aes128Gcm(clientWriteKey);
        }

        /// <inheritdoc />
        public void Dispose()
        {
            this.serverWriteCipher.Dispose();
            this.clientWriteCipher.Dispose();
        }

        /// <inheritdoc />
        private static int GetEncryptedSizeImpl(int dataSize)
        {
            return dataSize + Aes128Gcm.CiphertextOverhead;
        }

        /// <inheritdoc />
        public int GetEncryptedSize(int dataSize)
        {
            return GetEncryptedSizeImpl(dataSize);
        }

        private static int GetDecryptedSizeImpl(int dataSize)
        {
            return dataSize - Aes128Gcm.CiphertextOverhead;
        }

        /// <inheritdoc />
        public int GetDecryptedSize(int dataSize)
        {
            return GetDecryptedSizeImpl(dataSize);
        }

        /// <inheritdoc />
        public void EncryptServerPlaintext(ByteSpan output, ByteSpan input, ref Record record)
        {
            EncryptPlaintext(output, input, ref record, this.serverWriteCipher, this.serverWriteIV);
        }

        /// <inheritdoc />
        public void EncryptClientPlaintext(ByteSpan output, ByteSpan input, ref Record record)
        {
            EncryptPlaintext(output, input, ref record, this.clientWriteCipher, this.clientWriteIV);
        }

        private static void EncryptPlaintext(ByteSpan output, ByteSpan input, ref Record record, Aes128Gcm cipher, ByteSpan writeIV)
        {
            Debug.Assert(output.Length >= GetEncryptedSizeImpl(input.Length));

            // Build GCM nonce (authenticated data)
            ByteSpan nonce = new byte[ImplicitNonceSize + ExplicitNonceSize];
            writeIV.CopyTo(nonce);
            nonce.WriteBigEndian16(record.Epoch, ImplicitNonceSize);
            nonce.WriteBigEndian48(record.SequenceNumber, ImplicitNonceSize + 2);

            // Serialize record as additional data
            Record plaintextRecord = record;
            plaintextRecord.Length = (ushort)input.Length;
            ByteSpan associatedData = new byte[Record.Size];
            plaintextRecord.Encode(associatedData);

            cipher.Seal(output, nonce, input, associatedData);
        }

        /// <inheritdoc />
        public bool DecryptCiphertextFromServer(ByteSpan output, ByteSpan input, ref Record record)
        {
            return DecryptCiphertext(output, input, ref record, this.serverWriteCipher, this.serverWriteIV);
        }

        /// <inheritdoc />
        public bool DecryptCiphertextFromClient(ByteSpan output, ByteSpan input, ref Record record)
        {
            return DecryptCiphertext(output, input, ref record, this.clientWriteCipher, this.clientWriteIV);
        }

        private static bool DecryptCiphertext(ByteSpan output, ByteSpan input, ref Record record, Aes128Gcm cipher, ByteSpan writeIV)
        {
            Debug.Assert(output.Length >= GetDecryptedSizeImpl(input.Length));

            // Build GCM nonce (authenticated data)
            ByteSpan nonce = new byte[ImplicitNonceSize + ExplicitNonceSize];
            writeIV.CopyTo(nonce);
            nonce.WriteBigEndian16(record.Epoch, ImplicitNonceSize);
            nonce.WriteBigEndian48(record.SequenceNumber, ImplicitNonceSize + 2);

            // Serialize record as additional data
            Record plaintextRecord = record;
            plaintextRecord.Length = (ushort)GetDecryptedSizeImpl(input.Length);
            ByteSpan associatedData = new byte[Record.Size];
            plaintextRecord.Encode(associatedData);

            return cipher.Open(output, nonce, input, associatedData);
        }
    }
}