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))