mirror of
https://github.com/FreeRDP/FreeRDP.git
synced 2024-11-27 20:04:11 +08:00
Merge pull request #4499 from mfleisz/cssp_v6
cssp: Add support for protocol version 6
This commit is contained in:
commit
de83f4df21
@ -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;
|
||||
}
|
||||
|
||||
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 */
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
if (nla->version < 3 || nla->errorCode == 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;
|
||||
}
|
||||
else
|
||||
client_nonce_length = (nla->ClientNonce.cbBuffer > 0) ? nla_sizeof_client_nonce(
|
||||
nla->ClientNonce.cbBuffer) : 0;
|
||||
|
||||
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);
|
||||
|
@ -83,6 +83,7 @@ struct rdp_nla
|
||||
SecBuffer negoToken;
|
||||
SecBuffer pubKeyAuth;
|
||||
SecBuffer authInfo;
|
||||
SecBuffer ClientNonce;
|
||||
SecBuffer PublicKey;
|
||||
SecBuffer tsCredentials;
|
||||
LPTSTR ServicePrincipalName;
|
||||
|
@ -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
|
||||
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user