Merge pull request #4499 from mfleisz/cssp_v6

cssp: Add support for protocol version 6
This commit is contained in:
akallabeth 2018-03-20 11:02:40 +01:00 committed by GitHub
commit de83f4df21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 265 additions and 18 deletions

View File

@ -104,7 +104,9 @@ static int nla_recv(rdpNla* nla);
static void nla_buffer_print(rdpNla* nla); static void nla_buffer_print(rdpNla* nla);
static void nla_buffer_free(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_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_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_encrypt_ts_credentials(rdpNla* nla);
static SECURITY_STATUS nla_decrypt_ts_credentials(rdpNla* nla); static SECURITY_STATUS nla_decrypt_ts_credentials(rdpNla* nla);
static BOOL nla_read_ts_password_creds(rdpNla* nla, wStream* s); 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_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) #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) void nla_identity_free(SEC_WINNT_AUTH_IDENTITY* identity)
{ {
if (identity) if (identity)
@ -511,7 +517,10 @@ static int nla_client_recv(rdpNla* nla)
return -1; return -1;
} }
if (nla->version < 5)
nla->status = nla_encrypt_public_key_echo(nla); nla->status = nla_encrypt_public_key_echo(nla);
else
nla->status = nla_encrypt_public_key_hash(nla);
if (nla->status != SEC_E_OK) if (nla->status != SEC_E_OK)
return -1; return -1;
@ -538,7 +547,11 @@ static int nla_client_recv(rdpNla* nla)
else if (nla->state == NLA_STATE_PUB_KEY_AUTH) else if (nla->state == NLA_STATE_PUB_KEY_AUTH)
{ {
/* Verify Server Public Key Echo */ /* Verify Server Public Key Echo */
if (nla->version < 5)
nla->status = nla_decrypt_public_key_echo(nla); nla->status = nla_decrypt_public_key_echo(nla);
else
nla->status = nla_decrypt_public_key_hash(nla);
nla_buffer_free(nla); nla_buffer_free(nla);
if (nla->status != SEC_E_OK) if (nla->status != SEC_E_OK)
@ -856,7 +869,10 @@ static int nla_server_authenticate(rdpNla* nla)
return -1; return -1;
} }
if (nla->version < 5)
nla->status = nla_decrypt_public_key_echo(nla); nla->status = nla_decrypt_public_key_echo(nla);
else
nla->status = nla_decrypt_public_key_hash(nla);
if (nla->status != SEC_E_OK) if (nla->status != SEC_E_OK)
{ {
@ -868,7 +884,11 @@ static int nla_server_authenticate(rdpNla* nla)
sspi_SecBufferFree(&nla->negoToken); sspi_SecBufferFree(&nla->negoToken);
nla->negoToken.pvBuffer = NULL; nla->negoToken.pvBuffer = NULL;
nla->negoToken.cbBuffer = 0; nla->negoToken.cbBuffer = 0;
if (nla->version < 5)
nla->status = nla_encrypt_public_key_echo(nla); nla->status = nla_encrypt_public_key_echo(nla);
else
nla->status = nla_encrypt_public_key_hash(nla);
if (nla->status != SEC_E_OK) if (nla->status != SEC_E_OK)
return -1; return -1;
@ -1073,6 +1093,93 @@ SECURITY_STATUS nla_encrypt_public_key_echo(rdpNla* nla)
return status; 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) SECURITY_STATUS nla_decrypt_public_key_echo(rdpNla* nla)
{ {
size_t length; size_t length;
@ -1184,6 +1291,111 @@ fail:
return status; 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) static size_t nla_sizeof_ts_password_creds(rdpNla* nla)
{ {
size_t length = 0; size_t length = 0;
@ -1587,6 +1799,13 @@ static size_t nla_sizeof_auth_info(size_t length)
return 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) static size_t nla_sizeof_ts_request(size_t length)
{ {
length += ber_sizeof_integer(2); length += ber_sizeof_integer(2);
@ -1609,23 +1828,23 @@ BOOL nla_send(rdpNla* nla)
size_t auth_info_length = 0; size_t auth_info_length = 0;
size_t error_code_context_length = 0; size_t error_code_context_length = 0;
size_t error_code_length = 0; size_t error_code_length = 0;
size_t client_nonce_length = 0;
if (nla->version < 3 || nla->errorCode == 0)
{
nego_tokens_length = (nla->negoToken.cbBuffer > 0) ? nla_sizeof_nego_tokens( nego_tokens_length = (nla->negoToken.cbBuffer > 0) ? nla_sizeof_nego_tokens(
nla->negoToken.cbBuffer) : 0; nla->negoToken.cbBuffer) : 0;
pub_key_auth_length = (nla->pubKeyAuth.cbBuffer > 0) ? nla_sizeof_pub_key_auth( pub_key_auth_length = (nla->pubKeyAuth.cbBuffer > 0) ? nla_sizeof_pub_key_auth(
nla->pubKeyAuth.cbBuffer) : 0; nla->pubKeyAuth.cbBuffer) : 0;
auth_info_length = (nla->authInfo.cbBuffer > 0) ? nla_sizeof_auth_info(nla->authInfo.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(
else nla->ClientNonce.cbBuffer) : 0;
if (nla->version >= 3 && nla->version != 5 && nla->errorCode != 0)
{ {
error_code_length = ber_sizeof_integer(nla->errorCode); error_code_length = ber_sizeof_integer(nla->errorCode);
error_code_context_length = ber_sizeof_contextual_tag(error_code_length); 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 + 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); ts_request_length = nla_sizeof_ts_request(length);
s = Stream_New(NULL, ber_sizeof_sequence(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); 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); Stream_SealLength(s);
transport_write(nla->transport, s); transport_write(nla->transport, s);
Stream_Free(s, TRUE); 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_sequence_tag(s, &length) || /* NegoDataItem */
!ber_read_contextual_tag(s, 0, &length, TRUE) || /* [0] negoToken */ !ber_read_contextual_tag(s, 0, &length, TRUE) || /* [0] negoToken */
!ber_read_octet_string_tag(s, &length) || /* OCTET STRING */ !ber_read_octet_string_tag(s, &length) || /* OCTET STRING */
((int) Stream_GetRemainingLength(s)) < length) Stream_GetRemainingLength(s) < length)
{ {
return -1; 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_contextual_tag(s, 2, &length, TRUE) != FALSE)
{ {
if (!ber_read_octet_string_tag(s, &length) || /* OCTET STRING */ if (!ber_read_octet_string_tag(s, &length) || /* OCTET STRING */
((int) Stream_GetRemainingLength(s)) < length) Stream_GetRemainingLength(s) < length)
return -1; return -1;
if (!sspi_SecBufferAlloc(&nla->authInfo, length)) 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_contextual_tag(s, 3, &length, TRUE) != FALSE)
{ {
if (!ber_read_octet_string_tag(s, &length) || /* OCTET STRING */ if (!ber_read_octet_string_tag(s, &length) || /* OCTET STRING */
((int) Stream_GetRemainingLength(s)) < length) Stream_GetRemainingLength(s) < length)
return -1; return -1;
if (!sspi_SecBufferAlloc(&nla->pubKeyAuth, length)) 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)) if (!ber_read_integer(s, &nla->errorCode))
return -1; 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; return 1;
@ -1981,7 +2224,7 @@ rdpNla* nla_new(freerdp* instance, rdpTransport* transport, rdpSettings* setting
nla->transport = transport; nla->transport = transport;
nla->sendSeqNum = 0; nla->sendSeqNum = 0;
nla->recvSeqNum = 0; nla->recvSeqNum = 0;
nla->version = 3; nla->version = 6;
if (settings->NtlmSamFile) if (settings->NtlmSamFile)
{ {
@ -1998,6 +2241,7 @@ rdpNla* nla_new(freerdp* instance, rdpTransport* transport, rdpSettings* setting
ZeroMemory(&nla->negoToken, sizeof(SecBuffer)); ZeroMemory(&nla->negoToken, sizeof(SecBuffer));
ZeroMemory(&nla->pubKeyAuth, sizeof(SecBuffer)); ZeroMemory(&nla->pubKeyAuth, sizeof(SecBuffer));
ZeroMemory(&nla->authInfo, sizeof(SecBuffer)); ZeroMemory(&nla->authInfo, sizeof(SecBuffer));
ZeroMemory(&nla->ClientNonce, sizeof(SecBuffer));
SecInvalidateHandle(&nla->context); SecInvalidateHandle(&nla->context);
if (nla->server) if (nla->server)
@ -2079,6 +2323,7 @@ void nla_free(rdpNla* nla)
free(nla->SamFile); free(nla->SamFile);
nla->SamFile = NULL; nla->SamFile = NULL;
sspi_SecBufferFree(&nla->ClientNonce);
sspi_SecBufferFree(&nla->PublicKey); sspi_SecBufferFree(&nla->PublicKey);
sspi_SecBufferFree(&nla->tsCredentials); sspi_SecBufferFree(&nla->tsCredentials);
free(nla->ServicePrincipalName); free(nla->ServicePrincipalName);

View File

@ -83,6 +83,7 @@ struct rdp_nla
SecBuffer negoToken; SecBuffer negoToken;
SecBuffer pubKeyAuth; SecBuffer pubKeyAuth;
SecBuffer authInfo; SecBuffer authInfo;
SecBuffer ClientNonce;
SecBuffer PublicKey; SecBuffer PublicKey;
SecBuffer tsCredentials; SecBuffer tsCredentials;
LPTSTR ServicePrincipalName; LPTSTR ServicePrincipalName;

View File

@ -617,6 +617,7 @@ BOOL CryptBinaryToStringA(CONST BYTE* pbBinary, DWORD cbBinary, DWORD dwFlags, L
#define WINPR_MD4_DIGEST_LENGTH 16 #define WINPR_MD4_DIGEST_LENGTH 16
#define WINPR_MD5_DIGEST_LENGTH 16 #define WINPR_MD5_DIGEST_LENGTH 16
#define WINPR_SHA1_DIGEST_LENGTH 20 #define WINPR_SHA1_DIGEST_LENGTH 20
#define WINPR_SHA256_DIGEST_LENGTH 32
/** /**