BOOL tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname) { int match; int index; char* common_name = NULL; int common_name_length = 0; char** alt_names = NULL; int alt_names_count = 0; int* alt_names_lengths = NULL; BOOL certificate_status; BOOL hostname_match = FALSE; BOOL verification_status = FALSE; rdpCertificateData* certificate_data; /* ignore certificate verification if user explicitly required it (discouraged) */ if (tls->settings->IgnoreCertificate) return TRUE; /* success! */ /* if user explicitly specified a certificate name, use it instead of the hostname */ if (tls->settings->CertificateName) hostname = tls->settings->CertificateName; /* attempt verification using OpenSSL and the ~/.freerdp/certs certificate store */ certificate_status = x509_verify_certificate(cert, tls->certificate_store->path); /* verify certificate name match */ certificate_data = crypto_get_certificate_data(cert->px509, hostname); /* extra common name and alternative names */ common_name = crypto_cert_subject_common_name(cert->px509, &common_name_length); alt_names = crypto_cert_subject_alt_name(cert->px509, &alt_names_count, &alt_names_lengths); /* compare against common name */ if (common_name != NULL) { if (strlen(hostname) == common_name_length) { if (memcmp((void*) hostname, (void*) common_name, common_name_length) == 0) hostname_match = TRUE; } } /* compare against alternative names */ if (alt_names != NULL) { for (index = 0; index < alt_names_count; index++) { if (strlen(hostname) == alt_names_lengths[index]) { if (memcmp((void*) hostname, (void*) alt_names[index], alt_names_lengths[index]) == 0) hostname_match = TRUE; } } } /* if the certificate is valid and the certificate name matches, verification succeeds */ if (certificate_status && hostname_match) { if (common_name) { free(common_name); common_name = NULL; } verification_status = TRUE; /* success! */ } /* if the certificate is valid but the certificate name does not match, warn user, do not accept */ if (certificate_status && !hostname_match) tls_print_certificate_name_mismatch_error(hostname, common_name, alt_names, alt_names_count); /* verification could not succeed with OpenSSL, use known_hosts file and prompt user for manual verification */ if (!certificate_status) { char* issuer; char* subject; char* fingerprint; freerdp* instance = (freerdp*) tls->settings->instance; BOOL accept_certificate = FALSE; issuer = crypto_cert_issuer(cert->px509); subject = crypto_cert_subject(cert->px509); fingerprint = crypto_cert_fingerprint(cert->px509); /* search for matching entry in known_hosts file */ match = certificate_data_match(tls->certificate_store, certificate_data); if (match == 1) { /* no entry was found in known_hosts file, prompt user for manual verification */ if (!hostname_match) tls_print_certificate_name_mismatch_error(hostname, common_name, alt_names, alt_names_count); if (instance->VerifyCertificate) accept_certificate = instance->VerifyCertificate(instance, subject, issuer, fingerprint); if (!accept_certificate) { /* user did not accept, abort and do not add entry in known_hosts file */ verification_status = FALSE; /* failure! */ } else { /* user accepted certificate, add entry in known_hosts file */ certificate_data_print(tls->certificate_store, certificate_data); verification_status = TRUE; /* success! */ } } else if (match == -1) { /* entry was found in known_hosts file, but fingerprint does not match. ask user to use it */ tls_print_certificate_error(hostname, fingerprint, tls->certificate_store->file); if (instance->VerifyChangedCertificate) accept_certificate = instance->VerifyChangedCertificate(instance, subject, issuer, fingerprint, ""); if (!accept_certificate) { /* user did not accept, abort and do not change known_hosts file */ verification_status = FALSE; /* failure! */ } else { /* user accepted new certificate, add replace fingerprint for this host in known_hosts file */ certificate_data_replace(tls->certificate_store, certificate_data); verification_status = TRUE; /* success! */ } } else if (match == 0) { verification_status = TRUE; /* success! */ } free(issuer); free(subject); free(fingerprint); } if (certificate_data) { free(certificate_data->fingerprint); free(certificate_data->hostname); free(certificate_data); } #ifndef _WIN32 free(common_name); #endif return verification_status; }
int tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname, int port) { int match; int index; char* common_name = NULL; int common_name_length = 0; char** alt_names = NULL; int alt_names_count = 0; int* alt_names_lengths = NULL; BOOL certificate_status; BOOL hostname_match = FALSE; BOOL verification_status = FALSE; rdpCertificateData* certificate_data; if (tls->settings->ExternalCertificateManagement) { BIO* bio; int status; int length; int offset; BYTE* pemCert; freerdp* instance = (freerdp*) tls->settings->instance; /** * Don't manage certificates internally, leave it up entirely to the external client implementation */ bio = BIO_new(BIO_s_mem()); if (!bio) { fprintf(stderr, "%s: BIO_new() failure\n", __FUNCTION__); return -1; } status = PEM_write_bio_X509(bio, cert->px509); if (status < 0) { fprintf(stderr, "%s: PEM_write_bio_X509 failure: %d\n", __FUNCTION__, status); return -1; } offset = 0; length = 2048; pemCert = (BYTE*) malloc(length + 1); status = BIO_read(bio, pemCert, length); if (status < 0) { fprintf(stderr, "%s: failed to read certificate\n", __FUNCTION__); return -1; } offset += status; while (offset >= length) { length *= 2; pemCert = (BYTE*) realloc(pemCert, length + 1); status = BIO_read(bio, &pemCert[offset], length); if (status < 0) break; offset += status; } if (status < 0) { fprintf(stderr, "%s: failed to read certificate\n", __FUNCTION__); return -1; } length = offset; pemCert[length] = '\0'; status = -1; if (instance->VerifyX509Certificate) { status = instance->VerifyX509Certificate(instance, pemCert, length, hostname, port, tls->isGatewayTransport); } fprintf(stderr, "%s: (length = %d) status: %d\n%s\n", __FUNCTION__, length, status, pemCert); free(pemCert); BIO_free(bio); if (status < 0) return -1; return (status == 0) ? 0 : 1; } /* ignore certificate verification if user explicitly required it (discouraged) */ if (tls->settings->IgnoreCertificate) return 1; /* success! */ /* if user explicitly specified a certificate name, use it instead of the hostname */ if (tls->settings->CertificateName) hostname = tls->settings->CertificateName; /* attempt verification using OpenSSL and the ~/.freerdp/certs certificate store */ certificate_status = x509_verify_certificate(cert, tls->certificate_store->path); /* verify certificate name match */ certificate_data = crypto_get_certificate_data(cert->px509, hostname); /* extra common name and alternative names */ common_name = crypto_cert_subject_common_name(cert->px509, &common_name_length); alt_names = crypto_cert_subject_alt_name(cert->px509, &alt_names_count, &alt_names_lengths); /* compare against common name */ if (common_name) { if (tls_match_hostname(common_name, common_name_length, hostname)) hostname_match = TRUE; } /* compare against alternative names */ if (alt_names) { for (index = 0; index < alt_names_count; index++) { if (tls_match_hostname(alt_names[index], alt_names_lengths[index], hostname)) { hostname_match = TRUE; break; } } } /* if the certificate is valid and the certificate name matches, verification succeeds */ if (certificate_status && hostname_match) { if (common_name) { free(common_name); common_name = NULL; } verification_status = TRUE; /* success! */ } /* if the certificate is valid but the certificate name does not match, warn user, do not accept */ if (certificate_status && !hostname_match) tls_print_certificate_name_mismatch_error(hostname, common_name, alt_names, alt_names_count); /* verification could not succeed with OpenSSL, use known_hosts file and prompt user for manual verification */ if (!certificate_status) { char* issuer; char* subject; char* fingerprint; freerdp* instance = (freerdp*) tls->settings->instance; BOOL accept_certificate = FALSE; issuer = crypto_cert_issuer(cert->px509); subject = crypto_cert_subject(cert->px509); fingerprint = crypto_cert_fingerprint(cert->px509); /* search for matching entry in known_hosts file */ match = certificate_data_match(tls->certificate_store, certificate_data); if (match == 1) { /* no entry was found in known_hosts file, prompt user for manual verification */ if (!hostname_match) tls_print_certificate_name_mismatch_error(hostname, common_name, alt_names, alt_names_count); if (instance->VerifyCertificate) { accept_certificate = instance->VerifyCertificate(instance, subject, issuer, fingerprint); } if (!accept_certificate) { /* user did not accept, abort and do not add entry in known_hosts file */ verification_status = FALSE; /* failure! */ } else { /* user accepted certificate, add entry in known_hosts file */ certificate_data_print(tls->certificate_store, certificate_data); verification_status = TRUE; /* success! */ } } else if (match == -1) { /* entry was found in known_hosts file, but fingerprint does not match. ask user to use it */ tls_print_certificate_error(hostname, fingerprint, tls->certificate_store->file); if (instance->VerifyChangedCertificate) { accept_certificate = instance->VerifyChangedCertificate(instance, subject, issuer, fingerprint, ""); } if (!accept_certificate) { /* user did not accept, abort and do not change known_hosts file */ verification_status = FALSE; /* failure! */ } else { /* user accepted new certificate, add replace fingerprint for this host in known_hosts file */ certificate_data_replace(tls->certificate_store, certificate_data); verification_status = TRUE; /* success! */ } } else if (match == 0) { verification_status = TRUE; /* success! */ } free(issuer); free(subject); free(fingerprint); } if (certificate_data) { free(certificate_data->fingerprint); free(certificate_data->hostname); free(certificate_data); } #ifndef _WIN32 if (common_name) free(common_name); #endif if (alt_names) crypto_cert_subject_alt_name_free(alt_names_count, alt_names_lengths, alt_names); return (verification_status == 0) ? 0 : 1; }