Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement DATA_V3 protocol features #77

Merged
merged 1 commit into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;

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[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
Loading