From 795675d1aa9a2e18e7bbe774e29716759fb1ac39 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Tue, 26 Oct 2021 16:18:13 +0200 Subject: [PATCH] implement certificate verification API for GnuTLS Signed-off-by: Steffen Jaeckel --- src/tls_gnutls.c | 185 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 174 insertions(+), 11 deletions(-) diff --git a/src/tls_gnutls.c b/src/tls_gnutls.c index bd754cc..f85bf8a 100644 --- a/src/tls_gnutls.c +++ b/src/tls_gnutls.c @@ -22,12 +22,9 @@ #include "tls.h" #include "sock.h" -/* FIXME this shouldn't be a constant string */ -#define CAFILE "/etc/ssl/certs/ca-certificates.crt" - struct _tls { xmpp_ctx_t *ctx; /* do we need this? */ - sock_t sock; + xmpp_conn_t *conn; gnutls_session_t session; gnutls_certificate_credentials_t cred; gnutls_x509_crt_t client_cert; @@ -173,6 +170,144 @@ unsigned int tls_id_on_xmppaddr_num(xmpp_conn_t *conn) return ret; } +static xmpp_tlscert_t *_x509_to_tlscert(xmpp_ctx_t *ctx, gnutls_x509_crt_t cert) +{ + int res; + char buf[512], smallbuf[64]; + size_t size, m; + unsigned int algo, n; + gnutls_datum_t data; + time_t time_val; + xmpp_tlscert_t *tlscert = tlscert_new(ctx); + + gnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_PEM, &data); + tlscert->pem = xmpp_alloc(ctx, data.size + 1); + memcpy(tlscert->pem, data.data, data.size); + tlscert->pem[data.size] = '\0'; + gnutls_free(data.data); + + size = sizeof(buf); + gnutls_x509_crt_get_dn(cert, buf, &size); + tlscert->elements[XMPP_CERT_SUBJECT] = xmpp_strdup(ctx, buf); + size = sizeof(buf); + gnutls_x509_crt_get_issuer_dn(cert, buf, &size); + tlscert->elements[XMPP_CERT_ISSUER] = xmpp_strdup(ctx, buf); + + time_val = gnutls_x509_crt_get_activation_time(cert); + tlscert->elements[XMPP_CERT_NOTBEFORE] = xmpp_strdup(ctx, ctime(&time_val)); + tlscert->elements[XMPP_CERT_NOTBEFORE] + [strlen(tlscert->elements[XMPP_CERT_NOTBEFORE]) - 1] = + '\0'; + time_val = gnutls_x509_crt_get_expiration_time(cert); + tlscert->elements[XMPP_CERT_NOTAFTER] = xmpp_strdup(ctx, ctime(&time_val)); + tlscert->elements[XMPP_CERT_NOTAFTER] + [strlen(tlscert->elements[XMPP_CERT_NOTAFTER]) - 1] = '\0'; + + size = sizeof(smallbuf); + gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA1, smallbuf, &size); + hex_encode(buf, smallbuf, size); + tlscert->elements[XMPP_CERT_FINGERPRINT_SHA1] = xmpp_strdup(ctx, buf); + size = sizeof(smallbuf); + gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA256, smallbuf, &size); + hex_encode(buf, smallbuf, size); + tlscert->elements[XMPP_CERT_FINGERPRINT_SHA256] = xmpp_strdup(ctx, buf); + + xmpp_snprintf(buf, sizeof(buf), "%d", gnutls_x509_crt_get_version(cert)); + tlscert->elements[XMPP_CERT_VERSION] = xmpp_strdup(ctx, buf); + + algo = gnutls_x509_crt_get_pk_algorithm(cert, NULL); + tlscert->elements[XMPP_CERT_KEYALG] = + xmpp_strdup(ctx, gnutls_pk_algorithm_get_name(algo)); + algo = gnutls_x509_crt_get_signature_algorithm(cert); + tlscert->elements[XMPP_CERT_SIGALG] = + xmpp_strdup(ctx, gnutls_sign_get_name(algo)); + + size = sizeof(smallbuf); + gnutls_x509_crt_get_serial(cert, smallbuf, &size); + hex_encode(buf, smallbuf, size); + tlscert->elements[XMPP_CERT_SERIALNUMBER] = xmpp_strdup(ctx, buf); + + for (n = 0, m = 0, res = 0; res != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + ++n) { + size = sizeof(buf); + res = gnutls_x509_crt_get_subject_alt_name(cert, n, buf, &size, NULL); + if (res == GNUTLS_SAN_DNSNAME) { + if (tlscert_add_dnsname(tlscert, buf)) + xmpp_debug(ctx, "tls", "Can't store dnsName(%zu): %s", m, buf); + m++; + } + } + + return tlscert; +} + +static int _tls_verify(gnutls_session_t session) +{ + tls_t *tls = gnutls_session_get_ptr(session); + const gnutls_datum_t *cert_list; + gnutls_certificate_type_t type; + gnutls_datum_t out; + unsigned int cert_list_size = 0, status; + gnutls_x509_crt_t cert; + + if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) + return -1; + + if (gnutls_certificate_verify_peers2(session, &status) < 0) { + xmpp_error(tls->ctx, "tls", "Verify peers failed"); + return -1; + } + type = gnutls_certificate_type_get(session); + if (gnutls_certificate_verification_status_print(status, type, &out, 0) < + 0) { + xmpp_error(tls->ctx, "tls", "Status print failed"); + return -1; + } + + /* Return early if the Certificate is trusted + * OR if we trust all Certificates */ + if (status == 0 || tls->conn->tls_trust) + return 0; + + if (!tls->conn->certfail_handler) { + xmpp_error(tls->ctx, "tls", + "No certfail handler set, canceling connection attempt"); + return -1; + } + + cert_list = gnutls_certificate_get_peers(session, &cert_list_size); + + /* OpenSSL displays the certificate chain in reverse order than GnuTLS. + * To show consistent behavior to the user, traverse the list from the + * end. + */ + while (cert_list_size--) { + gnutls_x509_crt_init(&cert); + + gnutls_x509_crt_import(cert, &cert_list[cert_list_size], + GNUTLS_X509_FMT_DER); + + xmpp_tlscert_t *tlscert = _x509_to_tlscert(tls->ctx, cert); + + if (!tlscert) { + gnutls_x509_crt_deinit(cert); + gnutls_free(out.data); + return -1; + } + + if (tls->conn->certfail_handler(tlscert, (char *)out.data) == 0) { + xmpp_tlscert_free(tlscert); + gnutls_x509_crt_deinit(cert); + gnutls_free(out.data); + return -1; + } + xmpp_tlscert_free(tlscert); + gnutls_x509_crt_deinit(cert); + } + gnutls_free(out.data); + return 0; +} + tls_t *tls_new(xmpp_conn_t *conn) { tls_t *tls = xmpp_alloc(conn->ctx, sizeof(tls_t)); @@ -180,11 +315,11 @@ tls_t *tls_new(xmpp_conn_t *conn) if (tls) { memset(tls, 0, sizeof(*tls)); tls->ctx = conn->ctx; - tls->sock = conn->sock; + tls->conn = conn; gnutls_init(&tls->session, GNUTLS_CLIENT); gnutls_certificate_allocate_credentials(&tls->cred); - tls_set_credentials(tls, CAFILE); + tls_set_credentials(tls, NULL); if (conn->tls_client_cert && conn->tls_client_key) { tls->client_cert = _tls_load_cert(conn); @@ -201,7 +336,10 @@ tls_t *tls_new(xmpp_conn_t *conn) GNUTLS_X509_FMT_PEM); } + gnutls_certificate_set_verify_function(tls->cred, _tls_verify); + gnutls_set_default_priority(tls->session); + gnutls_session_set_ptr(tls->session, tls); /* fixme: this may require setting a callback on win32? */ gnutls_transport_set_int(tls->session, conn->sock); @@ -219,13 +357,38 @@ void tls_free(tls_t *tls) xmpp_free(tls->ctx, tls); } +xmpp_tlscert_t *tls_peer_cert(xmpp_conn_t *conn) +{ + xmpp_tlscert_t *tlscert = NULL; + if (conn && conn->tls && conn->tls->session) { + unsigned int list_size = 0; + const gnutls_datum_t *der_cert = + gnutls_certificate_get_peers(conn->tls->session, &list_size); + if (der_cert && list_size) { + gnutls_x509_crt_t cert; + if (gnutls_x509_crt_init(&cert) < 0) + return NULL; + if (gnutls_x509_crt_import(cert, der_cert, GNUTLS_X509_FMT_DER) == + 0) + tlscert = _x509_to_tlscert(conn->ctx, cert); + gnutls_x509_crt_deinit(cert); + } + } + return tlscert; +} + int tls_set_credentials(tls_t *tls, const char *cafilename) { - int err; + UNUSED(cafilename); /* set trusted credentials -- takes a .pem filename */ - err = gnutls_certificate_set_x509_trust_file(tls->cred, cafilename, - GNUTLS_X509_FMT_PEM); + int err = gnutls_certificate_set_x509_system_trust(tls->cred); + if (err >= 0 && tls->conn->tls_cafile) + err = gnutls_certificate_set_x509_trust_file( + tls->cred, tls->conn->tls_cafile, GNUTLS_X509_FMT_PEM); + if (err >= 0 && tls->conn->tls_capath) + err = gnutls_certificate_set_x509_trust_dir( + tls->cred, tls->conn->tls_capath, GNUTLS_X509_FMT_PEM); if (err >= 0) { err = gnutls_credentials_set(tls->session, GNUTLS_CRD_CERTIFICATE, tls->cred); @@ -237,9 +400,9 @@ int tls_set_credentials(tls_t *tls, const char *cafilename) int tls_start(tls_t *tls) { - sock_set_blocking(tls->sock); + sock_set_blocking(tls->conn->sock); tls->lasterror = gnutls_handshake(tls->session); - sock_set_nonblocking(tls->sock); + sock_set_nonblocking(tls->conn->sock); return tls->lasterror == GNUTLS_E_SUCCESS; }