Skip to content

Commit

Permalink
Implement DATA_V3 protocol features
Browse files Browse the repository at this point in the history
Add support for features:

 - AEAD auth tag at the end

 - 64bit pktid

For that, introduce a new OVPN_IOCTL_NEW_KEY_V2 ioctl,
which accepts a new data structure, which includes a
field to store bit flags for crypto options for
abovementioned features.

Refactor encrypt/decrypt routine to accomodate possible
wire format changes.

Refactor pktid code to support 64bit pktid. While on it,
remove unused variable.

Bump version to 1.4.0.

#74

Signed-off-by: Lev Stipakov <[email protected]>
  • Loading branch information
lstipakov committed Aug 15, 2024
1 parent f4adb27 commit e20c201
Show file tree
Hide file tree
Showing 14 changed files with 212 additions and 89 deletions.
6 changes: 6 additions & 0 deletions Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,12 @@ OvpnEvtIoDeviceControl(WDFQUEUE queue, WDFREQUEST request, size_t outputBufferLe
ExReleaseSpinLockExclusive(&device->SpinLock, kirql);
break;

case OVPN_IOCTL_NEW_KEY_V2:
kirql = ExAcquireSpinLockExclusive(&device->SpinLock);
status = OvpnPeerNewKeyV2(device, request);
ExReleaseSpinLockExclusive(&device->SpinLock, kirql);
break;

case OVPN_IOCTL_SWAP_KEYS:
kirql = ExAcquireSpinLockExclusive(&device->SpinLock);
status = OvpnPeerSwapKeys(device);
Expand Down
2 changes: 1 addition & 1 deletion PropertySheet.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros">
<OVPN_DCO_VERSION_MAJOR>1</OVPN_DCO_VERSION_MAJOR>
<OVPN_DCO_VERSION_MINOR>3</OVPN_DCO_VERSION_MINOR>
<OVPN_DCO_VERSION_MINOR>4</OVPN_DCO_VERSION_MINOR>
<OVPN_DCO_VERSION_PATCH>0</OVPN_DCO_VERSION_PATCH>
</PropertyGroup>
<PropertyGroup />
Expand Down
1 change: 0 additions & 1 deletion bufferpool.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ struct OVPN_RX_BUFFER
UCHAR Data[OVPN_SOCKET_RX_PACKET_BUFFER_SIZE];
};

_Must_inspect_result_
UCHAR*
OvpnTxBufferPut(_In_ OVPN_TX_BUFFER* work, SIZE_T len);

Expand Down
127 changes: 90 additions & 37 deletions crypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,14 @@ OvpnProtoOp32Compose(UINT opcode, UINT keyId, UINT opPeerId)
OVPN_CRYPTO_DECRYPT OvpnCryptoDecryptNone;

