FreeRDP
Loading...
Searching...
No Matches
rdpear_main.c
1
19#include <krb5.h>
20#include <errno.h>
21
22#include <winpr/assert.h>
23#include <winpr/wtypes.h>
24
25#include <winpr/crt.h>
26#include <winpr/wlog.h>
27#include <winpr/print.h>
28#include <winpr/asn1.h>
29#include <winpr/sspi.h>
30#include <winpr/collections.h>
31
32#include <rdpear-common/ndr.h>
33#include <rdpear-common/rdpear_common.h>
34#include <rdpear-common/rdpear_asn1.h>
35
36#include <freerdp/config.h>
37#include <freerdp/freerdp.h>
38#include <freerdp/addin.h>
39#include <freerdp/client/channels.h>
40#include <freerdp/channels/log.h>
41#include <freerdp/channels/rdpear.h>
42
43#define TAG CHANNELS_TAG("rdpear.client")
44
45#ifndef MAX_KEYTAB_NAME_LEN
46#define MAX_KEYTAB_NAME_LEN 1100 /* Defined in MIT krb5.h */
47#endif
48
49/* defined in libkrb5 */
50krb5_error_code encode_krb5_authenticator(const krb5_authenticator* rep, krb5_data** code_out);
51krb5_error_code encode_krb5_ap_rep(const krb5_ap_rep* rep, krb5_data** code_out);
52
53typedef struct
54{
56 rdpContext* rdp_context;
57 krb5_context krbContext;
58} RDPEAR_PLUGIN;
59
60static const BYTE payloadHeader[16] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
61 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
62
63static krb5_error_code RPC_ENCRYPTION_KEY_to_keyblock(krb5_context ctx,
64 const KERB_RPC_ENCRYPTION_KEY* key,
65 krb5_keyblock** pkeyblock)
66{
67 WINPR_ASSERT(ctx);
68 WINPR_ASSERT(key);
69 WINPR_ASSERT(pkeyblock);
70
71 if (!key->reserved3.length)
72 return KRB5KDC_ERR_NULL_KEY;
73
74 krb5_error_code rv =
75 krb5_init_keyblock(ctx, (krb5_enctype)key->reserved2, key->reserved3.length, pkeyblock);
76 if (rv)
77 return rv;
78
79 krb5_keyblock* keyblock = *pkeyblock;
80 memcpy(keyblock->contents, key->reserved3.value, key->reserved3.length);
81 return rv;
82}
83
84static krb5_error_code kerb_do_checksum(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key,
85 krb5_keyusage kusage, krb5_cksumtype cksumtype,
86 const KERB_ASN1_DATA* plain, krb5_checksum* out)
87{
88 WINPR_ASSERT(ctx);
89 WINPR_ASSERT(key);
90 WINPR_ASSERT(plain);
91 WINPR_ASSERT(out);
92
93 krb5_keyblock* keyblock = nullptr;
94 krb5_data data = WINPR_C_ARRAY_INIT;
95
96 krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock);
97 if (rv)
98 return rv;
99
100 data.data = (char*)plain->Asn1Buffer;
101 data.length = plain->Asn1BufferHints.count;
102
103 rv = krb5_c_make_checksum(ctx, cksumtype, keyblock, kusage, &data, out);
104
105 krb5_free_keyblock(ctx, keyblock);
106 return rv;
107}
108
109static krb5_error_code kerb_do_encrypt(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key,
110 krb5_keyusage kusage, const KERB_ASN1_DATA* plain,
111 krb5_data* out)
112{
113 WINPR_ASSERT(ctx);
114 WINPR_ASSERT(key);
115 WINPR_ASSERT(plain);
116 WINPR_ASSERT(out);
117
118 out->data = nullptr;
119 out->length = 0;
120
121 krb5_keyblock* keyblock = nullptr;
122 krb5_data data = WINPR_C_ARRAY_INIT;
123 krb5_enc_data enc = WINPR_C_ARRAY_INIT;
124 size_t elen = 0;
125
126 krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock);
127 if (rv)
128 return rv;
129
130 data.data = (char*)plain->Asn1Buffer;
131 data.length = plain->Asn1BufferHints.count;
132
133 rv = krb5_c_encrypt_length(ctx, keyblock->enctype, data.length, &elen);
134 if (rv)
135 goto out;
136 if (!elen || (elen > UINT32_MAX))
137 {
138 rv = KRB5_PARSE_MALFORMED;
139 goto out;
140 }
141 enc.ciphertext.length = (unsigned int)elen;
142 enc.ciphertext.data = malloc(elen);
143 if (!enc.ciphertext.data)
144 {
145 rv = ENOMEM;
146 goto out;
147 }
148
149 rv = krb5_c_encrypt(ctx, keyblock, kusage, nullptr, &data, &enc);
150 if (rv)
151 {
152 free(enc.ciphertext.data);
153 goto out;
154 }
155
156 out->data = enc.ciphertext.data;
157 out->length = enc.ciphertext.length;
158out:
159 krb5_free_keyblock(ctx, keyblock);
160 return rv;
161}
162
163static krb5_error_code kerb_do_decrypt(krb5_context ctx, const KERB_RPC_ENCRYPTION_KEY* key,
164 krb5_keyusage kusage, const krb5_data* cipher,
165 KERB_ASN1_DATA* plain)
166{
167 WINPR_ASSERT(ctx);
168 WINPR_ASSERT(key);
169 WINPR_ASSERT(cipher);
170 WINPR_ASSERT(cipher->length);
171 WINPR_ASSERT(plain);
172
173 plain->Asn1Buffer = nullptr;
174 plain->Asn1BufferHints.count = 0;
175
176 krb5_keyblock* keyblock = nullptr;
177 krb5_data data = WINPR_C_ARRAY_INIT;
178 krb5_enc_data enc = WINPR_C_ARRAY_INIT;
179
180 krb5_error_code rv = RPC_ENCRYPTION_KEY_to_keyblock(ctx, key, &keyblock);
181 if (rv)
182 return rv;
183
184 enc.kvno = KRB5_PVNO;
185 enc.enctype = (krb5_enctype)key->reserved2;
186 enc.ciphertext.length = cipher->length;
187 enc.ciphertext.data = cipher->data;
188
189 data.length = cipher->length;
190 data.data = (char*)malloc(cipher->length);
191 if (!data.data)
192 {
193 rv = ENOMEM;
194 goto out;
195 }
196
197 rv = krb5_c_decrypt(ctx, keyblock, kusage, nullptr, &enc, &data);
198 if (rv)
199 {
200 free(data.data);
201 goto out;
202 }
203
204 plain->Asn1Buffer = (BYTE*)data.data;
205 plain->Asn1BufferHints.count = data.length;
206out:
207 krb5_free_keyblock(ctx, keyblock);
208 return rv;
209}
210
211static BOOL rdpear_send_payload(RDPEAR_PLUGIN* rdpear, IWTSVirtualChannelCallback* pChannelCallback,
212 BOOL isKerb, wStream* payload)
213{
214 GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
215 BOOL ret = FALSE;
216 wStream* finalStream = nullptr;
217 SecBuffer cryptedBuffer = WINPR_C_ARRAY_INIT;
218 wStream* unencodedContent = rdpear_encodePayload(isKerb, payload);
219 if (!unencodedContent)
220 goto out;
221
222 const size_t unencodedLen = Stream_GetPosition(unencodedContent);
223
224#if UINT32_MAX < SIZE_MAX
225 if (unencodedLen > UINT32_MAX)
226 goto out;
227#endif
228
229 SecBuffer inBuffer = { (ULONG)unencodedLen, SECBUFFER_DATA, Stream_Buffer(unencodedContent) };
230
231 if (!freerdp_nla_encrypt(rdpear->rdp_context, &inBuffer, &cryptedBuffer))
232 goto out;
233
234 finalStream = Stream_New(nullptr, 200);
235 if (!finalStream)
236 goto out;
237 Stream_Write_UINT32(finalStream, 0x4EACC3C8); /* ProtocolMagic (4 bytes) */
238 Stream_Write_UINT32(finalStream, cryptedBuffer.cbBuffer); /* Length (4 bytes) */
239 Stream_Write_UINT32(finalStream, 0x00000000); /* Version (4 bytes) */
240 Stream_Write_UINT32(finalStream, 0x00000000); /* Reserved (4 bytes) */
241 Stream_Write_UINT64(finalStream, 0); /* TsPkgContext (8 bytes) */
242
243 /* payload */
244 if (!Stream_EnsureRemainingCapacity(finalStream, cryptedBuffer.cbBuffer))
245 goto out;
246
247 Stream_Write(finalStream, cryptedBuffer.pvBuffer, cryptedBuffer.cbBuffer);
248
249 const size_t pos = Stream_GetPosition(finalStream);
250#if UINT32_MAX < SIZE_MAX
251 if (pos > UINT32_MAX)
252 goto out;
253#endif
254
255 UINT status = callback->channel->Write(callback->channel, (ULONG)pos,
256 Stream_Buffer(finalStream), nullptr);
257 ret = (status == CHANNEL_RC_OK);
258 if (!ret)
259 WLog_DBG(TAG, "rdpear_send_payload=0x%x", status);
260out:
261 sspi_SecBufferFree(&cryptedBuffer);
262 Stream_Free(unencodedContent, TRUE);
263 Stream_Free(finalStream, TRUE);
264 return ret;
265}
266
267static BOOL rdpear_prepare_response(NdrContext* rcontext, BOOL isKerb, UINT16 callId, UINT32 status,
268 NdrContext** pwcontext, wStream* retStream)
269{
270 WINPR_ASSERT(rcontext);
271 WINPR_ASSERT(pwcontext);
272
273 BOOL ret = FALSE;
274 *pwcontext = nullptr;
275 NdrContext* wcontext = ndr_context_copy(rcontext);
276 if (!wcontext)
277 return FALSE;
278
279 if (!Stream_EnsureRemainingCapacity(retStream, sizeof(payloadHeader)))
280 goto out;
281
282 Stream_Write(retStream, payloadHeader, sizeof(payloadHeader));
283
284 if (!ndr_write_header(wcontext, retStream) || !ndr_start_constructed(wcontext, retStream) ||
285 !ndr_write_pickle(wcontext, retStream)) /* pickle header */
286 goto out;
287 if (isKerb)
288 {
289 /* for some reason there's 4 zero undocumented bytes here after the pickle record
290 * in the kerberos package packets */
291 UINT32 v = 0;
292 if (!ndr_write_uint32(wcontext, retStream, v))
293 goto out;
294 }
295 if (!ndr_write_uint16(wcontext, retStream, callId) || /* callId */
296 !ndr_write_uint16(wcontext, retStream, 0x0000) || /* align padding */
297 !ndr_write_uint32(wcontext, retStream, status) || /* status */
298 !ndr_write_uint16(wcontext, retStream, callId) || /* callId */
299 !ndr_write_uint16(wcontext, retStream, 0x0000)) /* align padding */
300 goto out;
301
302 *pwcontext = wcontext;
303 ret = TRUE;
304out:
305 if (!ret)
306 ndr_context_destroy(&wcontext);
307 return ret;
308}
309
310static BOOL rdpear_kerb_version(NdrContext* rcontext, wStream* s, UINT32* pstatus, UINT32* pversion)
311{
312 *pstatus = ERROR_INVALID_DATA;
313
314 if (!ndr_read_uint32(rcontext, s, pversion))
315 return TRUE;
316
317 WLog_DBG(TAG, "-> KerbNegotiateVersion(v=0x%x)", *pversion);
318 *pstatus = 0;
319
320 return TRUE;
321}
322
323static BOOL rdpear_kerb_ComputeTgsChecksum(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s,
324 UINT32* pstatus, KERB_ASN1_DATA* resp)
325{
326 ComputeTgsChecksumReq req = WINPR_C_ARRAY_INIT;
327 krb5_checksum checksum = WINPR_C_ARRAY_INIT;
328 wStream* asn1Payload = nullptr;
329
330 *pstatus = ERROR_INVALID_DATA;
331 WLog_DBG(TAG, "-> ComputeTgsChecksum");
332
333 if (!ndr_read_ComputeTgsChecksumReq(rcontext, s, nullptr, &req) ||
334 !ndr_treat_deferred_read(rcontext, s))
335 goto out;
336 // ComputeTgsChecksumReq_dump(WLog_Get(""), WLOG_DEBUG, &req);
337
338 krb5_error_code rv =
339 kerb_do_checksum(rdpear->krbContext, req.Key, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM,
340 (krb5_cksumtype)req.ChecksumType, req.requestBody, &checksum);
341 if (rv)
342 goto out;
343
344 asn1Payload = rdpear_enc_Checksum(req.ChecksumType, &checksum);
345 if (!asn1Payload)
346 goto out;
347
348 resp->Pdu = 8;
349 resp->Asn1Buffer = Stream_Buffer(asn1Payload);
350 const size_t pos = Stream_GetPosition(asn1Payload);
351 if (pos > UINT32_MAX)
352 goto out;
353 resp->Asn1BufferHints.count = (UINT32)pos;
354 *pstatus = 0;
355
356out:
357 ndr_destroy_ComputeTgsChecksumReq(rcontext, nullptr, &req);
358 krb5_free_checksum_contents(rdpear->krbContext, &checksum);
359 Stream_Free(asn1Payload, FALSE);
360 return TRUE;
361}
362
363static BOOL rdpear_kerb_BuildEncryptedAuthData(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext,
364 wStream* s, UINT32* pstatus, KERB_ASN1_DATA* asn1)
365{
366 BuildEncryptedAuthDataReq req = WINPR_C_ARRAY_INIT;
367 krb5_data encrypted = WINPR_C_ARRAY_INIT;
368 wStream* asn1Payload = nullptr;
369 krb5_error_code rv = 0;
370
371 *pstatus = ERROR_INVALID_DATA;
372 WLog_DBG(TAG, "-> BuildEncryptedAuthData");
373
374 if (!ndr_read_BuildEncryptedAuthDataReq(rcontext, s, nullptr, &req) ||
375 !ndr_treat_deferred_read(rcontext, s))
376 goto out;
377
378 rv = kerb_do_encrypt(rdpear->krbContext, req.Key, (krb5_keyusage)req.KeyUsage,
379 req.PlainAuthData, &encrypted);
380 if (rv)
381 goto out;
382
383 /* do the encoding */
384 asn1Payload = rdpear_enc_EncryptedData(req.Key->reserved2, &encrypted);
385 if (!asn1Payload)
386 goto out;
387
388 // WLog_DBG(TAG, "rdpear_kerb_BuildEncryptedAuthData resp=");
389 // winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(asn1Payload), Stream_GetPosition(asn1Payload));
390 asn1->Pdu = 6;
391 asn1->Asn1Buffer = Stream_Buffer(asn1Payload);
392 const size_t pos = Stream_GetPosition(asn1Payload);
393 if (pos > UINT32_MAX)
394 goto out;
395 asn1->Asn1BufferHints.count = (UINT32)pos;
396 *pstatus = 0;
397
398out:
399 krb5_free_data_contents(rdpear->krbContext, &encrypted);
400 ndr_destroy_BuildEncryptedAuthDataReq(rcontext, nullptr, &req);
401 Stream_Free(asn1Payload, FALSE);
402 return TRUE;
403}
404
405static char* KERB_RPC_UNICODESTR_to_charptr(const RPC_UNICODE_STRING* src)
406{
407 WINPR_ASSERT(src);
408 return ConvertWCharNToUtf8Alloc(src->Buffer, src->strLength, nullptr);
409}
410
411static BOOL extractAuthData(const KERB_ASN1_DATA* src, krb5_authdata* authData, BOOL* haveData)
412{
413 WinPrAsn1Decoder dec = WinPrAsn1Decoder_init();
414 WinPrAsn1Decoder dec2 = WinPrAsn1Decoder_init();
415 WinPrAsn1Decoder dec3 = WinPrAsn1Decoder_init();
416 WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count);
417 BOOL error = FALSE;
418 WinPrAsn1_INTEGER adType = 0;
419 WinPrAsn1_OctetString os = WINPR_C_ARRAY_INIT;
420
421 *haveData = FALSE;
422 if (!WinPrAsn1DecReadSequence(&dec, &dec2))
423 return FALSE;
424
425 wStream subStream = WinPrAsn1DecGetStream(&dec2);
426 if (!Stream_GetRemainingLength(&subStream))
427 return TRUE;
428
429 if (!WinPrAsn1DecReadSequence(&dec2, &dec3))
430 return FALSE;
431
432 if (!WinPrAsn1DecReadContextualInteger(&dec3, 0, &error, &adType) ||
433 !WinPrAsn1DecReadContextualOctetString(&dec3, 1, &error, &os, FALSE))
434 return FALSE;
435
436 if (os.len > UINT32_MAX)
437 return FALSE;
438
439 authData->ad_type = adType;
440 authData->length = (unsigned int)os.len;
441 authData->contents = os.data;
442 *haveData = TRUE;
443 return TRUE;
444}
445
446static BOOL extractChecksum(const KERB_ASN1_DATA* src, krb5_checksum* dst)
447{
448 WinPrAsn1Decoder dec = WinPrAsn1Decoder_init();
449 WinPrAsn1Decoder dec2 = WinPrAsn1Decoder_init();
450 WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count);
451 BOOL error = FALSE;
453
454 if (!WinPrAsn1DecReadSequence(&dec, &dec2))
455 return FALSE;
456
457 WinPrAsn1_INTEGER cksumtype = 0;
458 if (!WinPrAsn1DecReadContextualInteger(&dec2, 0, &error, &cksumtype) ||
459 !WinPrAsn1DecReadContextualOctetString(&dec2, 1, &error, &os, FALSE))
460 return FALSE;
461
462 if (os.len > UINT32_MAX)
463 return FALSE;
464 dst->checksum_type = cksumtype;
465 dst->length = (unsigned int)os.len;
466 dst->contents = os.data;
467 return TRUE;
468}
469
470#define FILETIME_TO_UNIX_OFFSET_S 11644473600LL
471
472static LONGLONG krb5_time_to_FILETIME(const krb5_timestamp* ts, krb5_int32 usec)
473{
474 WINPR_ASSERT(ts);
475 return (((*ts + FILETIME_TO_UNIX_OFFSET_S) * (1000LL * 1000LL) + usec) * 10LL);
476}
477
478static void krb5_free_principal_contents(krb5_context ctx, krb5_principal principal)
479{
480 WINPR_ASSERT(principal);
481 krb5_free_data_contents(ctx, &principal->realm);
482 krb5_free_data(ctx, principal->data);
483}
484
485static BOOL rdpear_kerb_CreateApReqAuthenticator(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext,
486 wStream* s, UINT32* pstatus,
488{
489 krb5_error_code rv = 0;
490 wStream* asn1EncodedAuth = nullptr;
491 CreateApReqAuthenticatorReq req = WINPR_C_ARRAY_INIT;
492 krb5_data authenticator = WINPR_C_ARRAY_INIT;
493 krb5_data* der = nullptr;
494 krb5_keyblock* subkey = nullptr;
495 krb5_principal_data client = WINPR_C_ARRAY_INIT;
496
497 *pstatus = ERROR_INVALID_DATA;
498 WLog_DBG(TAG, "-> CreateApReqAuthenticator");
499
500 if (!ndr_read_CreateApReqAuthenticatorReq(rcontext, s, nullptr, &req) ||
501 !ndr_treat_deferred_read(rcontext, s))
502 goto out;
503
504 krb5_authdata authdata = WINPR_C_ARRAY_INIT;
505 krb5_authdata* authDataPtr[2] = { &authdata, nullptr };
506 BOOL haveData = 0;
507
508 if (!extractAuthData(req.AuthData, &authdata, &haveData))
509 {
510 WLog_ERR(TAG, "error retrieving auth data");
511 winpr_HexDump(TAG, WLOG_DEBUG, req.AuthData->Asn1Buffer,
512 req.AuthData->Asn1BufferHints.count);
513 goto out;
514 }
515
516 if (req.SkewTime->QuadPart)
517 {
518 WLog_ERR(TAG, "!!!!! should handle SkewTime !!!!!");
519 }
520
521 if (req.SubKey)
522 {
523 rv = RPC_ENCRYPTION_KEY_to_keyblock(rdpear->krbContext, req.SubKey, &subkey);
524 if (rv)
525 {
526 WLog_ERR(TAG, "error importing subkey");
527 goto out;
528 }
529 }
530
531 krb5_authenticator authent = { .checksum = nullptr,
532 .subkey = nullptr,
533 .seq_number = req.SequenceNumber,
534 .authorization_data = haveData ? authDataPtr : nullptr };
535
536 client.type = req.ClientName->NameType;
537 if (req.ClientName->nameHints.count > INT32_MAX)
538 goto out;
539
540 client.length = (krb5_int32)req.ClientName->nameHints.count;
541 client.data = calloc(req.ClientName->nameHints.count, sizeof(krb5_data));
542 if (!client.data)
543 goto out;
544
545 for (int i = 0; i < client.length; i++)
546 {
547 krb5_data* cur = &client.data[i];
548 cur->data = KERB_RPC_UNICODESTR_to_charptr(&req.ClientName->Names[i]);
549 if (!cur->data)
550 goto out;
551 const size_t len = strnlen(cur->data, MAX_KEYTAB_NAME_LEN + 1);
552 if (len > MAX_KEYTAB_NAME_LEN)
553 {
554 WLog_ERR(TAG,
555 "Invalid ClientName length %d, limited to %" PRIuz
556 " characters. ClientName: (%s)",
557 MAX_KEYTAB_NAME_LEN, len, cur->data);
558 goto out;
559 }
560 cur->length = (unsigned int)len;
561 }
562 client.realm.data = KERB_RPC_UNICODESTR_to_charptr(req.ClientRealm);
563 if (!client.realm.data)
564 goto out;
565
566 const size_t len = strnlen(client.realm.data, MAX_KEYTAB_NAME_LEN + 1);
567 if (len > MAX_KEYTAB_NAME_LEN)
568 {
569 WLog_ERR(TAG, "Invalid realm length %d, limited to %" PRIuz " characters. Realm: (%s)",
570 MAX_KEYTAB_NAME_LEN, len, client.realm.data);
571 goto out;
572 }
573 client.realm.length = (unsigned int)len;
574 authent.client = &client;
575
576 krb5_checksum checksum;
577 krb5_checksum* pchecksum = nullptr;
578 if (req.GssChecksum)
579 {
580 if (!extractChecksum(req.GssChecksum, &checksum))
581 {
582 WLog_ERR(TAG, "Error getting the checksum");
583 goto out;
584 }
585 pchecksum = &checksum;
586 }
587 authent.checksum = pchecksum;
588
589 krb5_us_timeofday(rdpear->krbContext, &authent.ctime, &authent.cusec);
590
591 rv = encode_krb5_authenticator(&authent, &der);
592 if (rv)
593 {
594 WLog_ERR(TAG, "error encoding authenticator");
595 goto out;
596 }
597
598 KERB_ASN1_DATA plain_authent = { .Pdu = 0,
599 .Asn1Buffer = (BYTE*)der->data,
600 .Asn1BufferHints = { .count = der->length } };
601
602 rv = kerb_do_encrypt(rdpear->krbContext, req.EncryptionKey, (krb5_keyusage)req.KeyUsage,
603 &plain_authent, &authenticator);
604 if (rv)
605 {
606 WLog_ERR(TAG, "error encrypting authenticator");
607 goto out;
608 }
609
610 asn1EncodedAuth = rdpear_enc_EncryptedData(req.EncryptionKey->reserved2, &authenticator);
611 if (!asn1EncodedAuth)
612 {
613 WLog_ERR(TAG, "error encoding to ASN1");
614 rv = ENOMEM;
615 goto out;
616 }
617
618 // WLog_DBG(TAG, "authenticator=");
619 // winpr_HexDump(TAG, WLOG_DEBUG, Stream_Buffer(asn1EncodedAuth),
620 // Stream_GetPosition(asn1EncodedAuth));
621
622 const size_t size = Stream_GetPosition(asn1EncodedAuth);
623 if (size > UINT32_MAX)
624 goto out;
625 resp->Authenticator.Asn1BufferHints.count = (UINT32)size;
626 resp->Authenticator.Asn1Buffer = Stream_Buffer(asn1EncodedAuth);
627 resp->AuthenticatorTime.QuadPart = krb5_time_to_FILETIME(&authent.ctime, authent.cusec);
628 *pstatus = 0;
629
630out:
631 resp->Authenticator.Pdu = 7;
632 resp->KerbProtocolError = rv;
633 krb5_free_principal_contents(rdpear->krbContext, &client);
634 krb5_free_data(rdpear->krbContext, der);
635 krb5_free_data_contents(rdpear->krbContext, &authenticator);
636 if (subkey)
637 krb5_free_keyblock(rdpear->krbContext, subkey);
638 ndr_destroy_CreateApReqAuthenticatorReq(rcontext, nullptr, &req);
639 Stream_Free(asn1EncodedAuth, FALSE);
640 return TRUE;
641}
642
643static BOOL rdpear_findEncryptedData(const KERB_ASN1_DATA* src, int* penctype, krb5_data* data)
644{
645 WinPrAsn1Decoder dec = WinPrAsn1Decoder_init();
646 WinPrAsn1Decoder dec2 = WinPrAsn1Decoder_init();
647 WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, src->Asn1Buffer, src->Asn1BufferHints.count);
648 BOOL error = FALSE;
649 WinPrAsn1_INTEGER encType = 0;
650 WinPrAsn1_OctetString os = WINPR_C_ARRAY_INIT;
651
652 if (!WinPrAsn1DecReadSequence(&dec, &dec2) ||
653 !WinPrAsn1DecReadContextualInteger(&dec2, 0, &error, &encType) ||
654 !WinPrAsn1DecReadContextualOctetString(&dec2, 2, &error, &os, FALSE))
655 return FALSE;
656
657 if (os.len > UINT32_MAX)
658 return FALSE;
659 data->data = (char*)os.data;
660 data->length = (unsigned int)os.len;
661 *penctype = encType;
662 return TRUE;
663}
664
665static BOOL rdpear_kerb_UnpackKdcReplyBody(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s,
666 UINT32* pstatus, UnpackKdcReplyBodyResp* resp)
667{
668 UnpackKdcReplyBodyReq req = WINPR_C_ARRAY_INIT;
669
670 *pstatus = ERROR_INVALID_DATA;
671
672 if (!ndr_read_UnpackKdcReplyBodyReq(rcontext, s, nullptr, &req) ||
673 !ndr_treat_deferred_read(rcontext, s))
674 goto out;
675
676 if (req.StrengthenKey)
677 {
678 WLog_ERR(TAG, "StrengthenKey not supported yet");
679 goto out;
680 }
681
682 WLog_DBG(TAG, "-> UnpackKdcReplyBody: KeyUsage=0x%x PDU=0x%x", req.KeyUsage, req.Pdu);
683 // WLog_DBG(TAG, "encryptedPayload=");
684 // winpr_HexDump(TAG, WLOG_DEBUG, req.EncryptedData->Asn1Buffer,
685 // req.EncryptedData->Asn1BufferHints.count);
686
687 krb5_data asn1Data = WINPR_C_ARRAY_INIT;
688 int encType = 0;
689 if (!rdpear_findEncryptedData(req.EncryptedData, &encType, &asn1Data) || !asn1Data.length)
690 goto out;
691
692 resp->KerbProtocolError = kerb_do_decrypt(
693 rdpear->krbContext, req.Key, (krb5_keyusage)req.KeyUsage, &asn1Data, &resp->ReplyBody);
694 resp->ReplyBody.Pdu = req.Pdu;
695
696 *pstatus = 0;
697
698out:
699 ndr_destroy_UnpackKdcReplyBodyReq(rcontext, nullptr, &req);
700 return TRUE;
701}
702
703static BOOL rdpear_kerb_DecryptApReply(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s,
704 UINT32* pstatus, KERB_ASN1_DATA* resp)
705{
706 DecryptApReplyReq req = WINPR_C_ARRAY_INIT;
707
708 *pstatus = ERROR_INVALID_DATA;
709 if (!ndr_read_DecryptApReplyReq(rcontext, s, nullptr, &req) ||
710 !ndr_treat_deferred_read(rcontext, s))
711 goto out;
712
713 WLog_DBG(TAG, "-> DecryptApReply");
714 // winpr_HexDump(TAG, WLOG_DEBUG, req.EncryptedReply->Asn1Buffer,
715 // req.EncryptedReply->Asn1BufferHints.count);
716
717 krb5_data asn1Data = WINPR_C_ARRAY_INIT;
718 int encType = 0;
719 if (!rdpear_findEncryptedData(req.EncryptedReply, &encType, &asn1Data) || !asn1Data.length)
720 goto out;
721
722 resp->Pdu = 0x31;
723 krb5_error_code rv =
724 kerb_do_decrypt(rdpear->krbContext, req.Key, KRB5_KEYUSAGE_AP_REP_ENCPART, &asn1Data, resp);
725 if (rv != 0)
726 {
727 WLog_ERR(TAG, "error decrypting");
728 goto out;
729 }
730
731 // WLog_DBG(TAG, "response=");
732 // winpr_HexDump(TAG, WLOG_DEBUG, resp->Asn1Buffer, resp->Asn1BufferHints.count);
733 *pstatus = 0;
734out:
735 ndr_destroy_DecryptApReplyReq(rcontext, nullptr, &req);
736 return TRUE;
737}
738
739static BOOL rdpear_kerb_PackApReply(RDPEAR_PLUGIN* rdpear, NdrContext* rcontext, wStream* s,
740 UINT32* pstatus, PackApReplyResp* resp)
741{
742 PackApReplyReq req = WINPR_C_ARRAY_INIT;
743 krb5_data asn1Data = WINPR_C_ARRAY_INIT;
744 krb5_data* out = nullptr;
745
746 WLog_DBG(TAG, "-> PackApReply");
747 *pstatus = ERROR_INVALID_DATA;
748 if (!ndr_read_PackApReplyReq(rcontext, s, nullptr, &req) ||
749 !ndr_treat_deferred_read(rcontext, s))
750 goto out;
751
752 krb5_error_code rv = kerb_do_encrypt(rdpear->krbContext, req.SessionKey,
753 KRB5_KEYUSAGE_AP_REP_ENCPART, req.ReplyBody, &asn1Data);
754 if (rv)
755 goto out;
756
757 krb5_ap_rep reply;
758 reply.enc_part.kvno = KRB5_PVNO;
759 reply.enc_part.enctype = (krb5_enctype)req.SessionKey->reserved2;
760 reply.enc_part.ciphertext.length = asn1Data.length;
761 reply.enc_part.ciphertext.data = asn1Data.data;
762
763 rv = encode_krb5_ap_rep(&reply, &out);
764 if (rv)
765 goto out;
766
767 resp->PackedReply = (BYTE*)out->data;
768 resp->PackedReplyHints.count = out->length;
769 *pstatus = 0;
770out:
771 free(out);
772 krb5_free_data_contents(rdpear->krbContext, &asn1Data);
773 ndr_destroy_PackApReplyReq(rcontext, nullptr, &req);
774 return TRUE;
775}
776
777static BOOL rdpear_ntlm_version(NdrContext* rcontext, wStream* s, UINT32* pstatus, UINT32* pversion)
778{
779 *pstatus = ERROR_INVALID_DATA;
780
781 if (!ndr_read_uint32(rcontext, s, pversion))
782 return TRUE;
783
784 WLog_DBG(TAG, "-> NtlmNegotiateVersion(v=0x%x)", *pversion);
785 *pstatus = 0;
786
787 return TRUE;
788}
789
790static UINT rdpear_decode_payload(RDPEAR_PLUGIN* rdpear,
791 IWTSVirtualChannelCallback* pChannelCallback,
792 const WinPrAsn1_OctetString* packageName, wStream* s)
793{
794 UINT ret = ERROR_INVALID_DATA;
795 NdrContext* context = nullptr;
796 NdrContext* wcontext = nullptr;
797 UINT32 status = 0;
798
799 UINT32 uint32Resp = 0;
800 KERB_ASN1_DATA asn1Data = WINPR_C_ARRAY_INIT;
801 CreateApReqAuthenticatorResp createApReqAuthenticatorResp = WINPR_C_ARRAY_INIT;
802 UnpackKdcReplyBodyResp unpackKdcReplyBodyResp = WINPR_C_ARRAY_INIT;
803 PackApReplyResp packApReplyResp = WINPR_C_ARRAY_INIT;
804 void* resp = nullptr;
805 NdrMessageType respDescr = nullptr;
806
807 wStream* respStream = Stream_New(nullptr, 500);
808 if (!respStream)
809 goto out;
810
811 BOOL isKerb = FALSE;
812 switch (rdpear_packageType_from_name(packageName))
813 {
814 case RDPEAR_PACKAGE_KERBEROS:
815 isKerb = TRUE;
816 break;
817 case RDPEAR_PACKAGE_NTLM:
818 isKerb = FALSE;
819 break;
820 default:
821 WLog_ERR(TAG, "unknown package type");
822 goto out;
823 }
824
825 Stream_Seek(s, 16); /* skip first 16 bytes */
826 wStream commandStream = WINPR_C_ARRAY_INIT;
827 UINT16 callId = 0;
828 UINT16 callId2 = 0;
829
830 context = ndr_read_header(s);
831 if (!context || !ndr_read_constructed(context, s, &commandStream) ||
832 !ndr_read_pickle(context, &commandStream))
833 goto out;
834
835 if (isKerb)
836 {
837 /* for some reason there's 4 zero undocumented bytes here after the pickle record
838 * in the kerberos package packets */
839 UINT32 v = 0;
840 if (!ndr_read_uint32(context, &commandStream, &v))
841 goto out;
842 }
843
844 if (!ndr_read_uint16(context, &commandStream, &callId) ||
845 !ndr_read_uint16(context, &commandStream, &callId2) || (callId != callId2))
846 goto out;
847
848 ret = CHANNEL_RC_NOT_OPEN;
849 switch (callId)
850 {
851 case RemoteCallKerbNegotiateVersion:
852 resp = &uint32Resp;
853 respDescr = ndr_uint32_descr();
854
855 if (rdpear_kerb_version(context, &commandStream, &status, &uint32Resp))
856 ret = CHANNEL_RC_OK;
857 break;
858 case RemoteCallKerbCreateApReqAuthenticator:
859 resp = &createApReqAuthenticatorResp;
860 respDescr = ndr_CreateApReqAuthenticatorResp_descr();
861
862 if (rdpear_kerb_CreateApReqAuthenticator(rdpear, context, &commandStream, &status,
863 &createApReqAuthenticatorResp))
864 ret = CHANNEL_RC_OK;
865 break;
866 case RemoteCallKerbDecryptApReply:
867 resp = &asn1Data;
868 respDescr = ndr_KERB_ASN1_DATA_descr();
869
870 if (rdpear_kerb_DecryptApReply(rdpear, context, &commandStream, &status, &asn1Data))
871 ret = CHANNEL_RC_OK;
872 break;
873 case RemoteCallKerbComputeTgsChecksum:
874 resp = &asn1Data;
875 respDescr = ndr_KERB_ASN1_DATA_descr();
876
877 if (rdpear_kerb_ComputeTgsChecksum(rdpear, context, &commandStream, &status, &asn1Data))
878 ret = CHANNEL_RC_OK;
879 break;
880 case RemoteCallKerbBuildEncryptedAuthData:
881 resp = &asn1Data;
882 respDescr = ndr_KERB_ASN1_DATA_descr();
883
884 if (rdpear_kerb_BuildEncryptedAuthData(rdpear, context, &commandStream, &status,
885 &asn1Data))
886 ret = CHANNEL_RC_OK;
887 break;
888 case RemoteCallKerbUnpackKdcReplyBody:
889 resp = &unpackKdcReplyBodyResp;
890 respDescr = ndr_UnpackKdcReplyBodyResp_descr();
891
892 if (rdpear_kerb_UnpackKdcReplyBody(rdpear, context, &commandStream, &status,
893 &unpackKdcReplyBodyResp))
894 ret = CHANNEL_RC_OK;
895 break;
896 case RemoteCallKerbPackApReply:
897 resp = &packApReplyResp;
898 respDescr = ndr_PackApReplyResp_descr();
899
900 if (rdpear_kerb_PackApReply(rdpear, context, &commandStream, &status, &packApReplyResp))
901 ret = CHANNEL_RC_OK;
902 break;
903 case RemoteCallNtlmNegotiateVersion:
904 resp = &uint32Resp;
905 respDescr = ndr_uint32_descr();
906
907 if (rdpear_ntlm_version(context, &commandStream, &status, &uint32Resp))
908 ret = CHANNEL_RC_OK;
909 break;
910
911 default:
912 WLog_DBG(TAG, "Unhandled callId=0x%x", callId);
913 winpr_HexDump(TAG, WLOG_DEBUG, Stream_PointerAs(&commandStream, BYTE),
914 Stream_GetRemainingLength(&commandStream));
915 break;
916 }
917
918 if (!rdpear_prepare_response(context, isKerb, callId, status, &wcontext, respStream))
919 goto out;
920
921 if (resp && respDescr)
922 {
923 WINPR_ASSERT(respDescr->writeFn);
924
925 BOOL r = respDescr->writeFn(wcontext, respStream, nullptr, resp) &&
926 ndr_treat_deferred_write(wcontext, respStream);
927
928 if (respDescr->destroyFn)
929 respDescr->destroyFn(wcontext, nullptr, resp);
930
931 if (!r)
932 {
933 WLog_DBG(TAG, "!writeFn || !ndr_treat_deferred_write");
934 goto out;
935 }
936 }
937
938 if (!ndr_end_constructed(wcontext, respStream) ||
939 !rdpear_send_payload(rdpear, pChannelCallback, isKerb, respStream))
940 {
941 WLog_DBG(TAG, "rdpear_send_payload !!!!!!!!");
942 goto out;
943 }
944out:
945 if (context)
946 ndr_context_destroy(&context);
947
948 if (wcontext)
949 ndr_context_destroy(&wcontext);
950
951 if (respStream)
952 Stream_Free(respStream, TRUE);
953 return ret;
954}
955
956static UINT rdpear_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* s)
957{
958 GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
959 WINPR_ASSERT(callback);
960 UINT ret = ERROR_INVALID_DATA;
961
962 // winpr_HexDump(TAG, WLOG_DEBUG, Stream_PointerAs(s, BYTE), Stream_GetRemainingLength(s));
963
964 if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
965 return ERROR_INVALID_DATA;
966
967 UINT32 protocolMagic = 0;
968 UINT32 Length = 0;
969 UINT32 Version = 0;
970 Stream_Read_UINT32(s, protocolMagic);
971 if (protocolMagic != 0x4EACC3C8)
972 return ERROR_INVALID_DATA;
973
974 Stream_Read_UINT32(s, Length);
975
976 Stream_Read_UINT32(s, Version);
977 if (Version != 0x00000000)
978 return ERROR_INVALID_DATA;
979
980 Stream_Seek(s, 4); /* Reserved (4 bytes) */
981 Stream_Seek(s, 8); /* TsPkgContext (8 bytes) */
982
983 if (!Stream_CheckAndLogRequiredLength(TAG, s, Length))
984 return ERROR_INVALID_DATA;
985
986 SecBuffer inBuffer = { Length, SECBUFFER_TOKEN, Stream_PointerAs(s, void) };
987 SecBuffer decrypted = WINPR_C_ARRAY_INIT;
988
989 RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)callback->plugin;
990 WINPR_ASSERT(rdpear);
991 if (!freerdp_nla_decrypt(rdpear->rdp_context, &inBuffer, &decrypted))
992 goto out;
993
994 WinPrAsn1Decoder dec = WinPrAsn1Decoder_init();
995 WinPrAsn1Decoder dec2 = WinPrAsn1Decoder_init();
996 wStream decodedStream = WINPR_C_ARRAY_INIT;
997 Stream_StaticInit(&decodedStream, decrypted.pvBuffer, decrypted.cbBuffer);
998 WinPrAsn1Decoder_Init(&dec, WINPR_ASN1_DER, &decodedStream);
999
1000 if (!WinPrAsn1DecReadSequence(&dec, &dec2))
1001 goto out;
1002
1003 WinPrAsn1_OctetString packageName = WINPR_C_ARRAY_INIT;
1004 WinPrAsn1_OctetString payload = WINPR_C_ARRAY_INIT;
1005 BOOL error = 0;
1006 if (!WinPrAsn1DecReadContextualOctetString(&dec2, 1, &error, &packageName, FALSE))
1007 goto out;
1008
1009 if (!WinPrAsn1DecReadContextualOctetString(&dec2, 2, &error, &payload, FALSE))
1010 goto out;
1011
1012 wStream payloadStream = WINPR_C_ARRAY_INIT;
1013 Stream_StaticInit(&payloadStream, payload.data, payload.len);
1014
1015 ret = rdpear_decode_payload(rdpear, pChannelCallback, &packageName, &payloadStream);
1016out:
1017 sspi_SecBufferFree(&decrypted);
1018 return ret;
1019}
1020
1026static UINT rdpear_on_open(IWTSVirtualChannelCallback* pChannelCallback)
1027{
1028 WINPR_UNUSED(pChannelCallback);
1029 return CHANNEL_RC_OK;
1030}
1031
1037static UINT rdpear_on_close(IWTSVirtualChannelCallback* pChannelCallback)
1038{
1039 WINPR_UNUSED(pChannelCallback);
1040 return CHANNEL_RC_OK;
1041}
1042
1043static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base)
1044{
1045 WINPR_ASSERT(base);
1046
1047 RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)base;
1048 krb5_free_context(rdpear->krbContext);
1049}
1050
1051static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings)
1052{
1053 WINPR_ASSERT(base);
1054 WINPR_UNUSED(settings);
1055
1056 RDPEAR_PLUGIN* rdpear = (RDPEAR_PLUGIN*)base;
1057 rdpear->rdp_context = rcontext;
1058 if (krb5_init_context(&rdpear->krbContext))
1059 return CHANNEL_RC_INITIALIZATION_ERROR;
1060 return CHANNEL_RC_OK;
1061}
1062
1063static const IWTSVirtualChannelCallback rdpear_callbacks = { rdpear_on_data_received,
1064 rdpear_on_open, rdpear_on_close,
1065 nullptr };
1066
1072FREERDP_ENTRY_POINT(UINT rdpear_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
1073{
1074 return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, RDPEAR_DVC_CHANNEL_NAME,
1075 sizeof(RDPEAR_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
1076 &rdpear_callbacks, init_plugin_cb, terminate_plugin_cb);
1077}
2.2.2.1.8 BuildEncryptedAuthData
2.2.2.1.7 ComputeTgsChecksum
2.2.2.1.4 CreateApReqAuthenticator
2.2.2.1.4 CreateApReqAuthenticator
2.2.1.2.1 KERB_ASN1_DATA
2.2.1.2.8 KERB_RPC_ENCRYPTION_KEY
message descriptor
Definition ndr.h:84
2.3.10 RPC_UNICODE_STRING (MS-DTYP)
2.2.2.1.6 UnpackKdcReplyBody
2.2.2.1.6 UnpackKdcReplyBody