/** * FreeRDP: A Remote Desktop Protocol Implementation * Certificate Handling * * Copyright 2011 Jiten Pathy * Copyright 2011 Marc-Andre Moreau * Copyright 2015 Thincast Technologies GmbH * Copyright 2015 DI (FH) Martin Haimberger * Copyright 2023 Armin Novak * Copyright 2023 Thincast Technologies GmbH * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) #include #include #include #include #endif #include "certificate.h" #include "cert_common.h" #include "crypto.h" #include "x509_utils.h" #include "privatekey.h" #include "opensslcompat.h" #define TAG FREERDP_TAG("core") #ifdef WITH_DEBUG_CERTIFICATE #define CERTIFICATE_TAG FREERDP_TAG("core.certificate") #define DEBUG_CERTIFICATE(...) WLog_DBG(TAG, __VA_ARGS__) #else #define DEBUG_CERTIFICATE(...) \ do \ { \ } while (0) #endif #define TSSK_KEY_LENGTH 64 struct rdp_CertBlob { UINT32 length; BYTE* data; }; typedef struct rdp_CertBlob rdpCertBlob; struct rdp_X509CertChain { UINT32 count; rdpCertBlob* array; }; typedef struct rdp_X509CertChain rdpX509CertChain; struct rdp_certificate { X509* x509; STACK_OF(X509) * chain; rdpCertInfo cert_info; rdpX509CertChain x509_cert_chain; }; /** * * X.509 Certificate Structure * * Certificate ::= SEQUENCE * { * tbsCertificate TBSCertificate, * signatureAlgorithm AlgorithmIdentifier, * signatureValue BIT_STRING * } * * TBSCertificate ::= SEQUENCE * { * version [0] EXPLICIT Version DEFAULT v1, * serialNumber CertificateSerialNumber, * signature AlgorithmIdentifier, * issuer Name, * validity Validity, * subject Name, * subjectPublicKeyInfo SubjectPublicKeyInfo, * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, * subjectUniqueId [2] IMPLICIT UniqueIdentifier OPTIONAL, * extensions [3] EXPLICIT Extensions OPTIONAL * } * * Version ::= INTEGER { v1(0), v2(1), v3(2) } * * CertificateSerialNumber ::= INTEGER * * AlgorithmIdentifier ::= SEQUENCE * { * algorithm OBJECT_IDENTIFIER, * parameters ANY DEFINED BY algorithm OPTIONAL * } * * Name ::= CHOICE { RDNSequence } * * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName * * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue * * AttributeTypeAndValue ::= SEQUENCE * { * type AttributeType, * value AttributeValue * } * * AttributeType ::= OBJECT_IDENTIFIER * * AttributeValue ::= ANY DEFINED BY AttributeType * * Validity ::= SEQUENCE * { * notBefore Time, * notAfter Time * } * * Time ::= CHOICE * { * utcTime UTCTime, * generalTime GeneralizedTime * } * * UniqueIdentifier ::= BIT_STRING * * SubjectPublicKeyInfo ::= SEQUENCE * { * algorithm AlgorithmIdentifier, * subjectPublicKey BIT_STRING * } * * RSAPublicKey ::= SEQUENCE * { * modulus INTEGER * publicExponent INTEGER * } * * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension * * Extension ::= SEQUENCE * { * extnID OBJECT_IDENTIFIER * critical BOOLEAN DEFAULT FALSE, * extnValue OCTET_STRING * } * */ static const char rsa_magic[4] = "RSA1"; static const char* certificate_read_errors[] = { "Certificate tag", "TBSCertificate", "Explicit Contextual Tag [0]", "version", "CertificateSerialNumber", "AlgorithmIdentifier", "Issuer Name", "Validity", "Subject Name", "SubjectPublicKeyInfo Tag", "subjectPublicKeyInfo::AlgorithmIdentifier", "subjectPublicKeyInfo::subjectPublicKey", "RSAPublicKey Tag", "modulusLength", "zero padding", "modulusLength", "modulus", "publicExponent length", "publicExponent" }; static const BYTE initial_signature[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01 }; #if defined(CERT_VALIDATE_RSA) static const BYTE tssk_exponent[] = { 0x5b, 0x7b, 0x88, 0xc0 }; #endif static void certificate_free_int(rdpCertificate* certificate); static BOOL cert_clone_int(rdpCertificate* dst, const rdpCertificate* src); /* [MS-RDPBCGR] 5.3.3.2 X.509 Certificate Chains: * * More detail[MS-RDPELE] section 2.2.1.4.2. */ static BOOL cert_blob_copy(rdpCertBlob* dst, const rdpCertBlob* src); static void cert_blob_free(rdpCertBlob* blob); static BOOL cert_blob_write(const rdpCertBlob* blob, wStream* s); static BOOL cert_blob_read(rdpCertBlob* blob, wStream* s); BOOL cert_blob_read(rdpCertBlob* blob, wStream* s) { UINT32 certLength = 0; WINPR_ASSERT(blob); cert_blob_free(blob); if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) goto fail; Stream_Read_UINT32(s, certLength); if (!Stream_CheckAndLogRequiredLength(TAG, s, certLength)) goto fail; DEBUG_CERTIFICATE("X.509 Certificate length:%" PRIu32 "", certLength); blob->data = (BYTE*)malloc(certLength); if (!blob->data) goto fail; Stream_Read(s, blob->data, certLength); blob->length = certLength; return TRUE; fail: cert_blob_free(blob); return FALSE; } BOOL cert_blob_write(const rdpCertBlob* blob, wStream* s) { WINPR_ASSERT(blob); if (!Stream_EnsureRemainingCapacity(s, 4 + blob->length)) return FALSE; Stream_Write_UINT32(s, blob->length); Stream_Write(s, blob->data, blob->length); return TRUE; } void cert_blob_free(rdpCertBlob* blob) { if (!blob) return; free(blob->data); blob->data = NULL; blob->length = 0; } /** * Read X.509 Certificate */ static BOOL is_rsa_key(const X509* x509) { EVP_PKEY* evp = X509_get0_pubkey(x509); if (!evp) return FALSE; return (EVP_PKEY_id(evp) == EVP_PKEY_RSA); } static BOOL certificate_read_x509_certificate(const rdpCertBlob* cert, rdpCertInfo* info) { wStream sbuffer = { 0 }; wStream* s = NULL; size_t length = 0; BYTE padding = 0; UINT32 version = 0; size_t modulus_length = 0; size_t exponent_length = 0; int error = 0; WINPR_ASSERT(cert); WINPR_ASSERT(info); cert_info_free(info); s = Stream_StaticConstInit(&sbuffer, cert->data, cert->length); if (!s) return FALSE; if (!ber_read_sequence_tag(s, &length)) /* Certificate (SEQUENCE) */ goto error; error++; if (!ber_read_sequence_tag(s, &length)) /* TBSCertificate (SEQUENCE) */ goto error; error++; if (!ber_read_contextual_tag(s, 0, &length, TRUE)) /* Explicit Contextual Tag [0] */ goto error; error++; if (!ber_read_integer(s, &version)) /* version (INTEGER) */ goto error; error++; version++; /* serialNumber */ if (!ber_read_integer(s, NULL)) /* CertificateSerialNumber (INTEGER) */ goto error; error++; /* signature */ if (!ber_read_sequence_tag(s, &length) || !Stream_SafeSeek(s, length)) /* AlgorithmIdentifier (SEQUENCE) */ goto error; error++; /* issuer */ if (!ber_read_sequence_tag(s, &length) || !Stream_SafeSeek(s, length)) /* Name (SEQUENCE) */ goto error; error++; /* validity */ if (!ber_read_sequence_tag(s, &length) || !Stream_SafeSeek(s, length)) /* Validity (SEQUENCE) */ goto error; error++; /* subject */ if (!ber_read_sequence_tag(s, &length) || !Stream_SafeSeek(s, length)) /* Name (SEQUENCE) */ goto error; error++; /* subjectPublicKeyInfo */ if (!ber_read_sequence_tag(s, &length)) /* SubjectPublicKeyInfo (SEQUENCE) */ goto error; error++; /* subjectPublicKeyInfo::AlgorithmIdentifier */ if (!ber_read_sequence_tag(s, &length) || !Stream_SafeSeek(s, length)) /* AlgorithmIdentifier (SEQUENCE) */ goto error; error++; /* subjectPublicKeyInfo::subjectPublicKey */ if (!ber_read_bit_string(s, &length, &padding)) /* BIT_STRING */ goto error; error++; /* RSAPublicKey (SEQUENCE) */ if (!ber_read_sequence_tag(s, &length)) /* SEQUENCE */ goto error; error++; if (!ber_read_integer_length(s, &modulus_length)) /* modulus (INTEGER) */ goto error; error++; /* skip zero padding, if any */ do { if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) goto error; Stream_Peek_UINT8(s, padding); if (padding == 0) { if (!Stream_SafeSeek(s, 1)) goto error; modulus_length--; } } while (padding == 0); error++; if (!cert_info_read_modulus(info, modulus_length, s)) goto error; error++; if (!ber_read_integer_length(s, &exponent_length)) /* publicExponent (INTEGER) */ goto error; error++; if (!cert_info_read_exponent(info, exponent_length, s)) goto error; return TRUE; error: WLog_ERR(TAG, "error reading when reading certificate: part=%s error=%d", certificate_read_errors[error], error); cert_info_free(info); return FALSE; } /** * Instantiate new X.509 Certificate Chain. * @param count certificate chain count * @return new X.509 certificate chain */ static rdpX509CertChain certificate_new_x509_certificate_chain(UINT32 count) { rdpX509CertChain x509_cert_chain = { 0 }; x509_cert_chain.array = (rdpCertBlob*)calloc(count, sizeof(rdpCertBlob)); if (x509_cert_chain.array) x509_cert_chain.count = count; return x509_cert_chain; } /** * Free X.509 Certificate Chain. * @param x509_cert_chain X.509 certificate chain to be freed */ static void certificate_free_x509_certificate_chain(rdpX509CertChain* x509_cert_chain) { if (!x509_cert_chain) return; if (x509_cert_chain->array) { for (UINT32 i = 0; i < x509_cert_chain->count; i++) { rdpCertBlob* element = &x509_cert_chain->array[i]; cert_blob_free(element); } } free(x509_cert_chain->array); } #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) static OSSL_PARAM* get_params(const BIGNUM* e, const BIGNUM* mod) { WINPR_ASSERT(e); WINPR_ASSERT(mod); OSSL_PARAM* parameters = NULL; OSSL_PARAM_BLD* param = OSSL_PARAM_BLD_new(); if (!param) return NULL; const int bits = BN_num_bits(e); if ((bits < 0) || (bits > 32)) goto fail; UINT ie = 0; const int ne = BN_bn2nativepad(e, (BYTE*)&ie, sizeof(ie)); if ((ne < 0) || (ne > 4)) goto fail; if (OSSL_PARAM_BLD_push_BN(param, OSSL_PKEY_PARAM_RSA_N, mod) != 1) goto fail; if (OSSL_PARAM_BLD_push_uint(param, OSSL_PKEY_PARAM_RSA_E, ie) != 1) goto fail; parameters = OSSL_PARAM_BLD_to_param(param); fail: OSSL_PARAM_BLD_free(param); return parameters; } #endif static BOOL update_x509_from_info(rdpCertificate* cert) { BOOL rc = FALSE; WINPR_ASSERT(cert); X509_free(cert->x509); cert->x509 = NULL; rdpCertInfo* info = &cert->cert_info; BIGNUM* e = BN_new(); BIGNUM* mod = BN_new(); #if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3) RSA* rsa = RSA_new(); if (!rsa) goto fail; #endif if (!mod || !e) goto fail; WINPR_ASSERT(info->ModulusLength <= INT_MAX); if (!BN_bin2bn(info->Modulus, (int)info->ModulusLength, mod)) goto fail; if (!BN_bin2bn(info->exponent, (int)sizeof(info->exponent), e)) goto fail; #if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3) const int rec = RSA_set0_key(rsa, mod, e, NULL); if (rec != 1) goto fail; cert->x509 = x509_from_rsa(rsa); #else EVP_PKEY* pkey = NULL; EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); if (!ctx) goto fail2; const int xx = EVP_PKEY_fromdata_init(ctx); if (xx != 1) goto fail2; OSSL_PARAM* parameters = get_params(e, mod); if (!parameters) goto fail2; const int rc2 = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, parameters); OSSL_PARAM_free(parameters); if (rc2 <= 0) goto fail2; cert->x509 = X509_new(); if (!cert->x509) goto fail2; if (X509_set_pubkey(cert->x509, pkey) != 1) { X509_free(cert->x509); cert->x509 = NULL; } fail2: EVP_PKEY_free(pkey); EVP_PKEY_CTX_free(ctx); #endif if (!cert->x509) goto fail; rc = TRUE; fail: if (!rc) WLog_ERR(TAG, "failed to update x509 from rdpCertInfo"); #if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3) if (rsa) RSA_free(rsa); else #endif { BN_free(mod); BN_free(e); } return rc; } static BOOL certificate_process_server_public_key(rdpCertificate* cert, wStream* s, UINT32 length) { char magic[sizeof(rsa_magic)] = { 0 }; UINT32 keylen = 0; UINT32 bitlen = 0; UINT32 datalen = 0; WINPR_ASSERT(cert); WINPR_ASSERT(s); if (!Stream_CheckAndLogRequiredLength(TAG, s, 20)) return FALSE; Stream_Read(s, magic, sizeof(magic)); if (memcmp(magic, rsa_magic, sizeof(magic)) != 0) { WLog_ERR(TAG, "invalid RSA magic bytes"); return FALSE; } rdpCertInfo* info = &cert->cert_info; cert_info_free(info); Stream_Read_UINT32(s, keylen); Stream_Read_UINT32(s, bitlen); Stream_Read_UINT32(s, datalen); Stream_Read(s, info->exponent, 4); if (keylen <= 8) { WLog_ERR(TAG, "Invalid RSA keylen=%" PRIu32 " <= 8", keylen); return FALSE; } if (!Stream_CheckAndLogRequiredLength(TAG, s, keylen)) return FALSE; if (keylen != (bitlen / 8ull) + 8ull) { WLog_ERR(TAG, "Invalid RSA key bitlen %" PRIu32 ", expected %" PRIu32, bitlen, (keylen - 8) * 8); return FALSE; } if (datalen != (bitlen / 8ull) - 1ull) { WLog_ERR(TAG, "Invalid RSA key datalen %" PRIu32 ", expected %" PRIu32, datalen, (bitlen / 8ull) - 1ull); return FALSE; } info->ModulusLength = keylen - 8; BYTE* tmp = realloc(info->Modulus, info->ModulusLength); if (!tmp) { WLog_ERR(TAG, "Failed to reallocate modulus of length %" PRIu32, info->ModulusLength); return FALSE; } info->Modulus = tmp; Stream_Read(s, info->Modulus, info->ModulusLength); Stream_Seek(s, 8); /* 8 bytes of zero padding */ return update_x509_from_info(cert); } static BOOL certificate_process_server_public_signature(rdpCertificate* certificate, const BYTE* sigdata, size_t sigdatalen, wStream* s, UINT32 siglen) { WINPR_ASSERT(certificate); #if defined(CERT_VALIDATE_RSA) BYTE sig[TSSK_KEY_LENGTH]; #endif BYTE encsig[TSSK_KEY_LENGTH + 8]; #if defined(CERT_VALIDATE_MD5) && defined(CERT_VALIDATE_RSA) BYTE md5hash[WINPR_MD5_DIGEST_LENGTH]; #endif #if !defined(CERT_VALIDATE_MD5) || !defined(CERT_VALIDATE_RSA) (void)sigdata; (void)sigdatalen; #endif (void)certificate; /* Do not bother with validation of server proprietary certificate. The use of MD5 here is not * allowed under FIPS. Since the validation is not protecting against anything since the * private/public keys are well known and documented in MS-RDPBCGR section 5.3.3.1, we are not * gaining any security by using MD5 for signature comparison. Rather then use MD5 * here we just dont do the validation to avoid its use. Historically, freerdp has been ignoring * a failed validation anyways. */ #if defined(CERT_VALIDATE_MD5) if (!winpr_Digest(WINPR_MD_MD5, sigdata, sigdatalen, md5hash, sizeof(md5hash))) return FALSE; #endif Stream_Read(s, encsig, siglen); if (siglen < 8) return FALSE; /* Last 8 bytes shall be all zero. */ #if defined(CERT_VALIDATE_PADDING) { size_t sum = 0; for (size_t i = sizeof(encsig) - 8; i < sizeof(encsig); i++) sum += encsig[i]; if (sum != 0) { WLog_ERR(TAG, "invalid signature"); return FALSE; } } #endif #if defined(CERT_VALIDATE_RSA) if (crypto_rsa_public_decrypt(encsig, siglen - 8, TSSK_KEY_LENGTH, tssk_modulus, tssk_exponent, sig) <= 0) { WLog_ERR(TAG, "invalid RSA decrypt"); return FALSE; } /* Verify signature. */ /* Do not bother with validation of server proprietary certificate as described above. */ #if defined(CERT_VALIDATE_MD5) if (memcmp(md5hash, sig, sizeof(md5hash)) != 0) { WLog_ERR(TAG, "invalid signature"); return FALSE; } #endif /* * Verify rest of decrypted data: * The 17th byte is 0x00. * The 18th through 62nd bytes are each 0xFF. * The 63rd byte is 0x01. */ { size_t sum = 0; for (size_t i = 17; i < 62; i++) sum += sig[i]; if (sig[16] != 0x00 || sum != 0xFF * (62 - 17) || sig[62] != 0x01) { WLog_ERR(TAG, "invalid signature"); return FALSE; } } #endif return TRUE; } static BOOL certificate_read_server_proprietary_certificate(rdpCertificate* certificate, wStream* s) { UINT32 dwSigAlgId = 0; UINT32 dwKeyAlgId = 0; UINT16 wPublicKeyBlobType = 0; UINT16 wPublicKeyBlobLen = 0; UINT16 wSignatureBlobType = 0; UINT16 wSignatureBlobLen = 0; size_t sigdatalen = 0; WINPR_ASSERT(certificate); if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) return FALSE; /* -4, because we need to include dwVersion */ const BYTE* sigdata = Stream_PointerAs(s, const BYTE) - 4; Stream_Read_UINT32(s, dwSigAlgId); Stream_Read_UINT32(s, dwKeyAlgId); if (!((dwSigAlgId == SIGNATURE_ALG_RSA) && (dwKeyAlgId == KEY_EXCHANGE_ALG_RSA))) { WLog_ERR(TAG, "unsupported signature or key algorithm, dwSigAlgId=%" PRIu32 " dwKeyAlgId=%" PRIu32 "", dwSigAlgId, dwKeyAlgId); return FALSE; } Stream_Read_UINT16(s, wPublicKeyBlobType); if (wPublicKeyBlobType != BB_RSA_KEY_BLOB) { WLog_ERR(TAG, "unsupported public key blob type %" PRIu16 "", wPublicKeyBlobType); return FALSE; } Stream_Read_UINT16(s, wPublicKeyBlobLen); if (!Stream_CheckAndLogRequiredLength(TAG, s, wPublicKeyBlobLen)) return FALSE; if (!certificate_process_server_public_key(certificate, s, wPublicKeyBlobLen)) return FALSE; if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) return FALSE; sigdatalen = Stream_PointerAs(s, const BYTE) - sigdata; Stream_Read_UINT16(s, wSignatureBlobType); if (wSignatureBlobType != BB_RSA_SIGNATURE_BLOB) { WLog_ERR(TAG, "unsupported blob signature %" PRIu16 "", wSignatureBlobType); return FALSE; } Stream_Read_UINT16(s, wSignatureBlobLen); if (!Stream_CheckAndLogRequiredLength(TAG, s, wSignatureBlobLen)) return FALSE; if (wSignatureBlobLen != 72) { WLog_ERR(TAG, "invalid signature length (got %" PRIu16 ", expected 72)", wSignatureBlobLen); return FALSE; } if (!certificate_process_server_public_signature(certificate, sigdata, sigdatalen, s, wSignatureBlobLen)) { WLog_ERR(TAG, "unable to parse server public signature"); return FALSE; } return TRUE; } /* [MS-RDPBCGR] 2.2.1.4.3.1.1.1 RSA Public Key (RSA_PUBLIC_KEY) */ static BOOL cert_write_rsa_public_key(wStream* s, const rdpCertificate* cert) { WINPR_ASSERT(cert); WINPR_ASSERT(freerdp_certificate_is_rsa(cert)); const rdpCertInfo* info = &cert->cert_info; const UINT32 keyLen = info->ModulusLength + 8; const UINT32 bitLen = info->ModulusLength * 8; const UINT32 dataLen = (bitLen / 8) - 1; const size_t pubExpLen = sizeof(info->exponent); const BYTE* pubExp = info->exponent; const BYTE* modulus = info->Modulus; const size_t wPublicKeyBlobLen = 16 + pubExpLen + keyLen; WINPR_ASSERT(wPublicKeyBlobLen <= UINT16_MAX); if (!Stream_EnsureRemainingCapacity(s, 2 + wPublicKeyBlobLen)) return FALSE; Stream_Write_UINT16(s, (UINT16)wPublicKeyBlobLen); Stream_Write(s, rsa_magic, sizeof(rsa_magic)); Stream_Write_UINT32(s, keyLen); Stream_Write_UINT32(s, bitLen); Stream_Write_UINT32(s, dataLen); Stream_Write(s, pubExp, pubExpLen); Stream_Write(s, modulus, info->ModulusLength); Stream_Zero(s, 8); return TRUE; } static BOOL cert_write_rsa_signature(wStream* s, const void* sigData, size_t sigDataLen) { BYTE encryptedSignature[TSSK_KEY_LENGTH] = { 0 }; BYTE signature[sizeof(initial_signature)] = { 0 }; memcpy(signature, initial_signature, sizeof(initial_signature)); if (!winpr_Digest(WINPR_MD_MD5, sigData, sigDataLen, signature, sizeof(signature))) return FALSE; crypto_rsa_private_encrypt(signature, sizeof(signature), priv_key_tssk, encryptedSignature, sizeof(encryptedSignature)); if (!Stream_EnsureRemainingCapacity(s, 2 * sizeof(UINT16) + sizeof(encryptedSignature) + 8)) return FALSE; Stream_Write_UINT16(s, BB_RSA_SIGNATURE_BLOB); Stream_Write_UINT16(s, sizeof(encryptedSignature) + 8); /* wSignatureBlobLen */ Stream_Write(s, encryptedSignature, sizeof(encryptedSignature)); Stream_Zero(s, 8); return TRUE; } /* [MS-RDPBCGR] 2.2.1.4.3.1.1 Server Proprietary Certificate (PROPRIETARYSERVERCERTIFICATE) */ static BOOL cert_write_server_certificate_v1(wStream* s, const rdpCertificate* certificate) { const size_t start = Stream_GetPosition(s); const BYTE* sigData = Stream_PointerAs(s, const BYTE) - sizeof(UINT32); WINPR_ASSERT(start >= 4); if (!Stream_EnsureRemainingCapacity(s, 10)) return FALSE; Stream_Write_UINT32(s, SIGNATURE_ALG_RSA); Stream_Write_UINT32(s, KEY_EXCHANGE_ALG_RSA); Stream_Write_UINT16(s, BB_RSA_KEY_BLOB); if (!cert_write_rsa_public_key(s, certificate)) return FALSE; const size_t end = Stream_GetPosition(s); return cert_write_rsa_signature(s, sigData, end - start + sizeof(UINT32)); } static BOOL cert_write_server_certificate_v2(wStream* s, const rdpCertificate* certificate) { WINPR_ASSERT(certificate); const rdpX509CertChain* chain = &certificate->x509_cert_chain; const size_t padding = 8ull + 4ull * chain->count; if (!Stream_EnsureRemainingCapacity(s, sizeof(UINT32))) return FALSE; Stream_Write_UINT32(s, chain->count); for (UINT32 x = 0; x < chain->count; x++) { const rdpCertBlob* cert = &chain->array[x]; if (!cert_blob_write(cert, s)) return FALSE; } if (!Stream_EnsureRemainingCapacity(s, padding)) return FALSE; Stream_Zero(s, padding); return TRUE; } SSIZE_T freerdp_certificate_write_server_cert(const rdpCertificate* certificate, UINT32 dwVersion, wStream* s) { if (!certificate) return -1; const size_t start = Stream_GetPosition(s); if (!Stream_EnsureRemainingCapacity(s, 4)) return -1; Stream_Write_UINT32(s, dwVersion); switch (dwVersion & CERT_CHAIN_VERSION_MASK) { case CERT_CHAIN_VERSION_1: if (!cert_write_server_certificate_v1(s, certificate)) return -1; break; case CERT_CHAIN_VERSION_2: if (!cert_write_server_certificate_v2(s, certificate)) return -1; break; default: WLog_ERR(TAG, "invalid certificate chain version:%" PRIu32 "", dwVersion & CERT_CHAIN_VERSION_MASK); return -1; } const size_t end = Stream_GetPosition(s); if (start > end) return -1; const size_t diff = end - start; WINPR_ASSERT(diff <= SSIZE_MAX); return (SSIZE_T)diff; } /** * Read an X.509 Certificate Chain. * @param cert certificate module * @param s stream * @return \b TRUE for success, \b FALSE otherwise. */ static BOOL certificate_read_server_x509_certificate_chain(rdpCertificate* cert, wStream* s) { UINT32 numCertBlobs = 0; DEBUG_CERTIFICATE("Server X.509 Certificate Chain"); WINPR_ASSERT(cert); if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) return FALSE; Stream_Read_UINT32(s, numCertBlobs); /* numCertBlobs */ certificate_free_x509_certificate_chain(&cert->x509_cert_chain); cert->x509_cert_chain = certificate_new_x509_certificate_chain(numCertBlobs); for (UINT32 i = 0; i < cert->x509_cert_chain.count; i++) { rdpCertBlob* blob = &cert->x509_cert_chain.array[i]; if (!cert_blob_read(blob, s)) return FALSE; if (numCertBlobs - i == 1) { DEBUG_CERTIFICATE("Terminal Server Certificate"); BOOL res = certificate_read_x509_certificate(blob, &cert->cert_info); if (res) { if (!update_x509_from_info(cert)) res = FALSE; } if (!res) { WLog_ERR(TAG, "Failed to read x509 certificate"); return FALSE; } DEBUG_CERTIFICATE("modulus length:%" PRIu32 "", cert->cert_info.ModulusLength); } } return update_x509_from_info(cert); } static BOOL certificate_write_server_x509_certificate_chain(const rdpCertificate* certificate, wStream* s) { UINT32 numCertBlobs = 0; WINPR_ASSERT(certificate); WINPR_ASSERT(s); numCertBlobs = certificate->x509_cert_chain.count; if (!Stream_EnsureRemainingCapacity(s, 4)) return FALSE; Stream_Write_UINT32(s, numCertBlobs); /* numCertBlobs */ for (UINT32 i = 0; i < numCertBlobs; i++) { const rdpCertBlob* cert = &certificate->x509_cert_chain.array[i]; if (!cert_blob_write(cert, s)) return FALSE; } return TRUE; } /** * Read a Server Certificate. * @param certificate certificate module * @param server_cert server certificate * @param length certificate length */ BOOL freerdp_certificate_read_server_cert(rdpCertificate* certificate, const BYTE* server_cert, size_t length) { BOOL ret = FALSE; wStream* s = NULL; wStream sbuffer; UINT32 dwVersion = 0; WINPR_ASSERT(certificate); if (length < 4) /* NULL certificate is not an error see #1795 */ { WLog_DBG(TAG, "Received empty certificate, ignoring..."); return TRUE; } WINPR_ASSERT(server_cert); s = Stream_StaticConstInit(&sbuffer, server_cert, length); if (!s) { WLog_ERR(TAG, "Stream_New failed!"); return FALSE; } Stream_Read_UINT32(s, dwVersion); /* dwVersion (4 bytes) */ switch (dwVersion & CERT_CHAIN_VERSION_MASK) { case CERT_CHAIN_VERSION_1: ret = certificate_read_server_proprietary_certificate(certificate, s); break; case CERT_CHAIN_VERSION_2: ret = certificate_read_server_x509_certificate_chain(certificate, s); break; default: WLog_ERR(TAG, "invalid certificate chain version:%" PRIu32 "", dwVersion & CERT_CHAIN_VERSION_MASK); ret = FALSE; break; } return ret; } static BOOL cert_blob_copy(rdpCertBlob* dst, const rdpCertBlob* src) { WINPR_ASSERT(dst); WINPR_ASSERT(src); cert_blob_free(dst); if (src->length > 0) { dst->data = malloc(src->length); if (!dst->data) return FALSE; dst->length = src->length; memcpy(dst->data, src->data, src->length); } return TRUE; } static BOOL cert_x509_chain_copy(rdpX509CertChain* cert, const rdpX509CertChain* src) { WINPR_ASSERT(cert); certificate_free_x509_certificate_chain(cert); if (!src) return TRUE; if (src->count > 0) { cert->array = calloc(src->count, sizeof(rdpCertBlob)); if (!cert->array) { return FALSE; } cert->count = src->count; for (UINT32 x = 0; x < cert->count; x++) { const rdpCertBlob* srcblob = &src->array[x]; rdpCertBlob* dstblob = &cert->array[x]; if (!cert_blob_copy(dstblob, srcblob)) { certificate_free_x509_certificate_chain(cert); return FALSE; } } } return TRUE; } BOOL cert_clone_int(rdpCertificate* dst, const rdpCertificate* src) { WINPR_ASSERT(dst); WINPR_ASSERT(src); if (src->x509) { dst->x509 = X509_dup(src->x509); if (!dst->x509) return FALSE; } if (!cert_info_clone(&dst->cert_info, &src->cert_info)) return FALSE; return cert_x509_chain_copy(&dst->x509_cert_chain, &src->x509_cert_chain); } rdpCertificate* freerdp_certificate_clone(const rdpCertificate* certificate) { if (!certificate) return NULL; rdpCertificate* _certificate = freerdp_certificate_new(); if (!_certificate) return NULL; if (!cert_clone_int(_certificate, certificate)) goto out_fail; return _certificate; out_fail: freerdp_certificate_free(_certificate); return NULL; } /** * Instantiate new certificate module. * @return new certificate module */ rdpCertificate* freerdp_certificate_new(void) { return (rdpCertificate*)calloc(1, sizeof(rdpCertificate)); } void certificate_free_int(rdpCertificate* cert) { WINPR_ASSERT(cert); if (cert->x509) X509_free(cert->x509); if (cert->chain) sk_X509_pop_free(cert->chain, X509_free); certificate_free_x509_certificate_chain(&cert->x509_cert_chain); cert_info_free(&cert->cert_info); } /** * Free certificate module. * @param cert certificate module to be freed */ void freerdp_certificate_free(rdpCertificate* cert) { if (!cert) return; certificate_free_int(cert); free(cert); } static BOOL freerdp_rsa_from_x509(rdpCertificate* cert) { BOOL rc = FALSE; WINPR_ASSERT(cert); if (!freerdp_certificate_is_rsa(cert)) return TRUE; #if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3) RSA* rsa = NULL; const BIGNUM* rsa_n = NULL; const BIGNUM* rsa_e = NULL; #else BIGNUM* rsa_n = NULL; BIGNUM* rsa_e = NULL; #endif EVP_PKEY* pubkey = X509_get0_pubkey(cert->x509); if (!pubkey) goto fail; #if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3) rsa = EVP_PKEY_get1_RSA(pubkey); /* If this is not a RSA key return success */ rc = TRUE; if (!rsa) goto fail; /* Now we return failure again if something is wrong. */ rc = FALSE; RSA_get0_key(rsa, &rsa_n, &rsa_e, NULL); #else if (!EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_E, &rsa_e)) goto fail; if (!EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_N, &rsa_n)) goto fail; #endif if (!rsa_n || !rsa_e) goto fail; if (!cert_info_create(&cert->cert_info, rsa_n, rsa_e)) goto fail; rc = TRUE; fail: #if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3) RSA_free(rsa); #else BN_free(rsa_n); BN_free(rsa_e); #endif return rc; } rdpCertificate* freerdp_certificate_new_from_der(const BYTE* data, size_t length) { rdpCertificate* cert = freerdp_certificate_new(); if (!cert || !data || (length == 0) || (length > INT_MAX)) goto fail; const BYTE* ptr = data; cert->x509 = d2i_X509(NULL, &ptr, (int)length); if (!cert->x509) goto fail; if (!freerdp_rsa_from_x509(cert)) goto fail; return cert; fail: freerdp_certificate_free(cert); return NULL; } rdpCertificate* freerdp_certificate_new_from_x509(const X509* xcert, const STACK_OF(X509) * chain) { WINPR_ASSERT(xcert); rdpCertificate* cert = freerdp_certificate_new(); if (!cert) return NULL; X509* wcert = WINPR_CAST_CONST_PTR_AWAY(xcert, X509*); cert->x509 = X509_dup(wcert); if (!cert->x509) goto fail; if (!freerdp_rsa_from_x509(cert)) goto fail; if (chain) cert->chain = sk_X509_deep_copy(chain, X509_const_dup, X509_free); return cert; fail: freerdp_certificate_free(cert); return NULL; } static rdpCertificate* freerdp_certificate_new_from(const char* file, BOOL isFile) { X509* x509 = x509_utils_from_pem(file, strlen(file), isFile); if (!x509) return NULL; rdpCertificate* cert = freerdp_certificate_new_from_x509(x509, NULL); X509_free(x509); return cert; } rdpCertificate* freerdp_certificate_new_from_file(const char* file) { return freerdp_certificate_new_from(file, TRUE); } rdpCertificate* freerdp_certificate_new_from_pem(const char* pem) { return freerdp_certificate_new_from(pem, FALSE); } const rdpCertInfo* freerdp_certificate_get_info(const rdpCertificate* cert) { WINPR_ASSERT(cert); if (!freerdp_certificate_is_rsa(cert)) return NULL; return &cert->cert_info; } char* freerdp_certificate_get_fingerprint(const rdpCertificate* cert) { return freerdp_certificate_get_fingerprint_by_hash(cert, "sha256"); } char* freerdp_certificate_get_fingerprint_by_hash(const rdpCertificate* cert, const char* hash) { return freerdp_certificate_get_fingerprint_by_hash_ex(cert, hash, TRUE); } char* freerdp_certificate_get_fingerprint_by_hash_ex(const rdpCertificate* cert, const char* hash, BOOL separator) { size_t fp_len = 0; size_t pos = 0; size_t size = 0; BYTE* fp = NULL; char* fp_buffer = NULL; if (!cert || !cert->x509) { WLog_ERR(TAG, "Invalid certificate [%p, %p]", cert, cert ? cert->x509 : NULL); return NULL; } if (!hash) { WLog_ERR(TAG, "Invalid certificate hash %p", hash); return NULL; } fp = x509_utils_get_hash(cert->x509, hash, &fp_len); if (!fp) return NULL; if (fp_len < 1) goto fail; size = fp_len * 3 + 1; fp_buffer = calloc(size, sizeof(char)); if (!fp_buffer) goto fail; pos = 0; size_t i = 0; for (; i < (fp_len - 1); i++) { int rc = 0; char* p = &fp_buffer[pos]; if (separator) rc = sprintf_s(p, size - pos, "%02" PRIx8 ":", fp[i]); else rc = sprintf_s(p, size - pos, "%02" PRIx8, fp[i]); if (rc <= 0) goto fail; pos += (size_t)rc; } (void)sprintf_s(&fp_buffer[pos], size - pos, "%02" PRIx8 "", fp[i]); free(fp); return fp_buffer; fail: free(fp); free(fp_buffer); return NULL; } static BOOL bio_read_pem(BIO* bio, char** ppem, size_t* plength) { BOOL rc = FALSE; WINPR_ASSERT(bio); WINPR_ASSERT(ppem); const size_t blocksize = 2048; size_t offset = 0; size_t length = blocksize; char* pem = NULL; while (offset < length) { char* tmp = realloc(pem, length + 1); if (!tmp) goto fail; pem = tmp; ERR_clear_error(); const int status = BIO_read(bio, &pem[offset], (int)(length - offset)); if (status < 0) { WLog_ERR(TAG, "failed to read certificate"); goto fail; } if (status == 0) break; offset += (size_t)status; if (length - offset > 0) break; length += blocksize; } pem[offset] = '\0'; *ppem = pem; if (plength) *plength = offset; rc = TRUE; fail: return rc; } char* freerdp_certificate_get_pem(const rdpCertificate* cert, size_t* pLength) { return freerdp_certificate_get_pem_ex(cert, pLength, TRUE); } char* freerdp_certificate_get_pem_ex(const rdpCertificate* cert, size_t* pLength, BOOL withCertChain) { char* pem = NULL; WINPR_ASSERT(cert); if (!cert->x509) return NULL; BIO* bio = NULL; int status = 0; /** * Don't manage certificates internally, leave it up entirely to the external client * implementation */ bio = BIO_new(BIO_s_mem()); if (!bio) { WLog_ERR(TAG, "BIO_new() failure"); return NULL; } status = PEM_write_bio_X509(bio, cert->x509); if (status < 0) { WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status); goto fail; } if (cert->chain && withCertChain) { int count = sk_X509_num(cert->chain); for (int x = 0; x < count; x++) { X509* c = sk_X509_value(cert->chain, x); status = PEM_write_bio_X509(bio, c); if (status < 0) { WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status); goto fail; } } } if (!bio_read_pem(bio, &pem, pLength)) goto fail; fail: BIO_free_all(bio); return pem; } char* freerdp_certificate_get_subject(const rdpCertificate* cert) { WINPR_ASSERT(cert); return x509_utils_get_subject(cert->x509); } char* freerdp_certificate_get_issuer(const rdpCertificate* cert) { WINPR_ASSERT(cert); return x509_utils_get_issuer(cert->x509); } char* freerdp_certificate_get_upn(const rdpCertificate* cert) { WINPR_ASSERT(cert); return x509_utils_get_upn(cert->x509); } char* freerdp_certificate_get_email(const rdpCertificate* cert) { WINPR_ASSERT(cert); return x509_utils_get_email(cert->x509); } char* freerdp_certificate_get_validity(const rdpCertificate* cert, BOOL startDate) { WINPR_ASSERT(cert); return x509_utils_get_date(cert->x509, startDate); } BOOL freerdp_certificate_check_eku(const rdpCertificate* cert, int nid) { WINPR_ASSERT(cert); return x509_utils_check_eku(cert->x509, nid); } BOOL freerdp_certificate_get_public_key(const rdpCertificate* cert, BYTE** PublicKey, DWORD* PublicKeyLength) { BYTE* ptr = NULL; BYTE* optr = NULL; int length = 0; BOOL status = FALSE; EVP_PKEY* pkey = NULL; WINPR_ASSERT(cert); pkey = X509_get0_pubkey(cert->x509); if (!pkey) { WLog_ERR(TAG, "X509_get_pubkey() failed"); goto exit; } length = i2d_PublicKey(pkey, NULL); if (length < 1) { WLog_ERR(TAG, "i2d_PublicKey() failed"); goto exit; } *PublicKey = optr = ptr = (BYTE*)calloc(length, sizeof(BYTE)); if (!ptr) goto exit; const int length2 = i2d_PublicKey(pkey, &ptr); if (length != length2) goto exit; *PublicKeyLength = (DWORD)length2; status = TRUE; exit: if (!status) free(optr); return status; } BOOL freerdp_certificate_verify(const rdpCertificate* cert, const char* certificate_store_path) { WINPR_ASSERT(cert); return x509_utils_verify(cert->x509, cert->chain, certificate_store_path); } char** freerdp_certificate_get_dns_names(const rdpCertificate* cert, size_t* pcount, size_t** pplengths) { WINPR_ASSERT(cert); return x509_utils_get_dns_names(cert->x509, pcount, pplengths); } char* freerdp_certificate_get_common_name(const rdpCertificate* cert, size_t* plength) { WINPR_ASSERT(cert); return x509_utils_get_common_name(cert->x509, plength); } WINPR_MD_TYPE freerdp_certificate_get_signature_alg(const rdpCertificate* cert) { WINPR_ASSERT(cert); return x509_utils_get_signature_alg(cert->x509); } void freerdp_certificate_free_dns_names(size_t count, size_t* lengths, char** names) { x509_utils_dns_names_free(count, lengths, names); } char* freerdp_certificate_get_hash(const rdpCertificate* cert, const char* hash, size_t* plength) { WINPR_ASSERT(cert); return (char*)x509_utils_get_hash(cert->x509, hash, plength); } X509* freerdp_certificate_get_x509(rdpCertificate* cert) { WINPR_ASSERT(cert); return cert->x509; } BOOL freerdp_certificate_publickey_encrypt(const rdpCertificate* cert, const BYTE* input, size_t cbInput, BYTE** poutput, size_t* pcbOutput) { WINPR_ASSERT(cert); WINPR_ASSERT(input); WINPR_ASSERT(poutput); WINPR_ASSERT(pcbOutput); BOOL ret = FALSE; BYTE* output = NULL; EVP_PKEY* pkey = X509_get0_pubkey(cert->x509); if (!pkey) return FALSE; EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); if (!ctx) return FALSE; size_t outputSize = EVP_PKEY_size(pkey); output = malloc(outputSize); if (output == NULL) goto out; *pcbOutput = outputSize; if (EVP_PKEY_encrypt_init(ctx) != 1 || EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) != 1 || EVP_PKEY_encrypt(ctx, output, pcbOutput, input, cbInput) != 1) { WLog_ERR(TAG, "error when setting up public key"); goto out; } *poutput = output; output = NULL; ret = TRUE; out: EVP_PKEY_CTX_free(ctx); free(output); return ret; } #if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3) static RSA* freerdp_certificate_get_RSA(const rdpCertificate* cert) { WINPR_ASSERT(cert); if (!freerdp_certificate_is_rsa(cert)) return NULL; EVP_PKEY* pubkey = X509_get0_pubkey(cert->x509); if (!pubkey) return NULL; return EVP_PKEY_get1_RSA(pubkey); } #endif BYTE* freerdp_certificate_get_der(const rdpCertificate* cert, size_t* pLength) { WINPR_ASSERT(cert); if (pLength) *pLength = 0; const int rc = i2d_X509(cert->x509, NULL); if (rc <= 0) return NULL; BYTE* ptr = calloc(rc + 1, sizeof(BYTE)); if (!ptr) return NULL; BYTE* i2d_ptr = ptr; const int rc2 = i2d_X509(cert->x509, &i2d_ptr); if (rc2 <= 0) { free(ptr); return NULL; } if (pLength) *pLength = (size_t)rc2; return ptr; } BOOL freerdp_certificate_is_rsa(const rdpCertificate* cert) { WINPR_ASSERT(cert); return is_rsa_key(cert->x509); } BOOL freerdp_certificate_is_rdp_security_compatible(const rdpCertificate* cert) { const rdpCertInfo* info = freerdp_certificate_get_info(cert); if (!freerdp_certificate_is_rsa(cert) || !info || (info->ModulusLength != 2048 / 8)) { WLog_INFO(TAG, "certificate is not RSA 2048, RDP security not supported."); return FALSE; } return TRUE; } char* freerdp_certificate_get_param(const rdpCertificate* cert, enum FREERDP_CERT_PARAM what, size_t* psize) { WINPR_ASSERT(cert); WINPR_ASSERT(psize); *psize = 0; #if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3) const BIGNUM* bn = NULL; RSA* rsa = freerdp_certificate_get_RSA(cert); switch (what) { case FREERDP_CERT_RSA_E: RSA_get0_key(rsa, NULL, &bn, NULL); break; case FREERDP_CERT_RSA_N: RSA_get0_key(rsa, &bn, NULL, NULL); break; default: RSA_free(rsa); return NULL; } RSA_free(rsa); #else EVP_PKEY* pkey = X509_get0_pubkey(cert->x509); if (!pkey) return NULL; BIGNUM* bn = NULL; switch (what) { case FREERDP_CERT_RSA_E: if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn)) return NULL; break; case FREERDP_CERT_RSA_N: if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn)) return NULL; break; default: return NULL; } #endif const size_t bnsize = BN_num_bytes(bn); char* rc = calloc(bnsize + 1, sizeof(char)); if (!rc) goto fail; BN_bn2bin(bn, (BYTE*)rc); *psize = bnsize; fail: #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR < 3) BN_free(bn); #endif return rc; }