/** * lm_ssl_get_fingerprint: * @ssl: an #LmSSL * * Returns the fingerprint of the remote server's certificate. * * Return value: A null terminated string representing the fingerprint or %NULL if unknown. **/ const gchar * lm_ssl_get_fingerprint (LmSSL *ssl) { g_return_val_if_fail (ssl != NULL, NULL); return LM_SSL_BASE(ssl)->fingerprint; }
/** * lm_ssl_get_require_starttls: * * Return value: TRUE if @ssl requires that STARTTLS succeed. **/ gboolean lm_ssl_get_require_starttls (LmSSL *ssl) { LmSSLBase *base; base = LM_SSL_BASE (ssl); return base->require_starttls; }
/** * lm_ssl_ref: * @ssl: an #LmSSL * * Adds a reference to @ssl. * * Return value: the ssl **/ LmSSL * lm_ssl_ref (LmSSL *ssl) { g_return_val_if_fail (ssl != NULL, NULL); LM_SSL_BASE(ssl)->ref_count++; return ssl; }
void _lm_ssl_free (LmSSL *ssl) { SSL_CTX_free(ssl->ssl_ctx); ssl->ssl_ctx = NULL; _lm_ssl_base_free_fields (LM_SSL_BASE(ssl)); g_free (ssl); }
/** * lm_ssl_use_starttls: * @ssl: an #LmSSL * * Set whether STARTTLS should be used. **/ void lm_ssl_use_starttls (LmSSL *ssl, gboolean use_starttls, gboolean require_starttls) { LmSSLBase *base; base = LM_SSL_BASE (ssl); base->use_starttls = use_starttls; base->require_starttls = require_starttls; }
/** * lm_ssl_unref * @ssl: an #LmSSL * * Removes a reference from @ssl. When no more references are present * @ssl is freed. **/ void lm_ssl_unref (LmSSL *ssl) { LmSSLBase *base; g_return_if_fail (ssl != NULL); base = LM_SSL_BASE (ssl); base->ref_count --; if (base->ref_count == 0) { if (base->data_notify) { (* base->data_notify) (base->func_data); } _lm_ssl_free (ssl); } }
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; }
void _lm_ssl_free (LmSSL *ssl) { _lm_ssl_base_free_fields (LM_SSL_BASE (ssl)); g_free (ssl); }
gboolean _lm_ssl_begin (LmSSL *ssl, gint fd, const gchar *server, GError **error) { int ret; LmSSLBase *base; gboolean auth_ok = TRUE; base = LM_SSL_BASE(ssl); gnutls_init (&ssl->gnutls_session, GNUTLS_CLIENT); if (base->cipher_list) { gnutls_priority_set_direct (ssl->gnutls_session, base->cipher_list, NULL); } else { gnutls_priority_set_direct (ssl->gnutls_session, "NORMAL", NULL); } if (base->ca_path) { _lm_ssl_set_ca(ssl, base->ca_path); } else { gnutls_certificate_set_x509_system_trust(ssl->gnutls_xcred); } gnutls_credentials_set (ssl->gnutls_session, GNUTLS_CRD_CERTIFICATE, ssl->gnutls_xcred); gnutls_transport_set_ptr (ssl->gnutls_session, (gnutls_transport_ptr_t)(glong) fd); ret = gnutls_handshake (ssl->gnutls_session); if (ret >= 0) { auth_ok = ssl_verify_certificate (ssl, server); } if (ret < 0 || !auth_ok) { char *errmsg; if (!auth_ok) { errmsg = "authentication error"; } else { errmsg = "handshake failed"; } g_set_error (error, LM_ERROR, LM_ERROR_CONNECTION_OPEN, "*** GNUTLS %s: %s", errmsg, gnutls_strerror (ret)); return FALSE; } lm_verbose ("GNUTLS negotiated cipher suite: %s", gnutls_cipher_suite_get_name(gnutls_kx_get(ssl->gnutls_session), gnutls_cipher_get(ssl->gnutls_session), gnutls_mac_get(ssl->gnutls_session))); lm_verbose ("GNUTLS negotiated compression: %s", gnutls_compression_get_name (gnutls_compression_get (ssl->gnutls_session))); ssl->started = TRUE; return TRUE; }
/** * lm_ssl_set_ca: * @ssl: an #LmSSL * @ca_path: path to a certificate or a directory containing certificates * * Sets a path to certificates which should be trusted. * **/ void lm_ssl_set_ca (LmSSL *ssl, const gchar *ca_path) { _lm_ssl_base_set_ca_path(LM_SSL_BASE(ssl), ca_path); }
/** * lm_ssl_set_cipher_list: * @ssl: an #LmSSL * @cipher_list: list of ciphers * * Sets the list of availeble ciphers. * **/ void lm_ssl_set_cipher_list (LmSSL *ssl, const gchar *cipher_list) { _lm_ssl_base_set_cipher_list(LM_SSL_BASE(ssl), cipher_list); }
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; }
gboolean _lm_ssl_begin (LmSSL *ssl, gint fd, const gchar *server, GError **error) { gint ssl_ret; GIOStatus status; LmSSLBase *base; base = LM_SSL_BASE(ssl); if (!ssl->ssl_ctx) { g_set_error (error, LM_ERROR, LM_ERROR_CONNECTION_OPEN, "No SSL Context for OpenSSL"); return FALSE; } if (base->cipher_list) { SSL_CTX_set_cipher_list(ssl->ssl_ctx, base->cipher_list); } if (base->ca_path) { _lm_ssl_set_ca (ssl, base->ca_path); } else { SSL_CTX_set_default_verify_paths (ssl->ssl_ctx); } ssl->ssl = SSL_new(ssl->ssl_ctx); if (ssl->ssl == NULL) { g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "SSL_new() == NULL"); g_set_error(error, LM_ERROR, LM_ERROR_CONNECTION_OPEN, "SSL_new()"); return FALSE; } if (!SSL_set_fd (ssl->ssl, fd)) { g_log (LM_LOG_DOMAIN, LM_LOG_LEVEL_SSL, "SSL_set_fd() failed"); g_set_error(error, LM_ERROR, LM_ERROR_CONNECTION_OPEN, "SSL_set_fd()"); return FALSE; } /*ssl->bio = BIO_new_socket (fd, BIO_NOCLOSE); if (ssl->bio == NULL) { g_warning("BIO_new_socket() failed"); g_set_error(error, LM_ERROR, LM_ERROR_CONNECTION_OPEN, "BIO_new_socket()"); return FALSE; } SSL_set_bio(ssl->ssl, ssl->bio, ssl->bio);*/ do { ssl_ret = SSL_connect(ssl->ssl); if (ssl_ret <= 0) { status = ssl_io_status_from_return(ssl, ssl_ret); if (status != G_IO_STATUS_AGAIN) { ssl_print_state(ssl, "SSL_connect", ssl_ret); g_set_error(error, LM_ERROR, LM_ERROR_CONNECTION_OPEN, "SSL_connect()"); return FALSE; } } } while (ssl_ret <= 0); if (!ssl_verify_certificate (ssl, server)) { g_set_error (error, LM_ERROR, LM_ERROR_CONNECTION_OPEN, "*** SSL certificate verification failed"); 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; }