core, channels: client-side remote credential guard

This patch implements the client-side part of the remote credential guard feature
as described in MS-RDPEAR. The 2 main changes are: shipping the TSRemoteGuardaCreds in
NLA, and implement the rdpear channel that allows LSASS to remote all the calls to
our client. For now it's UNIX only as the windows implementation would be implemented
in a completely different way.
To test, you may establish you ccache and then connect with (RCG enabled on the server):
	xfreerdp /remoteGuard /u:<user> /d:<domain> /v<server>

That should log you in, and in the session you should not be asked for credentials when
doing mstsc /remoteGuard /v:<other server>.
This commit is contained in:
David Fort 2024-02-15 10:27:11 +01:00
parent 9c52238d24
commit a4bd5ba886
29 changed files with 4302 additions and 264 deletions

View File

@ -0,0 +1,38 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2023 David Fort <contact@hardening-consulting.com>
#
# 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()

View File

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

View File

@ -0,0 +1,31 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2023 David Fort <contact@hardening-consulting.com>
#
# 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")

View File

@ -0,0 +1,965 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Authentication redirection virtual channel
*
* Copyright 2023 David Fort <contact@hardening-consulting.com>
*
* 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 <krb5/krb5.h>
#include <errno.h>
#include <winpr/assert.h>
#include <winpr/crt.h>
#include <winpr/wlog.h>
#include <winpr/print.h>
#include <winpr/asn1.h>
#include <winpr/sspi.h>
#include <winpr/collections.h>
#include <rdpear-common/ndr.h>
#include <rdpear-common/rdpear_common.h>
#include <rdpear-common/rdpear_asn1.h>
#include <freerdp/config.h>
#include <freerdp/freerdp.h>
#include <freerdp/addin.h>
#include <freerdp/client/channels.h>
#include <freerdp/channels/log.h>
#include <freerdp/channels/rdpear.h>
#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);
}

View File

@ -0,0 +1,43 @@
# FreeRDP: A Remote Desktop Protocol Implementation
# FreeRDP cmake build script
#
# Copyright 2024 David Fort <contact@hardening-consulting.com>
#
# 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}
$<TARGET_OBJECTS:rdpear-common-obj>
)
channel_install(rdpear-common ${FREERDP_ADDIN_PATH} "FreeRDPTargets")
if (BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@ -0,0 +1,994 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Authentication redirection virtual channel
*
* Copyright 2024 David Fort <contact@hardening-consulting.com>
*
* 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 <winpr/assert.h>
#include <winpr/collections.h>
#include <winpr/wlog.h>
#include <freerdp/log.h>
#include <rdpear-common/ndr.h>
#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<no dump function>", 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;
}

View File

@ -0,0 +1,192 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Authentication redirection virtual channel
*
* Copyright 2024 David Fort <contact@hardening-consulting.com>
*
* 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 <winpr/stream.h>
#include <freerdp/api.h>
#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_ */

View File

@ -0,0 +1,31 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* ASN1 routines for RDPEAR
*
* Copyright 2024 David Fort <contact@hardening-consulting.com>
*
* 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 <krb5.h>
#include <winpr/stream.h>
wStream* rdpear_enc_Checksum(UINT32 cksumtype, krb5_checksum* csum);
wStream* rdpear_enc_EncryptedData(UINT32 encType, krb5_data* payload);
#endif /* RPDEAR_RDPEAR_ASN1_H__ */

View File

@ -0,0 +1,244 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
*
* Copyright 2023 David Fort <contact@hardening-consulting.com>
*
* 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 <winpr/stream.h>
#include <winpr/asn1.h>
#include <winpr/wlog.h>
#include <winpr/sspi.h>
#include <freerdp/api.h>
#include <rdpear-common/ndr.h>
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 */

View File

@ -0,0 +1,104 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* ASN1 routines for RDPEAR
*
* Copyright 2024 David Fort <contact@hardening-consulting.com>
*
* 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 <rdpear-common/rdpear_asn1.h>
#include <winpr/asn1.h>
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;
}

View File

@ -0,0 +1,488 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
*
* Copyright 2023 David Fort <contact@hardening-consulting.com>
*
* 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 <rdpear-common/rdpear_common.h>
#include <rdpear-common/rdpear_asn1.h>
#include <rdpear-common/ndr.h>
#include <stddef.h>
#include <winpr/print.h>
#include <freerdp/channels/log.h>
#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)

View File

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

View File

@ -0,0 +1,36 @@
#include <rdpear-common/ndr.h>
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;
}

