diff options
Diffstat (limited to '')
-rw-r--r-- | libfreerdp/crypto/certificate.c | 1732 |
1 files changed, 1732 insertions, 0 deletions
diff --git a/libfreerdp/crypto/certificate.c b/libfreerdp/crypto/certificate.c new file mode 100644 index 0000000..ddfe776 --- /dev/null +++ b/libfreerdp/crypto/certificate.c @@ -0,0 +1,1732 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Certificate Handling + * + * Copyright 2011 Jiten Pathy + * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2023 Armin Novak <anovak@thincast.com> + * 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 <freerdp/config.h> + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include <winpr/assert.h> +#include <winpr/wtypes.h> +#include <winpr/crt.h> +#include <winpr/file.h> +#include <winpr/print.h> +#include <winpr/crypto.h> + +#include <freerdp/crypto/certificate.h> + +#include <openssl/err.h> +#include <openssl/pem.h> +#include <openssl/rsa.h> +#include <openssl/bn.h> + +#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) +#include <openssl/core_names.h> +#include <openssl/param_build.h> +#include <openssl/evp.h> +#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") + +#define CERTIFICATE_TAG FREERDP_TAG("core.certificate") +#ifdef WITH_DEBUG_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; + + if (!cert || !info) + return FALSE; + + 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; + if (!BN_bin2bn(info->Modulus, info->ModulusLength, mod)) + goto fail; + if (!BN_bin2bn(info->exponent, 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 !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, "magic error"); + 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) || (!Stream_CheckAndLogRequiredLength(TAG, s, keylen))) + return FALSE; + + info->ModulusLength = keylen - 8; + BYTE* tmp = realloc(info->Modulus, info->ModulusLength); + + if (!tmp) + 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); + return end - start; +} + +/** + * 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) + { + 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_free(cert->chain); + + 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)) + goto fail; + const BYTE* ptr = data; + cert->x509 = d2i_X509(NULL, &ptr, 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; + + cert->x509 = X509_dup(xcert); + if (!cert->x509) + goto fail; + + if (!freerdp_rsa_from_x509(cert)) + goto fail; + + if (chain) + { + cert->chain = sk_X509_dup(chain); + } + 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; + + 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; + } + + 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); + + size_t offset = 0; + size_t length = 2048; + 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], 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 *= 2; + } + 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) +{ + 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 0 + if (chain) + { + int count = sk_X509_num(chain); + for (int x = 0; x < count; x++) + { + X509* c = sk_X509_value(chain, x); + status = PEM_write_bio_X509(bio, c); + if (status < 0) + { + WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status); + goto fail; + } + } + } +#endif + 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); +} + +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); + *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; +} |