static gboolean ssl_verify_certificate (LmSSL *ssl, const gchar *server) { gboolean retval = TRUE; LmSSLBase *base; long verify_res; unsigned int digest_len; X509 *srv_crt; gchar *cn; X509_NAME *crt_subj; base = LM_SSL_BASE(ssl); g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: Cipher: %s/%s/%i\n", __FILE__, SSL_get_cipher_version(ssl->ssl), SSL_get_cipher_name(ssl->ssl), SSL_get_cipher_bits(ssl->ssl, NULL)); verify_res = SSL_get_verify_result(ssl->ssl); srv_crt = SSL_get_peer_certificate(ssl->ssl); if (base->expected_fingerprint != NULL) { X509_digest(srv_crt, EVP_md5(), (guchar *) base->fingerprint, &digest_len); if (memcmp(base->expected_fingerprint, base->fingerprint, digest_len) != 0) { if (base->func(ssl, LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } } g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: SSL_get_verify_result() = %ld\n", __FILE__, verify_res); switch (verify_res) { case X509_V_OK: break; case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: /* special case for self signed certificates? */ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: case X509_V_ERR_UNABLE_TO_GET_CRL: case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: if (base->func(ssl, LM_SSL_STATUS_NO_CERT_FOUND, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } break; case X509_V_ERR_INVALID_CA: case X509_V_ERR_CERT_UNTRUSTED: case X509_V_ERR_CERT_REVOKED: if (base->func(ssl, LM_SSL_STATUS_UNTRUSTED_CERT, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } break; case X509_V_ERR_CERT_NOT_YET_VALID: case X509_V_ERR_CRL_NOT_YET_VALID: if (base->func(ssl, LM_SSL_STATUS_CERT_NOT_ACTIVATED, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } break; case X509_V_ERR_CERT_HAS_EXPIRED: case X509_V_ERR_CRL_HAS_EXPIRED: if (base->func(ssl, LM_SSL_STATUS_CERT_EXPIRED, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } break; default: if (base->func(ssl, LM_SSL_STATUS_GENERIC_ERROR, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } } /*if (retval == FALSE) { g_set_error (error, LM_ERROR, LM_ERROR_CONNECTION_OPEN, ssl_get_x509_err(verify_res), NULL); }*/ crt_subj = X509_get_subject_name(srv_crt); cn = (gchar *) g_malloc0(LM_SSL_CN_MAX + 1); if (X509_NAME_get_text_by_NID(crt_subj, NID_commonName, cn, LM_SSL_CN_MAX) > 0) { gchar *domain = cn; g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: server = '%s', cn = '%s'\n", __FILE__, server, cn); if ((cn[0] == '*') && (cn[1] == '.')) { domain = strstr (cn, server); } if ((domain == NULL) || (strncmp (server, domain, LM_SSL_CN_MAX) != 0)) { if (base->func (ssl, LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } } } else { g_warning ("X509_NAME_get_text_by_NID() failed"); } g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s:\n\tIssuer: %s\n\tSubject: %s\n\tFor: %s\n", __FILE__, X509_NAME_oneline(X509_get_issuer_name(srv_crt), NULL, 0), X509_NAME_oneline(X509_get_subject_name(srv_crt), NULL, 0), cn); g_free(cn); return retval; }
static gboolean ssl_verify_certificate (LmSSL *ssl, const gchar *server) { LmSSLBase *base; unsigned int status; int rc; base = LM_SSL_BASE (ssl); /* This verification function uses the trusted CAs in the credentials * structure. So you must have installed one or more CA certificates. */ rc = gnutls_certificate_verify_peers2 (ssl->gnutls_session, &status); if (rc == GNUTLS_E_NO_CERTIFICATE_FOUND) { if (base->func (ssl, LM_SSL_STATUS_NO_CERT_FOUND, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } if (rc != 0) { if (base->func (ssl, LM_SSL_STATUS_GENERIC_ERROR, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } if (rc == GNUTLS_E_NO_CERTIFICATE_FOUND) { if (base->func (ssl, LM_SSL_STATUS_NO_CERT_FOUND, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } if (status & GNUTLS_CERT_INVALID || status & GNUTLS_CERT_REVOKED) { if (base->func (ssl, LM_SSL_STATUS_UNTRUSTED_CERT, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } if (gnutls_certificate_expiration_time_peers (ssl->gnutls_session) < time (0)) { if (base->func (ssl, LM_SSL_STATUS_CERT_EXPIRED, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } if (gnutls_certificate_activation_time_peers (ssl->gnutls_session) > time (0)) { if (base->func (ssl, LM_SSL_STATUS_CERT_NOT_ACTIVATED, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } if (gnutls_certificate_type_get (ssl->gnutls_session) == GNUTLS_CRT_X509) { const gnutls_datum_t* cert_list; guint cert_list_size; gnutls_digest_algorithm_t digest = GNUTLS_DIG_SHA256; guchar digest_bin[LM_FINGERPRINT_LENGTH]; size_t digest_size; gnutls_x509_crt_t cert; cert_list = gnutls_certificate_get_peers (ssl->gnutls_session, &cert_list_size); if (cert_list == NULL) { if (base->func (ssl, LM_SSL_STATUS_NO_CERT_FOUND, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } gnutls_x509_crt_init (&cert); if (gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER) != 0) { if (base->func (ssl, LM_SSL_STATUS_NO_CERT_FOUND, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } if (!gnutls_x509_crt_check_hostname (cert, server)) { if (base->func (ssl, LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } gnutls_x509_crt_deinit (cert); digest_size = gnutls_hash_get_len(digest); g_assert(digest_size < sizeof(digest_bin)); if (gnutls_fingerprint (digest, &cert_list[0], digest_bin, &digest_size) >= 0) { _lm_ssl_base_set_fingerprint(base, digest_bin, digest_size); if (_lm_ssl_base_check_fingerprint(base) != 0 && base->func (ssl, LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } else if (base->func (ssl, LM_SSL_STATUS_GENERIC_ERROR, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } return TRUE; }
/* side effect: fills the ssl->fingerprint buffer */ static gboolean ssl_verify_certificate (LmSSL *ssl, const gchar *server) { gboolean retval = TRUE; gboolean match_result = FALSE; LmSSLBase *base; long verify_res; int rc; const EVP_MD *digest = EVP_sha256(); unsigned int digest_len; guchar digest_bin[EVP_MD_size(digest)]; X509 *srv_crt; gchar *cn; X509_NAME *crt_subj; base = LM_SSL_BASE(ssl); g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: Cipher: %s/%s/%i\n", __FILE__, SSL_get_cipher_version(ssl->ssl), SSL_get_cipher_name(ssl->ssl), SSL_get_cipher_bits(ssl->ssl, NULL)); verify_res = SSL_get_verify_result(ssl->ssl); srv_crt = SSL_get_peer_certificate(ssl->ssl); rc = X509_digest(srv_crt, digest, digest_bin, &digest_len); if ((rc != 0) && (digest_len == EVP_MD_size(digest))) { _lm_ssl_base_set_fingerprint(base, digest_bin, digest_len); if (_lm_ssl_base_check_fingerprint(base) != 0) { if (base->func(ssl, LM_SSL_STATUS_CERT_FINGERPRINT_MISMATCH, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } } else { if (base->func(ssl, LM_SSL_STATUS_GENERIC_ERROR, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { return FALSE; } } g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: SSL_get_verify_result() = %ld\n", __FILE__, verify_res); switch (verify_res) { case X509_V_OK: break; case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: case X509_V_ERR_UNABLE_TO_GET_CRL: if (base->func(ssl, LM_SSL_STATUS_NO_CERT_FOUND, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } break; case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: /* special case for self signed certificates? */ case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: case X509_V_ERR_INVALID_CA: case X509_V_ERR_CERT_UNTRUSTED: case X509_V_ERR_CERT_REVOKED: if (base->func(ssl, LM_SSL_STATUS_UNTRUSTED_CERT, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } break; case X509_V_ERR_CERT_NOT_YET_VALID: case X509_V_ERR_CRL_NOT_YET_VALID: if (base->func(ssl, LM_SSL_STATUS_CERT_NOT_ACTIVATED, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } break; case X509_V_ERR_CERT_HAS_EXPIRED: case X509_V_ERR_CRL_HAS_EXPIRED: if (base->func(ssl, LM_SSL_STATUS_CERT_EXPIRED, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } break; default: if (base->func(ssl, LM_SSL_STATUS_GENERIC_ERROR, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } } /*if (retval == FALSE) { g_set_error (error, LM_ERROR, LM_ERROR_CONNECTION_OPEN, ssl_get_x509_err(verify_res), NULL); }*/ crt_subj = X509_get_subject_name(srv_crt); cn = (gchar *) g_malloc0(LM_SSL_CN_MAX + 1); /* FWB: deprecated call, can only get first entry */ if (X509_NAME_get_text_by_NID(crt_subj, NID_commonName, cn, LM_SSL_CN_MAX) > 0) { g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: server = '%s', cn = '%s'\n", __FILE__, server, cn); if (cn != NULL && ssl_match_domain_name(server, cn)) { match_result = TRUE; } else { /* g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: CN does not match server name\n", __FILE__); */ } } else { g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "X509_NAME_get_text_by_NID() failed"); } /* RFC6125: "...However, it is perfectly acceptable for the subject field to be empty, * as long as the certificate contains a subject alternative name ("subjectAltName") * extension that includes at least one subjectAltName entry" */ if (!match_result) { /* FWB: CN doesn't match, try SANs */ int subject_alt_names_nb = -1; int san_counter; STACK_OF(GENERAL_NAME) *subject_alt_names = NULL; // Try to extract the names within the SAN extension from the certificate subject_alt_names = X509_get_ext_d2i((X509 *) srv_crt, NID_subject_alt_name, NULL, NULL); if (subject_alt_names != NULL) { // Check each name within the extension subject_alt_names_nb = sk_GENERAL_NAME_num(subject_alt_names); for (san_counter=0; san_counter<subject_alt_names_nb; san_counter++) { const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(subject_alt_names, san_counter); if (current_name->type == GEN_DNS) { // Current name is a DNS name, let's check it, it's ASCII if (ssl_match_domain_name(server, (const char *)current_name->d.dNSName->data)) { g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: found SAN '%s' - MATCH\n", __FILE__, current_name->d.dNSName->data); match_result = TRUE; /* break; */ } else { g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s: found SAN '%s'\n", __FILE__, current_name->d.dNSName->data); } } } } sk_GENERAL_NAME_pop_free(subject_alt_names, GENERAL_NAME_free); } if (!match_result) { if (base->func (ssl, LM_SSL_STATUS_CERT_HOSTNAME_MISMATCH, base->func_data) != LM_SSL_RESPONSE_CONTINUE) { retval = FALSE; } } g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "%s:\n\tIssuer: %s\n\tSubject: %s\n\tFor: %s\n", __FILE__, X509_NAME_oneline(X509_get_issuer_name(srv_crt), NULL, 0), X509_NAME_oneline(X509_get_subject_name(srv_crt), NULL, 0), cn); g_free(cn); return retval; }