View File

@ -0,0 +1,357 @@
#include <winpr/print.h>
#include <rdpear-common/ndr.h>
#include <rdpear-common/rdpear_common.h>
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);
}

View File

@ -40,6 +40,7 @@
#include <freerdp/channels/drdynvc.h> #include <freerdp/channels/drdynvc.h>
#include <freerdp/channels/cliprdr.h> #include <freerdp/channels/cliprdr.h>
#include <freerdp/channels/encomsp.h> #include <freerdp/channels/encomsp.h>
#include <freerdp/channels/rdpear.h>
#include <freerdp/channels/rdp2tcp.h> #include <freerdp/channels/rdp2tcp.h>
#include <freerdp/channels/remdesk.h> #include <freerdp/channels/remdesk.h>
#include <freerdp/channels/rdpsnd.h> #include <freerdp/channels/rdpsnd.h>
@ -4403,6 +4404,13 @@ static int freerdp_client_settings_parse_command_line_arguments_int(
if (!freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeRequired, enable)) if (!freerdp_settings_set_bool(settings, FreeRDP_RestrictedAdminModeRequired, enable))
return fail_at(arg, COMMAND_LINE_ERROR); 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") CommandLineSwitchCase(arg, "pth")
{ {
if (!freerdp_settings_set_bool(settings, FreeRDP_ConsoleSession, TRUE)) 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_SupportDisplayControl, DISP_CHANNEL_NAME, NULL },
{ FreeRDP_SupportGeometryTracking, GEOMETRY_CHANNEL_NAME, NULL }, { FreeRDP_SupportGeometryTracking, GEOMETRY_CHANNEL_NAME, NULL },
{ FreeRDP_SupportVideoOptimized, VIDEO_CHANNEL_NAME, NULL }, { FreeRDP_SupportVideoOptimized, VIDEO_CHANNEL_NAME, NULL },
{ FreeRDP_RemoteCredentialGuard, RDPEAR_CHANNEL_NAME, NULL },
}; };
ChannelToLoad staticChannels[] = { ChannelToLoad staticChannels[] = {

View File

@ -393,6 +393,8 @@ static const COMMAND_LINE_ARGUMENT_A global_cmd_args[] = {
"connecting to a buggy server" }, "connecting to a buggy server" },
{ "restricted-admin", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "restrictedAdmin", { "restricted-admin", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, "restrictedAdmin",
"Restricted admin mode" }, "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", COMMAND_LINE_VALUE_FLAG, NULL, NULL, NULL, -1, NULL, "RemoteFX" },
{ "rfx-mode", COMMAND_LINE_VALUE_REQUIRED, "[image|video]", NULL, NULL, -1, NULL, { "rfx-mode", COMMAND_LINE_VALUE_REQUIRED, "[image|video]", NULL, NULL, -1, NULL,
"RemoteFX mode" }, "RemoteFX mode" },

View File

@ -77,7 +77,7 @@ endfunction()
function(GET_KRB5_BY_CONFIG KRB5_CONFIG) function(GET_KRB5_BY_CONFIG KRB5_CONFIG)
if (NOT KRB5_CONFIG) if (NOT KRB5_CONFIG)
find_file(KRB5_CONFIG find_program(KRB5_CONFIG
NAMES NAMES
"krb5-config" "krb5-config"
"krb5-config.mit" "krb5-config.mit"
@ -89,6 +89,9 @@ function(GET_KRB5_BY_CONFIG KRB5_CONFIG)
REQUIRED REQUIRED
) )
message("autodetected krb5-config at ${KRB5_CONFIG}") message("autodetected krb5-config at ${KRB5_CONFIG}")
if (NOT KRB5_CONFIG)
return()
endif()
else() else()
message("using krb5-config ${KRB5_CONFIG} provided by KRB5_ROOT_CONFIG") message("using krb5-config ${KRB5_CONFIG} provided by KRB5_ROOT_CONFIG")
endif() endif()
@ -195,7 +198,7 @@ elseif(KRB5_ANY_FOUND)
GET_KRB5_VENDOR(ANY_VENDOR) GET_KRB5_VENDOR(ANY_VENDOR)
PROVIDES_KRB5(NAME "${ANY_VENDOR}") PROVIDES_KRB5(NAME "${ANY_VENDOR}")
else() else()
GET_KRB5_BY_CONFIG(${KRB5_ROOT_CONFIG}) GET_KRB5_BY_CONFIG("${KRB5_ROOT_CONFIG}")
endif() endif()
#message("using KRB5_FOUND ${KRB5_FOUND} ") #message("using KRB5_FOUND ${KRB5_FOUND} ")

View File

@ -0,0 +1,39 @@
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* Authentication redirection channel
*
* Copyright 2023 David Fort <contact@hardening-consulting.com>
*
* 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 <freerdp/api.h>
#include <freerdp/dvc.h>
#include <freerdp/types.h>
#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 */

View File

@ -23,6 +23,7 @@
#define FREERDP_H #define FREERDP_H
#include <winpr/stream.h> #include <winpr/stream.h>
#include <winpr/sspi.h>
#include <freerdp/api.h> #include <freerdp/api.h>
#include <freerdp/types.h> #include <freerdp/types.h>
@ -538,10 +539,10 @@ owned by rdpRdp */
It is used to get the username/password. The reason It is used to get the username/password. The reason
argument tells why it was called. */ argument tells why it was called. */
ALIGN64 pChooseSmartcard ALIGN64 pChooseSmartcard
ChooseSmartcard; /* (offset 70) ChooseSmartcard; /* (offset 70)
Callback for choosing a smartcard for logon. Callback for choosing a smartcard for logon.
Used when multiple smartcards are available. Returns an index into a list Used when multiple smartcards are available. Returns an index into a list
of SmartcardCertInfo pointers */ of SmartcardCertInfo pointers */
ALIGN64 pGetAccessToken GetAccessToken; /* (offset 71) ALIGN64 pGetAccessToken GetAccessToken; /* (offset 71)
Callback for obtaining an access token Callback for obtaining an access token
for \b AccessTokenType authentication */ for \b AccessTokenType authentication */
@ -660,6 +661,40 @@ owned by rdpRdp */
FREERDP_API UINT32 freerdp_get_nla_sspi_error(rdpContext* context); 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 void clearChannelError(rdpContext* context);
FREERDP_API HANDLE getChannelErrorEventHandle(rdpContext* context); FREERDP_API HANDLE getChannelErrorEventHandle(rdpContext* context);
FREERDP_API UINT getChannelError(rdpContext* context); FREERDP_API UINT getChannelError(rdpContext* context);

View File

@ -402,6 +402,7 @@ BOOL rdp_client_connect(rdpRdp* rdp)
nego_set_preconnection_blob(rdp->nego, settings->PreconnectionBlob); nego_set_preconnection_blob(rdp->nego, settings->PreconnectionBlob);
nego_set_negotiation_enabled(rdp->nego, settings->NegotiateSecurityLayer); nego_set_negotiation_enabled(rdp->nego, settings->NegotiateSecurityLayer);
nego_set_restricted_admin_mode_required(rdp->nego, settings->RestrictedAdminModeRequired); 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_enabled(rdp->nego, settings->GatewayEnabled);
nego_set_gateway_bypass_local(rdp->nego, settings->GatewayBypassLocal); nego_set_gateway_bypass_local(rdp->nego, settings->GatewayBypassLocal);
nego_enable_rdp(rdp->nego, settings->RdpSecurity); nego_enable_rdp(rdp->nego, settings->RdpSecurity);

View File

@ -31,6 +31,7 @@
#include <winpr/assert.h> #include <winpr/assert.h>
#include <winpr/library.h> #include <winpr/library.h>
#include <winpr/registry.h> #include <winpr/registry.h>
#include <winpr/sspi.h>
#include <freerdp/log.h> #include <freerdp/log.h>
@ -692,6 +693,17 @@ UINT32 credssp_auth_sspi_error(rdpCredsspAuth* auth)
return (UINT32)auth->sspi_error; 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) void credssp_auth_free(rdpCredsspAuth* auth)
{ {
SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL; SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL;

View File

@ -30,6 +30,7 @@ typedef struct rdp_credssp_auth rdpCredsspAuth;
#include <freerdp/freerdp.h> #include <freerdp/freerdp.h>
#include <winpr/tchar.h> #include <winpr/tchar.h>
#include <winpr/sspi.h> #include <winpr/sspi.h>
#include <winpr/secapi.h>
FREERDP_LOCAL void credssp_auth_free(rdpCredsspAuth* auth); 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 size_t credssp_auth_trailer_size(rdpCredsspAuth* auth);
FREERDP_LOCAL UINT32 credssp_auth_sspi_error(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 */ #endif /* FREERDP_LIB_CORE_CREDSSP_AUTH_H */

View File

@ -1224,6 +1224,32 @@ UINT32 freerdp_get_nla_sspi_error(rdpContext* context)
return nla_get_sspi_error(nla); 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) HANDLE getChannelErrorEventHandle(rdpContext* context)
{ {
WINPR_ASSERT(context); WINPR_ASSERT(context);

View File

@ -1405,6 +1405,78 @@ static BOOL nla_read_ts_credentials(rdpNla* nla, SecBuffer* data)
return ret; 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. * Encode TSCredentials structure.
* @param nla A pointer to the NLA to use * @param nla A pointer to the NLA to use
@ -1547,6 +1619,29 @@ static BOOL nla_encode_ts_credentials(rdpNla* nla)
break; break;
} }
case TSCREDS_REMOTEGUARD: 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: default:
goto out; goto out;
} }
@ -2107,3 +2202,30 @@ UINT32 nla_get_sspi_error(rdpNla* nla)
WINPR_ASSERT(nla); WINPR_ASSERT(nla);
return credssp_auth_sspi_error(nla->auth); 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);
}

