int pn_ssl_domain_set_trusted_ca_db(pn_ssl_domain_t *domain, const char *certificate_db) { if (!domain) return -1; // certificates can be either a file or a directory, which determines how it is passed // to SSL_CTX_load_verify_locations() struct stat sbuf; if (stat( certificate_db, &sbuf ) != 0) { pn_transport_logf(NULL, "stat(%s) failed: %s", certificate_db, strerror(errno)); return -1; } const char *file; const char *dir; if (S_ISDIR(sbuf.st_mode)) { dir = certificate_db; file = NULL; } else { dir = NULL; file = certificate_db; } if (SSL_CTX_load_verify_locations( domain->ctx, file, dir ) != 1) { ssl_log_error("SSL_CTX_load_verify_locations( %s ) failed", certificate_db); return -1; } domain->has_ca_db = true; return 0; }
int pn_ssl_domain_set_credentials( pn_ssl_domain_t *domain, const char *certificate_file, const char *private_key_file, const char *password) { if (!domain || !domain->ctx) return -1; if (SSL_CTX_use_certificate_chain_file(domain->ctx, certificate_file) != 1) { ssl_log_error("SSL_CTX_use_certificate_chain_file( %s ) failed", certificate_file); return -3; } if (password) { domain->keyfile_pw = pn_strdup(password); // @todo: obfuscate me!!! SSL_CTX_set_default_passwd_cb(domain->ctx, keyfile_pw_cb); SSL_CTX_set_default_passwd_cb_userdata(domain->ctx, domain->keyfile_pw); } if (SSL_CTX_use_PrivateKey_file(domain->ctx, private_key_file, SSL_FILETYPE_PEM) != 1) { ssl_log_error("SSL_CTX_use_PrivateKey_file( %s ) failed", private_key_file); return -4; } if (SSL_CTX_check_private_key(domain->ctx) != 1) { ssl_log_error("The key file %s is not consistent with the certificate %s", private_key_file, certificate_file); return -5; } domain->has_certificate = true; // bug in older versions of OpenSSL: servers may request client cert even if anonymous // cipher was negotiated. TLSv1 will reject such a request. Hack: once a cert is // configured, allow only authenticated ciphers. if (!SSL_CTX_set_cipher_list( domain->ctx, CIPHERS_AUTHENTICATE )) { ssl_log_error("Failed to set cipher list to %s", CIPHERS_AUTHENTICATE); return -6; } return 0; }
const char* pn_ssl_get_remote_subject_subfield(pn_ssl_t *ssl0, pn_ssl_cert_subject_subfield field) { int openssl_field = 0; // Assign openssl internal representations of field values to openssl_field switch (field) { case PN_SSL_CERT_SUBJECT_COUNTRY_NAME : openssl_field = NID_countryName; break; case PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE : openssl_field = NID_stateOrProvinceName; break; case PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY : openssl_field = NID_localityName; break; case PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME : openssl_field = NID_organizationName; break; case PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT : openssl_field = NID_organizationalUnitName; break; case PN_SSL_CERT_SUBJECT_COMMON_NAME : openssl_field = NID_commonName; break; default: ssl_log_error("Unknown or unhandled certificate subject subfield %i \n", field); return NULL; } pni_ssl_t *ssl = get_ssl_internal(ssl0); X509 *cert = get_peer_certificate(ssl); X509_NAME *subject_name = X509_get_subject_name(cert); // TODO (gmurthy) - A server side cert subject field can have more than one common name like this - Subject: CN=www.domain1.com, CN=www.domain2.com, see https://bugzilla.mozilla.org/show_bug.cgi?id=380656 // For now, we will only return the first common name if there is more than one common name in the cert int index = X509_NAME_get_index_by_NID(subject_name, openssl_field, -1); if (index > -1) { X509_NAME_ENTRY *ne = X509_NAME_get_entry(subject_name, index); if(ne) { ASN1_STRING *name_asn1 = X509_NAME_ENTRY_get_data(ne); return (char *) name_asn1->data; } } return NULL; }
pn_ssl_domain_t *pn_ssl_domain( pn_ssl_mode_t mode ) { if (!ssl_initialized) { ssl_initialized = 1; SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); ssl_ex_data_index = SSL_get_ex_new_index( 0, (void *) "org.apache.qpid.proton.ssl", NULL, NULL, NULL); } pn_ssl_domain_t *domain = (pn_ssl_domain_t *) calloc(1, sizeof(pn_ssl_domain_t)); if (!domain) return NULL; domain->ref_count = 1; domain->mode = mode; // enable all supported protocol versions, then explicitly disable the // known vulnerable ones. This should allow us to use the latest version // of the TLS standard that the installed library supports. switch(mode) { case PN_SSL_MODE_CLIENT: domain->ctx = SSL_CTX_new(SSLv23_client_method()); // and TLSv1+ if (!domain->ctx) { ssl_log_error("Unable to initialize OpenSSL context."); free(domain); return NULL; } break; case PN_SSL_MODE_SERVER: domain->ctx = SSL_CTX_new(SSLv23_server_method()); // and TLSv1+ if (!domain->ctx) { ssl_log_error("Unable to initialize OpenSSL context."); free(domain); return NULL; } break; default: pn_transport_logf(NULL, "Invalid value for pn_ssl_mode_t: %d", mode); free(domain); return NULL; } const long reject_insecure = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; SSL_CTX_set_options(domain->ctx, reject_insecure); #ifdef SSL_OP_NO_COMPRESSION // Mitigate the CRIME vulnerability SSL_CTX_set_options(domain->ctx, SSL_OP_NO_COMPRESSION); #endif // by default, allow anonymous ciphers so certificates are not required 'out of the box' if (!SSL_CTX_set_cipher_list( domain->ctx, CIPHERS_ANONYMOUS )) { ssl_log_error("Failed to set cipher list to %s", CIPHERS_ANONYMOUS); pn_ssl_domain_free(domain); return NULL; } // ditto: by default do not authenticate the peer (can be done by SASL). if (pn_ssl_domain_set_peer_authentication( domain, PN_SSL_ANONYMOUS_PEER, NULL )) { pn_ssl_domain_free(domain); return NULL; } DH *dh = get_dh2048(); if (dh) { SSL_CTX_set_tmp_dh(domain->ctx, dh); DH_free(dh); SSL_CTX_set_options(domain->ctx, SSL_OP_SINGLE_DH_USE); } return domain; }
int pn_ssl_get_cert_fingerprint(pn_ssl_t *ssl0, char *fingerprint, size_t fingerprint_length, pn_ssl_hash_alg hash_alg) { const char *digest_name = NULL; size_t min_required_length; // old versions of python expect fingerprint to contain a valid string on // return from this function fingerprint[0] = 0; // Assign the correct digest_name value based on the enum values. switch (hash_alg) { case PN_SSL_SHA1 : min_required_length = 41; // 40 hex characters + 1 '\0' character digest_name = "sha1"; break; case PN_SSL_SHA256 : min_required_length = 65; // 64 hex characters + 1 '\0' character digest_name = "sha256"; break; case PN_SSL_SHA512 : min_required_length = 129; // 128 hex characters + 1 '\0' character digest_name = "sha512"; break; case PN_SSL_MD5 : min_required_length = 33; // 32 hex characters + 1 '\0' character digest_name = "md5"; break; default: ssl_log_error("Unknown or unhandled hash algorithm %i \n", hash_alg); return PN_ERR; } if(fingerprint_length < min_required_length) { ssl_log_error("Insufficient fingerprint_length %i. fingerprint_length must be %i or above for %s digest\n", fingerprint_length, min_required_length, digest_name); return PN_ERR; } const EVP_MD *digest = EVP_get_digestbyname(digest_name); pni_ssl_t *ssl = get_ssl_internal(ssl0); X509 *cert = get_peer_certificate(ssl); if(cert) { unsigned int len; unsigned char bytes[64]; // sha512 uses 64 octets, we will use that as the maximum. if (X509_digest(cert, digest, bytes, &len) != 1) { ssl_log_error("Failed to extract X509 digest\n"); return PN_ERR; } char *cursor = fingerprint; for (size_t i=0; i<len ; i++) { cursor += snprintf((char *)cursor, fingerprint_length, "%02x", bytes[i]); fingerprint_length = fingerprint_length - 2; } return PN_OK; } else { ssl_log_error("No certificate is available yet \n"); return PN_ERR; } return 0; }