From 8df96364f2d556d3dc34241de27fdb89c0d5bb42 Mon Sep 17 00:00:00 2001 From: Martin Fleisz Date: Tue, 20 Mar 2018 09:57:30 +0100 Subject: [PATCH] cssp: Add support for protocol version 6 --- libfreerdp/core/nla.c | 281 ++++++++++++++++++++++++++++++++++++++++--- libfreerdp/core/nla.h | 1 + winpr/include/winpr/crypto.h | 1 + 3 files changed, 265 insertions(+), 18 deletions(-) diff --git a/libfreerdp/core/nla.c b/libfreerdp/core/nla.c index cec1777..e601c2e 100644 --- a/libfreerdp/core/nla.c +++ b/libfreerdp/core/nla.c @@ -104,7 +104,9 @@ static int nla_recv(rdpNla* nla); static void nla_buffer_print(rdpNla* nla); static void nla_buffer_free(rdpNla* nla); static SECURITY_STATUS nla_encrypt_public_key_echo(rdpNla* nla); +static SECURITY_STATUS nla_encrypt_public_key_hash(rdpNla* nla); static SECURITY_STATUS nla_decrypt_public_key_echo(rdpNla* nla); +static SECURITY_STATUS nla_decrypt_public_key_hash(rdpNla* nla); static SECURITY_STATUS nla_encrypt_ts_credentials(rdpNla* nla); static SECURITY_STATUS nla_decrypt_ts_credentials(rdpNla* nla); static BOOL nla_read_ts_password_creds(rdpNla* nla, wStream* s); @@ -113,6 +115,10 @@ static void nla_identity_free(SEC_WINNT_AUTH_IDENTITY* identity); #define ber_sizeof_sequence_octet_string(length) ber_sizeof_contextual_tag(ber_sizeof_octet_string(length)) + ber_sizeof_octet_string(length) #define ber_write_sequence_octet_string(stream, context, value, length) ber_write_contextual_tag(stream, context, ber_sizeof_octet_string(length), TRUE) + ber_write_octet_string(stream, value, length) +static const CHAR ClientServerHashMagic[] = "CredSSP Client-To-Server Binding Hash\0"; +static const CHAR ServerClientHashMagic[] = "CredSSP Server-To-Client Binding Hash\0"; +static const UINT32 NonceLength = 32; + void nla_identity_free(SEC_WINNT_AUTH_IDENTITY* identity) { if (identity) @@ -511,7 +517,10 @@ static int nla_client_recv(rdpNla* nla) return -1; } - nla->status = nla_encrypt_public_key_echo(nla); + if (nla->version < 5) + nla->status = nla_encrypt_public_key_echo(nla); + else + nla->status = nla_encrypt_public_key_hash(nla); if (nla->status != SEC_E_OK) return -1; @@ -538,7 +547,11 @@ static int nla_client_recv(rdpNla* nla) else if (nla->state == NLA_STATE_PUB_KEY_AUTH) { /* Verify Server Public Key Echo */ - nla->status = nla_decrypt_public_key_echo(nla); + if (nla->version < 5) + nla->status = nla_decrypt_public_key_echo(nla); + else + nla->status = nla_decrypt_public_key_hash(nla); + nla_buffer_free(nla); if (nla->status != SEC_E_OK) @@ -856,7 +869,10 @@ static int nla_server_authenticate(rdpNla* nla) return -1; } - nla->status = nla_decrypt_public_key_echo(nla); + if (nla->version < 5) + nla->status = nla_decrypt_public_key_echo(nla); + else + nla->status = nla_decrypt_public_key_hash(nla); if (nla->status != SEC_E_OK) { @@ -868,7 +884,11 @@ static int nla_server_authenticate(rdpNla* nla) sspi_SecBufferFree(&nla->negoToken); nla->negoToken.pvBuffer = NULL; nla->negoToken.cbBuffer = 0; - nla->status = nla_encrypt_public_key_echo(nla); + + if (nla->version < 5) + nla->status = nla_encrypt_public_key_echo(nla); + else + nla->status = nla_encrypt_public_key_hash(nla); if (nla->status != SEC_E_OK) return -1; @@ -1073,6 +1093,93 @@ SECURITY_STATUS nla_encrypt_public_key_echo(rdpNla* nla) return status; } +SECURITY_STATUS nla_encrypt_public_key_hash(rdpNla* nla) +{ + SecBuffer Buffers[2] = { { 0 } }; + SecBufferDesc Message; + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + WINPR_DIGEST_CTX* sha256 = NULL; + const BOOL krb = (_tcsncmp(nla->packageName, KERBEROS_SSP_NAME, ARRAYSIZE(KERBEROS_SSP_NAME)) == 0); + const ULONG auth_data_length = krb ? WINPR_SHA256_DIGEST_LENGTH : + (nla->ContextSizes.cbSecurityTrailer + + WINPR_SHA256_DIGEST_LENGTH); + const CHAR* hashMagic = nla->server ? ServerClientHashMagic : ClientServerHashMagic; + + if (!sspi_SecBufferAlloc(&nla->ClientNonce, NonceLength)) + { + status = SEC_E_INSUFFICIENT_MEMORY; + goto out; + } + + if (!sspi_SecBufferAlloc(&nla->pubKeyAuth, auth_data_length)) + { + status = SEC_E_INSUFFICIENT_MEMORY; + goto out; + } + + /* generate random 32-byte nonce */ + if (winpr_RAND(nla->ClientNonce.pvBuffer, NonceLength) < 0) + goto out; + + /* generate SHA256 of following data: ClientServerHashMagic, Nonce, SubjectPublicKey */ + if (!(sha256 = winpr_Digest_New())) + goto out; + + if (!winpr_Digest_Init(sha256, WINPR_MD_SHA256)) + goto out; + + /* include trailing \0 from hashMagic */ + if (!winpr_Digest_Update(sha256, hashMagic, strlen(hashMagic) + 1)) + goto out; + + if (!winpr_Digest_Update(sha256, nla->ClientNonce.pvBuffer, nla->ClientNonce.cbBuffer)) + goto out; + + /* SubjectPublicKey */ + if (!winpr_Digest_Update(sha256, nla->PublicKey.pvBuffer, nla->PublicKey.cbBuffer)) + goto out; + + Message.pBuffers = (PSecBuffer)&Buffers; + Message.ulVersion = SECBUFFER_VERSION; + + if (krb) + { + Message.cBuffers = 1; + Buffers[0].BufferType = SECBUFFER_DATA; /* SHA256 hash */ + Buffers[0].cbBuffer = WINPR_SHA256_DIGEST_LENGTH; + Buffers[0].pvBuffer = nla->pubKeyAuth.pvBuffer; + + if (!winpr_Digest_Final(sha256, Buffers[0].pvBuffer, Buffers[0].cbBuffer)) + goto out; + } + else + { + Message.cBuffers = 2; + Buffers[0].BufferType = SECBUFFER_TOKEN; /* Signature */ + Buffers[0].cbBuffer = nla->ContextSizes.cbSecurityTrailer; + Buffers[0].pvBuffer = nla->pubKeyAuth.pvBuffer; + Buffers[1].BufferType = SECBUFFER_DATA; /* SHA256 hash */ + Buffers[1].cbBuffer = WINPR_SHA256_DIGEST_LENGTH; + Buffers[1].pvBuffer = ((BYTE*)nla->pubKeyAuth.pvBuffer) + nla->ContextSizes.cbSecurityTrailer; + + if (!winpr_Digest_Final(sha256, Buffers[1].pvBuffer, Buffers[1].cbBuffer)) + goto out; + } + + /* encrypt message */ + status = nla->table->EncryptMessage(&nla->context, 0, &Message, nla->sendSeqNum++); + + if (status != SEC_E_OK) + { + WLog_ERR(TAG, "EncryptMessage status %s [0x%08"PRIX32"]", + GetSecurityStatusString(status), status); + } + +out: + winpr_Digest_Free(sha256); + return status; +} + SECURITY_STATUS nla_decrypt_public_key_echo(rdpNla* nla) { size_t length; @@ -1184,6 +1291,111 @@ fail: return status; } +SECURITY_STATUS nla_decrypt_public_key_hash(rdpNla* nla) +{ + unsigned long length; + BYTE* buffer = NULL; + ULONG pfQOP = 0; + int signature_length; + SecBuffer Buffers[2] = { { 0 } }; + SecBufferDesc Message; + WINPR_DIGEST_CTX* sha256 = NULL; + BYTE serverClientHash[WINPR_SHA256_DIGEST_LENGTH]; + SECURITY_STATUS status = SEC_E_INVALID_TOKEN; + const BOOL krb = (_tcsncmp(nla->packageName, KERBEROS_SSP_NAME, ARRAYSIZE(KERBEROS_SSP_NAME)) == 0); + const CHAR* hashMagic = nla->server ? ClientServerHashMagic : ServerClientHashMagic; + signature_length = nla->pubKeyAuth.cbBuffer - WINPR_SHA256_DIGEST_LENGTH; + + if ((signature_length < 0) || (signature_length > (int)nla->ContextSizes.cbSecurityTrailer)) + { + WLog_ERR(TAG, "unexpected pubKeyAuth buffer size: %"PRIu32"", nla->pubKeyAuth.cbBuffer); + goto fail; + } + + if ((nla->ContextSizes.cbSecurityTrailer + WINPR_SHA256_DIGEST_LENGTH) != nla->pubKeyAuth.cbBuffer) + { + WLog_ERR(TAG, "unexpected pubKeyAuth buffer size: %"PRIu32"", (int)nla->pubKeyAuth.cbBuffer); + goto fail; + } + + length = nla->pubKeyAuth.cbBuffer; + buffer = (BYTE*)malloc(length); + + if (!buffer) + { + status = SEC_E_INSUFFICIENT_MEMORY; + goto fail; + } + + if (krb) + { + CopyMemory(buffer, nla->pubKeyAuth.pvBuffer, length); + Buffers[0].BufferType = SECBUFFER_DATA; /* Encrypted Hash */ + Buffers[0].cbBuffer = length; + Buffers[0].pvBuffer = buffer; + Message.cBuffers = 1; + Message.ulVersion = SECBUFFER_VERSION; + Message.pBuffers = (PSecBuffer)&Buffers; + } + else + { + CopyMemory(buffer, nla->pubKeyAuth.pvBuffer, length); + Buffers[0].BufferType = SECBUFFER_TOKEN; /* Signature */ + Buffers[0].cbBuffer = signature_length; + Buffers[0].pvBuffer = buffer; + Buffers[1].BufferType = SECBUFFER_DATA; /* Encrypted Hash */ + Buffers[1].cbBuffer = WINPR_SHA256_DIGEST_LENGTH; + Buffers[1].pvBuffer = buffer + signature_length; + Message.cBuffers = 2; + Message.ulVersion = SECBUFFER_VERSION; + Message.pBuffers = (PSecBuffer)&Buffers; + } + + status = nla->table->DecryptMessage(&nla->context, &Message, nla->recvSeqNum++, &pfQOP); + + if (status != SEC_E_OK) + { + WLog_ERR(TAG, "DecryptMessage failure %s [%08"PRIX32"]", + GetSecurityStatusString(status), status); + goto fail; + } + + /* generate SHA256 of following data: ServerClientHashMagic, Nonce, SubjectPublicKey */ + if (!(sha256 = winpr_Digest_New())) + goto fail; + + if (!winpr_Digest_Init(sha256, WINPR_MD_SHA256)) + goto fail; + + /* include trailing \0 from hashMagic */ + if (!winpr_Digest_Update(sha256, hashMagic, strlen(hashMagic) + 1)) + goto fail; + + if (!winpr_Digest_Update(sha256, nla->ClientNonce.pvBuffer, nla->ClientNonce.cbBuffer)) + goto fail; + + /* SubjectPublicKey */ + if (!winpr_Digest_Update(sha256, nla->PublicKey.pvBuffer, nla->PublicKey.cbBuffer)) + goto fail; + + if (!winpr_Digest_Final(sha256, serverClientHash, sizeof(serverClientHash))) + goto fail; + + /* verify hash */ + if (memcmp(serverClientHash, Buffers[krb ? 0 : 1].pvBuffer, WINPR_SHA256_DIGEST_LENGTH) != 0) + { + WLog_ERR(TAG, "Could not verify server's hash"); + status = SEC_E_MESSAGE_ALTERED; /* DO NOT SEND CREDENTIALS! */ + goto fail; + } + + status = SEC_E_OK; +fail: + free(buffer); + winpr_Digest_Free(sha256); + return status; +} + static size_t nla_sizeof_ts_password_creds(rdpNla* nla) { size_t length = 0; @@ -1587,6 +1799,13 @@ static size_t nla_sizeof_auth_info(size_t length) return length; } +static size_t nla_sizeof_client_nonce(size_t length) +{ + length = ber_sizeof_octet_string(length); + length += ber_sizeof_contextual_tag(length); + return length; +} + static size_t nla_sizeof_ts_request(size_t length) { length += ber_sizeof_integer(2); @@ -1609,23 +1828,23 @@ BOOL nla_send(rdpNla* nla) size_t auth_info_length = 0; size_t error_code_context_length = 0; size_t error_code_length = 0; + size_t client_nonce_length = 0; + nego_tokens_length = (nla->negoToken.cbBuffer > 0) ? nla_sizeof_nego_tokens( + nla->negoToken.cbBuffer) : 0; + pub_key_auth_length = (nla->pubKeyAuth.cbBuffer > 0) ? nla_sizeof_pub_key_auth( + nla->pubKeyAuth.cbBuffer) : 0; + auth_info_length = (nla->authInfo.cbBuffer > 0) ? nla_sizeof_auth_info(nla->authInfo.cbBuffer) : 0; + client_nonce_length = (nla->ClientNonce.cbBuffer > 0) ? nla_sizeof_client_nonce( + nla->ClientNonce.cbBuffer) : 0; - if (nla->version < 3 || nla->errorCode == 0) - { - nego_tokens_length = (nla->negoToken.cbBuffer > 0) ? nla_sizeof_nego_tokens( - nla->negoToken.cbBuffer) : 0; - pub_key_auth_length = (nla->pubKeyAuth.cbBuffer > 0) ? nla_sizeof_pub_key_auth( - nla->pubKeyAuth.cbBuffer) : 0; - auth_info_length = (nla->authInfo.cbBuffer > 0) ? nla_sizeof_auth_info(nla->authInfo.cbBuffer) : 0; - } - else + if (nla->version >= 3 && nla->version != 5 && nla->errorCode != 0) { error_code_length = ber_sizeof_integer(nla->errorCode); error_code_context_length = ber_sizeof_contextual_tag(error_code_length); } length = nego_tokens_length + pub_key_auth_length + auth_info_length + error_code_context_length + - error_code_length; + error_code_length + client_nonce_length; ts_request_length = nla_sizeof_ts_request(length); s = Stream_New(NULL, ber_sizeof_sequence(ts_request_length)); @@ -1682,6 +1901,14 @@ BOOL nla_send(rdpNla* nla) ber_write_integer(s, nla->errorCode); } + /* [5] clientNonce (OCTET STRING) */ + if (client_nonce_length > 0) + { + if (ber_write_sequence_octet_string(s, 5, nla->ClientNonce.pvBuffer, + nla->ClientNonce.cbBuffer) != client_nonce_length) + return FALSE; + } + Stream_SealLength(s); transport_write(nla->transport, s); Stream_Free(s, TRUE); @@ -1711,7 +1938,7 @@ static int nla_decode_ts_request(rdpNla* nla, wStream* s) !ber_read_sequence_tag(s, &length) || /* NegoDataItem */ !ber_read_contextual_tag(s, 0, &length, TRUE) || /* [0] negoToken */ !ber_read_octet_string_tag(s, &length) || /* OCTET STRING */ - ((int) Stream_GetRemainingLength(s)) < length) + Stream_GetRemainingLength(s) < length) { return -1; } @@ -1727,7 +1954,7 @@ static int nla_decode_ts_request(rdpNla* nla, wStream* s) if (ber_read_contextual_tag(s, 2, &length, TRUE) != FALSE) { if (!ber_read_octet_string_tag(s, &length) || /* OCTET STRING */ - ((int) Stream_GetRemainingLength(s)) < length) + Stream_GetRemainingLength(s) < length) return -1; if (!sspi_SecBufferAlloc(&nla->authInfo, length)) @@ -1741,7 +1968,7 @@ static int nla_decode_ts_request(rdpNla* nla, wStream* s) if (ber_read_contextual_tag(s, 3, &length, TRUE) != FALSE) { if (!ber_read_octet_string_tag(s, &length) || /* OCTET STRING */ - ((int) Stream_GetRemainingLength(s)) < length) + Stream_GetRemainingLength(s) < length) return -1; if (!sspi_SecBufferAlloc(&nla->pubKeyAuth, length)) @@ -1759,6 +1986,22 @@ static int nla_decode_ts_request(rdpNla* nla, wStream* s) if (!ber_read_integer(s, &nla->errorCode)) return -1; } + + if (nla->version >= 5) + { + if (ber_read_contextual_tag(s, 5, &length, TRUE) != FALSE) + { + if (!ber_read_octet_string_tag(s, &length) || /* OCTET STRING */ + Stream_GetRemainingLength(s) < length) + return -1; + + if (!sspi_SecBufferAlloc(&nla->ClientNonce, length)) + return -1; + + Stream_Read(s, nla->ClientNonce.pvBuffer, length); + nla->ClientNonce.cbBuffer = length; + } + } } return 1; @@ -1981,7 +2224,7 @@ rdpNla* nla_new(freerdp* instance, rdpTransport* transport, rdpSettings* setting nla->transport = transport; nla->sendSeqNum = 0; nla->recvSeqNum = 0; - nla->version = 3; + nla->version = 6; if (settings->NtlmSamFile) { @@ -1998,6 +2241,7 @@ rdpNla* nla_new(freerdp* instance, rdpTransport* transport, rdpSettings* setting ZeroMemory(&nla->negoToken, sizeof(SecBuffer)); ZeroMemory(&nla->pubKeyAuth, sizeof(SecBuffer)); ZeroMemory(&nla->authInfo, sizeof(SecBuffer)); + ZeroMemory(&nla->ClientNonce, sizeof(SecBuffer)); SecInvalidateHandle(&nla->context); if (nla->server) @@ -2079,6 +2323,7 @@ void nla_free(rdpNla* nla) free(nla->SamFile); nla->SamFile = NULL; + sspi_SecBufferFree(&nla->ClientNonce); sspi_SecBufferFree(&nla->PublicKey); sspi_SecBufferFree(&nla->tsCredentials); free(nla->ServicePrincipalName); diff --git a/libfreerdp/core/nla.h b/libfreerdp/core/nla.h index 60fc136..a6b3a78 100644 --- a/libfreerdp/core/nla.h +++ b/libfreerdp/core/nla.h @@ -83,6 +83,7 @@ struct rdp_nla SecBuffer negoToken; SecBuffer pubKeyAuth; SecBuffer authInfo; + SecBuffer ClientNonce; SecBuffer PublicKey; SecBuffer tsCredentials; LPTSTR ServicePrincipalName; diff --git a/winpr/include/winpr/crypto.h b/winpr/include/winpr/crypto.h index 3112143..b674960 100644 --- a/winpr/include/winpr/crypto.h +++ b/winpr/include/winpr/crypto.h @@ -617,6 +617,7 @@ BOOL CryptBinaryToStringA(CONST BYTE* pbBinary, DWORD cbBinary, DWORD dwFlags, L #define WINPR_MD4_DIGEST_LENGTH 16 #define WINPR_MD5_DIGEST_LENGTH 16 #define WINPR_SHA1_DIGEST_LENGTH 20 +#define WINPR_SHA256_DIGEST_LENGTH 32 /** -- 2.7.4