_Use_decl_annotations_
NTSTATUS OvpnCryptoDecryptNone(OvpnCryptoKeySlot* keySlot, UCHAR* bufIn, SIZE_T len, UCHAR* bufOut)
NTSTATUS OvpnCryptoDecryptNone(OvpnCryptoKeySlot* keySlot, UCHAR* bufIn, SIZE_T len, UCHAR* bufOut, INT32 cryptoOptions)
{
UNREFERENCED_PARAMETER(keySlot);

if (len < NONE_CRYPTO_OVERHEAD) {
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;
}
Expand All @@ -66,10 +69,11 @@ OVPN_CRYPTO_ENCRYPT OvpnCryptoEncryptNone;

_Use_decl_annotations_
NTSTATUS
OvpnCryptoEncryptNone(OvpnCryptoKeySlot* keySlot, UCHAR* buf, SIZE_T len)
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);
Expand Down Expand Up @@ -121,74 +125,116 @@ OvpnCryptoUninitAlgHandles(_In_ BCRYPT_ALG_HANDLE aesAlgHandle, BCRYPT_ALG_HANDL

static
NTSTATUS
OvpnCryptoAEADDoWork(BOOLEAN encrypt, OvpnCryptoKeySlot* keySlot, UCHAR *bufIn, SIZE_T len, UCHAR* bufOut)
OvpnCryptoAEADDoWork(BOOLEAN encrypt, OvpnCryptoKeySlot* keySlot, UCHAR *bufIn, SIZE_T len, UCHAR* bufOut, INT32 cryptoOptions)
{
/*
AEAD Nonce :
[Packet ID] [HMAC keying material]
[4 bytes ] [8 bytes ]
[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 bytes ] [16 bytes ]
[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;

if (len < AEAD_CRYPTO_OVERHEAD) {
BOOLEAN pktId64bit = cryptoOptions & CRYPTO_OPTIONS_64BIT_PKTID;

auto 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[OVPN_PKTID_LEN + OVPN_NONCE_TAIL_LEN];
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);
*(UINT32*)(bufOut) = op;
*reinterpret_cast<UINT32*>(bufOut) = op;

// calculate pktid
UINT32 pktid;
GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnPktidXmitNext(&keySlot->PktidXmit, &pktid));
ULONG pktidNetwork = RtlUlongByteSwap(pktid);
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);

// calculate nonce, which is pktid + nonce_tail
RtlCopyMemory(nonce, &pktidNetwork, OVPN_PKTID_LEN);
RtlCopyMemory(nonce + OVPN_PKTID_LEN, keySlot->EncNonceTail, OVPN_NONCE_TAIL_LEN);
// 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);

// prepend with pktid
*(UINT32*)(bufOut + OVPN_DATA_V2_LEN) = pktidNetwork;
// 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 {
RtlCopyMemory(nonce, bufIn + OVPN_DATA_V2_LEN, OVPN_PKTID_LEN);
RtlCopyMemory(nonce + OVPN_PKTID_LEN, &keySlot->DecNonceTail, sizeof(keySlot->DecNonceTail));
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)));
}

UINT32 pktId = RtlUlongByteSwap(*(UINT32*)nonce);
status = OvpnPktidRecvVerify(&keySlot->PktidRecv, pktId);

if (!NT_SUCCESS(status)) {
LOG_ERROR("Invalid pktId", TraceLoggingUInt32(pktId, "pktId"));
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 + OVPN_PKTID_LEN;
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 + OVPN_PKTID_LEN;

bufOut += AEAD_CRYPTO_OVERHEAD;
bufIn += AEAD_CRYPTO_OVERHEAD;
authInfo.cbAuthData = OVPN_DATA_V2_LEN + (pktId64bit ? 8 : 4);

len -= AEAD_CRYPTO_OVERHEAD;
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;
Expand All @@ -205,27 +251,29 @@ OVPN_CRYPTO_DECRYPT OvpnCryptoDecryptAEAD;

_Use_decl_annotations_
NTSTATUS
OvpnCryptoDecryptAEAD(OvpnCryptoKeySlot* keySlot, UCHAR* bufIn, SIZE_T len, UCHAR* bufOut)
OvpnCryptoDecryptAEAD(OvpnCryptoKeySlot* keySlot, UCHAR* bufIn, SIZE_T len, UCHAR* bufOut, INT32 cryptoOptions)
{
return OvpnCryptoAEADDoWork(FALSE, keySlot, bufIn, len, bufOut);
return OvpnCryptoAEADDoWork(FALSE, keySlot, bufIn, len, bufOut, cryptoOptions);
}

OVPN_CRYPTO_ENCRYPT OvpnCryptoEncryptAEAD;

_Use_decl_annotations_
NTSTATUS
OvpnCryptoEncryptAEAD(OvpnCryptoKeySlot* keySlot, UCHAR* buf, SIZE_T len)
OvpnCryptoEncryptAEAD(OvpnCryptoKeySlot* keySlot, UCHAR* buf, SIZE_T len, INT32 cryptoOptions)
{
return OvpnCryptoAEADDoWork(TRUE, keySlot, buf, len, buf);
return OvpnCryptoAEADDoWork(TRUE, keySlot, buf, len, buf, cryptoOptions);
}

_Use_decl_annotations_
NTSTATUS
OvpnCryptoNewKey(OvpnCryptoContext* cryptoContext, POVPN_CRYPTO_DATA cryptoData)
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;
}
Expand All @@ -237,6 +285,15 @@ OvpnCryptoNewKey(OvpnCryptoContext* cryptoContext, POVPN_CRYPTO_DATA cryptoData)
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) {
Expand Down Expand Up @@ -284,17 +341,13 @@ OvpnCryptoNewKey(OvpnCryptoContext* cryptoContext, POVPN_CRYPTO_DATA cryptoData)
keySlot->KeyId = cryptoData->KeyId;
keySlot->PeerId = cryptoData->PeerId;

cryptoContext->CryptoOverhead = AEAD_CRYPTO_OVERHEAD;

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;

cryptoContext->CryptoOverhead = NONE_CRYPTO_OVERHEAD;

LOG_INFO("Using cipher none");
}
else {
Expand Down
14 changes: 4 additions & 10 deletions crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,8 @@
#include "uapi\ovpn-dco.h"
#include "socket.h"

#define AEAD_CRYPTO_OVERHEAD 24 // 4 + 4 + 16 data_v2 + pktid + auth_tag
#define NONE_CRYPTO_OVERHEAD 8 // 4 + 4 data_v2 + pktid
#define OVPN_PKTID_LEN 4
#define OVPN_NONCE_TAIL_LEN 8
#define OVPN_DATA_V2_LEN 4
#define AEAD_AUTH_TAG_LEN 16
#define AES_BLOCK_SIZE 16
#define AES_GCM_NONCE_LEN 12

// packet opcode (high 5 bits) and key-id (low 3 bits) are combined in one byte
#define OVPN_OP_DATA_V2 9
Expand All @@ -62,15 +56,15 @@ _IRQL_requires_max_(DISPATCH_LEVEL)
_Must_inspect_result_
typedef
NTSTATUS
OVPN_CRYPTO_ENCRYPT(_In_ OvpnCryptoKeySlot* keySlot, _In_ UCHAR* buf, _In_ SIZE_T len);
OVPN_CRYPTO_ENCRYPT(_In_ OvpnCryptoKeySlot* keySlot, _In_ UCHAR* buf, _In_ SIZE_T len, _In_ INT32 CryptoOptions);
typedef OVPN_CRYPTO_ENCRYPT* POVPN_CRYPTO_ENCRYPT;

_Function_class_(OVPN_CRYPTO_DECRYPT)
_IRQL_requires_max_(DISPATCH_LEVEL)
_Must_inspect_result_
typedef
NTSTATUS
OVPN_CRYPTO_DECRYPT(_In_ OvpnCryptoKeySlot* keySlot, _In_ UCHAR* bufIn, _In_ SIZE_T len, _In_ UCHAR* bufOut);
OVPN_CRYPTO_DECRYPT(_In_ OvpnCryptoKeySlot* keySlot, _In_ UCHAR* bufIn, _In_ SIZE_T len, _In_ UCHAR* bufOut, _In_ INT32 CryptoOptions);
typedef OVPN_CRYPTO_DECRYPT* POVPN_CRYPTO_DECRYPT;

struct OvpnCryptoContext
Expand All @@ -84,7 +78,7 @@ struct OvpnCryptoContext
POVPN_CRYPTO_ENCRYPT Encrypt;
POVPN_CRYPTO_DECRYPT Decrypt;

SIZE_T CryptoOverhead;
INT32 CryptoOptions;
};

_Must_inspect_result_
Expand All @@ -101,7 +95,7 @@ OvpnCryptoUninit(_In_ OvpnCryptoContext* cryptoContext);

_Must_inspect_result_
NTSTATUS
OvpnCryptoNewKey(_In_ OvpnCryptoContext* cryptoContext, _In_ POVPN_CRYPTO_DATA cryptoData);
OvpnCryptoNewKey(_In_ OvpnCryptoContext* cryptoContext, _In_ POVPN_CRYPTO_DATA_V2 cryptoData);

_Must_inspect_result_
OvpnCryptoKeySlot*
Expand Down
26 changes: 26 additions & 0 deletions peer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,35 @@ OvpnPeerNewKey(POVPN_DEVICE device, WDFREQUEST request)
}

POVPN_CRYPTO_DATA cryptoData = NULL;
OVPN_CRYPTO_DATA_V2 cryptoDataV2{};
NTSTATUS status;

GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveInputBuffer(request, sizeof(OVPN_CRYPTO_DATA), (PVOID*)&cryptoData, nullptr));

RtlCopyMemory(&cryptoDataV2.V1, cryptoData, sizeof(OVPN_CRYPTO_DATA));
GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnCryptoNewKey(&device->CryptoContext, &cryptoDataV2));

done:
LOG_EXIT();

return status;
}

_Use_decl_annotations_
NTSTATUS
OvpnPeerNewKeyV2(POVPN_DEVICE device, WDFREQUEST request)
{
LOG_ENTER();

if (InterlockedCompareExchange(&device->UserspacePid, 0, 0) == 0) {
LOG_ERROR("Peer not added");
return STATUS_INVALID_DEVICE_REQUEST;
}

POVPN_CRYPTO_DATA_V2 cryptoData = NULL;
NTSTATUS status;

GOTO_IF_NOT_NT_SUCCESS(done, status, WdfRequestRetrieveInputBuffer(request, sizeof(OVPN_CRYPTO_DATA_V2), (PVOID*)&cryptoData, nullptr));
GOTO_IF_NOT_NT_SUCCESS(done, status, OvpnCryptoNewKey(&device->CryptoContext, cryptoData));

done:
Expand Down
5 changes: 5 additions & 0 deletions peer.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ _Requires_exclusive_lock_held_(device->SpinLock)
NTSTATUS
OvpnPeerNewKey(_In_ POVPN_DEVICE device, WDFREQUEST request);

_Must_inspect_result_
_Requires_exclusive_lock_held_(device->SpinLock)
NTSTATUS
OvpnPeerNewKeyV2(_In_ POVPN_DEVICE device, WDFREQUEST request);

_Must_inspect_result_
_Requires_exclusive_lock_held_(device->SpinLock)
NTSTATUS
Expand Down
Loading

0 comments on commit e20c201

Please sign in to comment.