From f23ac83c9541fa2a6b3d17f14470a2202965f6fa Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Tue, 26 Oct 2021 14:09:58 +0200 Subject: [PATCH] add callback functionality on certificate verification failure Based on the differences to libmesode this functionality has been added. It allows a library-user to set a callback for cases where the TLS stack can't verify a received certificate and let the end-user decide what to do. examples/basic implements an example handler of said functionality. Signed-off-by: Steffen Jaeckel --- Makefile.am | 1 + examples/basic.c | 59 ++++++++++++- src/common.h | 3 + src/conn.c | 68 +++++++++++++++ src/tls.c | 214 +++++++++++++++++++++++++++++++++++++++++++++++ src/tls.h | 14 ++++ strophe.h | 58 +++++++++++++ 7 files changed, 415 insertions(+), 2 deletions(-) create mode 100644 src/tls.c diff --git a/Makefile.am b/Makefile.am index 8c9d039..48ad85d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -48,6 +48,7 @@ libstrophe_la_SOURCES = \ src/snprintf.c \ src/sock.c \ src/stanza.c \ + src/tls.c \ src/util.c \ src/uuid.c libstrophe_la_SOURCES += \ diff --git a/examples/basic.c b/examples/basic.c index a4ef340..491873b 100644 --- a/examples/basic.c +++ b/examples/basic.c @@ -19,6 +19,20 @@ #define KA_TIMEOUT 60 #define KA_INTERVAL 1 +static void print_tlscert(const xmpp_tlscert_t *cert) +{ + const char *name; + size_t n; + for (n = 0; n < (unsigned)XMPP_CERT_ELEMENT_MAX; ++n) { + printf("\t%32s: %s\n", xmpp_tlscert_get_description(n), + xmpp_tlscert_get_string(cert, n)); + } + n = 0; + while ((name = xmpp_tlscert_get_dnsname(cert, n++)) != NULL) + printf("\t%32s: %s\n", "dnsName", name); + printf("PEM:\n%s\n", xmpp_tlscert_get_pem(cert)); +} + /* define a handler for connection events */ static void conn_handler(xmpp_conn_t *conn, xmpp_conn_event_t status, @@ -37,6 +51,11 @@ static void conn_handler(xmpp_conn_t *conn, secured = xmpp_conn_is_secured(conn); fprintf(stderr, "DEBUG: connection is %s.\n", secured ? "secured" : "NOT secured"); + if (secured) { + xmpp_tlscert_t *cert = xmpp_conn_get_peer_cert(conn); + print_tlscert(cert); + xmpp_tlscert_free(cert); + } xmpp_disconnect(conn); } else { fprintf(stderr, "DEBUG: disconnected\n"); @@ -44,6 +63,23 @@ static void conn_handler(xmpp_conn_t *conn, } } +static int certfail_handler(const xmpp_tlscert_t *cert, + const char *const errormsg) +{ + char read_char[16] = {0}; + printf("Received certificate can't be validated!\n"); + printf("Reason: %s\n", errormsg); + print_tlscert(cert); + printf("Do you agree to connect?\n[y(es)|n(o)]: "); + fflush(stdout); + if (fgets(read_char, sizeof(read_char), stdin) == NULL) { + printf("fgets() failed\n"); + return 0; + } + printf("\n"); + return read_char[0] == 'y' || read_char[0] == 'Y'; +} + static void usage(int exit_code) { fprintf(stderr, @@ -52,10 +88,15 @@ static void usage(int exit_code) " --jid The JID to use to authenticate.\n" " --pass The password of the JID.\n" " --tls-cert Path to client certificate.\n" + " --capath Path to an additional CA trust store " + "(directory).\n" + " --cafile Path to an additional CA trust store " + "(single file).\n" " --tls-key Path to private key.\n\n" " --disable-tls Disable TLS.\n" " --mandatory-tls Deny plaintext connection.\n" " --trust-tls Trust TLS certificate.\n" + " --enable-certfail Enable certfail handler.\n" " --legacy-ssl Use old style SSL.\n" " --legacy-auth Allow legacy authentication.\n" " --verbose Increase the verbosity level.\n" @@ -71,9 +112,10 @@ int main(int argc, char **argv) xmpp_ctx_t *ctx; xmpp_conn_t *conn; xmpp_log_t *log; - char *jid = NULL, *password = NULL, *cert = NULL, *key = NULL, *host = NULL; + char *jid = NULL, *password = NULL, *cert = NULL, *key = NULL, *host = NULL, + *capath = NULL, *cafile = NULL; long flags = 0; - int tcp_keepalive = 0, verbosity = 0; + int tcp_keepalive = 0, verbosity = 0, certfail = 0; int i; unsigned long port = 0; @@ -95,6 +137,8 @@ int main(int argc, char **argv) verbosity++; else if (strcmp(argv[i], "--tcp-keepalive") == 0) tcp_keepalive = 1; + else if (strcmp(argv[i], "--enable-certfail") == 0) + certfail = 1; else if ((strcmp(argv[i], "--jid") == 0) && (++i < argc)) jid = argv[i]; else if ((strcmp(argv[i], "--pass") == 0) && (++i < argc)) @@ -103,6 +147,10 @@ int main(int argc, char **argv) cert = argv[i]; else if ((strcmp(argv[i], "--tls-key") == 0) && (++i < argc)) key = argv[i]; + else if ((strcmp(argv[i], "--capath") == 0) && (++i < argc)) + capath = argv[i]; + else if ((strcmp(argv[i], "--cafile") == 0) && (++i < argc)) + cafile = argv[i]; else break; } @@ -147,6 +195,13 @@ int main(int argc, char **argv) if (password) xmpp_conn_set_pass(conn, password); + if (certfail) + xmpp_conn_set_certfail_handler(conn, certfail_handler); + if (capath) + xmpp_conn_set_capath(conn, capath); + if (cafile) + xmpp_conn_set_cafile(conn, cafile); + /* initiate connection */ if (xmpp_connect_client(conn, host, port, conn_handler, ctx) == XMPP_EOK) { diff --git a/src/common.h b/src/common.h index 4f2a363..4941831 100644 --- a/src/common.h +++ b/src/common.h @@ -169,6 +169,8 @@ struct _xmpp_conn_t { int tls_mandatory; int tls_legacy_ssl; int tls_trust; + char *tls_cafile; + char *tls_capath; char *tls_client_cert; char *tls_client_key; int tls_failed; /* set when tls fails, so we don't try again */ @@ -176,6 +178,7 @@ struct _xmpp_conn_t { mechanisms */ int auth_legacy_enabled; int secured; /* set when stream is secured with TLS */ + xmpp_certfail_handler certfail_handler; /* if server returns or we must do them */ int bind_required; diff --git a/src/conn.c b/src/conn.c index f1bbd87..c2ae7e8 100644 --- a/src/conn.c +++ b/src/conn.c @@ -144,11 +144,14 @@ xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx) conn->tls_legacy_ssl = 0; conn->tls_trust = 0; conn->tls_failed = 0; + conn->tls_cafile = NULL; + conn->tls_capath = NULL; conn->tls_client_cert = NULL; conn->tls_client_key = NULL; conn->sasl_support = 0; conn->auth_legacy_enabled = 0; conn->secured = 0; + conn->certfail_handler = NULL; conn->bind_required = 0; conn->session_required = 0; @@ -345,6 +348,10 @@ int xmpp_conn_release(xmpp_conn_t *conn) xmpp_free(ctx, conn->tls_client_cert); if (conn->tls_client_key) xmpp_free(ctx, conn->tls_client_key); + if (conn->tls_cafile) + xmpp_free(ctx, conn->tls_cafile); + if (conn->tls_capath) + xmpp_free(ctx, conn->tls_capath); xmpp_free(ctx, conn); released = 1; } @@ -401,6 +408,64 @@ void xmpp_conn_set_jid(xmpp_conn_t *conn, const char *jid) conn->jid = xmpp_strdup(conn->ctx, jid); } +/** Set the Handler function which will be called when the TLS stack can't + * verify the CA of the server we're trying to connect to. + * + * @param conn a Strophe connection object + * @param hndl certfail Handler function + * + * @ingroup Connections + * @ingroup TLS + */ +void xmpp_conn_set_certfail_handler(xmpp_conn_t *const conn, + xmpp_certfail_handler hndl) +{ + conn->certfail_handler = hndl; +} + +/** Set the CAfile + * + * @param conn a Strophe connection object + * @param cert path to a certificate file + * + * @ingroup Connections + * @ingroup TLS + */ +void xmpp_conn_set_cafile(xmpp_conn_t *const conn, const char *path) +{ + conn->tls_cafile = xmpp_strdup(conn->ctx, path); +} + +/** Set the CApath + * + * @param conn a Strophe connection object + * @param cert path to a folder containing certificates + * + * @ingroup Connections + * @ingroup TLS + */ +void xmpp_conn_set_capath(xmpp_conn_t *const conn, const char *path) +{ + conn->tls_capath = xmpp_strdup(conn->ctx, path); +} + +/** Retrieve the peer certificate + * + * The returned Certificate object must be free'd by calling + * \ref xmpp_tlscert_free + * + * @param conn a Strophe connection object + * + * @return a Strophe Certificate object + * + * @ingroup Connections + * @ingroup TLS + */ +xmpp_tlscert_t *xmpp_conn_get_peer_cert(xmpp_conn_t *const conn) +{ + return tls_peer_cert(conn); +} + /** Set the Client Certificate and Private Key that will be bound to the * connection. If any of the both was previously set, it will be discarded. * This should not be used after a connection is created. The function will @@ -412,6 +477,7 @@ void xmpp_conn_set_jid(xmpp_conn_t *conn, const char *jid) * @param key path to a private key file * * @ingroup Connections + * @ingroup TLS */ void xmpp_conn_set_client_cert(xmpp_conn_t *const conn, const char *const cert, @@ -433,6 +499,7 @@ void xmpp_conn_set_client_cert(xmpp_conn_t *const conn, * @return the number of xmppAddr entries in the client certificate * * @ingroup Connections + * @ingroup TLS */ unsigned int xmpp_conn_cert_xmppaddr_num(xmpp_conn_t *const conn) { @@ -447,6 +514,7 @@ unsigned int xmpp_conn_cert_xmppaddr_num(xmpp_conn_t *const conn) * @return a string containing the xmppAddr or NULL if n is out of range * * @ingroup Connections + * @ingroup TLS */ char *xmpp_conn_cert_xmppaddr(xmpp_conn_t *const conn, unsigned int n) { diff --git a/src/tls.c b/src/tls.c new file mode 100644 index 0000000..2d746c0 --- /dev/null +++ b/src/tls.c @@ -0,0 +1,214 @@ +/* tls.c +** strophe XMPP client library -- generic TLS functions +** +** Copyright (C) 2021 Steffen Jaeckel +** +** This software is provided AS-IS with no warranty, either express +** or implied. +** +** This program is dual licensed under the MIT and GPLv3 licenses. +*/ + +/** @file + * Generic TLS functionality. + */ + +/** @defgroup TLS SSL/TLS specific functionality + * These functions provide SSL/TLS specific functionality. + */ + +#include +#include +#include + +#include "strophe.h" + +#include "common.h" + +struct _dnsname_t { + char **data; + size_t cur, max; +}; + +const size_t tlscert_dnsnames_increment = 4; + +/** Get the Strophe context which is assigned to this certificate. + * + * @param cert a Strophe TLS certificate object + * + * @return the Strophe context object where this certificate originates from + * + * @ingroup TLS + */ +xmpp_ctx_t *xmpp_tlscert_get_ctx(const xmpp_tlscert_t *cert) +{ + return cert->ctx; +} + +/** Get the Strophe connection which is assigned to this certificate. + * + * @param cert a Strophe TLS certificate object + * + * @return the Strophe connection object where this certificate originates from + * + * @ingroup TLS + */ +xmpp_conn_t *xmpp_tlscert_get_conn(const xmpp_tlscert_t *cert) +{ + return cert->conn; +} + +/** Get the complete PEM of this certificate. + * + * @param cert a Strophe TLS certificate object + * + * @return a string containing the PEM of this certificate + * + * @ingroup TLS + */ +const char *xmpp_tlscert_get_pem(const xmpp_tlscert_t *cert) +{ + return cert->pem; +} + +/** Get the dnsName entries out of the SubjectAlternativeNames. + * + * Note: Max. `MAX_NUM_DNSNAMES` are supported. + * + * @param cert a Strophe TLS certificate object + * @param n which dnsName entry + * + * @return a string with the n'th dnsName + * + * @ingroup TLS + */ +const char *xmpp_tlscert_get_dnsname(const xmpp_tlscert_t *cert, size_t n) +{ + if (n >= cert->dnsnames->cur) + return NULL; + return cert->dnsnames->data[n]; +} + +/** Get various parts of the certificate as String. + * + * c.f. \ref xmpp_cert_element_t for details. + * + * @param cert a Strophe TLS certificate object + * @param elmnt which part of the certificate + * + * @return a string with the part of the certificate + * + * @ingroup TLS + */ +const char *xmpp_tlscert_get_string(const xmpp_tlscert_t *cert, + xmpp_cert_element_t elmnt) +{ + if (elmnt >= XMPP_CERT_ELEMENT_MAX) + return NULL; + return cert->elements[elmnt]; +} + +/** Get a descriptive string for each xmpp_cert_element_t. + * + * c.f. \ref xmpp_cert_element_t for details. + * + * @param cert a Strophe TLS certificate object + * @param elmnt which element + * + * @return a string with the description + * + * @ingroup TLS + */ +const char *xmpp_tlscert_get_description(xmpp_cert_element_t elmnt) +{ + static const char *descriptions[] = { + "X.509 Version", + "SerialNumber", + "Subject", + "Issuer", + "Issued On", + "Expires On", + "Public Key Algorithm", + "Certificate Signature Algorithm", + "Fingerprint SHA-1", + "Fingerprint SHA-256", + }; + if (elmnt >= XMPP_CERT_ELEMENT_MAX) + return NULL; + return descriptions[elmnt]; +} + +/** Allocate and initialize a Strophe TLS certificate object. + * + * @param ctx a Strophe context object + * + * @return a certificate object or NULL + */ +xmpp_tlscert_t *tlscert_new(xmpp_ctx_t *ctx) +{ + xmpp_tlscert_t *tlscert = xmpp_alloc(ctx, sizeof(*tlscert)); + if (!tlscert) + return NULL; + memset(tlscert, 0, sizeof(*tlscert)); + + tlscert->dnsnames = xmpp_alloc(ctx, sizeof(*tlscert->dnsnames)); + if (!tlscert->dnsnames) { + xmpp_free(ctx, tlscert); + return NULL; + } + memset(tlscert->dnsnames, 0, sizeof(*tlscert->dnsnames)); + + tlscert->ctx = ctx; + + return tlscert; +} + +/** Free a certificate object. + * + * @param cert a Strophe TLS certificate object + * + * @ingroup TLS + */ +void xmpp_tlscert_free(xmpp_tlscert_t *cert) +{ + size_t n; + for (n = 0; n < ARRAY_SIZE(cert->elements); ++n) { + if (cert->elements[n]) + xmpp_free(cert->ctx, cert->elements[n]); + } + if (cert->dnsnames->data) { + for (n = 0; n < cert->dnsnames->cur; ++n) { + if (cert->dnsnames->data[n]) + xmpp_free(cert->ctx, cert->dnsnames->data[n]); + } + } + xmpp_free(cert->ctx, cert->dnsnames->data); + xmpp_free(cert->ctx, cert->dnsnames); + if (cert->pem) + xmpp_free(cert->ctx, cert->pem); + xmpp_free(cert->ctx, cert); +} + +/** Add a dnsName to the Strophe TLS certificate object. + * + * @param cert a Strophe TLS certificate object + * @param dnsname dnsName that shall be stored + * + * @return classic Unix style - 0=success, 1=error + */ +int tlscert_add_dnsname(xmpp_tlscert_t *cert, const char *dnsname) +{ + if ((cert->dnsnames->cur + 1) >= cert->dnsnames->max) { + char **dnsnames = + xmpp_realloc(cert->ctx, cert->dnsnames->data, + (cert->dnsnames->max + tlscert_dnsnames_increment) * + sizeof(char **)); + if (!dnsnames) + return 1; + cert->dnsnames->data = dnsnames; + cert->dnsnames->max += tlscert_dnsnames_increment; + } + cert->dnsnames->data[cert->dnsnames->cur++] = + xmpp_strdup(cert->ctx, dnsname); + return 0; +} diff --git a/src/tls.h b/src/tls.h index f840a75..7f13ed1 100644 --- a/src/tls.h +++ b/src/tls.h @@ -21,6 +21,16 @@ typedef struct _tls tls_t; +typedef struct _dnsname_t dnsname_t; + +struct _xmpp_tlscert_t { + xmpp_ctx_t *ctx; + xmpp_conn_t *conn; + char *pem; + char *elements[XMPP_CERT_ELEMENT_MAX]; + dnsname_t *dnsnames; +}; + void tls_initialize(void); void tls_shutdown(void); @@ -30,6 +40,7 @@ void tls_free(tls_t *tls); char *tls_id_on_xmppaddr(xmpp_conn_t *conn, unsigned int n); unsigned int tls_id_on_xmppaddr_num(xmpp_conn_t *conn); +xmpp_tlscert_t *tls_peer_cert(xmpp_conn_t *conn); int tls_set_credentials(tls_t *tls, const char *cafilename); int tls_start(tls_t *tls); @@ -44,4 +55,7 @@ int tls_write(tls_t *tls, const void *buff, size_t len); int tls_clear_pending_write(tls_t *tls); int tls_is_recoverable(int error); +xmpp_tlscert_t *tlscert_new(xmpp_ctx_t *ctx); +int tlscert_add_dnsname(xmpp_tlscert_t *cert, const char *dnsname); + #endif /* __LIBSTROPHE_TLS_H__ */ diff --git a/strophe.h b/strophe.h index ce90793..254f96a 100644 --- a/strophe.h +++ b/strophe.h @@ -122,6 +122,8 @@ typedef struct _xmpp_log_t xmpp_log_t; /* opaque run time context containing the above hooks */ typedef struct _xmpp_ctx_t xmpp_ctx_t; +typedef struct _xmpp_tlscert_t xmpp_tlscert_t; + xmpp_ctx_t *xmpp_ctx_new(const xmpp_mem_t *mem, const xmpp_log_t *log); void xmpp_ctx_free(xmpp_ctx_t *ctx); @@ -215,6 +217,24 @@ typedef enum { XMPP_SE_XML_NOT_WELL_FORMED } xmpp_error_type_t; +/** Certificate Elements + * + * @ingroup TLS + */ +typedef enum { + XMPP_CERT_VERSION, /**< X.509 Version */ + XMPP_CERT_SERIALNUMBER, /**< SerialNumber */ + XMPP_CERT_SUBJECT, /**< Subject */ + XMPP_CERT_ISSUER, /**< Issuer */ + XMPP_CERT_NOTBEFORE, /**< Issued on */ + XMPP_CERT_NOTAFTER, /**< Expires on */ + XMPP_CERT_KEYALG, /**< Public Key Algorithm */ + XMPP_CERT_SIGALG, /**< Certificate Signature Algorithm */ + XMPP_CERT_FINGERPRINT_SHA1, /**< Fingerprint SHA-1 */ + XMPP_CERT_FINGERPRINT_SHA256, /**< Fingerprint SHA-256 */ + XMPP_CERT_ELEMENT_MAX /**< Last element of the enum */ +} xmpp_cert_element_t; + typedef struct { xmpp_error_type_t type; char *text; @@ -227,6 +247,28 @@ typedef void (*xmpp_conn_handler)(xmpp_conn_t *conn, xmpp_stream_error_t *stream_error, void *userdata); +/** The Handler function which will be called when the TLS stack can't + * verify the authenticity of a Certificate that gets presented by + * the server we're trying to connect to. + * + * When this function is called and details of the `cert` have to be + * kept, please copy them yourself. The `cert` object will be free'd + * automatically when this function returns. + * + * NB: `errormsg` is specific per certificate on OpenSSL and the same + * for all certificates on GnuTLS. + * + * @param cert a Strophe certificate object + * @param errormsg The error that caused this. + * + * @return 0 if the connection attempt should be terminated, + * 1 if the connection should be established. + * + * @ingroup TLS + */ +typedef int (*xmpp_certfail_handler)(const xmpp_tlscert_t *cert, + const char *const errormsg); + void xmpp_send_error(xmpp_conn_t *conn, xmpp_error_type_t type, char *text); xmpp_conn_t *xmpp_conn_new(xmpp_ctx_t *ctx); xmpp_conn_t *xmpp_conn_clone(xmpp_conn_t *conn); @@ -237,6 +279,11 @@ int xmpp_conn_set_flags(xmpp_conn_t *conn, long flags); const char *xmpp_conn_get_jid(const xmpp_conn_t *conn); const char *xmpp_conn_get_bound_jid(const xmpp_conn_t *conn); void xmpp_conn_set_jid(xmpp_conn_t *conn, const char *jid); +void xmpp_conn_set_cafile(xmpp_conn_t *const conn, const char *path); +void xmpp_conn_set_capath(xmpp_conn_t *const conn, const char *path); +void xmpp_conn_set_certfail_handler(xmpp_conn_t *const conn, + xmpp_certfail_handler hndl); +xmpp_tlscert_t *xmpp_conn_get_peer_cert(xmpp_conn_t *const conn); void xmpp_conn_set_client_cert(xmpp_conn_t *conn, const char *cert, const char *key); @@ -437,6 +484,17 @@ void xmpp_run(xmpp_ctx_t *ctx); void xmpp_stop(xmpp_ctx_t *ctx); void xmpp_ctx_set_timeout(xmpp_ctx_t *ctx, unsigned long timeout); +/* TLS certificates */ + +xmpp_ctx_t *xmpp_tlscert_get_ctx(const xmpp_tlscert_t *cert); +xmpp_conn_t *xmpp_tlscert_get_conn(const xmpp_tlscert_t *cert); +const char *xmpp_tlscert_get_pem(const xmpp_tlscert_t *cert); +const char *xmpp_tlscert_get_dnsname(const xmpp_tlscert_t *cert, size_t n); +const char *xmpp_tlscert_get_string(const xmpp_tlscert_t *cert, + xmpp_cert_element_t elmnt); +const char *xmpp_tlscert_get_description(xmpp_cert_element_t elmnt); +void xmpp_tlscert_free(xmpp_tlscert_t *cert); + /* UUID */ char *xmpp_uuid_gen(xmpp_ctx_t *ctx);