From a4bd5ba8863c0959501d4604159042a311dae85a Mon Sep 17 00:00:00 2001 From: David Fort Date: Thu, 15 Feb 2024 10:27:11 +0100 Subject: [PATCH] core, channels: client-side remote credential guard This patch implements the client-side part of the remote credential guard feature as described in MS-RDPEAR. The 2 main changes are: shipping the TSRemoteGuardaCreds in NLA, and implement the rdpear channel that allows LSASS to remote all the calls to our client. For now it's UNIX only as the windows implementation would be implemented in a completely different way. To test, you may establish you ccache and then connect with (RCG enabled on the server): xfreerdp /remoteGuard /u: /d: /v That should log you in, and in the session you should not be asked for credentials when doing mstsc /remoteGuard /v:. --- channels/rdpear/CMakeLists.txt | 38 + channels/rdpear/ChannelOptions.cmake | 13 + channels/rdpear/client/CMakeLists.txt | 31 + channels/rdpear/client/rdpear_main.c | 965 +++++++++++++++++ channels/rdpear/common/CMakeLists.txt | 43 + channels/rdpear/common/ndr.c | 994 ++++++++++++++++++ channels/rdpear/common/rdpear-common/ndr.h | 192 ++++ .../rdpear/common/rdpear-common/rdpear_asn1.h | 31 + .../common/rdpear-common/rdpear_common.h | 244 +++++ channels/rdpear/common/rdpear_asn1.c | 104 ++ channels/rdpear/common/rdpear_common.c | 488 +++++++++ channels/rdpear/common/test/CMakeLists.txt | 33 + channels/rdpear/common/test/TestNdr.c | 36 + channels/rdpear/common/test/TestNdrEar.c | 357 +++++++ client/common/cmdline.c | 9 + client/common/cmdline.h | 2 + cmake/FindKRB5.cmake | 7 +- include/freerdp/channels/rdpear.h | 39 + include/freerdp/freerdp.h | 43 +- libfreerdp/core/connection.c | 1 + libfreerdp/core/credssp_auth.c | 12 + libfreerdp/core/credssp_auth.h | 5 + libfreerdp/core/freerdp.c | 26 + libfreerdp/core/nla.c | 122 +++ libfreerdp/core/nla.h | 3 + winpr/include/winpr/sspi.h | 2 + winpr/libwinpr/sspi/Kerberos/kerberos.c | 710 ++++++++----- winpr/libwinpr/utils/asn1/asn1.c | 4 +- winpr/libwinpr/utils/test/TestASN1.c | 12 + 29 files changed, 4302 insertions(+), 264 deletions(-) create mode 100644 channels/rdpear/CMakeLists.txt create mode 100644 channels/rdpear/ChannelOptions.cmake create mode 100644 channels/rdpear/client/CMakeLists.txt create mode 100644 channels/rdpear/client/rdpear_main.c create mode 100644 channels/rdpear/common/CMakeLists.txt create mode 100644 channels/rdpear/common/ndr.c create mode 100644 channels/rdpear/common/rdpear-common/ndr.h create mode 100644 channels/rdpear/common/rdpear-common/rdpear_asn1.h create mode 100644 channels/rdpear/common/rdpear-common/rdpear_common.h create mode 100644 channels/rdpear/common/rdpear_asn1.c create mode 100644 channels/rdpear/common/rdpear_common.c create mode 100644 channels/rdpear/common/test/CMakeLists.txt create mode 100644 channels/rdpear/common/test/TestNdr.c create mode 100644 channels/rdpear/common/test/TestNdrEar.c create mode 100644 include/freerdp/channels/rdpear.h diff --git a/channels/rdpear/CMakeLists.txt b/channels/rdpear/CMakeLists.txt new file mode 100644 index 000000000..31f98cca8 --- /dev/null +++ b/channels/rdpear/CMakeLists.txt @@ -0,0 +1,38 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2023 David Fort +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if (NOT IOS AND NOT WIN32 AND NOT ANDROID) + find_package(KRB5) + + if(KRB5_FOUND) + define_channel("rdpear") + + include_directories(common) + + if(WITH_CLIENT_CHANNELS OR WITH_SERVER_CHANNELS) + add_subdirectory(common) + endif() + + if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) + endif() + + #if(WITH_SERVER_CHANNELS) + # add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) + #endif() + endif() +endif() \ No newline at end of file diff --git a/channels/rdpear/ChannelOptions.cmake b/channels/rdpear/ChannelOptions.cmake new file mode 100644 index 000000000..3255ae1eb --- /dev/null +++ b/channels/rdpear/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "rdpear" TYPE "dynamic" + DESCRIPTION "Authentication redirection Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPEAR]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/rdpear/client/CMakeLists.txt b/channels/rdpear/client/CMakeLists.txt new file mode 100644 index 000000000..29d66f59b --- /dev/null +++ b/channels/rdpear/client/CMakeLists.txt @@ -0,0 +1,31 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2023 David Fort +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_client("rdpear") + +set(${MODULE_PREFIX}_SRCS + rdpear_main.c +) + +set(${MODULE_PREFIX}_LIBS + winpr + freerdp + rdpear-common +) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + diff --git a/channels/rdpear/client/rdpear_main.c b/channels/rdpear/client/rdpear_main.c new file mode 100644 index 000000000..f42ea86e9 --- /dev/null +++ b/channels/rdpear/client/rdpear_main.c @@ -0,0 +1,965 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Authentication redirection virtual channel + * + * Copyright 2023 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpear.client") + +/* defined in libkrb5 */ +krb5_error_code encode_krb5_authenticator(const krb5_authenticator* rep, krb5_data** code_out); +krb5_error_code encode_krb5_ap_rep(const krb5_ap_rep* rep, krb5_data** code_out); + +typedef struct +{ + GENERIC_DYNVC_PLUGIN base; + rdpContext* rdp_context; + krb5_context krbContext; +} RDPEAR_PLUGIN; + +const BYTE payloadHeader[16] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static krb5_error_code RPC_ENCRYPTION_KEY_to_keyblock(krb5_context ctx, + const KERB_RPC_ENCRYPTION_KEY* key, + krb5_keyblock** pkeyblock) +{ + WINPR_ASSERT(ctx); + WINPR_ASSERT(key); + WINPR_ASSERT(pkeyblock); + + if (!key->reserved3.length) + return KRB5KDC_ERR_NULL_KEY; + + krb5_error_code rv = + krb5_init_keyblock(ctx, (krb5_enctype)key->reserved2, key->reserved3.length, pkeyblock); + if (rv) + return rv; + + krb5_keyblock* keyblock = *pkeyblock; + memcpy(keyblock->contents, key->reserved3.value, key->reserved3.length); + return rv; +} + +static krb5_error_code kerb_do_checksum(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key, + krb5_keyusage kusage, krb5_cksumtype cksumtype, + const KERB_ASN1_DATA* plain, krb5_checksum* out) +{ + WINPR_ASSERT(ctx); + WINPR_ASSERT(key); + WINPR_ASSERT(plain); + WINPR_ASSERT(out); + + krb5_keyblock* keyblock = NULL; + krb5_data data = { 0 }; + + krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock); + if (rv) + return rv; + + data.data = (char*)plain->Asn1Buffer; + data.length = plain->Asn1BufferHints.count; + + rv = krb5_c_make_checksum(ctx, cksumtype, keyblock, kusage, &data, out); + + krb5_free_keyblock(ctx, keyblock); + return rv; +} + +static krb5_error_code kerb_do_encrypt(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key, + krb5_keyusage kusage, const KERB_ASN1_DATA* plain, + krb5_data* out) +{ + WINPR_ASSERT(ctx); + WINPR_ASSERT(key); + WINPR_ASSERT(plain); + WINPR_ASSERT(out); + + krb5_keyblock* keyblock = NULL; + krb5_data data = { 0 }; + krb5_enc_data enc = { 0 }; + size_t elen = 0; + + krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock); + if (rv) + return rv; + + data.data = (char*)plain->Asn1Buffer; + data.length = plain->Asn1BufferHints.count; + + rv = krb5_c_encrypt_length(ctx, keyblock->enctype, data.length, &elen); + if (rv) + goto out; + if (!elen) + { + rv = KRB5_PARSE_MALFORMED; + goto out; + } + enc.ciphertext.length = elen; + enc.ciphertext.data = malloc(elen); + if (!enc.ciphertext.data) + { + rv = ENOMEM; + goto out; + } + + rv = krb5_c_encrypt(ctx, keyblock, kusage, NULL, &data, &enc); + + out->data = enc.ciphertext.data; + out->length = enc.ciphertext.length; +out: + krb5_free_keyblock(ctx, keyblock); + return rv; +} + +static krb5_error_code kerb_do_decrypt(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key, + krb5_keyusage kusage, const krb5_data* cipher, + KERB_ASN1_DATA* plain) +{ + WINPR_ASSERT(ctx); + WINPR_ASSERT(key); + WINPR_ASSERT(cipher); + WINPR_ASSERT(cipher->length); + WINPR_ASSERT(plain); + + krb5_keyblock* keyblock = NULL; + krb5_data data = { 0 }; + krb5_enc_data enc = { 0 }; + + krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock); + if (rv) + return rv; + + enc.kvno = KRB5_PVNO; + enc.enctype = (krb5_enctype)key->reserved2; + enc.ciphertext.length = cipher->length; + enc.ciphertext.data = cipher->data; + + data.length = cipher->length; + data.data = (char*)malloc(cipher->length); + if (!data.data) + { + rv = ENOMEM; + goto out; + } + + rv = krb5_c_decrypt(ctx, keyblock, kusage, NULL, &enc, &data); + + plain->Asn1Buffer = (BYTE*)data.data; + plain->Asn1BufferHints.count = data.length; +out: + krb5_free_keyblock(ctx, keyblock); + return rv; +} + +static BOOL rdpear_send_payload(RDPEAR_PLUGIN* rdpear, IWTSVirtualChannelCallback* pChannelCallback, + RdpEarPackageType packageType, wStream* payload) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + BOOL ret = FALSE; + wStream* finalStream = NULL; + SecBuffer cryptedBuffer = { 0 }; + wStream* unencodedContent = rdpear_encodePayload(packageType, payload); + if (!unencodedContent) + goto out; + + size_t unencodedLen = Stream_GetPosition(unencodedContent); + SecBuffer inBuffer = { unencodedLen, SECBUFFER_DATA, Stream_Buffer(unencodedContent) }; + + if (!freerdp_nla_encrypt(rdpear->rdp_context, &inBuffer, &cryptedBuffer)) + goto out; + + finalStream = Stream_New(NULL, 200); + if (!finalStream) + goto out; + Stream_Write_UINT32(finalStream, 0x4EACC3C8); /* ProtocolMagic (4 bytes) */ + Stream_Write_UINT32(finalStream, cryptedBuffer.cbBuffer); /* Length (4 bytes) */ + Stream_Write_UINT32(finalStream, 0x00000000); /* Version (4 bytes) */ + Stream_Write_UINT32(finalStream, 0x00000000); /* Reserved (4 bytes) */ + Stream_Write_UINT64(finalStream, 0); /* TsPkgContext (8 bytes) */ + + /* payload */ + if (!Stream_EnsureRemainingCapacity(finalStream, cryptedBuffer.cbBuffer)) + goto out; + + Stream_Write(finalStream, cryptedBuffer.pvBuffer, cryptedBuffer.cbBuffer); + + UINT status = callback->channel->Write(callback->channel, Stream_GetPosition(finalStream), + Stream_Buffer(finalStream), NULL); + ret = (status == CHANNEL_RC_OK); + if (!ret) + WLog_DBG(TAG, "rdpear_send_payload=0x%x", status); +out: + sspi_SecBufferFree(&cryptedBuffer); + Stream_Free(unencodedContent, TRUE); + Stream_Free(finalStream, TRUE); + return ret; +} + +static BOOL rdpear_prepare_response(NdrContext* rcontext, UINT16 callId, UINT32 status, + NdrContext** pwcontext, wStream* retStream) +{ + WINPR_ASSERT(rcontext); + WINPR_ASSERT(pwcontext); + + BOOL ret = FALSE; + *pwcontext = NULL; + NdrContext* wcontext = ndr_context_copy(rcontext); + if (!wcontext) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(retStream, sizeof(payloadHeader))) + goto out; + + Stream_Write(retStream, payloadHeader, sizeof(payloadHeader)); + + if (!ndr_write_header(wcontext, retStream) || !ndr_start_constructed(wcontext, retStream) || + !ndr_write_pickle(wcontext, retStream) || /* pickle header */ + !ndr_write_uint16(wcontext, retStream, callId) || /* callId */ + !ndr_write_uint16(wcontext, retStream, 0x0000) || /* align padding */ + !ndr_write_uint32(wcontext, retStream, status) || /* status */ + !ndr_write_uint16(wcontext, retStream, callId) || /* callId */ + !ndr_write_uint16(wcontext, retStream, 0x0000)) /* align padding */ + goto out; + + *pwcontext = wcontext; + ret = TRUE; +out: + return ret; +} + +static BOOL rdpear_kerb_version(RDPEAR_PLUGIN* rdpear, IWTSVirtualChannelCallback* pChannelCallback, + NdrContext* rcontext, wStream* s, UINT32* pstatus, UINT32* pversion) +{ + *pstatus = ERROR_INVALID_DATA; + + if (!ndr_read_uint32(rcontext, s, pversion)) + return TRUE; + + WLog_DBG(TAG, "-> KerbNegotiateVersion(v=0x%x)", *pversion); + *pstatus = 0; + + return TRUE; +} + +static BOOL rdpear_kerb_ComputeTgsChecksum(RDPEAR_PLUGIN* rdpear, + IWTSVirtualChannelCallback* pChannelCallback, + NdrContext* rcontext, wStream* s, UINT32* pstatus, + KERB_ASN1_DATA* resp) +{ + ComputeTgsChecksumReq req = { 0 }; + krb5_checksum checksum = { 0 }; + wStream* asn1Payload = NULL; + + *pstatus = ERROR_INVALID_DATA; + WLog_DBG(TAG, "-> ComputeTgsChecksum"); + + if (!ndr_read_ComputeTgsChecksumReq(rcontext, s, NULL, &req) || + !ndr_treat_deferred_read(rcontext, s)) + goto out; + // ComputeTgsChecksumReq_dump(WLog_Get(""), WLOG_DEBUG, &req); + + krb5_error_code rv = + kerb_do_checksum(rdpear->krbContext, req.Key, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, + (krb5_cksumtype)req.ChecksumType, req.requestBody, &checksum); + if (rv) + goto out; + + asn1Payload = rdpear_enc_Checksum(req.ChecksumType, &checksum); + if (!asn1Payload) + goto out; + + resp->Pdu = 8; + resp->Asn1Buffer = Stream_Buffer(asn1Payload); + resp->Asn1BufferHints.count = Stream_GetPosition(asn1Payload); + *pstatus = 0; + +out: + ndr_destroy_ComputeTgsChecksumReq(rcontext, NULL, &req); + krb5_free_checksum_contents(rdpear->krbContext, &checksum); + Stream_Free(asn1Payload, FALSE); + return TRUE; +} + +static BOOL rdpear_kerb_BuildEncryptedAuthData(RDPEAR_PLUGIN* rdpear, + IWTSVirtualChannelCallback* pChannelCallback, + NdrContext* rcontext, wStream* s, UINT32* pstatus, + KERB_ASN1_DATA* asn1) +{ + BuildEncryptedAuthDataReq req = { 0 }; + krb5_data encrypted = { 0 }; + wStream* asn1Payload = NULL; + krb5_error_code rv = 0; + + *pstatus = ERROR_INVALID_DATA; + WLog_DBG(TAG, "-> BuildEncryptedAuthData"); + + if (!ndr_read_BuildEncryptedAuthDataReq(rcontext, s, NULL, &req) || + !ndr_treat_deferred_read(rcontext, s)) + goto out; + + rv = kerb_do_encrypt(rdpear->krbContext, req.Key, (krb5_keyusage)req.KeyUsage, + req.PlainAuthData, &encrypted); + if (rv) + goto out; + + /* do the encoding */ + asn1Payload = rdpear_enc_EncryptedData(req.Key->reserved2, &encrypted); + if (!asn1Payload) + goto out; + + // WLog_DBG(TAG, "rdpear_kerb_BuildEncryptedAuthData resp="); + // winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(asn1Payload), Stream_GetPosition(asn1Payload)); + asn1->Pdu = 6; + asn1->Asn1Buffer = Stream_Buffer(asn1Payload); + asn1->Asn1BufferHints.count = Stream_GetPosition(asn1Payload); + *pstatus = 0; + +out: + krb5_free_data_contents(rdpear->krbContext, &encrypted); + ndr_destroy_BuildEncryptedAuthDataReq(rcontext, NULL, &req); + Stream_Free(asn1Payload, FALSE); + return TRUE; +} + +static char* KERB_RPC_UNICODESTR_to_charptr(const RPC_UNICODE_STRING* src) +{ + WINPR_ASSERT(src); + return ConvertWCharNToUtf8Alloc(src->Buffer, src->strLength, NULL); +} + +static BOOL extractAuthData(const KERB_ASN1_DATA* src, krb5_authdata* authData, BOOL* haveData) +{ + WinPrAsn1Decoder dec, dec2, dec3; + WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count); + BOOL error = FALSE; + WinPrAsn1_INTEGER adType; + WinPrAsn1_OctetString os; + + *haveData = FALSE; + if (!WinPrAsn1DecReadSequence(&dec, &dec2)) + return FALSE; + + wStream subStream = WinPrAsn1DecGetStream(&dec2); + if (!Stream_GetRemainingLength(&subStream)) + return TRUE; + + if (!WinPrAsn1DecReadSequence(&dec2, &dec3)) + return FALSE; + + if (!WinPrAsn1DecReadContextualInteger(&dec3, 0, &error, &adType) || + !WinPrAsn1DecReadContextualOctetString(&dec3, 1, &error, &os, FALSE)) + return FALSE; + + authData->ad_type = adType; + authData->length = os.len; + authData->contents = os.data; + *haveData = TRUE; + return TRUE; +} + +static BOOL extractChecksum(const KERB_ASN1_DATA* src, krb5_checksum* dst) +{ + WinPrAsn1Decoder dec, dec2; + WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count); + BOOL error = FALSE; + WinPrAsn1_OctetString os; + + if (!WinPrAsn1DecReadSequence(&dec, &dec2)) + return FALSE; + + WinPrAsn1_INTEGER cksumtype; + if (!WinPrAsn1DecReadContextualInteger(&dec2, 0, &error, &cksumtype) || + !WinPrAsn1DecReadContextualOctetString(&dec2, 1, &error, &os, FALSE)) + return FALSE; + + dst->checksum_type = cksumtype; + dst->length = os.len; + dst->contents = os.data; + return TRUE; +} + +#define FILETIME_TO_UNIX_OFFSET_S 11644473600LL + +static LONGLONG krb5_time_to_FILETIME(const krb5_timestamp* ts, krb5_int32 usec) +{ + WINPR_ASSERT(ts); + return (((*ts + FILETIME_TO_UNIX_OFFSET_S) * (1000LL * 1000LL) + usec) * 10LL); +} + +static void krb5_free_principal_contents(krb5_context ctx, krb5_principal principal) +{ + WINPR_ASSERT(principal); + krb5_free_data_contents(ctx, &principal->realm); + krb5_free_data(ctx, principal->data); +} + +static BOOL rdpear_kerb_CreateApReqAuthenticator(RDPEAR_PLUGIN* rdpear, + IWTSVirtualChannelCallback* pChannelCallback, + NdrContext* rcontext, wStream* s, UINT32* pstatus, + CreateApReqAuthenticatorResp* resp) +{ + krb5_error_code rv = 0; + wStream* asn1EncodedAuth = NULL; + CreateApReqAuthenticatorReq req = { 0 }; + krb5_data authenticator = { 0 }; + krb5_data* der = NULL; + krb5_keyblock* subkey = NULL; + krb5_principal_data client = { 0 }; + + *pstatus = ERROR_INVALID_DATA; + WLog_DBG(TAG, "-> CreateApReqAuthenticator"); + + if (!ndr_read_CreateApReqAuthenticatorReq(rcontext, s, NULL, &req) || + !ndr_treat_deferred_read(rcontext, s)) + goto out; + + krb5_authdata authdata; + krb5_authdata* authDataPtr[2] = { &authdata, NULL }; + BOOL haveData; + + if (!extractAuthData(req.AuthData, &authdata, &haveData)) + { + WLog_ERR(TAG, "error retrieving auth data"); + winpr_HexDump(TAG, WLOG_DEBUG, req.AuthData->Asn1Buffer, + req.AuthData->Asn1BufferHints.count); + goto out; + } + + if (req.SkewTime->QuadPart) + { + WLog_ERR(TAG, "!!!!! should handle SkewTime !!!!!"); + } + + if (req.SubKey) + { + rv = RPC_ENCRYPTION_KEY_to_keyblock(rdpear->krbContext, req.SubKey, &subkey); + if (rv) + { + WLog_ERR(TAG, "error importing subkey"); + goto out; + } + } + + krb5_authenticator authent = { .checksum = NULL, + .subkey = NULL, + .seq_number = req.SequenceNumber, + .authorization_data = haveData ? authDataPtr : NULL }; + + client.type = req.ClientName->NameType; + client.length = (krb5_int32)req.ClientName->nameHints.count; + client.data = calloc(client.length, sizeof(krb5_data)); + if (!client.data) + goto out; + + for (int i = 0; i < client.length; i++) + { + client.data[i].data = KERB_RPC_UNICODESTR_to_charptr(&req.ClientName->Names[i]); + if (!client.data[i].data) + goto out; + client.data[i].length = strlen(client.data[i].data); + } + client.realm.data = KERB_RPC_UNICODESTR_to_charptr(req.ClientRealm); + if (!client.realm.data) + goto out; + client.realm.length = strlen(client.realm.data); + authent.client = &client; + + krb5_checksum checksum; + krb5_checksum* pchecksum = NULL; + if (req.GssChecksum) + { + if (!extractChecksum(req.GssChecksum, &checksum)) + { + WLog_ERR(TAG, "Error getting the checksum"); + goto out; + } + pchecksum = &checksum; + } + authent.checksum = pchecksum; + + krb5_us_timeofday(rdpear->krbContext, &authent.ctime, &authent.cusec); + + rv = encode_krb5_authenticator(&authent, &der); + if (rv) + { + WLog_ERR(TAG, "error encoding authenticator"); + goto out; + } + + KERB_ASN1_DATA plain_authent = { .Pdu = 0, + .Asn1Buffer = (BYTE*)der->data, + .Asn1BufferHints = { .count = der->length } }; + + rv = kerb_do_encrypt(rdpear->krbContext, req.EncryptionKey, (krb5_keyusage)req.KeyUsage, + &plain_authent, &authenticator); + if (rv) + { + WLog_ERR(TAG, "error encrypting authenticator"); + goto out; + } + + asn1EncodedAuth = rdpear_enc_EncryptedData(req.EncryptionKey->reserved2, &authenticator); + if (!asn1EncodedAuth) + { + WLog_ERR(TAG, "error encoding to ASN1"); + rv = ENOMEM; + goto out; + } + + // WLog_DBG(TAG, "authenticator="); + // winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(asn1EncodedAuth), + // Stream_GetPosition(asn1EncodedAuth)); + + resp->Authenticator.Asn1BufferHints.count = Stream_GetPosition(asn1EncodedAuth); + resp->Authenticator.Asn1Buffer = Stream_Buffer(asn1EncodedAuth); + resp->AuthenticatorTime.QuadPart = krb5_time_to_FILETIME(&authent.ctime, authent.cusec); + *pstatus = 0; + +out: + resp->Authenticator.Pdu = 6; + resp->KerbProtocolError = rv; + krb5_free_principal_contents(rdpear->krbContext, &client); + krb5_free_data(rdpear->krbContext, der); + krb5_free_data_contents(rdpear->krbContext, &authenticator); + if (subkey) + krb5_free_keyblock(rdpear->krbContext, subkey); + ndr_destroy_CreateApReqAuthenticatorReq(rcontext, NULL, &req); + Stream_Free(asn1EncodedAuth, FALSE); + return TRUE; +} + +static BOOL rdpear_findEncryptedData(const KERB_ASN1_DATA* src, int* penctype, krb5_data* data) +{ + WinPrAsn1Decoder dec, dec2; + WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count); + BOOL error = FALSE; + WinPrAsn1_INTEGER encType; + WinPrAsn1_OctetString os; + + if (!WinPrAsn1DecReadSequence(&dec, &dec2) || + !WinPrAsn1DecReadContextualInteger(&dec2, 0, &error, &encType) || + !WinPrAsn1DecReadContextualOctetString(&dec2, 2, &error, &os, FALSE)) + return FALSE; + + data->data = (char*)os.data; + data->length = os.len; + *penctype = encType; + return TRUE; +} + +static BOOL rdpear_kerb_UnpackKdcReplyBody(RDPEAR_PLUGIN* rdpear, + IWTSVirtualChannelCallback* pChannelCallback, + NdrContext* rcontext, wStream* s, UINT32* pstatus, + UnpackKdcReplyBodyResp* resp) +{ + UnpackKdcReplyBodyReq req = { 0 }; + + *pstatus = ERROR_INVALID_DATA; + + if (!ndr_read_UnpackKdcReplyBodyReq(rcontext, s, NULL, &req) || + !ndr_treat_deferred_read(rcontext, s)) + goto out; + + if (req.StrengthenKey) + { + WLog_ERR(TAG, "StrengthenKey not supported yet"); + goto out; + } + + WLog_DBG(TAG, "-> UnpackKdcReplyBody: KeyUsage=0x%x PDU=0x%x", req.KeyUsage, req.Pdu); + // WLog_DBG(TAG, "encryptedPayload="); + // winpr_HexDump(TAG, WLOG_DEBUG, req.EncryptedData->Asn1Buffer, + // req.EncryptedData->Asn1BufferHints.count); + + krb5_data asn1Data; + int encType; + if (!rdpear_findEncryptedData(req.EncryptedData, &encType, &asn1Data) || !asn1Data.length) + goto out; + + resp->KerbProtocolError = kerb_do_decrypt( + rdpear->krbContext, req.Key, (krb5_keyusage)req.KeyUsage, &asn1Data, &resp->ReplyBody); + resp->ReplyBody.Pdu = req.Pdu; + + *pstatus = 0; + +out: + ndr_destroy_UnpackKdcReplyBodyReq(rcontext, NULL, &req); + return TRUE; +} + +static BOOL rdpear_kerb_DecryptApReply(RDPEAR_PLUGIN* rdpear, + IWTSVirtualChannelCallback* pChannelCallback, + NdrContext* rcontext, wStream* s, UINT32* pstatus, + KERB_ASN1_DATA* resp) +{ + DecryptApReplyReq req = { 0 }; + + *pstatus = ERROR_INVALID_DATA; + if (!ndr_read_DecryptApReplyReq(rcontext, s, NULL, &req) || + !ndr_treat_deferred_read(rcontext, s)) + goto out; + + WLog_DBG(TAG, "-> DecryptApReply"); + // winpr_HexDump(TAG, WLOG_DEBUG, req.EncryptedReply->Asn1Buffer, + // req.EncryptedReply->Asn1BufferHints.count); + + krb5_data asn1Data; + int encType; + if (!rdpear_findEncryptedData(req.EncryptedReply, &encType, &asn1Data) || !asn1Data.length) + goto out; + + resp->Pdu = 0x31; + krb5_error_code rv = + kerb_do_decrypt(rdpear->krbContext, req.Key, KRB5_KEYUSAGE_AP_REP_ENCPART, &asn1Data, resp); + if (rv != 0) + { + WLog_ERR(TAG, "error decrypting"); + goto out; + } + + // WLog_DBG(TAG, "response="); + // winpr_HexDump(TAG, WLOG_DEBUG, resp->Asn1Buffer, resp->Asn1BufferHints.count); + *pstatus = 0; +out: + ndr_destroy_DecryptApReplyReq(rcontext, NULL, &req); + return TRUE; +} + +static BOOL rdpear_kerb_PackApReply(RDPEAR_PLUGIN* rdpear, + IWTSVirtualChannelCallback* pChannelCallback, + NdrContext* rcontext, wStream* s, UINT32* pstatus, + PackApReplyResp* resp) +{ + PackApReplyReq req = { 0 }; + krb5_data asn1Data = { 0 }; + krb5_data* out = NULL; + + WLog_DBG(TAG, "-> PackApReply"); + *pstatus = ERROR_INVALID_DATA; + if (!ndr_read_PackApReplyReq(rcontext, s, NULL, &req) || !ndr_treat_deferred_read(rcontext, s)) + goto out; + + krb5_error_code rv = kerb_do_encrypt(rdpear->krbContext, req.SessionKey, + KRB5_KEYUSAGE_AP_REP_ENCPART, req.ReplyBody, &asn1Data); + if (rv) + goto out; + + krb5_ap_rep reply; + reply.enc_part.kvno = KRB5_PVNO; + reply.enc_part.enctype = (krb5_enctype)req.SessionKey->reserved2; + reply.enc_part.ciphertext.length = asn1Data.length; + reply.enc_part.ciphertext.data = asn1Data.data; + + rv = encode_krb5_ap_rep(&reply, &out); + if (rv) + goto out; + + resp->PackedReply = (BYTE*)out->data; + resp->PackedReplyHints.count = out->length; + *pstatus = 0; +out: + free(out); + krb5_free_data_contents(rdpear->krbContext, &asn1Data); + ndr_destroy_PackApReplyReq(rcontext, NULL, &req); + return TRUE; +} + +static UINT rdpear_decode_payload(RDPEAR_PLUGIN* rdpear, + IWTSVirtualChannelCallback* pChannelCallback, + RdpEarPackageType packageType, wStream* s) +{ + UINT ret = ERROR_INVALID_DATA; + NdrContext* context = NULL; + NdrContext* wcontext = NULL; + UINT32 status = 0; + + UINT32 uint32Resp = 0; + KERB_ASN1_DATA asn1Data = { 0 }; + CreateApReqAuthenticatorResp createApReqAuthenticatorResp = { 0 }; + UnpackKdcReplyBodyResp unpackKdcReplyBodyResp = { 0 }; + PackApReplyResp packApReplyResp = { 0 }; + + void* resp = NULL; + NdrMessageType respDescr = NULL; + + wStream* respStream = Stream_New(NULL, 500); + if (!respStream) + goto out; + + Stream_Seek(s, 16); /* skip first 16 bytes */ + + wStream commandStream; + UINT16 callId, callId2; + + context = ndr_read_header(s); + if (!context || !ndr_read_constructed(context, s, &commandStream) || + !ndr_read_pickle(context, &commandStream) || + !ndr_read_uint16(context, &commandStream, &callId) || + !ndr_read_uint16(context, &commandStream, &callId2) || (callId != callId2)) + goto out; + + ret = CHANNEL_RC_NOT_OPEN; + switch (callId) + { + case RemoteCallKerbNegotiateVersion: + resp = &uint32Resp; + respDescr = ndr_uint32_descr(); + + if (rdpear_kerb_version(rdpear, pChannelCallback, context, &commandStream, &status, + &uint32Resp)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbCreateApReqAuthenticator: + resp = &createApReqAuthenticatorResp; + respDescr = ndr_CreateApReqAuthenticatorResp_descr(); + + if (rdpear_kerb_CreateApReqAuthenticator(rdpear, pChannelCallback, context, + &commandStream, &status, + &createApReqAuthenticatorResp)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbDecryptApReply: + resp = &asn1Data; + respDescr = ndr_KERB_ASN1_DATA_descr(); + + if (rdpear_kerb_DecryptApReply(rdpear, pChannelCallback, context, &commandStream, + &status, &asn1Data)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbComputeTgsChecksum: + resp = &asn1Data; + respDescr = ndr_KERB_ASN1_DATA_descr(); + + if (rdpear_kerb_ComputeTgsChecksum(rdpear, pChannelCallback, context, &commandStream, + &status, &asn1Data)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbBuildEncryptedAuthData: + resp = &asn1Data; + respDescr = ndr_KERB_ASN1_DATA_descr(); + + if (rdpear_kerb_BuildEncryptedAuthData(rdpear, pChannelCallback, context, + &commandStream, &status, &asn1Data)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbUnpackKdcReplyBody: + resp = &unpackKdcReplyBodyResp; + respDescr = ndr_UnpackKdcReplyBodyResp_descr(); + + if (rdpear_kerb_UnpackKdcReplyBody(rdpear, pChannelCallback, context, &commandStream, + &status, &unpackKdcReplyBodyResp)) + ret = CHANNEL_RC_OK; + break; + case RemoteCallKerbPackApReply: + resp = &packApReplyResp; + respDescr = ndr_PackApReplyResp_descr(); + + if (rdpear_kerb_PackApReply(rdpear, pChannelCallback, context, &commandStream, &status, + &packApReplyResp)) + ret = CHANNEL_RC_OK; + break; + + case RemoteCallNtlmNegotiateVersion: + WLog_ERR(TAG, "don't wanna support NTLM"); + break; + + default: + WLog_DBG(TAG, "Unhandled callId=0x%x", callId); + winpr_HexDump(TAG, WLOG_DEBUG, Stream_PointerAs(&commandStream, BYTE), + Stream_GetRemainingLength(&commandStream)); + break; + } + + if (!rdpear_prepare_response(context, callId, status, &wcontext, respStream)) + goto out; + + if (resp && respDescr) + { + WINPR_ASSERT(respDescr->writeFn); + + BOOL r = respDescr->writeFn(wcontext, respStream, NULL, resp) && + ndr_treat_deferred_write(wcontext, respStream); + + if (respDescr->destroyFn) + respDescr->destroyFn(wcontext, NULL, resp); + + if (!r) + { + WLog_DBG(TAG, "!writeFn || !ndr_treat_deferred_write"); + goto out; + } + } + + if (!ndr_end_constructed(wcontext, respStream) || + !rdpear_send_payload(rdpear, pChannelCallback, RDPEAR_PACKAGE_KERBEROS, respStream)) + { + WLog_DBG(TAG, "rdpear_send_payload !!!!!!!!"); + goto out; + } +out: + if (context) + ndr_context_destroy(&context); + + if (wcontext) + ndr_context_destroy(&wcontext); + + if (respStream) + Stream_Free(respStream, TRUE); + return ret; +} + +static UINT rdpear_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* s) +{ + GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback; + WINPR_ASSERT(callback); + UINT ret = ERROR_INVALID_DATA; + + // winpr_HexDump(TAG, WLOG_DEBUG, Stream_PointerAs(s, BYTE), Stream_GetRemainingLength(s)); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 24)) + return ERROR_INVALID_DATA; + + UINT32 protocolMagic, Length, Version; + Stream_Read_UINT32(s, protocolMagic); + if (protocolMagic != 0x4EACC3C8) + return ERROR_INVALID_DATA; + + Stream_Read_UINT32(s, Length); + + Stream_Read_UINT32(s, Version); + if (Version != 0x00000000) + return ERROR_INVALID_DATA; + + Stream_Seek(s, 4); /* Reserved (4 bytes) */ + Stream_Seek(s, 8); /* TsPkgContext (8 bytes) */ + + if (!Stream_CheckAndLogRequiredLength(TAG, s, Length)) + return ERROR_INVALID_DATA; + + SecBuffer inBuffer = { Length, SECBUFFER_TOKEN, Stream_PointerAs(s, void) }; + SecBuffer decrypted = { 0 }; + + RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)callback->plugin; + WINPR_ASSERT(rdpear); + if (!freerdp_nla_decrypt(rdpear->rdp_context, &inBuffer, &decrypted)) + goto out; + + WinPrAsn1Decoder dec, dec2; + wStream decodedStream; + Stream_StaticInit(&decodedStream, decrypted.pvBuffer, decrypted.cbBuffer); + WinPrAsn1Decoder_Init(&dec, WINPR_ASN1_DER, &decodedStream); + + if (!WinPrAsn1DecReadSequence(&dec, &dec2)) + goto out; + + WinPrAsn1_OctetString packageName; + WinPrAsn1_OctetString payload; + BOOL error; + if (!WinPrAsn1DecReadContextualOctetString(&dec2, 1, &error, &packageName, FALSE)) + goto out; + + RdpEarPackageType packageType = rdpear_packageType_from_name(&packageName); + + if (!WinPrAsn1DecReadContextualOctetString(&dec2, 2, &error, &payload, FALSE)) + goto out; + + wStream payloadStream; + Stream_StaticInit(&payloadStream, payload.data, payload.len); + + ret = rdpear_decode_payload(rdpear, pChannelCallback, packageType, &payloadStream); +out: + sspi_SecBufferFree(&decrypted); + return ret; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpear_on_open(IWTSVirtualChannelCallback* pChannelCallback) +{ + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT rdpear_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + UINT error = CHANNEL_RC_OK; + return error; +} + +static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base) +{ + WINPR_ASSERT(base); + + RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)base; + krb5_free_context(rdpear->krbContext); +} + +static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings) +{ + WINPR_ASSERT(base); + + RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)base; + rdpear->rdp_context = rcontext; + if (krb5_init_context(&rdpear->krbContext)) + return CHANNEL_RC_INITIALIZATION_ERROR; + return CHANNEL_RC_OK; +} + +static const IWTSVirtualChannelCallback rdpear_callbacks = { rdpear_on_data_received, + rdpear_on_open, rdpear_on_close }; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +FREERDP_ENTRY_POINT(UINT rdpear_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)) +{ + return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, RDPEAR_DVC_CHANNEL_NAME, + sizeof(RDPEAR_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK), + &rdpear_callbacks, init_plugin_cb, terminate_plugin_cb); +} diff --git a/channels/rdpear/common/CMakeLists.txt b/channels/rdpear/common/CMakeLists.txt new file mode 100644 index 000000000..448fa9364 --- /dev/null +++ b/channels/rdpear/common/CMakeLists.txt @@ -0,0 +1,43 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2024 David Fort +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_library(rdpear-common INTERFACE) +add_library(rdpear-common-obj OBJECT + ndr.c + rdpear_asn1.c + rdpear_common.c + rdpear-common/ndr.h + rdpear-common/rdpear_asn1.h + rdpear-common/rdpear_common.h +) + +target_include_directories(rdpear-common + INTERFACE ${KRB5_INCLUDE_DIRS}) +target_include_directories(rdpear-common-obj + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${KRB5_INCLUDE_DIRS}) + +target_link_directories(rdpear-common INTERFACE ${KRB5_LIBRARY_DIRS}) +target_link_libraries(rdpear-common INTERFACE + ${KRB5_LIBRARIES} + $ +) + +channel_install(rdpear-common ${FREERDP_ADDIN_PATH} "FreeRDPTargets") + +if (BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/channels/rdpear/common/ndr.c b/channels/rdpear/common/ndr.c new file mode 100644 index 000000000..d67982801 --- /dev/null +++ b/channels/rdpear/common/ndr.c @@ -0,0 +1,994 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Authentication redirection virtual channel + * + * Copyright 2024 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include + +#include + +#include + +#define TAG FREERDP_TAG("ndr") + +#define NDR_MAX_CONSTRUCTS 16 +#define NDR_MAX_DEFERRED 50 + +/** @brief */ +struct NdrContext_s +{ + BYTE version; + BOOL bigEndianDrep; + size_t alignBytes; + + int currentLevel; + size_t indentLevels[16]; + + int constructLevel; + size_t constructs[NDR_MAX_CONSTRUCTS]; + + wHashTable* refPointers; + size_t ndeferred; + NdrDeferredEntry deferred[NDR_MAX_DEFERRED]; + + UINT32 refIdCounter; +}; + +NdrContext* ndr_context_new(BOOL bigEndianDrep, BYTE version) +{ + NdrContext* ret = calloc(1, sizeof(*ret)); + if (!ret) + return NULL; + + ret->version = version; + ret->bigEndianDrep = bigEndianDrep; + ret->alignBytes = 4; + ret->refPointers = HashTable_New(FALSE); + if (!ret->refPointers) + { + free(ret); + return NULL; + } + + ndr_context_reset(ret); + return ret; +} + +void ndr_context_reset(NdrContext* context) +{ + WINPR_ASSERT(context); + + context->currentLevel = 0; + context->constructLevel = -1; + memset(context->indentLevels, 0, sizeof(context->indentLevels)); + + if (context->refPointers) + HashTable_Clear(context->refPointers); + context->ndeferred = 0; + context->refIdCounter = 0x20000; +} + +NdrContext* ndr_context_copy(const NdrContext* src) +{ + WINPR_ASSERT(src); + + NdrContext* ret = calloc(1, sizeof(*ret)); + if (!ret) + return NULL; + + *ret = *src; + + ret->refPointers = HashTable_New(FALSE); + if (!ret->refPointers) + { + free(ret); + return NULL; + } + + ndr_context_reset(ret); + return ret; +} + +void ndr_context_destroy(NdrContext** pcontext) +{ + WINPR_ASSERT(pcontext); + + NdrContext* context = *pcontext; + if (context) + { + HashTable_Free(context->refPointers); + free(context); + } + *pcontext = NULL; +} + +void ndr_context_bytes_read(NdrContext* context, size_t len) +{ + context->indentLevels[context->currentLevel] += len; +} + +void ndr_context_bytes_written(NdrContext* context, size_t len) +{ + ndr_context_bytes_read(context, len); +} + +NdrContext* ndr_read_header(wStream* s) +{ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return NULL; + + BYTE version, drep; + Stream_Read_UINT8(s, version); + Stream_Read_UINT8(s, drep); + + UINT16 headerLen; + Stream_Read_UINT16(s, headerLen); + + if (headerLen < 4 || !Stream_CheckAndLogRequiredLength(TAG, s, headerLen - 4)) + return NULL; + + /* skip filler */ + Stream_Seek(s, headerLen - 4); + + return ndr_context_new((drep != 0x10), version); +} + +BOOL ndr_write_header(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return FALSE; + + Stream_Write_UINT8(s, context->version); + Stream_Write_UINT8(s, context->bigEndianDrep ? 0x00 : 0x10); + Stream_Write_UINT16(s, 0x8); /* header len */ + + BYTE filler[] = { 0xcc, 0xcc, 0xcc, 0xcc }; + Stream_Write(s, filler, sizeof(filler)); + return TRUE; +} +BOOL ndr_skip_bytes(NdrContext* context, wStream* s, size_t nbytes) +{ + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, nbytes)) + return FALSE; + + context->indentLevels[context->currentLevel] += nbytes; + Stream_Seek(s, nbytes); + return TRUE; +} + +BOOL ndr_read_align(NdrContext* context, wStream* s, size_t sz) +{ + WINPR_ASSERT(context); + + size_t rest = context->indentLevels[context->currentLevel] % sz; + if (rest) + { + size_t padding = (sz - rest); + if (!Stream_CheckAndLogRequiredLength(TAG, s, padding)) + return FALSE; + + Stream_Seek(s, padding); + context->indentLevels[context->currentLevel] += padding; + } + + return TRUE; +} + +BOOL ndr_write_align(NdrContext* context, wStream* s, size_t sz) +{ + WINPR_ASSERT(context); + + size_t rest = context->indentLevels[context->currentLevel] % sz; + if (rest) + { + size_t padding = (sz - rest); + + if (!Stream_EnsureRemainingCapacity(s, padding)) + return FALSE; + + Stream_Zero(s, padding); + context->indentLevels[context->currentLevel] += padding; + } + + return TRUE; +} + +BOOL ndr_read_pickle(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + + UINT32 v; + + /* NDR format label */ + if (!ndr_read_uint32(context, s, &v) || v != 0x20000) + return FALSE; + + return ndr_read_uint32(context, s, &v); // padding +} + +BOOL ndr_write_pickle(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + + /* NDR format label */ + if (!ndr_write_uint32(context, s, 0x20000)) + return FALSE; + + ndr_write_uint32(context, s, 0); /* padding */ + return TRUE; +} + +BOOL ndr_read_constructed(NdrContext* context, wStream* s, wStream* target) +{ + WINPR_ASSERT(context); + + UINT32 len; + + /* len */ + if (!ndr_read_uint32(context, s, &len)) + return FALSE; + + /* padding */ + if (!ndr_skip_bytes(context, s, 4)) + return FALSE; + + /* payload */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, len)) + return FALSE; + + Stream_StaticInit(target, Stream_PointerAs(s, BYTE), len); + Stream_Seek(s, len); + return TRUE; +} + +BOOL ndr_start_constructed(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + + if (!Stream_EnsureCapacity(s, 8)) + return FALSE; + + if (context->constructLevel == NDR_MAX_CONSTRUCTS) + return FALSE; + + context->constructLevel++; + context->constructs[context->constructLevel] = Stream_GetPosition(s); + + Stream_Zero(s, 8); + return TRUE; +} + +BOOL ndr_end_constructed(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->constructLevel >= 0); + + size_t offset = context->constructs[context->constructLevel]; + + wStream staticS; + Stream_StaticInit(&staticS, Stream_Buffer(s) + offset, 4); + + /* len */ + size_t len = Stream_GetPosition(s) - (offset + 8); + if (!ndr_write_uint32(context, &staticS, len)) + return FALSE; + + return TRUE; +} + +static size_t ndr_hintsCount(NdrMessageType msgType, const void* hints) +{ + WINPR_ASSERT(msgType); + + switch (msgType->arity) + { + case NDR_ARITY_SIMPLE: + return 1; + case NDR_ARITY_ARRAYOF: + WINPR_ASSERT(hints); + return ((const NdrArrayHints*)hints)->count; + case NDR_ARITY_VARYING_ARRAYOF: + WINPR_ASSERT(hints); + return ((const NdrVaryingArrayHints*)hints)->maxLength; + default: + WINPR_ASSERT(0 && "unknown arity"); + return 0; + } +} + +void Stream_Write_UINT64_BE(wStream* _s, UINT64 _v) +{ + WINPR_ASSERT(FALSE && "implement Stream_Write_UINT64_BE()"); +} + +BOOL ndr_read_uint8(NdrContext* context, wStream* s, BYTE* v) +{ + WINPR_ASSERT(context); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, *v); + + ndr_context_bytes_read(context, 1); + return TRUE; +} + +BOOL ndr_read_uint8_(NdrContext* context, wStream* s, const void* hints, void* v) +{ + return ndr_read_uint8(context, s, (BYTE*)v); +} + +BOOL ndr_write_uint8(NdrContext* context, wStream* s, BYTE v) +{ + if (!Stream_EnsureRemainingCapacity(s, 1)) + return FALSE; + + Stream_Write_UINT8(s, v); + ndr_context_bytes_written(context, 1); + return TRUE; +} + +BOOL ndr_write_uint8_(NdrContext* context, wStream* s, const void* hints, const void* v) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(v); + + return ndr_write_uint8(context, s, *(const BYTE*)v); +} + +const static NdrMessageDescr uint8_descr = { NDR_ARITY_SIMPLE, 1, ndr_read_uint8_, + ndr_write_uint8_, NULL, NULL }; + +NdrMessageType ndr_uint8_descr(void) +{ + return &uint8_descr; +} + +#define SIMPLE_TYPE_IMPL(UPPERTYPE, LOWERTYPE) \ + BOOL ndr_read_##LOWERTYPE(NdrContext* context, wStream* s, UPPERTYPE* v) \ + { \ + WINPR_ASSERT(context); \ + \ + if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UPPERTYPE))) \ + return FALSE; \ + \ + if (!ndr_read_align(context, s, sizeof(UPPERTYPE))) \ + return FALSE; \ + \ + if (context->bigEndianDrep) \ + Stream_Read_##UPPERTYPE##_BE(s, *v); \ + else \ + Stream_Read_##UPPERTYPE(s, *v); \ + \ + ndr_context_bytes_read(context, sizeof(UPPERTYPE)); \ + return TRUE; \ + } \ + \ + BOOL ndr_read_##LOWERTYPE##_(NdrContext* context, wStream* s, const void* hints, void* v) \ + { \ + return ndr_read_##LOWERTYPE(context, s, (UPPERTYPE*)v); \ + } \ + \ + BOOL ndr_write_##LOWERTYPE(NdrContext* context, wStream* s, UPPERTYPE v) \ + { \ + if (!ndr_write_align(context, s, sizeof(UPPERTYPE)) || \ + !Stream_EnsureRemainingCapacity(s, sizeof(UPPERTYPE))) \ + return FALSE; \ + \ + if (context->bigEndianDrep) \ + Stream_Write_##UPPERTYPE##_BE(s, v); \ + else \ + Stream_Write_##UPPERTYPE(s, v); \ + \ + ndr_context_bytes_written(context, sizeof(UPPERTYPE)); \ + return TRUE; \ + } \ + \ + BOOL ndr_write_##LOWERTYPE##_(NdrContext* context, wStream* s, const void* hints, \ + const void* v) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(s); \ + WINPR_ASSERT(v); \ + \ + return ndr_write_##LOWERTYPE(context, s, *(const UPPERTYPE*)v); \ + } \ + \ + const NdrMessageDescr ndr_##LOWERTYPE##_descr_s = { \ + NDR_ARITY_SIMPLE, sizeof(UPPERTYPE), ndr_read_##LOWERTYPE##_, \ + ndr_write_##LOWERTYPE##_, (NDR_DESTROY_FN)NULL, (NDR_DUMP_FN)NULL \ + }; \ + \ + NdrMessageType ndr_##LOWERTYPE##_descr(void) \ + { \ + return &ndr_##LOWERTYPE##_descr_s; \ + } + +SIMPLE_TYPE_IMPL(UINT32, uint32) +SIMPLE_TYPE_IMPL(UINT16, uint16) +SIMPLE_TYPE_IMPL(UINT64, uint64) + +#define ARRAY_OF_TYPE_IMPL(TYPE, UPPERTYPE) \ + BOOL ndr_read_##TYPE##Array(NdrContext* context, wStream* s, const void* hints, void* v) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(s); \ + WINPR_ASSERT(hints); \ + return ndr_read_uconformant_array(context, s, (NdrArrayHints*)hints, ndr_##TYPE##_descr(), \ + (void*)v); \ + } \ + \ + BOOL ndr_write_##TYPE##Array(NdrContext* context, wStream* s, const void* hints, \ + const void* v) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(s); \ + WINPR_ASSERT(hints); \ + NdrArrayHints* ahints = (NdrArrayHints*)hints; \ + return ndr_write_uconformant_array(context, s, ahints->count, ndr_##TYPE##_descr(), \ + (const void*)v); \ + } \ + void ndr_destroy_##TYPE##Array(NdrContext* context, const void* hints, void* obj) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(obj); \ + WINPR_ASSERT(hints); \ + NdrArrayHints* ahints = (NdrArrayHints*)hints; \ + NdrMessageType descr = ndr_##TYPE##_descr(); \ + if (descr->destroyFn) \ + { \ + UPPERTYPE* ptr = (UPPERTYPE*)obj; \ + for (UINT32 i = 0; i < ahints->count; i++, ptr++) \ + descr->destroyFn(context, NULL, ptr); \ + } \ + } \ + \ + const NdrMessageDescr ndr_##TYPE##Array_descr_s = { \ + NDR_ARITY_ARRAYOF, sizeof(UPPERTYPE), ndr_read_##TYPE##Array, \ + ndr_write_##TYPE##Array, ndr_destroy_##TYPE##Array, (NDR_DUMP_FN)NULL \ + }; \ + \ + NdrMessageType ndr_##TYPE##Array_descr(void) \ + { \ + return &ndr_##TYPE##Array_descr_s; \ + } \ + \ + BOOL ndr_read_##TYPE##VaryingArray(NdrContext* context, wStream* s, const void* hints, \ + void* v) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(s); \ + WINPR_ASSERT(hints); \ + return ndr_read_uconformant_varying_array(context, s, (NdrVaryingArrayHints*)hints, \ + ndr_##TYPE##_descr(), v); \ + } \ + BOOL ndr_write_##TYPE##VaryingArray(NdrContext* context, wStream* s, const void* hints, \ + const void* v) \ + { \ + WINPR_ASSERT(context); \ + WINPR_ASSERT(s); \ + WINPR_ASSERT(hints); \ + return ndr_write_uconformant_varying_array(context, s, (NdrVaryingArrayHints*)hints, \ + ndr_##TYPE##_descr(), v); \ + } \ + \ + const NdrMessageDescr ndr_##TYPE##VaryingArray_descr_s = { \ + NDR_ARITY_VARYING_ARRAYOF, sizeof(UPPERTYPE), ndr_read_##TYPE##VaryingArray, \ + ndr_write_##TYPE##VaryingArray, (NDR_DESTROY_FN)NULL, (NDR_DUMP_FN)NULL \ + }; \ + \ + NdrMessageType ndr_##TYPE##VaryingArray_descr(void) \ + { \ + return &ndr_##TYPE##VaryingArray_descr_s; \ + } + +ARRAY_OF_TYPE_IMPL(uint8, BYTE) +ARRAY_OF_TYPE_IMPL(uint16, UINT16) + +BOOL ndr_read_wchar(NdrContext* context, wStream* s, WCHAR* ptr) +{ + return ndr_read_uint16(context, s, (UINT16*)ptr); +} + +BOOL ndr_read_uconformant_varying_array(NdrContext* context, wStream* s, + const NdrVaryingArrayHints* hints, NdrMessageType itemType, + void* ptarget) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(hints); + WINPR_ASSERT(itemType); + WINPR_ASSERT(ptarget); + + UINT32 maxCount, offset, length; + + if (!ndr_read_uint32(context, s, &maxCount) || !ndr_read_uint32(context, s, &offset) || + !ndr_read_uint32(context, s, &length)) + return FALSE; + + if ((length * itemType->itemSize) < hints->length) + return FALSE; + + if ((maxCount * itemType->itemSize) < hints->maxLength) + return FALSE; + + BYTE* target = (BYTE*)ptarget; + for (UINT32 i = 0; i < length; i++, target += itemType->itemSize) + { + if (!itemType->readFn(context, s, NULL, target)) + return FALSE; + } + + return ndr_read_align(context, s, 4); +} + +BOOL ndr_write_uconformant_varying_array(NdrContext* context, wStream* s, + const NdrVaryingArrayHints* hints, NdrMessageType itemType, + const void* psrc) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(hints); + WINPR_ASSERT(itemType); + WINPR_ASSERT(psrc); + + if (!ndr_write_uint32(context, s, hints->maxLength) || !ndr_write_uint32(context, s, 0) || + !ndr_write_uint32(context, s, hints->length)) + return FALSE; + + const BYTE* src = (const BYTE*)psrc; + for (UINT32 i = 0; i < hints->length; i++, src += itemType->itemSize) + { + if (!itemType->writeFn(context, s, NULL, src)) + return FALSE; + } + + return TRUE; +} + +BOOL ndr_read_uconformant_array(NdrContext* context, wStream* s, const NdrArrayHints* hints, + NdrMessageType itemType, void* vtarget) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(itemType); + WINPR_ASSERT(vtarget); + + UINT32 count; + + if (!ndr_read_uint32(context, s, &count)) + return FALSE; + + if ((count * itemType->itemSize < hints->count)) + return FALSE; + + BYTE* target = (BYTE*)vtarget; + for (UINT32 i = 0; i < count; i++, target += itemType->itemSize) + { + if (!itemType->readFn(context, s, NULL, target)) + return FALSE; + } + + return ndr_read_align(context, s, /*context->alignBytes*/ 4); +} + +BOOL ndr_write_uconformant_array(NdrContext* context, wStream* s, UINT32 len, + NdrMessageType itemType, const BYTE* ptr) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(itemType); + WINPR_ASSERT(ptr); + + size_t toWrite = len * itemType->itemSize; + size_t padding = (4 - (toWrite % 4)) % 4; + if (!ndr_write_uint32(context, s, len) || !Stream_EnsureRemainingCapacity(s, toWrite + padding)) + return FALSE; + + for (UINT32 i = 0; i < len; i++, ptr += itemType->itemSize) + { + if (!itemType->writeFn(context, s, NULL, ptr)) + return FALSE; + } + + if (padding) + { + Stream_Zero(s, padding); + ndr_context_bytes_written(context, padding); + } + return TRUE; +} + +BOOL ndr_struct_read_fromDescr(NdrContext* context, wStream* s, const NdrStructDescr* descr, + void* target) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(descr); + WINPR_ASSERT(target); + +#define NDR_MAX_STRUCT_DEFERRED 16 + NdrDeferredEntry deferreds[NDR_MAX_STRUCT_DEFERRED] = { 0 }; + size_t ndeferred = 0; + + for (size_t i = 0; i < descr->nfields; i++) + { + const NdrFieldStruct* field = &descr->fields[i]; + BYTE* ptr = target; + ptr += field->structOffset; + void* hints = NULL; + + if (field->hintsField >= 0) + { + /* computes the address of the hints field if any */ + WINPR_ASSERT(field->hintsField < descr->nfields); + const NdrFieldStruct* hintsField = &descr->fields[field->hintsField]; + + hints = (BYTE*)target + hintsField->structOffset; + } + + switch (field->pointerType) + { + case NDR_NOT_POINTER: + if (!field->typeDescr->readFn(context, s, hints, ptr)) + { + WLog_ERR(TAG, "error when reading %s.%s", descr->name, field->name); + return FALSE; + } + break; + case NDR_POINTER: + case NDR_POINTER_NON_NULL: + { + NdrDeferredEntry* deferred = &deferreds[ndeferred]; + if (ndeferred >= NDR_MAX_STRUCT_DEFERRED) + { + WLog_ERR(TAG, "too many deferred when calling ndr_read_struct_fromDescr for %s", + descr->name); + return FALSE; + } + + deferred->name = field->name; + deferred->hints = hints; + deferred->target = ptr; + deferred->msg = field->typeDescr; + if (!ndr_read_refpointer(context, s, &deferred->ptrId)) + { + WLog_ERR(TAG, "error when reading %s.%s", descr->name, field->name); + return FALSE; + } + + if (!deferred->ptrId && field->pointerType == NDR_POINTER_NON_NULL) + { + WLog_ERR(TAG, "%s.%s can't be null", descr->name, field->name); + return FALSE; + } + ndeferred++; + break; + } + default: + WLog_ERR(TAG, "%s.%s unknown pointer type 0x%x", descr->name, field->name, + field->pointerType); + return FALSE; + } + } + + return ndr_push_deferreds(context, deferreds, ndeferred); +} + +BOOL ndr_struct_write_fromDescr(NdrContext* context, wStream* s, const NdrStructDescr* descr, + const void* src) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(descr); + WINPR_ASSERT(src); + + NdrDeferredEntry deferreds[NDR_MAX_STRUCT_DEFERRED] = { 0 }; + size_t ndeferred = 0; + + for (size_t i = 0; i < descr->nfields; i++) + { + const NdrFieldStruct* field = &descr->fields[i]; + BYTE* ptr = (BYTE*)src + field->structOffset; + + void* hints = NULL; + + if (field->hintsField >= 0) + { + /* computes the address of the hints field if any */ + WINPR_ASSERT(field->hintsField < descr->nfields); + const NdrFieldStruct* hintsField = &descr->fields[field->hintsField]; + + hints = (BYTE*)src + hintsField->structOffset; + } + + switch (field->pointerType) + { + case NDR_POINTER: + case NDR_POINTER_NON_NULL: + { + ndr_refid ptrId = NDR_PTR_NULL; + BOOL isNew; + ptr = *(void**)ptr; + + if (!ptr && field->pointerType == NDR_POINTER_NON_NULL) + { + WLog_ERR(TAG, "%s.%s can't be null", descr->name, field->name); + return FALSE; + } + + if (!ndr_context_allocatePtr(context, ptr, &ptrId, &isNew)) + return FALSE; + + if (isNew) + { + NdrDeferredEntry* deferred = &deferreds[ndeferred]; + if (ndeferred >= NDR_MAX_STRUCT_DEFERRED) + { + WLog_ERR(TAG, + "too many deferred when calling ndr_read_struct_fromDescr for %s", + descr->name); + return FALSE; + } + + deferred->name = field->name; + deferred->hints = hints; + deferred->target = ptr; + deferred->msg = field->typeDescr; + ndeferred++; + } + + if (!ndr_write_uint32(context, s, ptrId)) + return FALSE; + break; + } + case NDR_NOT_POINTER: + if (!field->typeDescr->writeFn(context, s, hints, ptr)) + { + WLog_ERR(TAG, "error when writing %s.%s", descr->name, field->name); + return FALSE; + } + break; + default: + break; + } + } + + return ndr_push_deferreds(context, deferreds, ndeferred); +} + +void ndr_struct_dump_fromDescr(wLog* logger, UINT32 lvl, size_t identLevel, + const NdrStructDescr* descr, const void* obj) +{ + char tabArray[30 + 1]; + size_t ntabs = (identLevel <= 30) ? identLevel : 30; + + memset(tabArray, '\t', ntabs); + tabArray[ntabs] = 0; + + WLog_Print(logger, lvl, "%s%s", tabArray, descr->name); + for (size_t i = 0; i < descr->nfields; i++) + { + const NdrFieldStruct* field = &descr->fields[i]; + BYTE* ptr = (BYTE*)obj + field->structOffset; + + switch (field->pointerType) + { + case NDR_POINTER: + case NDR_POINTER_NON_NULL: + ptr = *(void**)ptr; + break; + case NDR_NOT_POINTER: + break; + default: + WLog_ERR(TAG, "invalid field->pointerType"); + break; + } + + WLog_Print(logger, lvl, "%s*%s:", tabArray, field->name); + if (field->typeDescr->dumpFn) + field->typeDescr->dumpFn(logger, lvl, identLevel + 1, ptr); + else + WLog_Print(logger, lvl, "%s\t", tabArray); + } +} + +void ndr_struct_destroy(NdrContext* context, const NdrStructDescr* descr, void* pptr) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(descr); + WINPR_ASSERT(pptr); + + for (size_t i = 0; i < descr->nfields; i++) + { + const NdrFieldStruct* field = &descr->fields[i]; + void* ptr = (BYTE*)pptr + field->structOffset; + void* hints = NULL; + + if (field->hintsField >= 0) + { + /* computes the address of the hints field if any */ + WINPR_ASSERT(field->hintsField < descr->nfields); + const NdrFieldStruct* hintsField = &descr->fields[field->hintsField]; + + hints = (BYTE*)pptr + hintsField->structOffset; + } + + if (field->pointerType != NDR_NOT_POINTER) + ptr = *(void**)ptr; + + if (ptr && field->typeDescr->destroyFn) + field->typeDescr->destroyFn(context, hints, ptr); + + if (field->pointerType != NDR_NOT_POINTER) + free(ptr); + } +} + +ndr_refid ndr_pointer_refid(const void* ptr) +{ + return (ndr_refid)((ULONG_PTR)ptr); +} + +BOOL ndr_read_refpointer(NdrContext* context, wStream* s, ndr_refid* refId) +{ + return ndr_read_uint32(context, s, refId); +} + +typedef struct +{ + const void* needle; + ndr_refid* presult; +} FindValueArgs; + +BOOL findValueRefFn(const void* key, void* value, void* parg) +{ + WINPR_ASSERT(parg); + + FindValueArgs* args = (FindValueArgs*)parg; + if (args->needle == value) + { + *args->presult = (ndr_refid)(UINT_PTR)key; + return FALSE; + } + return TRUE; +} + +BOOL ndr_context_allocatePtr(NdrContext* context, const void* ptr, ndr_refid* prefId, BOOL* pnewPtr) +{ + WINPR_ASSERT(context); + + FindValueArgs findArgs = { ptr, prefId }; + if (!HashTable_Foreach(context->refPointers, findValueRefFn, &findArgs)) + { + *pnewPtr = FALSE; + return TRUE; + } + + *pnewPtr = TRUE; + *prefId = context->refIdCounter + 4; + if (!HashTable_Insert(context->refPointers, (void*)(UINT_PTR)(*prefId), ptr)) + return FALSE; + + context->refIdCounter += 4; + return TRUE; +} + +BOOL ndr_read_pointedMessageEx(NdrContext* context, wStream* s, ndr_refid ptrId, + NdrMessageType descr, void* hints, void** target) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(descr); + WINPR_ASSERT(target); + + *target = NULL; + if (!ptrId) + return TRUE; + + void* ret = HashTable_GetItemValue(context->refPointers, (void*)(UINT_PTR)ptrId); + if (!ret) + { + size_t itemCount = ndr_hintsCount(descr, hints); + ret = calloc(itemCount, descr->itemSize); + if (!ret) + return FALSE; + + if (!descr->readFn(context, s, hints, ret) || + !HashTable_Insert(context->refPointers, (void*)(UINT_PTR)ptrId, ret)) + { + if (descr->destroyFn) + descr->destroyFn(context, hints, ret); + free(ret); + return FALSE; + } + } + + *target = ret; + return TRUE; +} + +BOOL ndr_push_deferreds(NdrContext* context, NdrDeferredEntry* deferreds, size_t ndeferred) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(deferreds); + + if (!ndeferred) + return TRUE; + + if (context->ndeferred + ndeferred > NDR_MAX_DEFERRED) + { + WLog_ERR(TAG, "too many deferred"); + return FALSE; + } + + for (size_t i = ndeferred; i > 0; i--, context->ndeferred++) + { + context->deferred[context->ndeferred] = deferreds[i - 1]; + } + return TRUE; +} + +BOOL ndr_treat_deferred_read(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + while (context->ndeferred) + { + NdrDeferredEntry current = context->deferred[context->ndeferred - 1]; + context->ndeferred--; + + WLog_VRB(TAG, "treating read deferred 0x%x for %s", current.ptrId, current.name); + if (!ndr_read_pointedMessageEx(context, s, current.ptrId, current.msg, current.hints, + (void**)current.target)) + { + WLog_ERR(TAG, "error parsing deferred %s", current.name); + return FALSE; + } + } + + return TRUE; +} + +BOOL ndr_treat_deferred_write(NdrContext* context, wStream* s) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + while (context->ndeferred) + { + NdrDeferredEntry current = context->deferred[context->ndeferred - 1]; + context->ndeferred--; + + WLog_VRB(TAG, "treating write deferred for %s", current.name); + if (!current.msg->writeFn(context, s, current.hints, current.target)) + { + WLog_ERR(TAG, "error writing deferred %s", current.name); + return FALSE; + } + } + + return TRUE; +} diff --git a/channels/rdpear/common/rdpear-common/ndr.h b/channels/rdpear/common/rdpear-common/ndr.h new file mode 100644 index 000000000..013b1e4e7 --- /dev/null +++ b/channels/rdpear/common/rdpear-common/ndr.h @@ -0,0 +1,192 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Authentication redirection virtual channel + * + * Copyright 2024 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CHANNELS_RDPEAR_NDR_H_ +#define CHANNELS_RDPEAR_NDR_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct NdrContext_s NdrContext; + + typedef UINT32 ndr_refid; +#define NDR_PTR_NULL (0UL) + + typedef BOOL (*NDR_READER_FN)(NdrContext* context, wStream* s, const void* hints, void* target); + typedef BOOL (*NDR_WRITER_FN)(NdrContext* context, wStream* s, const void* hints, + const void* obj); + typedef void (*NDR_DESTROY_FN)(NdrContext* context, const void* hints, void* obj); + typedef void (*NDR_DUMP_FN)(wLog* logger, UINT32 lvl, size_t indentLevel, const void* obj); + + /** @brief arity of a message */ + typedef enum + { + NDR_ARITY_SIMPLE, + NDR_ARITY_ARRAYOF, + NDR_ARITY_VARYING_ARRAYOF, + } NdrTypeArity; + + /** @brief message descriptor */ + typedef struct + { + NdrTypeArity arity; + size_t itemSize; + NDR_READER_FN readFn; + NDR_WRITER_FN writeFn; + NDR_DESTROY_FN destroyFn; + NDR_DUMP_FN dumpFn; + } NdrMessageDescr; + + typedef const NdrMessageDescr* NdrMessageType; + + /** @brief pointer or not and if null is accepted */ + typedef enum + { + NDR_NOT_POINTER, + NDR_POINTER_NON_NULL, + NDR_POINTER + } NdrPointerType; + + /** @brief descriptor of a field in a structure */ + typedef struct + { + const char* name; + size_t structOffset; + NdrPointerType pointerType; + ssize_t hintsField; + NdrMessageType typeDescr; + } NdrFieldStruct; + + /** @brief structure descriptor */ + typedef struct + { + const char* name; + size_t nfields; + const NdrFieldStruct* fields; + } NdrStructDescr; + + /** @brief a deferred pointer */ + typedef struct + { + ndr_refid ptrId; + const char* name; + void* hints; + void* target; + NdrMessageType msg; + } NdrDeferredEntry; + + NdrContext* ndr_context_new(BOOL bigEndianDrep, BYTE version); + void ndr_context_destroy(NdrContext** pcontext); + + void ndr_context_reset(NdrContext* context); + NdrContext* ndr_context_copy(const NdrContext* src); + + NdrContext* ndr_read_header(wStream* s); + BOOL ndr_write_header(NdrContext* context, wStream* s); + + BOOL ndr_write_uint8(NdrContext* context, wStream* s, BYTE v); + BOOL ndr_write_uint8_(NdrContext* context, wStream* s, const void* hints, const void* v); + NdrMessageType ndr_uint8_descr(void); + +#define NDR_SIMPLE_TYPE_DECL(LOWER, UPPER) \ + BOOL ndr_read_##LOWER(NdrContext* context, wStream* s, UPPER* v); \ + BOOL ndr_write_##LOWER(NdrContext* context, wStream* s, UPPER v); \ + BOOL ndr_write_##LOWER##_(NdrContext* context, wStream* s, const void* hints, const void* v); \ + extern const NdrMessageDescr ndr_##LOWER##_descr_s; \ + NdrMessageType ndr_##LOWER##_descr(void) + + NDR_SIMPLE_TYPE_DECL(uint16, UINT16); + NDR_SIMPLE_TYPE_DECL(uint32, UINT32); + NDR_SIMPLE_TYPE_DECL(uint64, UINT64); + + extern const NdrMessageDescr ndr_uint8Array_descr_s; + NdrMessageType ndr_uint8Array_descr(void); + NdrMessageType ndr_uint16Array_descr(void); + NdrMessageType ndr_uint16VaryingArray_descr(void); + + BOOL ndr_skip_bytes(NdrContext* context, wStream* s, size_t nbytes); + BOOL ndr_read_align(NdrContext* context, wStream* s, size_t sz); + BOOL ndr_write_align(NdrContext* context, wStream* s, size_t sz); + + BOOL ndr_read_pickle(NdrContext* context, wStream* s); + BOOL ndr_write_pickle(NdrContext* context, wStream* s); + + BOOL ndr_read_constructed(NdrContext* context, wStream* s, wStream* target); + BOOL ndr_write_constructed(NdrContext* context, wStream* s, wStream* payload); + + BOOL ndr_start_constructed(NdrContext* context, wStream* s); + BOOL ndr_end_constructed(NdrContext* context, wStream* s); + + BOOL ndr_read_wchar(NdrContext* context, wStream* s, WCHAR* ptr); + + /** @brief hints for a varying conformant array */ + typedef struct + { + UINT32 length; + UINT32 maxLength; + } NdrVaryingArrayHints; + + BOOL ndr_read_uconformant_varying_array(NdrContext* context, wStream* s, + const NdrVaryingArrayHints* hints, + NdrMessageType itemType, void* ptarget); + BOOL ndr_write_uconformant_varying_array(NdrContext* context, wStream* s, + const NdrVaryingArrayHints* hints, + NdrMessageType itemType, const void* src); + + /** @brief hints for a conformant array */ + typedef struct + { + UINT32 count; + } NdrArrayHints; + + BOOL ndr_read_uconformant_array(NdrContext* context, wStream* s, const NdrArrayHints* hints, + NdrMessageType itemType, void* vtarget); + BOOL ndr_write_uconformant_array(NdrContext* context, wStream* s, UINT32 len, + NdrMessageType itemType, const BYTE* ptr); + + BOOL ndr_struct_read_fromDescr(NdrContext* context, wStream* s, const NdrStructDescr* descr, + void* target); + BOOL ndr_struct_write_fromDescr(NdrContext* context, wStream* s, const NdrStructDescr* descr, + const void* src); + void ndr_struct_dump_fromDescr(wLog* logger, UINT32 lvl, size_t identLevel, + const NdrStructDescr* descr, const void* obj); + void ndr_struct_destroy(NdrContext* context, const NdrStructDescr* descr, void* pptr); + + ndr_refid ndr_pointer_refid(const void* ptr); + BOOL ndr_read_refpointer(NdrContext* context, wStream* s, UINT32* refId); + BOOL ndr_context_allocatePtr(NdrContext* context, const void* ptr, ndr_refid* prefId, + BOOL* pnewPtr); + + BOOL ndr_read_pointedMessageEx(NdrContext* context, wStream* s, ndr_refid ptrId, + NdrMessageType descr, void* hints, void** target); + + BOOL ndr_push_deferreds(NdrContext* context, NdrDeferredEntry* deferreds, size_t ndeferred); + BOOL ndr_treat_deferred_read(NdrContext* context, wStream* s); + BOOL ndr_treat_deferred_write(NdrContext* context, wStream* s); + +#ifdef __cplusplus +} +#endif + +#endif /* CHANNELS_RDPEAR_NDR_H_ */ diff --git a/channels/rdpear/common/rdpear-common/rdpear_asn1.h b/channels/rdpear/common/rdpear-common/rdpear_asn1.h new file mode 100644 index 000000000..9b865fdcf --- /dev/null +++ b/channels/rdpear/common/rdpear-common/rdpear_asn1.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * ASN1 routines for RDPEAR + * + * Copyright 2024 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef RPDEAR_RDPEAR_ASN1_H__ +#define RPDEAR_RDPEAR_ASN1_H__ + +#include + +#include + +wStream* rdpear_enc_Checksum(UINT32 cksumtype, krb5_checksum* csum); + +wStream* rdpear_enc_EncryptedData(UINT32 encType, krb5_data* payload); + +#endif /* RPDEAR_RDPEAR_ASN1_H__ */ diff --git a/channels/rdpear/common/rdpear-common/rdpear_common.h b/channels/rdpear/common/rdpear-common/rdpear_common.h new file mode 100644 index 000000000..94ae275e1 --- /dev/null +++ b/channels/rdpear/common/rdpear-common/rdpear_common.h @@ -0,0 +1,244 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEAR_COMMON_H +#define FREERDP_CHANNEL_RDPEAR_COMMON_H + +#include +#include +#include +#include + +#include + +#include + +typedef enum +{ + RDPEAR_PACKAGE_KERBEROS, + RDPEAR_PACKAGE_NTLM, + RDPEAR_PACKAGE_UNKNOWN +} RdpEarPackageType; + +/* RDPEAR 2.2.1.1 */ +typedef enum +{ + // Start Kerberos remote calls + RemoteCallKerbMinimum = 0x100, + RemoteCallKerbNegotiateVersion = 0x100, + RemoteCallKerbBuildAsReqAuthenticator, + RemoteCallKerbVerifyServiceTicket, + RemoteCallKerbCreateApReqAuthenticator, + RemoteCallKerbDecryptApReply, + RemoteCallKerbUnpackKdcReplyBody, + RemoteCallKerbComputeTgsChecksum, + RemoteCallKerbBuildEncryptedAuthData, + RemoteCallKerbPackApReply, + RemoteCallKerbHashS4UPreauth, + RemoteCallKerbSignS4UPreauthData, + RemoteCallKerbVerifyChecksum, + RemoteCallKerbReserved1, + RemoteCallKerbReserved2, + RemoteCallKerbReserved3, + RemoteCallKerbReserved4, + RemoteCallKerbReserved5, + RemoteCallKerbReserved6, + RemoteCallKerbReserved7, + RemoteCallKerbDecryptPacCredentials, + RemoteCallKerbCreateECDHKeyAgreement, + RemoteCallKerbCreateDHKeyAgreement, + RemoteCallKerbDestroyKeyAgreement, + RemoteCallKerbKeyAgreementGenerateNonce, + RemoteCallKerbFinalizeKeyAgreement, + RemoteCallKerbMaximum = 0x1ff, + // End Kerberos remote calls + + // Start NTLM remote calls + RemoteCallNtlmMinimum = 0x200, + RemoteCallNtlmNegotiateVersion = 0x200, + RemoteCallNtlmLm20GetNtlm3ChallengeResponse, + RemoteCallNtlmCalculateNtResponse, + RemoteCallNtlmCalculateUserSessionKeyNt, + RemoteCallNtlmCompareCredentials, + RemoteCallNtlmMaximum = 0x2ff, + // End NTLM remote calls +} RemoteGuardCallId; + +FREERDP_LOCAL RdpEarPackageType rdpear_packageType_from_name(WinPrAsn1_OctetString* package); +FREERDP_LOCAL wStream* rdpear_encodePayload(RdpEarPackageType packageType, wStream* payload); + +#define RDPEAR_COMMON_MESSAGE_DECL(V) \ + FREERDP_LOCAL BOOL ndr_read_##V(NdrContext* context, wStream* s, const void* hints, V* obj); \ + FREERDP_LOCAL BOOL ndr_write_##V(NdrContext* context, wStream* s, const void* hints, \ + const V* obj); \ + FREERDP_LOCAL void ndr_destroy_##V(NdrContext* context, const void* hints, V* obj); \ + FREERDP_LOCAL void ndr_dump_##V(wLog* logger, UINT32 lvl, size_t indentLevel, const V* obj); \ + FREERDP_LOCAL NdrMessageType ndr_##V##_descr() + +/** @brief 2.2.1.2.2 KERB_RPC_OCTET_STRING */ +typedef struct +{ + UINT32 length; + BYTE* value; +} KERB_RPC_OCTET_STRING; + +RDPEAR_COMMON_MESSAGE_DECL(KERB_RPC_OCTET_STRING); + +/** @brief 2.2.1.2.1 KERB_ASN1_DATA */ +typedef struct +{ + UINT32 Pdu; + NdrArrayHints Asn1BufferHints; + BYTE* Asn1Buffer; +} KERB_ASN1_DATA; + +RDPEAR_COMMON_MESSAGE_DECL(KERB_ASN1_DATA); + +/** @brief 2.3.10 RPC_UNICODE_STRING (MS-DTYP) */ +typedef struct +{ + NdrVaryingArrayHints lenHints; + UINT32 strLength; + WCHAR* Buffer; +} RPC_UNICODE_STRING; + +RDPEAR_COMMON_MESSAGE_DECL(RPC_UNICODE_STRING); + +/** @brief 2.2.1.2.3 KERB_RPC_INTERNAL_NAME */ +typedef struct +{ + UINT16 NameType; + NdrArrayHints nameHints; + RPC_UNICODE_STRING* Names; +} KERB_RPC_INTERNAL_NAME; + +FREERDP_LOCAL BOOL ndr_read_KERB_RPC_INTERNAL_NAME(NdrContext* context, wStream* s, + const void* hints, KERB_RPC_INTERNAL_NAME* res); +FREERDP_LOCAL BOOL ndr_write_KERB_RPC_INTERNAL_NAME(NdrContext* context, wStream* s, + const void* hints, + const KERB_RPC_INTERNAL_NAME* res); +FREERDP_LOCAL void ndr_dump_KERB_RPC_INTERNAL_NAME(wLog* logger, UINT32 lvl, size_t indentLevel, + const KERB_RPC_INTERNAL_NAME* obj); +FREERDP_LOCAL void ndr_destroy_KERB_RPC_INTERNAL_NAME(NdrContext* context, const void* hints, + KERB_RPC_INTERNAL_NAME* obj); + +/** @brief 2.2.1.2.8 KERB_RPC_ENCRYPTION_KEY */ +typedef struct +{ + UINT32 reserved1; + UINT32 reserved2; + KERB_RPC_OCTET_STRING reserved3; +} KERB_RPC_ENCRYPTION_KEY; + +RDPEAR_COMMON_MESSAGE_DECL(KERB_RPC_ENCRYPTION_KEY); + +/** @brief 2.2.2.1.8 BuildEncryptedAuthData */ +typedef struct +{ + UINT32 KeyUsage; + KERB_RPC_ENCRYPTION_KEY* Key; + KERB_ASN1_DATA* PlainAuthData; +} BuildEncryptedAuthDataReq; + +RDPEAR_COMMON_MESSAGE_DECL(BuildEncryptedAuthDataReq); + +/** @brief 2.2.2.1.7 ComputeTgsChecksum */ +typedef struct +{ + KERB_ASN1_DATA* requestBody; + KERB_RPC_ENCRYPTION_KEY* Key; + UINT32 ChecksumType; +} ComputeTgsChecksumReq; + +RDPEAR_COMMON_MESSAGE_DECL(ComputeTgsChecksumReq); + +/** @brief 2.2.2.1.4 CreateApReqAuthenticator */ +typedef struct +{ + KERB_RPC_ENCRYPTION_KEY* EncryptionKey; + ULONG SequenceNumber; + KERB_RPC_INTERNAL_NAME* ClientName; + RPC_UNICODE_STRING* ClientRealm; + PLARGE_INTEGER SkewTime; + KERB_RPC_ENCRYPTION_KEY* SubKey; // optional + KERB_ASN1_DATA* AuthData; // optional + KERB_ASN1_DATA* GssChecksum; // optional + ULONG KeyUsage; +} CreateApReqAuthenticatorReq; + +RDPEAR_COMMON_MESSAGE_DECL(CreateApReqAuthenticatorReq); + +/** @brief 2.2.2.1.4 CreateApReqAuthenticator */ +typedef struct +{ + LARGE_INTEGER AuthenticatorTime; + KERB_ASN1_DATA Authenticator; + LONG KerbProtocolError; +} CreateApReqAuthenticatorResp; + +FREERDP_LOCAL NdrMessageType ndr_CreateApReqAuthenticatorResp_descr(); + +/** @brief 2.2.2.1.6 UnpackKdcReplyBody */ +typedef struct +{ + KERB_ASN1_DATA* EncryptedData; + KERB_RPC_ENCRYPTION_KEY* Key; + KERB_RPC_ENCRYPTION_KEY* StrengthenKey; + ULONG Pdu; + ULONG KeyUsage; +} UnpackKdcReplyBodyReq; + +RDPEAR_COMMON_MESSAGE_DECL(UnpackKdcReplyBodyReq); + +/** @brief 2.2.2.1.6 UnpackKdcReplyBody */ +typedef struct +{ + LONG KerbProtocolError; + KERB_ASN1_DATA ReplyBody; +} UnpackKdcReplyBodyResp; + +FREERDP_LOCAL NdrMessageType ndr_UnpackKdcReplyBodyResp_descr(); + +typedef struct +{ + KERB_ASN1_DATA* EncryptedReply; + KERB_RPC_ENCRYPTION_KEY* Key; +} DecryptApReplyReq; + +RDPEAR_COMMON_MESSAGE_DECL(DecryptApReplyReq); + +typedef struct +{ + KERB_ASN1_DATA* Reply; + KERB_ASN1_DATA* ReplyBody; + KERB_RPC_ENCRYPTION_KEY* SessionKey; +} PackApReplyReq; + +RDPEAR_COMMON_MESSAGE_DECL(PackApReplyReq); + +typedef struct +{ + NdrArrayHints PackedReplyHints; + BYTE* PackedReply; +} PackApReplyResp; + +FREERDP_LOCAL NdrMessageType ndr_PackApReplyResp_descr(); + +#undef RDPEAR_COMMON_MESSAGE_DECL + +#endif /* FREERDP_CHANNEL_RDPEAR_COMMON_H */ diff --git a/channels/rdpear/common/rdpear_asn1.c b/channels/rdpear/common/rdpear_asn1.c new file mode 100644 index 000000000..c16006648 --- /dev/null +++ b/channels/rdpear/common/rdpear_asn1.c @@ -0,0 +1,104 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * ASN1 routines for RDPEAR + * + * Copyright 2024 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +wStream* rdpear_enc_Checksum(UINT32 cksumtype, krb5_checksum* csum) +{ + /** + * Checksum ::= SEQUENCE { + * cksumtype [0] Int32, + * checksum [1] OCTET STRING + * } + */ + wStream* ret = NULL; + WinPrAsn1Encoder* enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER); + if (!enc) + return NULL; + + if (!WinPrAsn1EncSeqContainer(enc)) + goto out; + + if (!WinPrAsn1EncContextualInteger(enc, 0, (WinPrAsn1_INTEGER)cksumtype)) + goto out; + + WinPrAsn1_OctetString octets; + octets.data = (BYTE*)csum->contents; + octets.len = csum->length; + if (!WinPrAsn1EncContextualOctetString(enc, 1, &octets) || !WinPrAsn1EncEndContainer(enc)) + goto out; + + ret = Stream_New(NULL, 1024); + if (!ret) + goto out; + + if (!WinPrAsn1EncToStream(enc, ret)) + { + Stream_Free(ret, TRUE); + ret = NULL; + goto out; + } + +out: + WinPrAsn1Encoder_Free(&enc); + return ret; +} + +wStream* rdpear_enc_EncryptedData(UINT32 encType, krb5_data* payload) +{ + /** + * EncryptedData ::= SEQUENCE { + * etype [0] Int32 -- EncryptionType --, + * kvno [1] UInt32 OPTIONAL, + * cipher [2] OCTET STRING -- ciphertext + * } + */ + wStream* ret = NULL; + WinPrAsn1Encoder* enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER); + if (!enc) + return NULL; + + if (!WinPrAsn1EncSeqContainer(enc)) + goto out; + + if (!WinPrAsn1EncContextualInteger(enc, 0, (WinPrAsn1_INTEGER)encType)) + goto out; + + WinPrAsn1_OctetString octets; + octets.data = (BYTE*)payload->data; + octets.len = payload->length; + if (!WinPrAsn1EncContextualOctetString(enc, 2, &octets) || !WinPrAsn1EncEndContainer(enc)) + goto out; + + ret = Stream_New(NULL, 1024); + if (!ret) + goto out; + + if (!WinPrAsn1EncToStream(enc, ret)) + { + Stream_Free(ret, TRUE); + ret = NULL; + goto out; + } + +out: + WinPrAsn1Encoder_Free(&enc); + return ret; +} diff --git a/channels/rdpear/common/rdpear_common.c b/channels/rdpear/common/rdpear_common.c new file mode 100644 index 000000000..799183903 --- /dev/null +++ b/channels/rdpear/common/rdpear_common.c @@ -0,0 +1,488 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2023 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include + +#define TAG CHANNELS_TAG("rdpear") + +const char kerberosPackageName[] = { + 'K', 0, 'e', 0, 'r', 0, 'b', 0, 'e', 0, 'r', 0, 'o', 0, 's', 0 +}; +const char ntlmPackageName[] = { 'N', 0, 'T', 0, 'L', 0, 'M', 0 }; + +RdpEarPackageType rdpear_packageType_from_name(WinPrAsn1_OctetString* package) +{ + if (package->len == sizeof(kerberosPackageName) && + memcmp(package->data, kerberosPackageName, package->len) == 0) + return RDPEAR_PACKAGE_KERBEROS; + + if (package->len == sizeof(ntlmPackageName) && + memcmp(package->data, ntlmPackageName, package->len) == 0) + return RDPEAR_PACKAGE_NTLM; + + return RDPEAR_PACKAGE_UNKNOWN; +} + +wStream* rdpear_encodePayload(RdpEarPackageType packageType, wStream* payload) +{ + wStream* ret = NULL; + WinPrAsn1Encoder* enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER); + if (!enc) + return NULL; + + /* TSRemoteGuardInnerPacket ::= SEQUENCE { */ + if (!WinPrAsn1EncSeqContainer(enc)) + goto out; + + /* packageName [1] OCTET STRING */ + WinPrAsn1_OctetString packageOctetString; + switch (packageType) + { + case RDPEAR_PACKAGE_KERBEROS: + packageOctetString.data = (BYTE*)kerberosPackageName; + packageOctetString.len = sizeof(kerberosPackageName); + break; + case RDPEAR_PACKAGE_NTLM: + packageOctetString.data = (BYTE*)ntlmPackageName; + packageOctetString.len = sizeof(ntlmPackageName); + break; + default: + goto out; + } + + if (!WinPrAsn1EncContextualOctetString(enc, 1, &packageOctetString)) + goto out; + + /* buffer [2] OCTET STRING*/ + WinPrAsn1_OctetString payloadOctetString = { Stream_GetPosition(payload), + Stream_Buffer(payload) }; + if (!WinPrAsn1EncContextualOctetString(enc, 2, &payloadOctetString)) + goto out; + + /* } */ + if (!WinPrAsn1EncEndContainer(enc)) + goto out; + + ret = Stream_New(NULL, 100); + if (!ret) + goto out; + + if (!WinPrAsn1EncToStream(enc, ret)) + { + Stream_Free(ret, TRUE); + ret = NULL; + goto out; + } +out: + WinPrAsn1Encoder_Free(&enc); + return ret; +} + +#define RDPEAR_SIMPLE_MESSAGE_TYPE(V) \ + BOOL ndr_read_##V(NdrContext* context, wStream* s, const void* hints, V* obj) \ + { \ + return ndr_struct_read_fromDescr(context, s, &V##_struct, obj); \ + } \ + BOOL ndr_write_##V(NdrContext* context, wStream* s, const void* hints, const V* obj) \ + { \ + return ndr_struct_write_fromDescr(context, s, &V##_struct, obj); \ + } \ + void ndr_destroy_##V(NdrContext* context, const void* hints, V* obj) \ + { \ + ndr_struct_destroy(context, &V##_struct, obj); \ + } \ + void ndr_dump_##V(wLog* logger, UINT32 lvl, size_t indentLevel, const V* obj) \ + { \ + ndr_struct_dump_fromDescr(logger, lvl, indentLevel, &V##_struct, obj); \ + } \ + \ + NdrMessageDescr ndr_##V##_descr_s = { \ + NDR_ARITY_SIMPLE, \ + sizeof(V), \ + (NDR_READER_FN)ndr_read_##V, \ + (NDR_WRITER_FN)ndr_write_##V, \ + (NDR_DESTROY_FN)ndr_destroy_##V, \ + (NDR_DUMP_FN)ndr_dump_##V, \ + }; \ + \ + NdrMessageType ndr_##V##_descr(void) \ + { \ + return &ndr_##V##_descr_s; \ + } + +static const NdrFieldStruct KERB_RPC_OCTET_STRING_fields[] = { + { "Length", offsetof(KERB_RPC_OCTET_STRING, length), NDR_NOT_POINTER, -1, &ndr_uint32_descr_s }, + { "value", offsetof(KERB_RPC_OCTET_STRING, value), NDR_POINTER_NON_NULL, 0, + &ndr_uint8Array_descr_s } +}; +static const NdrStructDescr KERB_RPC_OCTET_STRING_struct = { "KERB_RPC_OCTET_STRING", 2, + KERB_RPC_OCTET_STRING_fields }; + +RDPEAR_SIMPLE_MESSAGE_TYPE(KERB_RPC_OCTET_STRING) + +/* ============================= KERB_ASN1_DATA ============================== */ + +static const NdrFieldStruct KERB_ASN1_DATA_fields[] = { + { "Pdu", offsetof(KERB_ASN1_DATA, Pdu), NDR_NOT_POINTER, -1, &ndr_uint32_descr_s }, + { "Count", offsetof(KERB_ASN1_DATA, Asn1BufferHints.count), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "Asn1Buffer", offsetof(KERB_ASN1_DATA, Asn1Buffer), NDR_POINTER_NON_NULL, 1, + &ndr_uint8Array_descr_s } +}; +static const NdrStructDescr KERB_ASN1_DATA_struct = { "KERB_ASN1_DATA", + ARRAYSIZE(KERB_ASN1_DATA_fields), + KERB_ASN1_DATA_fields }; + +RDPEAR_SIMPLE_MESSAGE_TYPE(KERB_ASN1_DATA) + +/* ============================ RPC_UNICODE_STRING ========================== */ + +BOOL ndr_read_RPC_UNICODE_STRING(NdrContext* context, wStream* s, const void* hints, + RPC_UNICODE_STRING* res) +{ + NdrDeferredEntry bufferDesc = { NDR_PTR_NULL, "RPC_UNICODE_STRING.Buffer", &res->lenHints, + &res->Buffer, ndr_uint16VaryingArray_descr() }; + UINT16 Length, MaximumLength; + + if (!ndr_read_uint16(context, s, &Length) || !ndr_read_uint16(context, s, &MaximumLength) || + !ndr_read_refpointer(context, s, &bufferDesc.ptrId) || Length > MaximumLength) + return FALSE; + + res->lenHints.length = Length; + res->lenHints.maxLength = MaximumLength; + res->strLength = Length / 2; + + return ndr_push_deferreds(context, &bufferDesc, 1); +} + +#if 0 +BOOL ndr_write_RPC_UNICODE_STRING(NdrContext* context, wStream* s, const void* hints, + const RPC_UNICODE_STRING* res) +{ + return ndr_write_uint32(context, s, res->lenHints.length) && + ndr_write_uint32(context, s, res->lenHints.maxLength) /*&& + ndr_write_BYTE_ptr(context, s, (BYTE*)res->Buffer, res->Length)*/ + ; +} +#endif + +void ndr_dump_RPC_UNICODE_STRING(wLog* logger, UINT32 lvl, size_t indentLevel, + const RPC_UNICODE_STRING* obj) +{ + WLog_Print(logger, lvl, "\tLength=%d MaximumLength=%d", obj->lenHints.length, + obj->lenHints.maxLength); + winpr_HexLogDump(logger, lvl, obj->Buffer, obj->lenHints.length); +} + +void ndr_destroy_RPC_UNICODE_STRING(NdrContext* context, const void* hints, RPC_UNICODE_STRING* obj) +{ + if (!obj) + return; + free(obj->Buffer); + obj->Buffer = NULL; +} + +static const NdrMessageDescr RPC_UNICODE_STRING_descr_ = { + NDR_ARITY_SIMPLE, + sizeof(RPC_UNICODE_STRING), + (NDR_READER_FN)ndr_read_RPC_UNICODE_STRING, + (NDR_WRITER_FN) /*ndr_write_RPC_UNICODE_STRING*/ NULL, + (NDR_DESTROY_FN)ndr_destroy_RPC_UNICODE_STRING, + (NDR_DUMP_FN)ndr_dump_RPC_UNICODE_STRING +}; + +NdrMessageType RPC_UNICODE_STRING_descr() +{ + return &RPC_UNICODE_STRING_descr_; +} + +/* ========================= RPC_UNICODE_STRING_Array ======================== */ + +static BOOL ndr_read_RPC_UNICODE_STRING_Array(NdrContext* context, wStream* s, const void* hints, + void* v) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(hints); + return ndr_read_uconformant_array(context, s, hints, RPC_UNICODE_STRING_descr(), v); +} + +static BOOL ndr_write_RPC_UNICODE_STRING_Array(NdrContext* context, wStream* s, const void* ghints, + const void* v) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(s); + WINPR_ASSERT(ghints); + + const NdrArrayHints* hints = (const NdrArrayHints*)ghints; + + return ndr_write_uconformant_array(context, s, hints->count, RPC_UNICODE_STRING_descr(), v); +} + +static const NdrMessageDescr RPC_UNICODE_STRING_Array_descr_ = { NDR_ARITY_ARRAYOF, + sizeof(RPC_UNICODE_STRING), + ndr_read_RPC_UNICODE_STRING_Array, + ndr_write_RPC_UNICODE_STRING_Array, + (NDR_DESTROY_FN)NULL, + (NDR_DUMP_FN)NULL }; + +NdrMessageType RPC_UNICODE_STRING_Array_descr() +{ + return &RPC_UNICODE_STRING_Array_descr_; +} + +/* ========================== KERB_RPC_INTERNAL_NAME ======================== */ + +BOOL ndr_read_KERB_RPC_INTERNAL_NAME(NdrContext* context, wStream* s, const void* hints, + KERB_RPC_INTERNAL_NAME* res) +{ + NdrDeferredEntry names = { NDR_PTR_NULL, "KERB_RPC_INTERNAL_NAME.Names", &res->nameHints, + &res->Names, RPC_UNICODE_STRING_Array_descr() }; + + UINT16 nameCount; + if (!ndr_read_uint16(context, s, &res->NameType) || !ndr_read_uint16(context, s, &nameCount)) + return FALSE; + + res->nameHints.count = nameCount; + + return ndr_read_refpointer(context, s, &names.ptrId) && ndr_push_deferreds(context, &names, 1); +} + +BOOL ndr_write_KERB_RPC_INTERNAL_NAME(NdrContext* context, wStream* s, const void* hints, + const KERB_RPC_INTERNAL_NAME* res) +{ + return FALSE; +} + +void ndr_dump_KERB_RPC_INTERNAL_NAME(wLog* logger, UINT32 lvl, size_t indentLevel, + const KERB_RPC_INTERNAL_NAME* obj) +{ +} + +void ndr_destroy_KERB_RPC_INTERNAL_NAME(NdrContext* context, const void* hints, + KERB_RPC_INTERNAL_NAME* obj) +{ + if (!obj) + return; + + for (int i = 0; i < obj->nameHints.count; i++) + ndr_destroy_RPC_UNICODE_STRING(context, NULL, &obj->Names[i]); + + free(obj->Names); + obj->Names = NULL; +} + +NdrMessageDescr KERB_RPC_INTERNAL_NAME_descr_ = { + NDR_ARITY_SIMPLE, + sizeof(KERB_RPC_INTERNAL_NAME), + (NDR_READER_FN)ndr_read_KERB_RPC_INTERNAL_NAME, + (NDR_WRITER_FN)NULL, + (NDR_DESTROY_FN)ndr_destroy_KERB_RPC_INTERNAL_NAME, + (NDR_DUMP_FN)ndr_dump_KERB_RPC_INTERNAL_NAME +}; + +NdrMessageType KERB_RPC_INTERNAL_NAME_descr() +{ + return &KERB_RPC_INTERNAL_NAME_descr_; +} + +/* ========================== KERB_RPC_ENCRYPTION_KEY ======================== */ + +static const NdrFieldStruct KERB_RPC_ENCRYPTION_KEY_fields[] = { + { "reserved1", offsetof(KERB_RPC_ENCRYPTION_KEY, reserved1), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "reserved2", offsetof(KERB_RPC_ENCRYPTION_KEY, reserved2), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "reserved3", offsetof(KERB_RPC_ENCRYPTION_KEY, reserved3), NDR_NOT_POINTER, -1, + &ndr_KERB_RPC_OCTET_STRING_descr_s } +}; +static const NdrStructDescr KERB_RPC_ENCRYPTION_KEY_struct = { + "KERB_RPC_ENCRYPTION_KEY", ARRAYSIZE(KERB_RPC_ENCRYPTION_KEY_fields), + KERB_RPC_ENCRYPTION_KEY_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(KERB_RPC_ENCRYPTION_KEY) + +/* ========================== BuildEncryptedAuthDataReq ======================== */ + +static const NdrFieldStruct BuildEncryptedAuthDataReq_fields[] = { + { "KeyUsage", offsetof(BuildEncryptedAuthDataReq, KeyUsage), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "key", offsetof(BuildEncryptedAuthDataReq, Key), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "plainAuthData", offsetof(BuildEncryptedAuthDataReq, PlainAuthData), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s } +}; +static const NdrStructDescr BuildEncryptedAuthDataReq_struct = { + "BuildEncryptedAuthDataReq", ARRAYSIZE(BuildEncryptedAuthDataReq_fields), + BuildEncryptedAuthDataReq_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(BuildEncryptedAuthDataReq) + +/* ========================== ComputeTgsChecksumReq ======================== */ + +static const NdrFieldStruct ComputeTgsChecksumReq_fields[] = { + { "requestBody", offsetof(ComputeTgsChecksumReq, requestBody), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "key", offsetof(ComputeTgsChecksumReq, Key), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "ChecksumType", offsetof(ComputeTgsChecksumReq, ChecksumType), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s } +}; +static const NdrStructDescr ComputeTgsChecksumReq_struct = { + "ComputeTgsChecksumReq", ARRAYSIZE(ComputeTgsChecksumReq_fields), ComputeTgsChecksumReq_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(ComputeTgsChecksumReq) + +/* ========================== CreateApReqAuthenticatorReq ======================== */ + +static const NdrFieldStruct CreateApReqAuthenticatorReq_fields[] = { + { "EncryptionKey", offsetof(CreateApReqAuthenticatorReq, EncryptionKey), NDR_POINTER_NON_NULL, + -1, &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "SequenceNumber", offsetof(CreateApReqAuthenticatorReq, SequenceNumber), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "ClientName", offsetof(CreateApReqAuthenticatorReq, ClientName), NDR_POINTER_NON_NULL, -1, + &KERB_RPC_INTERNAL_NAME_descr_ }, + { "ClientRealm", offsetof(CreateApReqAuthenticatorReq, ClientRealm), NDR_POINTER_NON_NULL, -1, + &RPC_UNICODE_STRING_descr_ }, + { "SkewTime", offsetof(CreateApReqAuthenticatorReq, SkewTime), NDR_POINTER_NON_NULL, -1, + &ndr_uint64_descr_s }, + { "SubKey", offsetof(CreateApReqAuthenticatorReq, SubKey), NDR_POINTER, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "AuthData", offsetof(CreateApReqAuthenticatorReq, AuthData), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "GssChecksum", offsetof(CreateApReqAuthenticatorReq, GssChecksum), NDR_POINTER, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "KeyUsage", offsetof(CreateApReqAuthenticatorReq, KeyUsage), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, +}; +static const NdrStructDescr CreateApReqAuthenticatorReq_struct = { + "CreateApReqAuthenticatorReq", ARRAYSIZE(CreateApReqAuthenticatorReq_fields), + CreateApReqAuthenticatorReq_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(CreateApReqAuthenticatorReq) + +/* ========================== CreateApReqAuthenticatorResp ======================== */ + +static const NdrFieldStruct CreateApReqAuthenticatorResp_fields[] = { + { "AuthenticatorTime", offsetof(CreateApReqAuthenticatorResp, AuthenticatorTime), + NDR_NOT_POINTER, -1, &ndr_uint64_descr_s }, + { "Authenticator", offsetof(CreateApReqAuthenticatorResp, Authenticator), NDR_NOT_POINTER, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "KerbProtocolError", offsetof(CreateApReqAuthenticatorResp, KerbProtocolError), + NDR_NOT_POINTER, -1, &ndr_uint32_descr_s }, +}; + +static const NdrStructDescr CreateApReqAuthenticatorResp_struct = { + "CreateApReqAuthenticatorResp", ARRAYSIZE(CreateApReqAuthenticatorResp_fields), + CreateApReqAuthenticatorResp_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(CreateApReqAuthenticatorResp) + +/* ========================== UnpackKdcReplyBodyReq ======================== */ + +static const NdrFieldStruct UnpackKdcReplyBodyReq_fields[] = { + { "EncryptedData", offsetof(UnpackKdcReplyBodyReq, EncryptedData), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "Key", offsetof(UnpackKdcReplyBodyReq, Key), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "StrenghtenKey", offsetof(UnpackKdcReplyBodyReq, StrengthenKey), NDR_POINTER, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s }, + { "Pdu", offsetof(UnpackKdcReplyBodyReq, Pdu), NDR_NOT_POINTER, -1, &ndr_uint32_descr_s }, + { "KeyUsage", offsetof(UnpackKdcReplyBodyReq, KeyUsage), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, +}; + +static const NdrStructDescr UnpackKdcReplyBodyReq_struct = { + "UnpackKdcReplyBodyReq", ARRAYSIZE(UnpackKdcReplyBodyReq_fields), UnpackKdcReplyBodyReq_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(UnpackKdcReplyBodyReq) + +/* ========================== UnpackKdcReplyBodyResp ======================== */ + +static const NdrFieldStruct UnpackKdcReplyBodyResp_fields[] = { + { "KerbProtocolError", offsetof(UnpackKdcReplyBodyResp, KerbProtocolError), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "ReplyBody", offsetof(UnpackKdcReplyBodyResp, ReplyBody), NDR_NOT_POINTER, -1, + &ndr_KERB_ASN1_DATA_descr_s } +}; + +static const NdrStructDescr UnpackKdcReplyBodyResp_struct = { + "UnpackKdcReplyBodyResp", ARRAYSIZE(UnpackKdcReplyBodyResp_fields), + UnpackKdcReplyBodyResp_fields +}; + +RDPEAR_SIMPLE_MESSAGE_TYPE(UnpackKdcReplyBodyResp) + +/* ========================== DecryptApReplyReq ======================== */ + +static const NdrFieldStruct DecryptApReplyReq_fields[] = { + { "EncryptedReply", offsetof(DecryptApReplyReq, EncryptedReply), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "Key", offsetof(DecryptApReplyReq, Key), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s } +}; + +static const NdrStructDescr DecryptApReplyReq_struct = { "DecryptApReplyReq", + ARRAYSIZE(DecryptApReplyReq_fields), + DecryptApReplyReq_fields }; + +RDPEAR_SIMPLE_MESSAGE_TYPE(DecryptApReplyReq) + +/* ========================== PackApReplyReq ======================== */ + +static const NdrFieldStruct PackApReplyReq_fields[] = { + { "Reply", offsetof(PackApReplyReq, Reply), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "ReplyBody", offsetof(PackApReplyReq, ReplyBody), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_ASN1_DATA_descr_s }, + { "SessionKey", offsetof(PackApReplyReq, SessionKey), NDR_POINTER_NON_NULL, -1, + &ndr_KERB_RPC_ENCRYPTION_KEY_descr_s } +}; + +static const NdrStructDescr PackApReplyReq_struct = { "PackApReplyReq", + ARRAYSIZE(PackApReplyReq_fields), + PackApReplyReq_fields }; + +RDPEAR_SIMPLE_MESSAGE_TYPE(PackApReplyReq) + +/* ========================== PackApReplyResp ======================== */ + +static const NdrFieldStruct PackApReplyResp_fields[] = { + { "PackedReplySize", offsetof(PackApReplyResp, PackedReplyHints), NDR_NOT_POINTER, -1, + &ndr_uint32_descr_s }, + { "PackedReply", offsetof(PackApReplyResp, PackedReply), NDR_POINTER_NON_NULL, 0, + &ndr_uint8Array_descr_s }, +}; + +static const NdrStructDescr PackApReplyResp_struct = { "PackApReplyResp", + ARRAYSIZE(PackApReplyResp_fields), + PackApReplyResp_fields }; + +RDPEAR_SIMPLE_MESSAGE_TYPE(PackApReplyResp) diff --git a/channels/rdpear/common/test/CMakeLists.txt b/channels/rdpear/common/test/CMakeLists.txt new file mode 100644 index 000000000..f294568ef --- /dev/null +++ b/channels/rdpear/common/test/CMakeLists.txt @@ -0,0 +1,33 @@ + +set(MODULE_NAME "TestRdpear") +set(MODULE_PREFIX "TEST_RDPEAR") + +set(TEST_RDPEAR_DRIVER TestRdpear.c) + +set(TEST_RDPEAR_TESTS + TestNdr.c + TestNdrEar.c +) + +create_test_sourcelist(TEST_RDPEAR_SRCS + TestRdpear.c + ${TEST_RDPEAR_TESTS} +) + +add_executable(${MODULE_NAME} ${TEST_RDPEAR_SRCS}) + +add_definitions(-DTESTING_OUTPUT_DIRECTORY="${PROJECT_BINARY_DIR}") +add_definitions(-DTESTING_SRC_DIRECTORY="${PROJECT_SOURCE_DIR}") + +target_link_libraries(${MODULE_NAME} freerdp winpr freerdp-client) + + +set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}") + +foreach(test ${${MODULE_PREFIX}_TESTS}) + get_filename_component(TestName ${test} NAME_WE) + add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName}) +endforeach() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Rdpear/Test") + diff --git a/channels/rdpear/common/test/TestNdr.c b/channels/rdpear/common/test/TestNdr.c new file mode 100644 index 000000000..3697340ee --- /dev/null +++ b/channels/rdpear/common/test/TestNdr.c @@ -0,0 +1,36 @@ +#include + +int TestNdr(int argc, char* argv[]) +{ + int retCode = -2; + NdrContext* context = ndr_context_new(FALSE, 1); + if (!context) + return -1; + + BYTE payload[] = { + // == conformant array == + 0x02, 0x00, 0x00, 0x00, // (nitems) + 0x30, 0x00, // content + 0x00, 0x00 // (padding) + }; + wStream staticS; + wStream* s = Stream_StaticInit(&staticS, payload, sizeof(payload)); + + BYTE* target = NULL; + NdrArrayHints hints = { 2 }; + NdrDeferredEntry e = { 0x020028, "arrayContent", &hints, &target, ndr_uint8Array_descr() }; + + if (!ndr_push_deferreds(context, &e, 1)) + goto out; + + if (!ndr_treat_deferred_read(context, s)) + goto out; + + NdrMessageType descr = ndr_uint8Array_descr(); + descr->destroyFn(context, &hints, target); + free(target); + retCode = 0; +out: + ndr_context_destroy(&context); + return retCode; +} diff --git a/channels/rdpear/common/test/TestNdrEar.c b/channels/rdpear/common/test/TestNdrEar.c new file mode 100644 index 000000000..0fc7997aa --- /dev/null +++ b/channels/rdpear/common/test/TestNdrEar.c @@ -0,0 +1,357 @@ +#include + +#include +#include + +BYTE* parseHexBlock(const char* str, size_t* plen) +{ + WINPR_ASSERT(str); + WINPR_ASSERT(plen); + + BYTE* ret = malloc(strlen(str) / 2); + BYTE* dest = ret; + const char* ptr = str; + BYTE tmp = 0; + size_t nchars = 0; + size_t len = 0; + + for (; *ptr; ptr++) + { + switch (*ptr) + { + case ' ': + case '\n': + case '\t': + if (nchars) + { + WLog_ERR("", "error parsing hex block, unpaired char"); + free(ret); + return NULL; + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + tmp = tmp * 16 + (*ptr - '0'); + nchars++; + if (nchars == 2) + { + *dest = tmp; + dest++; + len++; + tmp = 0; + nchars = 0; + } + break; + + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + tmp = tmp * 16 + (10 + *ptr - 'a'); + nchars++; + if (nchars == 2) + { + *dest = tmp; + dest++; + len++; + tmp = 0; + nchars = 0; + } + break; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + tmp = tmp * 16 + (10 + *ptr - 'A'); + nchars++; + if (nchars == 2) + { + *dest = tmp; + dest++; + len++; + tmp = 0; + nchars = 0; + } + break; + default: + WLog_ERR("", "invalid char in hex block"); + free(ret); + return NULL; + } + } + + *plen = len; + return ret; +} + +int TestNdrEarWrite(int argc, char* argv[]) +{ + wStream* s = NULL; + + BYTE buffer[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + KERB_ASN1_DATA asn1; + asn1.Pdu = 7; + asn1.Asn1BufferHints.count = 16; + asn1.Asn1Buffer = buffer; + + s = Stream_New(NULL, 100); + if (!s) + return -1; + + NdrContext* context = ndr_context_new(FALSE, 1); + if (!context) + return -1; + + if (!ndr_write_KERB_ASN1_DATA(context, s, NULL, &asn1)) + return -2; + if (!ndr_treat_deferred_write(context, s)) + return -3; + + // winpr_HexDump("", WLOG_DEBUG, Stream_Buffer(s), Stream_GetPosition(s)); + ndr_context_destroy(&context); + return 0; +} + +int TestNdrEarRead(int argc, char* argv[]) +{ + int retCode = -2; + + /* ====================================================================== */ + NdrContext* context = ndr_context_new(FALSE, 1); + if (!context) + return -1; + + wStream staticS; + wStream* s; + +#if 0 + BYTE payload[] = { + 0x00, 0x00, 0x00, 0x00, // (PduType) + 0x02, 0x00, 0x00, 0x00, // (Length) + 0x28, 0x00, 0x02, 0x00, // (Asn1Buffer) + + // == conformant array == + 0x02, 0x00, 0x00, 0x00, // (nitems) + 0x30, 0x00, // content + 0x00, 0x00 // (padding) + }; + s = Stream_StaticInit(&staticS, payload, sizeof(payload)); + + KERB_ASN1_DATA asn1 = { 0 }; + if (!ndr_read_KERB_ASN1_DATA(context, s, NULL, &asn1) || !ndr_treat_deferred_read(context, s) || + asn1.Asn1BufferHints.count != 2 || *asn1.Asn1Buffer != 0x30) + goto out; + KERB_ASN1_DATA_destroy(context, &asn1); + ndr_context_reset(context); + + /* ====================================================================== */ + BYTE payload2[] = { + // ------------ a RPC_UNICODE_STRING: Administrateur ------------------------- + 0x1c, 0x00, // (Length) + 0x1e, 0x00, // (MaximumLength) + 0x1c, 0x00, 0x02, 0x00, // (Buffer ptr) + // == conformant array == + 0x0f, 0x00, 0x00, 0x00, // (maximum count) + 0x00, 0x00, 0x00, 0x00, // (offset) + 0x0e, 0x00, 0x00, 0x00, // (length) + + 0x48, 0x00, 0x41, 0x00, 0x52, 0x00, 0x44, 0x00, 0x45, 0x00, 0x4e, 0x00, 0x49, 0x00, 0x4e, + 0x00, 0x47, 0x00, 0x33, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x00, 0x00, + + 0x00, 0x00 + }; + retCode = -3; + + s = Stream_StaticInit(&staticS, payload2, sizeof(payload2)); + RPC_UNICODE_STRING unicode = { 0 }; + if (!ndr_read_RPC_UNICODE_STRING(context, s, NULL, &unicode) || !ndr_treat_deferred_read(context, s)) + goto out; + RPC_UNICODE_STRING_destroy(context, &unicode); + ndr_context_reset(context); + + /* ====================================================================== */ + BYTE payload3[] = { + // ------------ an KERB_RPC_INTERNAL_NAME: HARDENING3.COM ------------------------- + 0x01, 0x00, // (NameType) + 0x01, 0x00, // (NameCount) + 0x10, 0x00, 0x02, 0x00, // (Names) + // == conformant array == + 0x01, 0x00, 0x00, 0x00, // (nitems) + + // = RPC_UNICODE_STRING = + 0x1c, 0x00, // (Length) + 0x1e, 0x00, // (MaximumLength) + 0x14, 0x00, 0x02, 0x00, /// (Buffer ptr) + // == Uni-dimensional Conformant-varying Array == + 0x0f, 0x00, 0x00, 0x00, // (maximum count) + 0x00, 0x00, 0x00, 0x00, // (offset) + 0x0e, 0x00, 0x00, 0x00, // (length) + 0x41, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x73, 0x00, 0x74, + 0x00, 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x75, 0x00, 0x72, 0x00, 0x00, 0x00, + + 0x00, 0x00 + }; + KERB_RPC_INTERNAL_NAME intName = { 0 }; + retCode = -4; + s = Stream_StaticInit(&staticS, payload3, sizeof(payload3)); + if (!ndr_read_KERB_RPC_INTERNAL_NAME(context, s, NULL, &intName) || !ndr_treat_deferred_read(context, s)) + goto out; + KERB_RPC_INTERNAL_NAME_destroy(context, &intName); + ndr_context_reset(context); +#endif + + /* ====================================================================== */ +#if 0 + BYTE payload4[] = { + 0x03, 0x01, 0x03, 0x01, // unionId / unionId + 0x04, 0x00, 0x02, 0x00, // (EncryptionKey ptr) + 0xf8, 0xca, 0x95, 0x11, // (SequenceNumber) + 0x0c, 0x00, 0x02, 0x00, // (ClientName ptr) + 0x18, 0x00, 0x02, 0x00, // (ClientRealm ptr) + 0x20, 0x00, 0x02, 0x00, // (SkewTime ptr) + 0x00, 0x00, 0x00, 0x00, // (SubKey ptr) + 0x24, 0x00, 0x02, 0x00, // (AuthData ptr) + 0x2c, 0x00, 0x02, 0x00, // (GssChecksum ptr) + 0x07, 0x00, 0x00, 0x00, // (KeyUsage) + + // === EncryptionKey === + 0x40, 0xe9, 0x12, 0xdf, // reserved1 + 0x12, 0x00, 0x00, 0x00, // reserved2 + // KERB_RPC_OCTET_STRING + 0x4c, 0x00, 0x00, 0x00, // (length) + 0x08, 0x00, 0x02, 0x00, // (value ptr) + // == conformant array == + 0x4c, 0x00, 0x00, 0x00, // (length) + 0xc4, 0x41, 0xee, 0x34, + 0x82, 0x2b, 0x29, 0x61, 0xe2, 0x96, 0xb5, 0x75, 0x61, 0x2d, 0xbf, 0x86, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x08, 0x60, 0x2e, + 0x30, 0x3e, 0xfe, 0x56, 0x11, 0xf0, 0x31, 0xf2, 0xd6, 0x2e, 0x3d, 0x33, 0xfe, 0xce, 0x56, 0x12, + 0xbf, 0xb2, 0xe5, 0x86, 0x29, 0x8d, 0x29, 0x74, 0x1f, 0x8a, 0xf9, 0xb9, 0x8c, 0xd4, 0x86, 0x3a, + 0x21, 0x92, 0xb2, 0x07, 0x95, 0x4b, 0xea, 0xee, + + //=== ClientName - KERB_RPC_INTERNAL_NAME === + 0x01, 0x00, // (NameType) + 0x01, 0x00, // (NameCount) + 0x10, 0x00, 0x02, 0x00, // (Names) + + 0x01, 0x00, 0x00, 0x00, // (nitems) + + // = RPC_UNICODE_STRING = + 0x1c, 0x00, // (Length) + 0x1e, 0x00, // (MaximumLength) + 0x14, 0x00, 0x02, 0x00, //(Buffer ptr) + // == Uni-dimensional Conformant-varying Array == + 0x0f, 0x00, 0x00, 0x00, // (maximum count) + 0x00, 0x00, 0x00, 0x00, // (offset) + 0x0e, 0x00, 0x00, 0x00, // (length) + 0x41, 0x00, 0x64, 0x00, 0x6d, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x72, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x75, 0x00, 0x72, 0x00, + + // === ClientRealm - RPC_UNICODE_STRING === + 0x1c, 0x00, // (Length) + 0x1e, 0x00, // (MaximumLength) + 0x1c, 0x00, 0x02, 0x00, // (Buffer ptr) + // == Uni-dimensional conformant varying array == + 0x0f, 0x00, 0x00, 0x00, // (maximum count) + 0x00, 0x00, 0x00, 0x00, // (offset) + 0x0e, 0x00, 0x00, 0x00, // (length) + 0x48, 0x00, 0x41, 0x00, 0x52, 0x00, 0x44, 0x00, 0x45, 0x00, 0x4e, 0x00, 0x49, 0x00, 0x4e, 0x00, + 0x47, 0x00, 0x33, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x4f, 0x00, 0x4d, 0x00, + + 0x00, 0x00, 0x00, 0x00, // padding + + // == SkewTime == + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + // === AuthData - KERB_ASN1_DATA == + 0x00, 0x00, 0x00, 0x00, // (PduType) + 0x02, 0x00, 0x00, 0x00, // (Length) + 0x28, 0x00, 0x02, 0x00, // (Asn1Buffer) + // == conformant array == + 0x02, 0x00, 0x00, 0x00, // (nitems) + 0x30, 0x00, + 0x00, 0x00, // (padding) + + // === GssChecksum - KERB_ASN1_DATA === + 0x08, 0x00, 0x00, 0x00, // (PduType) + 0x1b, 0x00, 0x00, 0x00, // (Length) + 0x30, 0x00, 0x02, 0x00, // (Asn1Buffer) + // == conformant array == + 0x1b, 0x00, 0x00, 0x00, // (length) + 0x30, 0x19, + 0xa0, 0x03, + 0x02, 0x01, 0x07, + 0xa1, 0x12, + 0x04, 0x10, 0xb9, 0x4f, 0xcd, 0xae, 0xd9, 0xa8, 0xff, 0x49, 0x69, 0x5a, 0xd1, + 0x1d, 0x38, 0x49, 0xb6, 0x92, 0x00 + }; + size_t sizeofPayload4 = sizeof(payload4); +#endif +#if 1 + size_t sizeofPayload4; + BYTE* payload4 = parseHexBlock("03 01 03 01 \ + 04 00 02 00 38 9e ef 6b 0c 00 02 00 18 00 02 00 \ + 20 00 02 00 00 00 00 00 24 00 02 00 2c 00 02 00 \ + 07 00 00 00 13 8a a5 a8 12 00 00 00 20 00 00 00 \ + 08 00 02 00 20 00 00 00 c9 03 42 a8 17 8f d9 c4 \ + 9b d2 c4 6e 73 64 98 7b 90 f5 9a 28 77 8e ca de \ + 29 2e a3 8d 8a 56 36 d5 01 00 01 00 10 00 02 00 \ + 01 00 00 00 1c 00 1e 00 14 00 02 00 0f 00 00 00 \ + 00 00 00 00 0e 00 00 00 41 00 64 00 6d 00 69 00 \ + 6e 00 69 00 73 00 74 00 72 00 61 00 74 00 65 00 \ + 75 00 72 00 1c 00 1e 00 1c 00 02 00 0f 00 00 00 \ + 00 00 00 00 0e 00 00 00 48 00 41 00 52 00 44 00 \ + 45 00 4e 00 49 00 4e 00 47 00 33 00 2e 00 43 00 \ + 4f 00 4d 00 00 00 00 00 00 00 00 00 00 00 00 00 \ + 02 00 00 00 28 00 02 00 02 00 00 00 30 00 00 00 \ + 08 00 00 00 1b 00 00 00 30 00 02 00 1b 00 00 00 \ + 30 19 a0 03 02 01 07 a1 12 04 10 e4 aa ff 2b 93 \ + 97 4c f2 5c 0b 49 85 72 92 94 54 00", + &sizeofPayload4); + + if (!payload4) + goto out; +#endif + + CreateApReqAuthenticatorReq createApReqAuthenticatorReq = { 0 }; + s = Stream_StaticInit(&staticS, payload4, sizeofPayload4); + if (!ndr_skip_bytes(context, s, 4) || /* skip union id */ + !ndr_read_CreateApReqAuthenticatorReq(context, s, NULL, &createApReqAuthenticatorReq) || + !ndr_treat_deferred_read(context, s) || createApReqAuthenticatorReq.KeyUsage != 7 || + !createApReqAuthenticatorReq.EncryptionKey || createApReqAuthenticatorReq.SubKey || + !createApReqAuthenticatorReq.ClientName || + createApReqAuthenticatorReq.ClientName->nameHints.count != 1 || + !createApReqAuthenticatorReq.ClientRealm || !createApReqAuthenticatorReq.AuthData || + createApReqAuthenticatorReq.AuthData->Asn1BufferHints.count != 2 || + !createApReqAuthenticatorReq.SkewTime || + createApReqAuthenticatorReq.SkewTime->QuadPart != 0) + goto out; + ndr_destroy_CreateApReqAuthenticatorReq(context, NULL, &createApReqAuthenticatorReq); + ndr_context_reset(context); + + /* ============ successful end of test =============== */ + retCode = 0; +out: + free(payload4); + ndr_context_destroy(&context); + return retCode; +} + +int TestNdrEar(int argc, char* argv[]) +{ + return TestNdrEarRead(argc, argv); +} diff --git a/client/common/cmdline.c b/client/common/cmdline.c index 91c25b5e7..0d8f5f6d9 100644 --- a/client/common/cmdline.c +++ b/client/common/cmdline.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -4403,6 +4404,13 @@ static int freerdp_client_settings_parse_command_line_arguments_int( if (!freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeRequired, enable)) return fail_at(arg, COMMAND_LINE_ERROR); } + CommandLineSwitchCase(arg, "remoteGuard") + { + if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteCredentialGuard, TRUE)) + return fail_at(arg, COMMAND_LINE_ERROR); + if (!freerdp_settings_set_bool(settings, FreeRDP_ExtSecurity, TRUE)) + return fail_at(arg, COMMAND_LINE_ERROR); + } CommandLineSwitchCase(arg, "pth") { if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession, TRUE)) @@ -5770,6 +5778,7 @@ BOOL freerdp_client_load_addins(rdpChannels* channels, rdpSettings* settings) { FreeRDP_SupportDisplayControl, DISP_CHANNEL_NAME, NULL }, { FreeRDP_SupportGeometryTracking, GEOMETRY_CHANNEL_NAME, NULL }, { FreeRDP_SupportVideoOptimized, VIDEO_CHANNEL_NAME, NULL }, + { FreeRDP_RemoteCredentialGuard, RDPEAR_CHANNEL_NAME, NULL }, }; ChannelToLoad staticChannels[] = { diff --git a/client/common/cmdline.h b/client/common/cmdline.h index 7c6cd8e50..0e964517f 100644 --- a/client/common/cmdline.h +++ b/client/common/cmdline.h @@ -393,6 +393,8 @@ static const COMMAND_LINE_ARGUMENT_A global_cmd_args[] = { "connecting to a buggy server" }, { "restricted-admin", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "restrictedAdmin", "Restricted admin mode" }, + { "remoteGuard", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "remoteGuard", + "Remote guard credentials" }, { "rfx", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "RemoteFX" }, { "rfx-mode", COMMAND_LINE_VALUE_REQUIRED, "[image|video]", NULL, NULL, -1, NULL, "RemoteFX mode" }, diff --git a/cmake/FindKRB5.cmake b/cmake/FindKRB5.cmake index 979270484..5d5cde1cd 100644 --- a/cmake/FindKRB5.cmake +++ b/cmake/FindKRB5.cmake @@ -77,7 +77,7 @@ endfunction() function(GET_KRB5_BY_CONFIG KRB5_CONFIG) if (NOT KRB5_CONFIG) - find_file(KRB5_CONFIG + find_program(KRB5_CONFIG NAMES "krb5-config" "krb5-config.mit" @@ -89,6 +89,9 @@ function(GET_KRB5_BY_CONFIG KRB5_CONFIG) REQUIRED ) message("autodetected krb5-config at ${KRB5_CONFIG}") + if (NOT KRB5_CONFIG) + return() + endif() else() message("using krb5-config ${KRB5_CONFIG} provided by KRB5_ROOT_CONFIG") endif() @@ -195,7 +198,7 @@ elseif(KRB5_ANY_FOUND) GET_KRB5_VENDOR(ANY_VENDOR) PROVIDES_KRB5(NAME "${ANY_VENDOR}") else() - GET_KRB5_BY_CONFIG(${KRB5_ROOT_CONFIG}) + GET_KRB5_BY_CONFIG("${KRB5_ROOT_CONFIG}") endif() #message("using KRB5_FOUND ${KRB5_FOUND} ") diff --git a/include/freerdp/channels/rdpear.h b/include/freerdp/channels/rdpear.h new file mode 100644 index 000000000..4be5b76b7 --- /dev/null +++ b/include/freerdp/channels/rdpear.h @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Authentication redirection channel + * + * Copyright 2023 David Fort + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_RDPEAR_H +#define FREERDP_CHANNEL_RDPEAR_H + +#include +#include +#include + +#define RDPEAR_CHANNEL_NAME "rdpear" +#define RDPEAR_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::AuthRedirection" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CHANNEL_RDPEAR_H */ diff --git a/include/freerdp/freerdp.h b/include/freerdp/freerdp.h index 03a150a4c..5ec14a92f 100644 --- a/include/freerdp/freerdp.h +++ b/include/freerdp/freerdp.h @@ -23,6 +23,7 @@ #define FREERDP_H #include +#include #include #include @@ -538,10 +539,10 @@ owned by rdpRdp */ It is used to get the username/password. The reason argument tells why it was called. */ ALIGN64 pChooseSmartcard - ChooseSmartcard; /* (offset 70) - Callback for choosing a smartcard for logon. - Used when multiple smartcards are available. Returns an index into a list - of SmartcardCertInfo pointers */ + ChooseSmartcard; /* (offset 70) + Callback for choosing a smartcard for logon. + Used when multiple smartcards are available. Returns an index into a list + of SmartcardCertInfo pointers */ ALIGN64 pGetAccessToken GetAccessToken; /* (offset 71) Callback for obtaining an access token for \b AccessTokenType authentication */ @@ -660,6 +661,40 @@ owned by rdpRdp */ FREERDP_API UINT32 freerdp_get_nla_sspi_error(rdpContext* context); + /** Encrypts the provided buffer using the NLA's GSSAPI context + * + * \param context the RDP context + * \param inBuffer the SecBuffer buffer to encrypt + * \param outBuffer a SecBuffer to hold the encrypted content + * \returns if the operation completed successfully + * \since version 3.9.0 + */ + FREERDP_API BOOL freerdp_nla_encrypt(rdpContext* context, const SecBuffer* inBuffer, + SecBuffer* outBuffer); + + /** Decrypts the provided buffer using the NLA's GSSAPI context + * + * \param context the RDP context + * \param inBuffer the SecBuffer buffer to decrypt + * \param outBuffer a SecBuffer to hold the decrypted content + * \returns if the operation completed successfully + * \since version 3.9.0 + */ + FREERDP_API BOOL freerdp_nla_decrypt(rdpContext* context, const SecBuffer* inBuffer, + SecBuffer* outBuffer); + + /** Calls QueryContextAttributes on the SSPI context associated with the NLA part of + * the RDP context + * + * \param context the RDP context + * \param ulAttr the attribute + * \param pBuffer an opaque pointer depending on ulAttr + * \returns a SECURITY_STATUS indicating if the operation completed successfully + * \since version 3.9.0 + */ + FREERDP_API SECURITY_STATUS freerdp_nla_QueryContextAttributes(rdpContext* context, + DWORD ulAttr, PVOID pBuffer); + FREERDP_API void clearChannelError(rdpContext* context); FREERDP_API HANDLE getChannelErrorEventHandle(rdpContext* context); FREERDP_API UINT getChannelError(rdpContext* context); diff --git a/libfreerdp/core/connection.c b/libfreerdp/core/connection.c index a7005d1ae..8d04b92f7 100644 --- a/libfreerdp/core/connection.c +++ b/libfreerdp/core/connection.c @@ -402,6 +402,7 @@ BOOL rdp_client_connect(rdpRdp* rdp) nego_set_preconnection_blob(rdp->nego, settings->PreconnectionBlob); nego_set_negotiation_enabled(rdp->nego, settings->NegotiateSecurityLayer); nego_set_restricted_admin_mode_required(rdp->nego, settings->RestrictedAdminModeRequired); + nego_set_RCG_required(rdp->nego, settings->RemoteCredentialGuard); nego_set_gateway_enabled(rdp->nego, settings->GatewayEnabled); nego_set_gateway_bypass_local(rdp->nego, settings->GatewayBypassLocal); nego_enable_rdp(rdp->nego, settings->RdpSecurity); diff --git a/libfreerdp/core/credssp_auth.c b/libfreerdp/core/credssp_auth.c index ccbbb36ee..35d364a1c 100644 --- a/libfreerdp/core/credssp_auth.c +++ b/libfreerdp/core/credssp_auth.c @@ -31,6 +31,7 @@ #include #include #include +#include #include @@ -692,6 +693,17 @@ UINT32 credssp_auth_sspi_error(rdpCredsspAuth* auth) return (UINT32)auth->sspi_error; } +void credssp_auth_tableAndContext(rdpCredsspAuth* auth, SecurityFunctionTable** ptable, + CtxtHandle* pcontext) +{ + WINPR_ASSERT(auth); + WINPR_ASSERT(ptable); + WINPR_ASSERT(pcontext); + + *ptable = auth->table; + *pcontext = auth->context; +} + void credssp_auth_free(rdpCredsspAuth* auth) { SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL; diff --git a/libfreerdp/core/credssp_auth.h b/libfreerdp/core/credssp_auth.h index 101c164be..e53231754 100644 --- a/libfreerdp/core/credssp_auth.h +++ b/libfreerdp/core/credssp_auth.h @@ -30,6 +30,7 @@ typedef struct rdp_credssp_auth rdpCredsspAuth; #include #include #include +#include FREERDP_LOCAL void credssp_auth_free(rdpCredsspAuth* auth); @@ -62,4 +63,8 @@ FREERDP_LOCAL const char* credssp_auth_pkg_name(rdpCredsspAuth* auth); FREERDP_LOCAL size_t credssp_auth_trailer_size(rdpCredsspAuth* auth); FREERDP_LOCAL UINT32 credssp_auth_sspi_error(rdpCredsspAuth* auth); +FREERDP_LOCAL void credssp_auth_tableAndContext(rdpCredsspAuth* auth, + SecurityFunctionTable** ptable, + CtxtHandle* pcontext); + #endif /* FREERDP_LIB_CORE_CREDSSP_AUTH_H */ diff --git a/libfreerdp/core/freerdp.c b/libfreerdp/core/freerdp.c index 5eaafd713..e4b3a364a 100644 --- a/libfreerdp/core/freerdp.c +++ b/libfreerdp/core/freerdp.c @@ -1224,6 +1224,32 @@ UINT32 freerdp_get_nla_sspi_error(rdpContext* context) return nla_get_sspi_error(nla); } +BOOL freerdp_nla_encrypt(rdpContext* context, const SecBuffer* inBuffer, SecBuffer* outBuffer) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->rdp); + + rdpNla* nla = context->rdp->nla; + return nla_encrypt(nla, inBuffer, outBuffer); +} + +BOOL freerdp_nla_decrypt(rdpContext* context, const SecBuffer* inBuffer, SecBuffer* outBuffer) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->rdp); + + rdpNla* nla = context->rdp->nla; + return nla_decrypt(nla, inBuffer, outBuffer); +} + +SECURITY_STATUS freerdp_nla_QueryContextAttributes(rdpContext* context, DWORD ulAttr, PVOID pBuffer) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->rdp); + + return nla_QueryContextAttributes(context->rdp->nla, ulAttr, pBuffer); +} + HANDLE getChannelErrorEventHandle(rdpContext* context) { WINPR_ASSERT(context); diff --git a/libfreerdp/core/nla.c b/libfreerdp/core/nla.c index 38d443246..35d36898d 100644 --- a/libfreerdp/core/nla.c +++ b/libfreerdp/core/nla.c @@ -1405,6 +1405,78 @@ static BOOL nla_read_ts_credentials(rdpNla* nla, SecBuffer* data) return ret; } +static BOOL nla_write_KERB_TICKET_LOGON(wStream* s, const KERB_TICKET_LOGON* ticket) +{ + WINPR_ASSERT(ticket); + + if (!Stream_EnsureRemainingCapacity(s, (4ULL * 4) + 16ULL + ticket->ServiceTicketLength + + ticket->TicketGrantingTicketLength)) + return FALSE; + + Stream_Write_UINT32(s, KerbTicketLogon); + Stream_Write_UINT32(s, ticket->Flags); + Stream_Write_UINT32(s, ticket->ServiceTicketLength); + Stream_Write_UINT32(s, ticket->TicketGrantingTicketLength); + + Stream_Write_UINT64(s, 0x20); /* offset of TGS in the packet */ + Stream_Write_UINT64(s, 0x20 + ticket->ServiceTicketLength); /* offset of TGT in packet */ + + Stream_Write(s, ticket->ServiceTicket, ticket->ServiceTicketLength); + Stream_Write(s, ticket->TicketGrantingTicket, ticket->TicketGrantingTicketLength); + return TRUE; +} + +BOOL nla_get_KERB_TICKET_LOGON(rdpNla* nla, KERB_TICKET_LOGON* logonTicket) +{ + WINPR_ASSERT(nla); + WINPR_ASSERT(logonTicket); + + SecurityFunctionTable* table; + CtxtHandle context; + credssp_auth_tableAndContext(nla->auth, &table, &context); + return table->QueryContextAttributes(&context, SECPKG_CRED_ATTR_TICKET_LOGON, logonTicket) == + SEC_E_OK; +} + +static BOOL nla_write_TSRemoteGuardKerbCred(rdpNla* nla, WinPrAsn1Encoder* enc) +{ + BOOL ret = FALSE; + wStream* s = NULL; + char kerberos[] = { 'K', '\0', 'e', '\0', 'r', '\0', 'b', '\0', + 'e', '\0', 'r', '\0', 'o', '\0', 's', '\0' }; + WinPrAsn1_OctetString packageName = { sizeof(kerberos), (BYTE*)kerberos }; + WinPrAsn1_OctetString credBuffer; + KERB_TICKET_LOGON logonTicket; + + logonTicket.ServiceTicket = NULL; + logonTicket.TicketGrantingTicket = NULL; + + /* packageName [0] OCTET STRING */ + if (!WinPrAsn1EncContextualOctetString(enc, 0, &packageName)) + goto out; + + /* credBuffer [1] OCTET STRING */ + if (!nla_get_KERB_TICKET_LOGON(nla, &logonTicket)) + goto out; + + s = Stream_New(NULL, 2000); + if (!s) + goto out; + + if (!nla_write_KERB_TICKET_LOGON(s, &logonTicket)) + goto out; + + credBuffer.len = Stream_GetPosition(s); + credBuffer.data = Stream_Buffer(s); + ret = WinPrAsn1EncContextualOctetString(enc, 1, &credBuffer) != 0; + +out: + free(logonTicket.ServiceTicket); + free(logonTicket.TicketGrantingTicket); + Stream_Free(s, TRUE); + return ret; +} + /** * Encode TSCredentials structure. * @param nla A pointer to the NLA to use @@ -1547,6 +1619,29 @@ static BOOL nla_encode_ts_credentials(rdpNla* nla) break; } case TSCREDS_REMOTEGUARD: + /* TSRemoteGuardCreds */ + if (!WinPrAsn1EncSeqContainer(enc)) + goto out; + + /* logonCred [0] TSRemoteGuardPackageCred, */ + if (!WinPrAsn1EncContextualSeqContainer(enc, 0)) + goto out; + + if (!nla_write_TSRemoteGuardKerbCred(nla, enc) || !WinPrAsn1EncEndContainer(enc)) + goto out; + + /* supplementalCreds [1] SEQUENCE OF TSRemoteGuardPackageCred OPTIONAL, + * + * no NTLM supplemental creds for now + * + */ + if (!WinPrAsn1EncContextualSeqContainer(enc, 1) || !WinPrAsn1EncEndContainer(enc)) + goto out; + + /* End TSRemoteGuardCreds */ + if (!WinPrAsn1EncEndContainer(enc)) + goto out; + break; default: goto out; } @@ -2107,3 +2202,30 @@ UINT32 nla_get_sspi_error(rdpNla* nla) WINPR_ASSERT(nla); return credssp_auth_sspi_error(nla->auth); } + +BOOL nla_encrypt(rdpNla* nla, const SecBuffer* inBuffer, SecBuffer* outBuffer) +{ + WINPR_ASSERT(nla); + WINPR_ASSERT(inBuffer); + WINPR_ASSERT(outBuffer); + return credssp_auth_encrypt(nla->auth, inBuffer, outBuffer, NULL, nla->sendSeqNum++); +} + +BOOL nla_decrypt(rdpNla* nla, const SecBuffer* inBuffer, SecBuffer* outBuffer) +{ + WINPR_ASSERT(nla); + WINPR_ASSERT(inBuffer); + WINPR_ASSERT(outBuffer); + return credssp_auth_decrypt(nla->auth, inBuffer, outBuffer, nla->recvSeqNum++); +} + +SECURITY_STATUS nla_QueryContextAttributes(rdpNla* nla, DWORD ulAttr, PVOID pBuffer) +{ + WINPR_ASSERT(nla); + + SecurityFunctionTable* table; + CtxtHandle context; + credssp_auth_tableAndContext(nla->auth, &table, &context); + + return table->QueryContextAttributes(&context, ulAttr, pBuffer); +} diff --git a/libfreerdp/core/nla.h b/libfreerdp/core/nla.h index cb30e12b3..8a1573777 100644 --- a/libfreerdp/core/nla.h +++ b/libfreerdp/core/nla.h @@ -75,5 +75,8 @@ WINPR_ATTR_MALLOC(nla_free, 1) FREERDP_LOCAL rdpNla* nla_new(rdpContext* context, rdpTransport* transport); FREERDP_LOCAL void nla_set_early_user_auth(rdpNla* nla, BOOL earlyUserAuth); +FREERDP_LOCAL BOOL nla_encrypt(rdpNla* nla, const SecBuffer* inBuffer, SecBuffer* outBuffer); +FREERDP_LOCAL BOOL nla_decrypt(rdpNla* nla, const SecBuffer* inBuffer, SecBuffer* outBuffer); +FREERDP_LOCAL SECURITY_STATUS nla_QueryContextAttributes(rdpNla* nla, DWORD ulAttr, PVOID pBuffer); #endif /* FREERDP_LIB_CORE_NLA_H */ diff --git a/winpr/include/winpr/sspi.h b/winpr/include/winpr/sspi.h index e565b4046..98afd3e87 100644 --- a/winpr/include/winpr/sspi.h +++ b/winpr/include/winpr/sspi.h @@ -1332,6 +1332,8 @@ extern "C" #define SECPKG_ATTR_AUTH_NTLM_MIC 1106 #define SECPKG_ATTR_AUTH_NTLM_MIC_VALUE 1107 +#define SECPKG_CRED_ATTR_TICKET_LOGON 1200 + typedef struct { char User[256 + 1]; diff --git a/winpr/libwinpr/sspi/Kerberos/kerberos.c b/winpr/libwinpr/sspi/Kerberos/kerberos.c index 1b7797dfb..59d6d3d39 100644 --- a/winpr/libwinpr/sspi/Kerberos/kerberos.c +++ b/winpr/libwinpr/sspi/Kerberos/kerberos.c @@ -29,7 +29,9 @@ #include #include +#include #include +#include #include #include #include @@ -40,6 +42,8 @@ #include #include #include +#include +#include #include "kerberos.h" @@ -93,10 +97,21 @@ enum KERBEROS_STATE KERBEROS_STATE_FINAL }; +typedef struct KRB_CREDENTIALS_st +{ + volatile LONG refCount; + krb5_context ctx; + char* kdc_url; + krb5_ccache ccache; + krb5_keytab keytab; + krb5_keytab client_keytab; + BOOL own_ccache; /**< Whether we created ccache, and must destroy it after use. */ +} KRB_CREDENTIALS; + struct s_KRB_CONTEXT { enum KERBEROS_STATE state; - krb5_context ctx; + KRB_CREDENTIALS* credentials; krb5_auth_context auth_ctx; BOOL acceptor; uint32_t flags; @@ -104,17 +119,9 @@ struct s_KRB_CONTEXT uint64_t remote_seq; struct krb5glue_keyset keyset; BOOL u2u; + char* targetHost; }; -typedef struct KRB_CREDENTIALS_st -{ - char* kdc_url; - krb5_ccache ccache; - krb5_keytab keytab; - krb5_keytab client_keytab; - BOOL own_ccache; /**< Whether we created ccache, and must destroy it after use. */ -} KRB_CREDENTIALS; - static const WinPrAsn1_OID kerberos_OID = { 9, (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; static const WinPrAsn1_OID kerberos_u2u_OID = { 10, (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" }; @@ -149,22 +156,35 @@ static krb5_error_code kerberos_log_msg(krb5_context ctx, krb5_error_code code, return code; } +static void credentials_unref(KRB_CREDENTIALS* credentials); + static void kerberos_ContextFree(KRB_CONTEXT* ctx, BOOL allocated) { - if (ctx && ctx->ctx) + if (!ctx) + return; + + free(ctx->targetHost); + ctx->targetHost = NULL; + + if (ctx->credentials) { - krb5glue_keys_free(ctx->ctx, &ctx->keyset); + krb5_context krbctx = ctx->credentials->ctx; + if (krbctx) + { + if (ctx->auth_ctx) + krb5_auth_con_free(krbctx, ctx->auth_ctx); - if (ctx->auth_ctx) - krb5_auth_con_free(ctx->ctx, ctx->auth_ctx); + krb5glue_keys_free(krbctx, &ctx->keyset); + } - krb5_free_context(ctx->ctx); + credentials_unref(ctx->credentials); } + if (allocated) free(ctx); } -static KRB_CONTEXT* kerberos_ContextNew(void) +static KRB_CONTEXT* kerberos_ContextNew(KRB_CREDENTIALS* credentials) { KRB_CONTEXT* context = NULL; @@ -172,6 +192,8 @@ static KRB_CONTEXT* kerberos_ContextNew(void) if (!context) return NULL; + context->credentials = credentials; + InterlockedIncrement(&credentials->refCount); return context; } @@ -216,6 +238,22 @@ static BOOL isValidIP(const char* ipAddress) return isValidIPv4(ipAddress) || isValidIPv6(ipAddress); } +static int build_krbtgt(krb5_context ctx, krb5_data* realm, krb5_principal* ptarget) +{ + /* "krbtgt/" + realm + "@" + realm */ + size_t len = 7ULL + realm->length + 1 + realm->length + 1; + char* name = malloc(len); + if (!name) + return KRB5_CC_NOMEM; + + (void)snprintf(name, len, "krbtgt/%s@%s", realm->data, realm->data); + krb5_principal target; + int rv = krb5_parse_name(ctx, name, &target); + free(name); + *ptarget = target; + return rv; +} + #endif /* WITH_KRB5 */ static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleA( @@ -358,14 +396,33 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleA( /* Get initial credentials if required */ if (fCredentialUse & SECPKG_CRED_OUTBOUND) { - if (krb_log_exec(krb5glue_get_init_creds, ctx, principal, ccache, krb5_prompter, password, - krb_settings)) + krb5_creds creds = { 0 }; + krb5_creds matchCreds = { 0 }; + int matchFlags = KRB5_TC_MATCH_TIMES; + + krb5_timeofday(ctx, &matchCreds.times.endtime); + matchCreds.times.endtime += 60; + matchCreds.client = principal; + + if (krb_log_exec(build_krbtgt, ctx, &principal->realm, &matchCreds.server)) goto cleanup; + + int rv = krb5_cc_retrieve_cred(ctx, ccache, matchFlags, &matchCreds, &creds); + krb5_free_principal(ctx, matchCreds.server); + krb5_free_cred_contents(ctx, &creds); + if (rv) + { + if (krb_log_exec(krb5glue_get_init_creds, ctx, principal, ccache, krb5_prompter, + password, krb_settings)) + goto cleanup; + } } credentials = calloc(1, sizeof(KRB_CREDENTIALS)); if (!credentials) goto cleanup; + credentials->refCount = 1; + credentials->ctx = ctx; credentials->ccache = ccache; credentials->keytab = keytab; credentials->own_ccache = own_ccache; @@ -391,8 +448,9 @@ cleanup: } if (keytab) krb5_kt_close(ctx, keytab); + + krb5_free_context(ctx); } - krb5_free_context(ctx); } /* If we managed to get credentials set the output */ @@ -442,34 +500,39 @@ fail: return status; } -static SECURITY_STATUS SEC_ENTRY kerberos_FreeCredentialsHandle(PCredHandle phCredential) -{ #ifdef WITH_KRB5 - KRB_CREDENTIALS* credentials = NULL; - krb5_context ctx = NULL; +static void credentials_unref(KRB_CREDENTIALS* credentials) +{ + WINPR_ASSERT(credentials); - credentials = sspi_SecureHandleGetLowerPointer(phCredential); - if (!credentials) - return SEC_E_INVALID_HANDLE; - - if (krb5_init_context(&ctx)) - return SEC_E_INTERNAL_ERROR; + if (InterlockedDecrement(&credentials->refCount)) + return; free(credentials->kdc_url); if (credentials->ccache) { if (credentials->own_ccache) - krb5_cc_destroy(ctx, credentials->ccache); + krb5_cc_destroy(credentials->ctx, credentials->ccache); else - krb5_cc_close(ctx, credentials->ccache); + krb5_cc_close(credentials->ctx, credentials->ccache); } if (credentials->keytab) - krb5_kt_close(ctx, credentials->keytab); - - krb5_free_context(ctx); + krb5_kt_close(credentials->ctx, credentials->keytab); + krb5_free_context(credentials->ctx); free(credentials); +} +#endif + +static SECURITY_STATUS SEC_ENTRY kerberos_FreeCredentialsHandle(PCredHandle phCredential) +{ +#ifdef WITH_KRB5 + KRB_CREDENTIALS* credentials = sspi_SecureHandleGetLowerPointer(phCredential); + if (!credentials) + return SEC_E_INVALID_HANDLE; + + credentials_unref(credentials); sspi_SecureHandleInvalidate(phCredential); return SEC_E_OK; @@ -483,13 +546,16 @@ static SECURITY_STATUS SEC_ENTRY kerberos_QueryCredentialsAttributesW(PCredHandl void* pBuffer) { #ifdef WITH_KRB5 - if (ulAttribute == SECPKG_CRED_ATTR_NAMES) + switch (ulAttribute) { - return SEC_E_OK; + case SECPKG_CRED_ATTR_NAMES: + return SEC_E_OK; + default: + WLog_ERR(TAG, "TODO: QueryCredentialsAttributesW, implement ulAttribute=%08" PRIx32, + ulAttribute); + return SEC_E_UNSUPPORTED_FUNCTION; } - WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute); - return SEC_E_UNSUPPORTED_FUNCTION; #else return SEC_E_UNSUPPORTED_FUNCTION; #endif @@ -753,9 +819,6 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr, PTimeStamp ptsExpiry) { #ifdef WITH_KRB5 - KRB_CREDENTIALS* credentials = NULL; - KRB_CONTEXT* context = NULL; - KRB_CONTEXT new_context = { 0 }; PSecBuffer input_buffer = NULL; PSecBuffer output_buffer = NULL; PSecBuffer bindings_buffer = NULL; @@ -774,8 +837,9 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( krb5_data cksum = { 0 }; krb5_creds in_creds = { 0 }; krb5_creds* creds = NULL; - - credentials = sspi_SecureHandleGetLowerPointer(phCredential); + BOOL isNewContext = FALSE; + KRB_CONTEXT* context = NULL; + KRB_CREDENTIALS* credentials = sspi_SecureHandleGetLowerPointer(phCredential); /* behave like windows SSPIs that don't want empty context */ if (phContext && !phContext->dwLower && !phContext->dwUpper) @@ -800,30 +864,6 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( if (fContextReq & ISC_REQ_USE_SESSION_KEY) ap_flags |= AP_OPTS_USE_SESSION_KEY; - if (!context) - { - context = &new_context; - - if (krb_log_exec_ptr(krb5_init_context, &context->ctx)) - return SEC_E_INTERNAL_ERROR; - - if (fContextReq & ISC_REQ_USE_SESSION_KEY) - { - context->state = KERBEROS_STATE_TGT_REQ; - context->u2u = TRUE; - } - else - context->state = KERBEROS_STATE_AP_REQ; - } - else - { - if (!input_buffer || !sspi_gss_unwrap_token(input_buffer, &oid, &tok_id, &input_token)) - goto bad_token; - if ((context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) || - (!context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_OID))) - goto bad_token; - } - /* Split target name into service/hostname components */ if (pszTargetName) { @@ -848,9 +888,44 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( } } + if (!context) + { + context = kerberos_ContextNew(credentials); + if (!context) + { + status = SEC_E_INSUFFICIENT_MEMORY; + goto cleanup; + } + + isNewContext = TRUE; + + context->targetHost = _strdup(host); + if (!context->targetHost) + { + status = SEC_E_INSUFFICIENT_MEMORY; + goto cleanup; + } + + if (fContextReq & ISC_REQ_USE_SESSION_KEY) + { + context->state = KERBEROS_STATE_TGT_REQ; + context->u2u = TRUE; + } + else + context->state = KERBEROS_STATE_AP_REQ; + } + else + { + if (!input_buffer || !sspi_gss_unwrap_token(input_buffer, &oid, &tok_id, &input_token)) + goto bad_token; + if ((context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) || + (!context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_OID))) + goto bad_token; + } + /* SSPI flags are compatible with GSS flags except INTEG_FLAG */ context->flags |= (fContextReq & 0x1F); - if (fContextReq & ISC_REQ_INTEGRITY && !(fContextReq & ISC_REQ_NO_INTEGRITY)) + if ((fContextReq & ISC_REQ_INTEGRITY) && !(fContextReq & ISC_REQ_NO_INTEGRITY)) context->flags |= SSPI_GSS_C_INTEG_FLAG; switch (context->state) @@ -861,9 +936,7 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( goto cleanup; context->state = KERBEROS_STATE_TGT_REP; - status = SEC_I_CONTINUE_NEEDED; - break; case KERBEROS_STATE_TGT_REP: @@ -881,25 +954,25 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( case KERBEROS_STATE_AP_REQ: /* Set auth_context options */ - if (krb_log_exec(krb5_auth_con_init, context->ctx, &context->auth_ctx)) + if (krb_log_exec(krb5_auth_con_init, credentials->ctx, &context->auth_ctx)) goto cleanup; - if (krb_log_exec(krb5_auth_con_setflags, context->ctx, context->auth_ctx, + if (krb_log_exec(krb5_auth_con_setflags, credentials->ctx, context->auth_ctx, KRB5_AUTH_CONTEXT_DO_SEQUENCE | KRB5_AUTH_CONTEXT_USE_SUBKEY)) goto cleanup; - if (krb_log_exec(krb5glue_auth_con_set_cksumtype, context->ctx, context->auth_ctx, + if (krb_log_exec(krb5glue_auth_con_set_cksumtype, credentials->ctx, context->auth_ctx, GSS_CHECKSUM_TYPE)) goto cleanup; /* Get a service ticket */ - if (krb_log_exec(krb5_sname_to_principal, context->ctx, host, sname, KRB5_NT_SRV_HST, - &in_creds.server)) + if (krb_log_exec(krb5_sname_to_principal, credentials->ctx, host, sname, + KRB5_NT_SRV_HST, &in_creds.server)) goto cleanup; - if (krb_log_exec(krb5_cc_get_principal, context->ctx, credentials->ccache, + if (krb_log_exec(krb5_cc_get_principal, credentials->ctx, credentials->ccache, &in_creds.client)) goto cleanup; - if (krb_log_exec(krb5_get_credentials, context->ctx, + if (krb_log_exec(krb5_get_credentials, credentials->ctx, context->u2u ? KRB5_GC_USER_USER : 0, credentials->ccache, &in_creds, &creds)) goto cleanup; @@ -942,7 +1015,7 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( } /* Make the AP_REQ message */ - if (krb_log_exec(krb5_mk_req_extended, context->ctx, &context->auth_ctx, ap_flags, + if (krb_log_exec(krb5_mk_req_extended, credentials->ctx, &context->auth_ctx, ap_flags, &cksum, creds, &output_token)) goto cleanup; @@ -953,13 +1026,13 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG) { - if (krb_log_exec(krb5_auth_con_getlocalseqnumber, context->ctx, context->auth_ctx, - (INT32*)&context->local_seq)) + if (krb_log_exec(krb5_auth_con_getlocalseqnumber, credentials->ctx, + context->auth_ctx, (INT32*)&context->local_seq)) goto cleanup; context->remote_seq ^= context->local_seq; } - if (krb_log_exec(krb5glue_update_keyset, context->ctx, context->auth_ctx, FALSE, + if (krb_log_exec(krb5glue_update_keyset, credentials->ctx, context->auth_ctx, FALSE, &context->keyset)) goto cleanup; @@ -969,21 +1042,20 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( status = SEC_I_CONTINUE_NEEDED; else status = SEC_E_OK; - break; case KERBEROS_STATE_AP_REP: if (tok_id == TOK_ID_AP_REP) { - if (krb_log_exec(krb5_rd_rep, context->ctx, context->auth_ctx, &input_token, + if (krb_log_exec(krb5_rd_rep, credentials->ctx, context->auth_ctx, &input_token, &reply)) goto cleanup; - krb5_free_ap_rep_enc_part(context->ctx, reply); + krb5_free_ap_rep_enc_part(credentials->ctx, reply); } else if (tok_id == TOK_ID_ERROR) { - krb5glue_log_error(context->ctx, &input_token, TAG); + krb5glue_log_error(credentials->ctx, &input_token, TAG); goto cleanup; } else @@ -991,12 +1063,12 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG) { - if (krb_log_exec(krb5_auth_con_getremoteseqnumber, context->ctx, context->auth_ctx, - (INT32*)&context->remote_seq)) + if (krb_log_exec(krb5_auth_con_getremoteseqnumber, credentials->ctx, + context->auth_ctx, (INT32*)&context->remote_seq)) goto cleanup; } - if (krb_log_exec(krb5glue_update_keyset, context->ctx, context->auth_ctx, FALSE, + if (krb_log_exec(krb5glue_update_keyset, credentials->ctx, context->auth_ctx, FALSE, &context->keyset)) goto cleanup; @@ -1005,7 +1077,6 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( if (output_buffer) output_buffer->cbBuffer = 0; status = SEC_E_OK; - break; case KERBEROS_STATE_FINAL: @@ -1014,41 +1085,36 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( goto cleanup; } - /* On first call allocate a new context */ - if (new_context.ctx) - { - const KRB_CONTEXT empty = { 0 }; - - context = kerberos_ContextNew(); - if (!context) - { - status = SEC_E_INSUFFICIENT_MEMORY; - goto cleanup; - } - *context = new_context; - new_context = empty; - - sspi_SecureHandleSetLowerPointer(phNewContext, context); - sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME); - } - cleanup: - { /* second_ticket is not allocated */ krb5_data edata = { 0 }; in_creds.second_ticket = edata; - krb5_free_cred_contents(context->ctx, &in_creds); + krb5_free_cred_contents(credentials->ctx, &in_creds); } - krb5_free_creds(context->ctx, creds); + krb5_free_creds(credentials->ctx, creds); if (output_token.data) - krb5glue_free_data_contents(context->ctx, &output_token); + krb5glue_free_data_contents(credentials->ctx, &output_token); winpr_Digest_Free(md5); free(target); - kerberos_ContextFree(&new_context, FALSE); + + if (isNewContext) + { + switch (status) + { + case SEC_E_OK: + case SEC_I_CONTINUE_NEEDED: + sspi_SecureHandleSetLowerPointer(phNewContext, context); + sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME); + break; + default: + kerberos_ContextFree(context, TRUE); + break; + } + } return status; @@ -1091,9 +1157,7 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcceptSecurityContext( PTimeStamp ptsExpity) { #ifdef WITH_KRB5 - KRB_CREDENTIALS* credentials = NULL; - KRB_CONTEXT* context = NULL; - KRB_CONTEXT new_context = { 0 }; + BOOL isNewContext = FALSE; PSecBuffer input_buffer = NULL; PSecBuffer output_buffer = NULL; WinPrAsn1_OID oid = { 0 }; @@ -1115,8 +1179,8 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcceptSecurityContext( if (phContext && !phContext->dwLower && !phContext->dwUpper) return SEC_E_INVALID_HANDLE; - context = sspi_SecureHandleGetLowerPointer(phContext); - credentials = sspi_SecureHandleGetLowerPointer(phCredential); + KRB_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext); + KRB_CREDENTIALS* credentials = sspi_SecureHandleGetLowerPointer(phCredential); if (pInput) input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); @@ -1131,10 +1195,9 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcceptSecurityContext( if (!context) { - context = &new_context; - - if (krb_log_exec_ptr(krb5_init_context, &context->ctx)) - return SEC_E_INTERNAL_ERROR; + isNewContext = TRUE; + context = kerberos_ContextNew(credentials); + context->acceptor = TRUE; if (sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) { @@ -1167,55 +1230,55 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcceptSecurityContext( realm++; } - if (krb_log_exec(krb5_parse_name_flags, context->ctx, sname ? sname : "", + if (krb_log_exec(krb5_parse_name_flags, credentials->ctx, sname ? sname : "", KRB5_PRINCIPAL_PARSE_NO_REALM, &principal)) goto cleanup; if (realm) { - if (krb_log_exec(krb5glue_set_principal_realm, context->ctx, principal, realm)) + if (krb_log_exec(krb5glue_set_principal_realm, credentials->ctx, principal, realm)) goto cleanup; } - if (krb_log_exec(krb5_kt_start_seq_get, context->ctx, credentials->keytab, &cur)) + if (krb_log_exec(krb5_kt_start_seq_get, credentials->ctx, credentials->keytab, &cur)) goto cleanup; do { - krb5_error_code rv = - krb_log_exec(krb5_kt_next_entry, context->ctx, credentials->keytab, &entry, &cur); + krb5_error_code rv = krb_log_exec(krb5_kt_next_entry, credentials->ctx, + credentials->keytab, &entry, &cur); if (rv == KRB5_KT_END) break; if (rv != 0) goto cleanup; - if ((!sname || krb_log_exec(krb5_principal_compare_any_realm, context->ctx, principal, - entry.principal)) && + if ((!sname || krb_log_exec(krb5_principal_compare_any_realm, credentials->ctx, + principal, entry.principal)) && (!realm || - krb_log_exec(krb5_realm_compare, context->ctx, principal, entry.principal))) + krb_log_exec(krb5_realm_compare, credentials->ctx, principal, entry.principal))) break; - if (krb_log_exec(krb5glue_free_keytab_entry_contents, context->ctx, &entry)) + if (krb_log_exec(krb5glue_free_keytab_entry_contents, credentials->ctx, &entry)) goto cleanup; } while (1); - if (krb_log_exec(krb5_kt_end_seq_get, context->ctx, credentials->keytab, &cur)) + if (krb_log_exec(krb5_kt_end_seq_get, credentials->ctx, credentials->keytab, &cur)) goto cleanup; if (!entry.principal) goto cleanup; /* Get the TGT */ - if (krb_log_exec(krb5_get_init_creds_keytab, context->ctx, &creds, entry.principal, + if (krb_log_exec(krb5_get_init_creds_keytab, credentials->ctx, &creds, entry.principal, credentials->keytab, 0, NULL, NULL)) goto cleanup; if (!kerberos_mk_tgt_token(output_buffer, KRB_TGT_REP, NULL, NULL, &creds.ticket)) goto cleanup; - if (krb_log_exec(krb5_auth_con_init, context->ctx, &context->auth_ctx)) + if (krb_log_exec(krb5_auth_con_init, credentials->ctx, &context->auth_ctx)) goto cleanup; - if (krb_log_exec(krb5glue_auth_con_setuseruserkey, context->ctx, context->auth_ctx, + if (krb_log_exec(krb5glue_auth_con_setuseruserkey, credentials->ctx, context->auth_ctx, &krb5glue_creds_getkey(creds))) goto cleanup; @@ -1223,27 +1286,27 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcceptSecurityContext( } else if (context->state == KERBEROS_STATE_AP_REQ && tok_id == TOK_ID_AP_REQ) { - if (krb_log_exec(krb5_rd_req, context->ctx, &context->auth_ctx, &input_token, NULL, + if (krb_log_exec(krb5_rd_req, credentials->ctx, &context->auth_ctx, &input_token, NULL, credentials->keytab, &ap_flags, NULL)) goto cleanup; - if (krb_log_exec(krb5_auth_con_setflags, context->ctx, context->auth_ctx, + if (krb_log_exec(krb5_auth_con_setflags, credentials->ctx, context->auth_ctx, KRB5_AUTH_CONTEXT_DO_SEQUENCE | KRB5_AUTH_CONTEXT_USE_SUBKEY)) goto cleanup; /* Retrieve and validate the checksum */ - if (krb_log_exec(krb5_auth_con_getauthenticator, context->ctx, context->auth_ctx, + if (krb_log_exec(krb5_auth_con_getauthenticator, credentials->ctx, context->auth_ctx, &authenticator)) goto cleanup; if (!krb5glue_authenticator_validate_chksum(authenticator, GSS_CHECKSUM_TYPE, &context->flags)) goto bad_token; - if (ap_flags & AP_OPTS_MUTUAL_REQUIRED && context->flags & SSPI_GSS_C_MUTUAL_FLAG) + if ((ap_flags & AP_OPTS_MUTUAL_REQUIRED) && (context->flags & SSPI_GSS_C_MUTUAL_FLAG)) { if (!output_buffer) goto bad_token; - if (krb_log_exec(krb5_mk_rep, context->ctx, context->auth_ctx, &output_token)) + if (krb_log_exec(krb5_mk_rep, credentials->ctx, context->auth_ctx, &output_token)) goto cleanup; if (!sspi_gss_wrap_token(output_buffer, context->u2u ? &kerberos_u2u_OID : &kerberos_OID, @@ -1256,21 +1319,21 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcceptSecurityContext( output_buffer->cbBuffer = 0; } - *pfContextAttr = context->flags & 0x1F; + *pfContextAttr = (context->flags & 0x1F); if (context->flags & SSPI_GSS_C_INTEG_FLAG) *pfContextAttr |= ASC_RET_INTEGRITY; if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG) { - if (krb_log_exec(krb5_auth_con_getlocalseqnumber, context->ctx, context->auth_ctx, + if (krb_log_exec(krb5_auth_con_getlocalseqnumber, credentials->ctx, context->auth_ctx, (INT32*)&context->local_seq)) goto cleanup; - if (krb_log_exec(krb5_auth_con_getremoteseqnumber, context->ctx, context->auth_ctx, + if (krb_log_exec(krb5_auth_con_getremoteseqnumber, credentials->ctx, context->auth_ctx, (INT32*)&context->remote_seq)) goto cleanup; } - if (krb_log_exec(krb5glue_update_keyset, context->ctx, context->auth_ctx, TRUE, + if (krb_log_exec(krb5glue_update_keyset, credentials->ctx, context->auth_ctx, TRUE, &context->keyset)) goto cleanup; @@ -1280,38 +1343,33 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcceptSecurityContext( goto bad_token; /* On first call allocate new context */ - if (new_context.ctx) - { - const KRB_CONTEXT empty = { 0 }; - - context = kerberos_ContextNew(); - if (!context) - { - status = SEC_E_INSUFFICIENT_MEMORY; - goto cleanup; - } - *context = new_context; - new_context = empty; - context->acceptor = TRUE; - - sspi_SecureHandleSetLowerPointer(phNewContext, context); - sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME); - } - if (context->state == KERBEROS_STATE_FINAL) status = SEC_E_OK; else status = SEC_I_CONTINUE_NEEDED; cleanup: - free(target); if (output_token.data) - krb5glue_free_data_contents(context->ctx, &output_token); + krb5glue_free_data_contents(credentials->ctx, &output_token); if (entry.principal) - krb5glue_free_keytab_entry_contents(context->ctx, &entry); + krb5glue_free_keytab_entry_contents(credentials->ctx, &entry); + + if (isNewContext) + { + switch (status) + { + case SEC_E_OK: + case SEC_I_CONTINUE_NEEDED: + sspi_SecureHandleSetLowerPointer(phNewContext, context); + sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME); + break; + default: + kerberos_ContextFree(context, TRUE); + break; + } + } - kerberos_ContextFree(&new_context, FALSE); return status; bad_token: @@ -1322,6 +1380,7 @@ bad_token: #endif /* WITH_KRB5 */ } +#ifdef WITH_KRB5 static KRB_CONTEXT* get_context(PCtxtHandle phContext) { if (!phContext) @@ -1336,6 +1395,22 @@ static KRB_CONTEXT* get_context(PCtxtHandle phContext) return sspi_SecureHandleGetLowerPointer(phContext); } +static BOOL copy_krb5_data(krb5_data* data, PUCHAR* ptr, ULONG* psize) +{ + WINPR_ASSERT(data); + WINPR_ASSERT(ptr); + WINPR_ASSERT(psize); + + *ptr = (PUCHAR)malloc(data->length); + if (!*ptr) + return FALSE; + + *psize = data->length; + memcpy(*ptr, data->data, data->length); + return TRUE; +} +#endif + static SECURITY_STATUS SEC_ENTRY kerberos_DeleteSecurityContext(PCtxtHandle phContext) { #ifdef WITH_KRB5 @@ -1351,72 +1426,179 @@ static SECURITY_STATUS SEC_ENTRY kerberos_DeleteSecurityContext(PCtxtHandle phCo #endif } +#ifdef WITH_KRB5 + +static SECURITY_STATUS krb5_error_to_SECURITY_STATUS(krb5_error_code code) +{ + switch (code) + { + case 0: + return SEC_E_OK; + default: + return SEC_E_INTERNAL_ERROR; + } +} + +static SECURITY_STATUS kerberos_ATTR_SIZES(KRB_CONTEXT* context, KRB_CREDENTIALS* credentials, + SecPkgContext_Sizes* ContextSizes) +{ + UINT header = 0; + UINT pad = 0; + UINT trailer = 0; + krb5glue_key key = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->auth_ctx); + + /* The MaxTokenSize by default is 12,000 bytes. This has been the default value + * since Windows 2000 SP2 and still remains in Windows 7 and Windows 2008 R2. + * For Windows Server 2012, the default value of the MaxTokenSize registry + * entry is 48,000 bytes.*/ + ContextSizes->cbMaxToken = KERBEROS_SecPkgInfoA.cbMaxToken; + ContextSizes->cbMaxSignature = 0; + ContextSizes->cbBlockSize = 1; + ContextSizes->cbSecurityTrailer = 0; + + key = get_key(&context->keyset); + + if (context->flags & SSPI_GSS_C_CONF_FLAG) + { + krb5_error_code rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key, + KRB5_CRYPTO_TYPE_HEADER, &header); + if (rv) + return krb5_error_to_SECURITY_STATUS(rv); + + rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key, KRB5_CRYPTO_TYPE_PADDING, + &pad); + if (rv) + return krb5_error_to_SECURITY_STATUS(rv); + + rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key, KRB5_CRYPTO_TYPE_TRAILER, + &trailer); + if (rv) + return krb5_error_to_SECURITY_STATUS(rv); + + /* GSS header (= 16 bytes) + encrypted header = 32 bytes */ + ContextSizes->cbSecurityTrailer = header + pad + trailer + 32; + } + + if (context->flags & SSPI_GSS_C_INTEG_FLAG) + { + krb5_error_code rv = krb_log_exec(krb5glue_crypto_length, credentials->ctx, key, + KRB5_CRYPTO_TYPE_CHECKSUM, &ContextSizes->cbMaxSignature); + if (rv) + return krb5_error_to_SECURITY_STATUS(rv); + + ContextSizes->cbMaxSignature += 16; + } + + return SEC_E_OK; +} + +static SECURITY_STATUS kerberos_ATTR_TICKET_LOGON(KRB_CONTEXT* context, + KRB_CREDENTIALS* credentials, + KERB_TICKET_LOGON* ticketLogon) +{ + krb5_creds matchCred = { 0 }; + krb5_auth_context authContext = NULL; + int getCredsFlags = KRB5_GC_CACHED; + BOOL firstRun = TRUE; + krb5_creds* hostCred = NULL; + SECURITY_STATUS ret = SEC_E_INSUFFICIENT_MEMORY; + int rv = krb_log_exec(krb5_sname_to_principal, credentials->ctx, context->targetHost, "HOST", + KRB5_NT_SRV_HST, &matchCred.server); + if (rv) + goto out; + + rv = krb_log_exec(krb5_cc_get_principal, credentials->ctx, credentials->ccache, + &matchCred.client); + if (rv) + goto out; + + /* try from the cache first, and then do a new request */ +again: + rv = krb_log_exec(krb5_get_credentials, credentials->ctx, getCredsFlags, credentials->ccache, + &matchCred, &hostCred); + switch (rv) + { + case 0: + break; + case KRB5_CC_NOTFOUND: + getCredsFlags = 0; + if (firstRun) + { + firstRun = FALSE; + goto again; + } + WINPR_FALLTHROUGH + default: + WLog_ERR(TAG, "krb5_get_credentials(hostCreds), rv=%d", rv); + goto out; + } + + if (krb_log_exec(krb5_auth_con_init, credentials->ctx, &authContext)) + goto out; + + krb5_data derOut = { 0 }; + if (krb_log_exec(krb5_fwd_tgt_creds, credentials->ctx, authContext, context->targetHost, + matchCred.client, matchCred.server, credentials->ccache, 1, &derOut)) + { + ret = SEC_E_LOGON_DENIED; + goto out; + } + + ticketLogon->MessageType = KerbTicketLogon; + ticketLogon->Flags = KERB_LOGON_FLAG_REDIRECTED; + + if (!copy_krb5_data(&hostCred->ticket, &ticketLogon->ServiceTicket, + &ticketLogon->ServiceTicketLength)) + { + krb5_free_data(credentials->ctx, &derOut); + goto out; + } + + ticketLogon->TicketGrantingTicketLength = derOut.length; + ticketLogon->TicketGrantingTicket = (PUCHAR)derOut.data; + + ret = SEC_E_OK; +out: + krb5_auth_con_free(credentials->ctx, authContext); + krb5_free_creds(credentials->ctx, hostCred); + krb5_free_cred_contents(credentials->ctx, &matchCred); + return ret; +} + +#endif /* WITH_KRB5 */ + static SECURITY_STATUS SEC_ENTRY kerberos_QueryContextAttributesA(PCtxtHandle phContext, ULONG ulAttribute, void* pBuffer) { -#ifdef WITH_KRB5 if (!phContext) return SEC_E_INVALID_HANDLE; if (!pBuffer) - return SEC_E_INSUFFICIENT_MEMORY; + return SEC_E_INVALID_PARAMETER; - if (ulAttribute == SECPKG_ATTR_SIZES) +#ifdef WITH_KRB5 + KRB_CONTEXT* context = get_context(phContext); + if (!context) + return SEC_E_INVALID_PARAMETER; + + KRB_CREDENTIALS* credentials = context->credentials; + + switch (ulAttribute) { - UINT header = 0; - UINT pad = 0; - UINT trailer = 0; - krb5glue_key key = NULL; - KRB_CONTEXT* context = get_context(phContext); - SecPkgContext_Sizes* ContextSizes = (SecPkgContext_Sizes*)pBuffer; + case SECPKG_ATTR_SIZES: + return kerberos_ATTR_SIZES(context, credentials, (SecPkgContext_Sizes*)pBuffer); - WINPR_ASSERT(context); - WINPR_ASSERT(context->ctx); - WINPR_ASSERT(context->auth_ctx); + case SECPKG_CRED_ATTR_TICKET_LOGON: + return kerberos_ATTR_TICKET_LOGON(context, credentials, (KERB_TICKET_LOGON*)pBuffer); - /* The MaxTokenSize by default is 12,000 bytes. This has been the default value - * since Windows 2000 SP2 and still remains in Windows 7 and Windows 2008 R2. - * For Windows Server 2012, the default value of the MaxTokenSize registry - * entry is 48,000 bytes.*/ - ContextSizes->cbMaxToken = KERBEROS_SecPkgInfoA.cbMaxToken; - ContextSizes->cbMaxSignature = 0; - ContextSizes->cbBlockSize = 1; - ContextSizes->cbSecurityTrailer = 0; - - key = get_key(&context->keyset); - - if (context->flags & SSPI_GSS_C_CONF_FLAG) - { - krb5_error_code rv = krb_log_exec(krb5glue_crypto_length, context->ctx, key, - KRB5_CRYPTO_TYPE_HEADER, &header); - if (rv) - return rv; - rv = krb_log_exec(krb5glue_crypto_length, context->ctx, key, KRB5_CRYPTO_TYPE_PADDING, - &pad); - if (rv) - return rv; - rv = krb_log_exec(krb5glue_crypto_length, context->ctx, key, KRB5_CRYPTO_TYPE_TRAILER, - &trailer); - if (rv) - return rv; - /* GSS header (= 16 bytes) + encrypted header = 32 bytes */ - ContextSizes->cbSecurityTrailer = header + pad + trailer + 32; - } - if (context->flags & SSPI_GSS_C_INTEG_FLAG) - { - krb5_error_code rv = - krb_log_exec(krb5glue_crypto_length, context->ctx, key, KRB5_CRYPTO_TYPE_CHECKSUM, - &ContextSizes->cbMaxSignature); - if (rv) - return rv; - ContextSizes->cbMaxSignature += 16; - } - - return SEC_E_OK; + default: + WLog_ERR(TAG, "TODO: QueryContextAttributes implement ulAttribute=0x%08" PRIx32, + ulAttribute); + return SEC_E_UNSUPPORTED_FUNCTION; } - - WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute); - return SEC_E_UNSUPPORTED_FUNCTION; #else return SEC_E_UNSUPPORTED_FUNCTION; #endif @@ -1461,38 +1643,46 @@ static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesX(PCredHandle if (!pBuffer) return SEC_E_INSUFFICIENT_MEMORY; - if (ulAttribute == SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS) + switch (ulAttribute) { - SecPkgCredentials_KdcProxySettingsW* kdc_settings = pBuffer; - - /* Sanity checks */ - if (cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) || - kdc_settings->Version != KDC_PROXY_SETTINGS_V1 || - kdc_settings->ProxyServerOffset < sizeof(SecPkgCredentials_KdcProxySettingsW) || - cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) + - kdc_settings->ProxyServerOffset + kdc_settings->ProxyServerLength) - return SEC_E_INVALID_TOKEN; - - if (credentials->kdc_url) + case SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS: { - free(credentials->kdc_url); - credentials->kdc_url = NULL; + SecPkgCredentials_KdcProxySettingsW* kdc_settings = pBuffer; + + /* Sanity checks */ + if (cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) || + kdc_settings->Version != KDC_PROXY_SETTINGS_V1 || + kdc_settings->ProxyServerOffset < sizeof(SecPkgCredentials_KdcProxySettingsW) || + cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) + + kdc_settings->ProxyServerOffset + kdc_settings->ProxyServerLength) + return SEC_E_INVALID_TOKEN; + + if (credentials->kdc_url) + { + free(credentials->kdc_url); + credentials->kdc_url = NULL; + } + + if (kdc_settings->ProxyServerLength > 0) + { + WCHAR* proxy = (WCHAR*)((BYTE*)pBuffer + kdc_settings->ProxyServerOffset); + + credentials->kdc_url = ConvertWCharNToUtf8Alloc( + proxy, kdc_settings->ProxyServerLength / sizeof(WCHAR), NULL); + if (!credentials->kdc_url) + return SEC_E_INSUFFICIENT_MEMORY; + } + + return SEC_E_OK; } - - if (kdc_settings->ProxyServerLength > 0) - { - WCHAR* proxy = (WCHAR*)((BYTE*)pBuffer + kdc_settings->ProxyServerOffset); - - credentials->kdc_url = ConvertWCharNToUtf8Alloc( - proxy, kdc_settings->ProxyServerLength / sizeof(WCHAR), NULL); - if (!credentials->kdc_url) - return SEC_E_INSUFFICIENT_MEMORY; - } - - return SEC_E_OK; + case SECPKG_CRED_ATTR_NAMES: + case SECPKG_ATTR_SUPPORTED_ALGS: + default: + WLog_ERR(TAG, "TODO: SetCredentialsAttributesX implement ulAttribute=0x%08" PRIx32, + ulAttribute); + return SEC_E_UNSUPPORTED_FUNCTION; } - return SEC_E_UNSUPPORTED_FUNCTION; #else return SEC_E_UNSUPPORTED_FUNCTION; #endif @@ -1536,6 +1726,8 @@ static SECURITY_STATUS SEC_ENTRY kerberos_EncryptMessage(PCtxtHandle phContext, if (!(context->flags & SSPI_GSS_C_CONF_FLAG)) return SEC_E_UNSUPPORTED_FUNCTION; + KRB_CREDENTIALS* creds = context->credentials; + sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN); data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA); @@ -1561,7 +1753,7 @@ static SECURITY_STATUS SEC_ENTRY kerberos_EncryptMessage(PCtxtHandle phContext, encrypt_iov[2].data.length = 16; /* Get the lengths of the header, trailer, and padding and ensure sig_buffer is large enough */ - if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, encrypt_iov, + if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, encrypt_iov, ARRAYSIZE(encrypt_iov))) return SEC_E_INTERNAL_ERROR; if (sig_buffer->cbBuffer < @@ -1589,7 +1781,7 @@ static SECURITY_STATUS SEC_ENTRY kerberos_EncryptMessage(PCtxtHandle phContext, /* Set the correct RRC */ Data_Write_UINT16_BE(header + 6, 16 + encrypt_iov[3].data.length + encrypt_iov[4].data.length); - if (krb_log_exec(krb5glue_encrypt_iov, context->ctx, key, usage, encrypt_iov, + if (krb_log_exec(krb5glue_encrypt_iov, creds->ctx, key, usage, encrypt_iov, ARRAYSIZE(encrypt_iov))) return SEC_E_INTERNAL_ERROR; @@ -1627,6 +1819,8 @@ static SECURITY_STATUS SEC_ENTRY kerberos_DecryptMessage(PCtxtHandle phContext, if (!(context->flags & SSPI_GSS_C_CONF_FLAG)) return SEC_E_UNSUPPORTED_FUNCTION; + KRB_CREDENTIALS* creds = context->credentials; + sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN); data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA); @@ -1648,7 +1842,8 @@ static SECURITY_STATUS SEC_ENTRY kerberos_DecryptMessage(PCtxtHandle phContext, if ((flags & FLAG_SENDER_IS_ACCEPTOR) == context->acceptor) return SEC_E_INVALID_TOKEN; - if (context->flags & ISC_REQ_SEQUENCE_DETECT && seq_no != context->remote_seq + MessageSeqNo) + if ((context->flags & ISC_REQ_SEQUENCE_DETECT) && + (seq_no != context->remote_seq + MessageSeqNo)) return SEC_E_OUT_OF_SEQUENCE; if (!(flags & FLAG_WRAP_CONFIDENTIAL)) @@ -1660,14 +1855,14 @@ static SECURITY_STATUS SEC_ENTRY kerberos_DecryptMessage(PCtxtHandle phContext, /* Find the proper key and key usage */ key = get_key(&context->keyset); - if (!key || (flags & FLAG_ACCEPTOR_SUBKEY && context->keyset.acceptor_key != key)) + if (!key || ((flags & FLAG_ACCEPTOR_SUBKEY) && (context->keyset.acceptor_key != key))) return SEC_E_INTERNAL_ERROR; usage = context->acceptor ? KG_USAGE_INITIATOR_SEAL : KG_USAGE_ACCEPTOR_SEAL; /* Fill in the lengths of the iov array */ iov[1].data.length = data_buffer->cbBuffer; iov[2].data.length = 16; - if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, iov, ARRAYSIZE(iov))) + if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, iov, ARRAYSIZE(iov))) return SEC_E_INTERNAL_ERROR; /* We don't expect a trailer buffer; everything must be in sig_buffer */ @@ -1683,7 +1878,7 @@ static SECURITY_STATUS SEC_ENTRY kerberos_DecryptMessage(PCtxtHandle phContext, iov[3].data.data = iov[2].data.data + iov[2].data.length; iov[4].data.data = iov[3].data.data + iov[3].data.length; - if (krb_log_exec(krb5glue_decrypt_iov, context->ctx, key, usage, iov, ARRAYSIZE(iov))) + if (krb_log_exec(krb5glue_decrypt_iov, creds->ctx, key, usage, iov, ARRAYSIZE(iov))) return SEC_E_INTERNAL_ERROR; /* Validate the encrypted header */ @@ -1721,6 +1916,8 @@ static SECURITY_STATUS SEC_ENTRY kerberos_MakeSignature(PCtxtHandle phContext, U if (!(context->flags & SSPI_GSS_C_INTEG_FLAG)) return SEC_E_UNSUPPORTED_FUNCTION; + KRB_CREDENTIALS* creds = context->credentials; + sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN); data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA); @@ -1739,7 +1936,7 @@ static SECURITY_STATUS SEC_ENTRY kerberos_MakeSignature(PCtxtHandle phContext, U /* Fill in the lengths of the iov array */ iov[0].data.length = data_buffer->cbBuffer; iov[1].data.length = 16; - if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, iov, ARRAYSIZE(iov))) + if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, iov, ARRAYSIZE(iov))) return SEC_E_INTERNAL_ERROR; /* Ensure the buffer is big enough */ @@ -1758,7 +1955,7 @@ static SECURITY_STATUS SEC_ENTRY kerberos_MakeSignature(PCtxtHandle phContext, U iov[1].data.data = header; iov[2].data.data = header + 16; - if (krb_log_exec(krb5glue_make_checksum_iov, context->ctx, key, usage, iov, ARRAYSIZE(iov))) + if (krb_log_exec(krb5glue_make_checksum_iov, creds->ctx, key, usage, iov, ARRAYSIZE(iov))) return SEC_E_INTERNAL_ERROR; sig_buffer->cbBuffer = iov[2].data.length + 16; @@ -1827,9 +2024,10 @@ static SECURITY_STATUS SEC_ENTRY kerberos_VerifySignature(PCtxtHandle phContext, usage = context->acceptor ? KG_USAGE_INITIATOR_SIGN : KG_USAGE_ACCEPTOR_SIGN; /* Fill in the iov array lengths */ + KRB_CREDENTIALS* creds = context->credentials; iov[0].data.length = data_buffer->cbBuffer; iov[1].data.length = 16; - if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, iov, ARRAYSIZE(iov))) + if (krb_log_exec(krb5glue_crypto_length_iov, creds->ctx, key, iov, ARRAYSIZE(iov))) return SEC_E_INTERNAL_ERROR; if (sig_buffer->cbBuffer != iov[2].data.length + 16) @@ -1840,7 +2038,7 @@ static SECURITY_STATUS SEC_ENTRY kerberos_VerifySignature(PCtxtHandle phContext, iov[1].data.data = header; iov[2].data.data = header + 16; - if (krb_log_exec(krb5glue_verify_checksum_iov, context->ctx, key, usage, iov, ARRAYSIZE(iov), + if (krb_log_exec(krb5glue_verify_checksum_iov, creds->ctx, key, usage, iov, ARRAYSIZE(iov), &is_valid)) return SEC_E_INTERNAL_ERROR; diff --git a/winpr/libwinpr/utils/asn1/asn1.c b/winpr/libwinpr/utils/asn1/asn1.c index 69ac667f2..b53f5f484 100644 --- a/winpr/libwinpr/utils/asn1/asn1.c +++ b/winpr/libwinpr/utils/asn1/asn1.c @@ -1000,7 +1000,7 @@ size_t WinPrAsn1DecReadBoolean(WinPrAsn1Decoder* dec, WinPrAsn1_BOOL* target) ret = readTagAndLen(dec, &dec->source, &tag, &len); if (!ret || tag != ER_TAG_BOOLEAN) return 0; - if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len) || len != 1) + if (Stream_GetRemainingLength(&dec->source) < len || len != 1) return 0; Stream_Read_UINT8(&dec->source, v); @@ -1020,7 +1020,7 @@ static size_t WinPrAsn1DecReadIntegerLike(WinPrAsn1Decoder* dec, WinPrAsn1_tag e size_t ret = readTagAndLen(dec, &dec->source, &tag, &len); if (!ret || (tag != expectedTag)) return 0; - if (len == 0 || !Stream_CheckAndLogRequiredLength(TAG, &dec->source, len) || (len > 4)) + if (len == 0 || Stream_GetRemainingLength(&dec->source) < len || (len > 4)) return 0; WinPrAsn1_INTEGER val = 0; diff --git a/winpr/libwinpr/utils/test/TestASN1.c b/winpr/libwinpr/utils/test/TestASN1.c index ecdd67f8f..99d9e5db2 100644 --- a/winpr/libwinpr/utils/test/TestASN1.c +++ b/winpr/libwinpr/utils/test/TestASN1.c @@ -6,6 +6,8 @@ static const BYTE badBoolContent[] = { 0x01, 0x04, 0xFF }; static const BYTE integerContent[] = { 0x02, 0x01, 0x02 }; static const BYTE badIntegerContent[] = { 0x02, 0x04, 0x02 }; +static const BYTE positiveIntegerContent[] = { 0x02, 0x02, 0x00, 0xff }; +static const BYTE negativeIntegerContent[] = { 0x02, 0x01, 0xff }; static const BYTE seqContent[] = { 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x1B, 0x44, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6C, 0x20, 0x53, 0x69, 0x67, @@ -46,6 +48,16 @@ int TestASN1Read(int argc, char* argv[]) if (!WinPrAsn1DecReadInteger(&decoder, &integerV)) return -1; + Stream_StaticConstInit(&staticS, positiveIntegerContent, sizeof(positiveIntegerContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (!WinPrAsn1DecReadInteger(&decoder, &integerV) && integerV != 0xff) + return -1; + + Stream_StaticConstInit(&staticS, negativeIntegerContent, sizeof(negativeIntegerContent)); + WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); + if (!WinPrAsn1DecReadInteger(&decoder, &integerV) && integerV != -1) + return -1; + Stream_StaticConstInit(&staticS, badIntegerContent, sizeof(badIntegerContent)); WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); if (WinPrAsn1DecReadInteger(&decoder, &integerV))