View File

@ -75,5 +75,8 @@ WINPR_ATTR_MALLOC(nla_free, 1)
FREERDP_LOCAL rdpNla* nla_new(rdpContext* context, rdpTransport* transport); FREERDP_LOCAL rdpNla* nla_new(rdpContext* context, rdpTransport* transport);
FREERDP_LOCAL void nla_set_early_user_auth(rdpNla* nla, BOOL earlyUserAuth); 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 */ #endif /* FREERDP_LIB_CORE_NLA_H */

View File

@ -1332,6 +1332,8 @@ extern "C"
#define SECPKG_ATTR_AUTH_NTLM_MIC 1106 #define SECPKG_ATTR_AUTH_NTLM_MIC 1106
#define SECPKG_ATTR_AUTH_NTLM_MIC_VALUE 1107 #define SECPKG_ATTR_AUTH_NTLM_MIC_VALUE 1107
#define SECPKG_CRED_ATTR_TICKET_LOGON 1200
typedef struct typedef struct
{ {
char User[256 + 1]; char User[256 + 1];

File diff suppressed because it is too large Load Diff

View File

@ -1000,7 +1000,7 @@ size_t WinPrAsn1DecReadBoolean(WinPrAsn1Decoder* dec, WinPrAsn1_BOOL* target)
ret = readTagAndLen(dec, &dec->source, &tag, &len); ret = readTagAndLen(dec, &dec->source, &tag, &len);
if (!ret || tag != ER_TAG_BOOLEAN) if (!ret || tag != ER_TAG_BOOLEAN)
return 0; return 0;
if (!Stream_CheckAndLogRequiredLength(TAG, &dec->source, len) || len != 1) if (Stream_GetRemainingLength(&dec->source) < len || len != 1)
return 0; return 0;
Stream_Read_UINT8(&dec->source, v); 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); size_t ret = readTagAndLen(dec, &dec->source, &tag, &len);
if (!ret || (tag != expectedTag)) if (!ret || (tag != expectedTag))
return 0; 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; return 0;
WinPrAsn1_INTEGER val = 0; WinPrAsn1_INTEGER val = 0;

View File

@ -6,6 +6,8 @@ static const BYTE badBoolContent[] = { 0x01, 0x04, 0xFF };
static const BYTE integerContent[] = { 0x02, 0x01, 0x02 }; static const BYTE integerContent[] = { 0x02, 0x01, 0x02 };
static const BYTE badIntegerContent[] = { 0x02, 0x04, 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, static const BYTE seqContent[] = { 0x30, 0x22, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x1B, 0x44,
0x69, 0x67, 0x69, 0x74, 0x61, 0x6C, 0x20, 0x53, 0x69, 0x67, 0x69, 0x67, 0x69, 0x74, 0x61, 0x6C, 0x20, 0x53, 0x69, 0x67,
@ -46,6 +48,16 @@ int TestASN1Read(int argc, char* argv[])
if (!WinPrAsn1DecReadInteger(&decoder, &integerV)) if (!WinPrAsn1DecReadInteger(&decoder, &integerV))
return -1; 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)); Stream_StaticConstInit(&staticS, badIntegerContent, sizeof(badIntegerContent));
WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS); WinPrAsn1Decoder_Init(&decoder, WINPR_ASN1_DER, &staticS);
if (WinPrAsn1DecReadInteger(&decoder, &integerV)) if (WinPrAsn1DecReadInteger(&decoder, &integerV))