-
Notifications
You must be signed in to change notification settings - Fork 22
/
crypto.cpp
416 lines (337 loc) · 13.7 KB
/
crypto.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
/*
* ovpn-dco-win OpenVPN protocol accelerator for Windows
*
* Copyright (C) 2020-2021 OpenVPN Inc <[email protected]>
*
* Author: Lev Stipakov <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <ntddk.h>
#include <bcrypt.h>
#include "crypto.h"
#include "trace.h"
#include "pktid.h"
#include "socket.h"
UINT
OvpnCryptoOpCompose(UINT opcode, UINT keyId)
{
return (opcode << OVPN_OPCODE_SHIFT) | keyId;
}
static
UINT
OvpnProtoOp32Compose(UINT opcode, UINT keyId, UINT opPeerId)
{
UINT op8 = OvpnCryptoOpCompose(opcode, keyId);
if (opcode == OVPN_OP_DATA_V2)
return (op8 << 24) | (opPeerId & 0x00FFFFFF);
return op8;
}
OVPN_CRYPTO_DECRYPT OvpnCryptoDecryptNone;
_Use_decl_annotations_
NTSTATUS OvpnCryptoDecryptNone(OvpnCryptoKeySlot* keySlot, UCHAR* bufIn, SIZE_T len, UCHAR* bufOut, INT32 cryptoOptions)
{
UNREFERENCED_PARAMETER(keySlot);
BOOLEAN pktId64bit = cryptoOptions & CRYPTO_OPTIONS_64BIT_PKTID;
BOOLEAN cryptoOverhead = OVPN_DATA_V2_LEN + pktId64bit ? 8 : 4;
if (len < cryptoOverhead) {
LOG_WARN("Packet too short", TraceLoggingValue(len, "len"));
return STATUS_DATA_ERROR;
}
RtlCopyMemory(bufOut, bufIn, len);
return STATUS_SUCCESS;
}
OVPN_CRYPTO_ENCRYPT OvpnCryptoEncryptNone;
_Use_decl_annotations_
NTSTATUS
OvpnCryptoEncryptNone(OvpnCryptoKeySlot* keySlot, UCHAR* buf, SIZE_T len, INT32 cryptoOptions)
{
UNREFERENCED_PARAMETER(keySlot);
UNREFERENCED_PARAMETER(len);
UNREFERENCED_PARAMETER(cryptoOptions);
// prepend with opcode, key-id and peer-id
UINT32 op = OvpnProtoOp32Compose(OVPN_OP_DATA_V2, 0, 0);
op = RtlUlongByteSwap(op);
*(UINT32*)(buf) = op;
// prepend with pktid
static ULONG pktid;
ULONG pktidNetwork = RtlUlongByteSwap(pktid++);
*(UINT32*)(buf + OVPN_DATA_V2_LEN) = pktidNetwork;
return STATUS_SUCCESS;
}
_Use_decl_annotations_
NTSTATUS
OvpnCryptoInitAlgHandles(BCRYPT_ALG_HANDLE* aesAlgHandle, BCRYPT_ALG_HANDLE* chachaAlgHandle)
{
NTSTATUS status;
GOTO_IF_NOT_NT_SUCCESS(done, status, BCryptOpenAlgorithmProvider(aesAlgHandle, BCRYPT_AES_ALGORITHM, NULL, BCRYPT_PROV_DISPATCH));
GOTO_IF_NOT_NT_SUCCESS(done, status, BCryptSetProperty(*aesAlgHandle, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0));
// available starting from Windows 11
LOG_IF_NOT_NT_SUCCESS(BCryptOpenAlgorithmProvider(chachaAlgHandle, BCRYPT_CHACHA20_POLY1305_ALGORITHM, NULL, BCRYPT_PROV_DISPATCH));
done:
return status;
}
_Use_decl_annotations_
VOID
OvpnCryptoUninitAlgHandles(_In_ BCRYPT_ALG_HANDLE aesAlgHandle, BCRYPT_ALG_HANDLE chachaAlgHandle)
{
if (aesAlgHandle) {
LOG_IF_NOT_NT_SUCCESS(BCryptCloseAlgorithmProvider(aesAlgHandle, 0));
}
if (chachaAlgHandle) {
LOG_IF_NOT_NT_SUCCESS(BCryptCloseAlgorithmProvider(chachaAlgHandle, 0));
}
}
#define GET_SYSTEM_ADDRESS_MDL(buf, mdl) { \
buf = (PUCHAR)MmGetSystemAddressForMdlSafe(mdl, LowPagePriority | MdlMappingNoExecute); \
if (buf == NULL) { \
LOG_ERROR("MmGetSystemAddressForMdlSafe() returned NULL"); \
return STATUS_DATA_ERROR; \
} \
}
static
NTSTATUS
OvpnCryptoAEADDoWork(BOOLEAN encrypt, OvpnCryptoKeySlot* keySlot, UCHAR *bufIn, SIZE_T len, UCHAR* bufOut, INT32 cryptoOptions)
{
/*
AEAD Nonce :
[Packet ID] [HMAC keying material]
[4/8 bytes] [8/4 bytes ]
[AEAD nonce total : 12 bytes ]
TLS wire protocol :
Packet ID is 8 bytes long with CRYPTO_OPTIONS_64BIT_PKTID.
[DATA_V2 opcode] [Packet ID] [AEAD Auth tag] [ciphertext]
[4 bytes ] [4/8 bytes] [16 bytes ]
[AEAD additional data(AD) ]
With CRYPTO_OPTIONS_AEAD_TAG_END AEAD Auth tag is placed after ciphertext:
[DATA_V2 opcode] [Packet ID] [ciphertext] [AEAD Auth tag]
[4 bytes ] [4/8 bytes] [16 bytes ]
[AEAD additional data(AD) ]
*/
NTSTATUS status = STATUS_SUCCESS;
BOOLEAN pktId64bit = cryptoOptions & CRYPTO_OPTIONS_64BIT_PKTID;
SIZE_T cryptoOverhead = OVPN_DATA_V2_LEN + AEAD_AUTH_TAG_LEN + (pktId64bit ? 8 : 4);
if (len < cryptoOverhead) {
LOG_WARN("Packet too short", TraceLoggingValue(len, "len"));
return STATUS_DATA_ERROR;
}
UCHAR nonce[12];
if (encrypt) {
// prepend with opcode, key-id and peer-id
UINT32 op = OvpnProtoOp32Compose(OVPN_OP_DATA_V2, keySlot->KeyId, keySlot->PeerId);
op = RtlUlongByteSwap(op);
*reinterpret_cast<UINT32*>(bufOut) = op;
if (pktId64bit)
{
// calculate pktid
UINT64 pktid;
GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnPktidXmitNext(&keySlot->PktidXmit, &pktid, true));
ULONG64 pktidNetwork = RtlUlonglongByteSwap(pktid);
// calculate nonce, which is pktid + nonce_tail
RtlCopyMemory(nonce, &pktidNetwork, 8);
RtlCopyMemory(nonce + 8, keySlot->EncNonceTail, 4);
// prepend with pktid
*reinterpret_cast<UINT64*>(bufOut + OVPN_DATA_V2_LEN) = pktidNetwork;
}
else
{
// calculate pktid
UINT32 pktid;
GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnPktidXmitNext(&keySlot->PktidXmit, &pktid, false));
ULONG pktidNetwork = RtlUlongByteSwap(pktid);
// calculate nonce, which is pktid + nonce_tail
RtlCopyMemory(nonce, &pktidNetwork, 4);
RtlCopyMemory(nonce + 4, keySlot->EncNonceTail, 8);
// prepend with pktid
*reinterpret_cast<UINT32*>(bufOut + OVPN_DATA_V2_LEN) = pktidNetwork;
}
}
else {
ULONG64 pktId;
RtlCopyMemory(nonce, bufIn + OVPN_DATA_V2_LEN, pktId64bit ? 8 : 4);
RtlCopyMemory(nonce + (pktId64bit ? 8 : 4), &keySlot->DecNonceTail, pktId64bit ? 4 : 8);
if (pktId64bit)
{
pktId = RtlUlonglongByteSwap(*reinterpret_cast<UINT64*>(nonce));
}
else
{
pktId = static_cast<ULONG64>(RtlUlongByteSwap(*reinterpret_cast<UINT32*>(nonce)));
}
status = OvpnPktidRecvVerify(&keySlot->PktidRecv, pktId);
if (!NT_SUCCESS(status)) {
LOG_ERROR("Invalid pktId", TraceLoggingUInt64(pktId, "pktId"));
return STATUS_DATA_ERROR;
}
}
// we prepended buf with crypto overhead
len -= cryptoOverhead;
BOOLEAN aeadTagEnd = cryptoOptions & CRYPTO_OPTIONS_AEAD_TAG_END;
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
authInfo.pbNonce = nonce;
authInfo.cbNonce = sizeof(nonce);
authInfo.pbTag = (encrypt ? bufOut : bufIn) + OVPN_DATA_V2_LEN + (pktId64bit ? 8 : 4) + (aeadTagEnd ? len : 0);
authInfo.cbTag = AEAD_AUTH_TAG_LEN;
authInfo.pbAuthData = (encrypt ? bufOut : bufIn);
authInfo.cbAuthData = OVPN_DATA_V2_LEN + (pktId64bit ? 8 : 4);
auto payloadOffset = OVPN_DATA_V2_LEN + (pktId64bit ? 8 : 4) + (aeadTagEnd ? 0 : AEAD_AUTH_TAG_LEN);
bufOut += payloadOffset;
bufIn += payloadOffset;
// non-chaining mode
ULONG bytesDone = 0;
GOTO_IF_NOT_NT_SUCCESS(done, status, encrypt ?
BCryptEncrypt(keySlot->EncKey, bufIn, (ULONG)len, &authInfo, NULL, 0, bufOut, (ULONG)len, &bytesDone, 0) :
BCryptDecrypt(keySlot->DecKey, bufIn, (ULONG)len, &authInfo, NULL, 0, bufOut, (ULONG)len, &bytesDone, 0)
);
done:
return status;
}
OVPN_CRYPTO_DECRYPT OvpnCryptoDecryptAEAD;
_Use_decl_annotations_
NTSTATUS
OvpnCryptoDecryptAEAD(OvpnCryptoKeySlot* keySlot, UCHAR* bufIn, SIZE_T len, UCHAR* bufOut, INT32 cryptoOptions)
{
return OvpnCryptoAEADDoWork(FALSE, keySlot, bufIn, len, bufOut, cryptoOptions);
}
OVPN_CRYPTO_ENCRYPT OvpnCryptoEncryptAEAD;
_Use_decl_annotations_
NTSTATUS
OvpnCryptoEncryptAEAD(OvpnCryptoKeySlot* keySlot, UCHAR* buf, SIZE_T len, INT32 cryptoOptions)
{
return OvpnCryptoAEADDoWork(TRUE, keySlot, buf, len, buf, cryptoOptions);
}
_Use_decl_annotations_
NTSTATUS
OvpnCryptoNewKey(OvpnCryptoContext* cryptoContext, POVPN_CRYPTO_DATA_V2 cryptoDataV2)
{
OvpnCryptoKeySlot* keySlot = NULL;
NTSTATUS status = STATUS_SUCCESS;
POVPN_CRYPTO_DATA cryptoData = &cryptoDataV2->V1;
if (cryptoData->KeySlot == OVPN_KEY_SLOT::OVPN_KEY_SLOT_PRIMARY) {
keySlot = &cryptoContext->Primary;
}
else if (cryptoData->KeySlot == OVPN_KEY_SLOT::OVPN_KEY_SLOT_SECONDARY) {
keySlot = &cryptoContext->Secondary;
}
else {
LOG_ERROR("Invalid key slot", TraceLoggingValue((int)cryptoData->KeySlot, "keySlot"));
return STATUS_INVALID_DEVICE_REQUEST;
}
if (cryptoDataV2->CryptoOptions & CRYPTO_OPTIONS_64BIT_PKTID)
{
cryptoContext->CryptoOptions |= CRYPTO_OPTIONS_64BIT_PKTID;
}
if (cryptoDataV2->CryptoOptions & CRYPTO_OPTIONS_AEAD_TAG_END)
{
cryptoContext->CryptoOptions |= CRYPTO_OPTIONS_AEAD_TAG_END;
}
if ((cryptoData->CipherAlg == OVPN_CIPHER_ALG_AES_GCM) || (cryptoData->CipherAlg == OVPN_CIPHER_ALG_CHACHA20_POLY1305)) {
// destroy previous keys
if (keySlot->EncKey) {
BCryptDestroyKey(keySlot->EncKey);
keySlot->EncKey = NULL;
}
if (keySlot->DecKey) {
BCryptDestroyKey(keySlot->DecKey);
keySlot->DecKey = NULL;
}
BCRYPT_ALG_HANDLE algHandle = NULL;
if (cryptoData->CipherAlg == OVPN_CIPHER_ALG_AES_GCM) {
algHandle = cryptoContext->AesAlgHandle;
}
else {
if (cryptoContext->ChachaAlgHandle == NULL) {
LOG_ERROR("CHACHA20-POLY1305 is not available");
status = STATUS_INVALID_DEVICE_REQUEST;
goto done;
}
algHandle = cryptoContext->ChachaAlgHandle;
}
if ((cryptoData->Encrypt.KeyLen > 32) || (cryptoData->Decrypt.KeyLen > 32))
{
status = STATUS_INVALID_DEVICE_REQUEST;
LOG_ERROR("Incorrect encrypt or decrypt key length", TraceLoggingValue(cryptoData->Encrypt.KeyLen, "Encrypt.KeyLen"),
TraceLoggingValue(cryptoData->Decrypt.KeyLen, "Decrypt.KeyLen"));
goto done;
}
// generate keys from key materials
GOTO_IF_NOT_NT_SUCCESS(done, status, BCryptGenerateSymmetricKey(algHandle, &keySlot->EncKey, NULL, 0, cryptoData->Encrypt.Key, cryptoData->Encrypt.KeyLen, 0));
GOTO_IF_NOT_NT_SUCCESS(done, status, BCryptGenerateSymmetricKey(algHandle, &keySlot->DecKey, NULL, 0, cryptoData->Decrypt.Key, cryptoData->Decrypt.KeyLen, 0));
// copy nonce tails
RtlCopyMemory(keySlot->EncNonceTail, cryptoData->Encrypt.NonceTail, sizeof(cryptoData->Encrypt.NonceTail));
RtlCopyMemory(keySlot->DecNonceTail, cryptoData->Decrypt.NonceTail, sizeof(cryptoData->Decrypt.NonceTail));
cryptoContext->Encrypt = OvpnCryptoEncryptAEAD;
cryptoContext->Decrypt = OvpnCryptoDecryptAEAD;
keySlot->KeyId = cryptoData->KeyId;
keySlot->PeerId = cryptoData->PeerId;
LOG_INFO("New key", TraceLoggingValue(cryptoData->CipherAlg == OVPN_CIPHER_ALG_AES_GCM ? "aes-gcm" : "chacha20-poly1305", "alg"),
TraceLoggingValue(cryptoData->KeyId, "KeyId"), TraceLoggingValue(cryptoData->KeyId, "PeerId"));
}
else if (cryptoData->CipherAlg == OVPN_CIPHER_ALG_NONE) {
cryptoContext->Encrypt = OvpnCryptoEncryptNone;
cryptoContext->Decrypt = OvpnCryptoDecryptNone;
LOG_INFO("Using cipher none");
}
else {
status = STATUS_INVALID_DEVICE_REQUEST;
LOG_ERROR("Unknown OVPN_CIPHER_ALG", TraceLoggingValue((int)cryptoData->CipherAlg, "CipherAlg"));
goto done;
}
// reset pktid for a new key
RtlZeroMemory(&keySlot->PktidXmit, sizeof(keySlot->PktidXmit));
RtlZeroMemory(&keySlot->PktidRecv, sizeof(keySlot->PktidRecv));
done:
return status;
}
_Use_decl_annotations_
OvpnCryptoKeySlot*
OvpnCryptoKeySlotFromKeyId(OvpnCryptoContext* cryptoContext, unsigned int keyId)
{
if (cryptoContext->Primary.KeyId == keyId)
return &cryptoContext->Primary;
else if (cryptoContext->Secondary.KeyId == keyId) {
return &cryptoContext->Secondary;
}
LOG_ERROR("No KeySlot for KeyId", TraceLoggingValue(keyId, "KeyId"));
return NULL;
}
_Use_decl_annotations_
VOID
OvpnCryptoSwapKeys(OvpnCryptoContext* cryptoContext)
{
OvpnCryptoKeySlot keySlot;
RtlCopyMemory(&keySlot, &cryptoContext->Primary, sizeof(keySlot));
RtlCopyMemory(&cryptoContext->Primary, &cryptoContext->Secondary, sizeof(keySlot));
RtlCopyMemory(&cryptoContext->Secondary, &keySlot, sizeof(keySlot));
LOG_INFO("Key swapped", TraceLoggingValue(cryptoContext->Primary.KeyId, "key1"), TraceLoggingValue(cryptoContext->Secondary.KeyId, "key2"));
}
_Use_decl_annotations_
VOID
OvpnCryptoUninit(OvpnCryptoContext* cryptoContext)
{
if (cryptoContext->Primary.EncKey) {
BCryptDestroyKey(cryptoContext->Primary.EncKey);
}
if (cryptoContext->Primary.DecKey) {
BCryptDestroyKey(cryptoContext->Primary.DecKey);
}
if (cryptoContext->Secondary.EncKey) {
BCryptDestroyKey(cryptoContext->Secondary.EncKey);
}
if (cryptoContext->Secondary.DecKey) {
BCryptDestroyKey(cryptoContext->Secondary.DecKey);
}
RtlZeroMemory(cryptoContext, sizeof(OvpnCryptoContext));
}