Skip to content

Commit

Permalink
implement certificate verification API for GnuTLS
Browse files Browse the repository at this point in the history
Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
  • Loading branch information
sjaeckel committed Oct 26, 2021
1 parent 3b10131 commit e490d01
Showing 1 changed file with 154 additions and 11 deletions.
165 changes: 154 additions & 11 deletions src/tls_gnutls.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -173,18 +170,156 @@ unsigned int tls_id_on_xmppaddr_num(xmpp_conn_t *conn)
return ret;
}

static xmpp_tlscert_t *_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 = _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) {
tlscert_free(tlscert);
gnutls_x509_crt_deinit(cert);
gnutls_free(out.data);
return -1;
}
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));

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);
Expand All @@ -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);
Expand All @@ -221,11 +359,16 @@ void tls_free(tls_t *tls)

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);
Expand All @@ -237,9 +380,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;
}
Expand Down

0 comments on commit e490d01

Please sign in to comment.