From c7a8ba6464d27161a4634147f572505f0d57bdb2 Mon Sep 17 00:00:00 2001 From: Eitan Isaacson Date: Thu, 18 Feb 2010 10:39:09 -0800 Subject: [PATCH] Split connector's handshake handler, and added a method for setting a user verify function. https://bugs.freedesktop.org/show_bug.cgi?id=26633 --- .gitignore | 1 + examples/Makefile.am | 3 ++ examples/accept-cert.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++ wocky/Makefile.am | 4 ++- wocky/wocky-connector.c | 65 +++++++++++++++++++++++++++++------ wocky/wocky-connector.h | 13 +++++++ wocky/wocky-openssl.c | 47 +++++++++++++++++++++---- wocky/wocky-tls.c | 46 +++++++++++++----------- wocky/wocky-tls.h | 8 ++-- 9 files changed, 230 insertions(+), 45 deletions(-) create mode 100644 examples/accept-cert.c diff --git a/.gitignore b/.gitignore index dee8f33..1f10f82 100644 --- a/.gitignore +++ b/.gitignore @@ -121,6 +121,7 @@ tests/wocky-tls-test examples/wocky-connect examples/wocky-register examples/wocky-unregister +examples/wocky-accept-cert coverage/ diff --git a/examples/Makefile.am b/examples/Makefile.am index b7f4a5e..b10c7b4 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -3,6 +3,7 @@ EXAMPLES = EXAMPLES += wocky-connect EXAMPLES += wocky-register EXAMPLES += wocky-unregister +EXAMPLES += wocky-accept-cert wocky_connect_SOURCES = connect.c wocky_connect_DEPENDENCIES = $(top_builddir)/wocky/libwocky.la @@ -13,6 +14,8 @@ wocky_register_DEPENDENCIES = $(top_builddir)/wocky/libwocky.la wocky_unregister_SOURCES = unregister.c wocky_unregister_DEPENDENCIES = $(top_builddir)/wocky/libwocky.la +wocky_accept_cert_SOURCES = accept-cert.c +wocky_accept_cert_DEPENDENCIES = $(top_builddir)/wocky/libwocky.la LDADD = \ @GLIB_LIBS@ \ diff --git a/examples/accept-cert.c b/examples/accept-cert.c new file mode 100644 index 0000000..0c538f4 --- /dev/null +++ b/examples/accept-cert.c @@ -0,0 +1,88 @@ +#include +#include + +#include + +#include + +#include +#include + +GMainLoop *mainloop; + +static void +connector_callback (GObject *source, GAsyncResult *res, gpointer user_data) +{ + GError *error = NULL; + gchar *jid = NULL; + gchar *sid = NULL; + WockyConnector *wcon = WOCKY_CONNECTOR (source); + WockyXmppConnection *connection = + wocky_connector_connect_finish (wcon, res, &error, &jid, &sid); + + if (connection != NULL) + { + printf ("connected (%s) [%s]!\n", jid, sid); + g_free (sid); + g_free (jid); + } + else + { + if (error) + g_warning ("%s: %d: %s\n", + g_quark_to_string (error->domain), error->code, error->message); + g_main_loop_quit (mainloop); + } +} + +static gboolean +make_up_mind (WockyConnector *connector) +{ + printf ("Finishing handshake\n"); + wocky_connector_verify_finish (connector, WOCKY_TLS_CERT_NAME_MISMATCH); + return FALSE; +} + +static void +accept_cert_cb (WockyConnector *connector, + GPtrArray *certificates, + WockyTLSCertStatus status, + gpointer user_data) +{ + printf ("accept_cert_cb, status=%d number of certs=%d\n", + status, certificates->len); + + g_timeout_add_seconds (5, (GSourceFunc)make_up_mind, connector); + + printf ("returning from accept_cert_cb\n"); +} + +int +main (int argc, + char **argv) +{ + WockyConnector *wcon = NULL; + + g_type_init (); + + if ((argc < 3) || (argc > 4)) + { + printf ("Usage: %s \n", argv[0]); + return -1; + } + + mainloop = g_main_loop_new (NULL, FALSE); + + wocky_init (); + + wcon = wocky_connector_new (argv[1], argv[2], NULL); + + g_object_set (wcon, "ignore-ssl-errors", FALSE, NULL); + + wocky_connector_set_verify_func (wcon, &accept_cert_cb, NULL); + + wocky_connector_connect_async (wcon, connector_callback, NULL); + g_main_loop_run (mainloop); + + return 0; +} diff --git a/wocky/Makefile.am b/wocky/Makefile.am index 62f861b..f253c28 100644 --- a/wocky/Makefile.am +++ b/wocky/Makefile.am @@ -13,7 +13,9 @@ BUILT_SOURCES = \ wocky-xmpp-reader-enumtypes.h \ wocky-xmpp-reader-enumtypes.c \ wocky-muc-enumtypes.h \ - wocky-muc-enumtypes.c + wocky-muc-enumtypes.c \ + wocky-tls-enumtypes.h \ + wocky-tls-enumtypes.c OPENSSL_SRC = \ wocky-openssl.c \ diff --git a/wocky/wocky-connector.c b/wocky/wocky-connector.c index 697362c..04c8ad7 100644 --- a/wocky/wocky-connector.c +++ b/wocky/wocky-connector.c @@ -286,6 +286,8 @@ struct _WockyConnectorPrivate gboolean legacy_ssl; gchar *session_id; gchar *ca; /* file or dir containing x509 CA files */ + WockyCertVerifyFunc *verify_func; + gpointer verify_func_data; /* XMPP connection data */ WockyXmppStanza *features; @@ -1549,6 +1551,7 @@ starttls_handshake_cb (GObject *source, long flags = WOCKY_TLS_VERIFY_NORMAL; const gchar *peer = NULL; guint status = WOCKY_TLS_CERT_UNKNOWN_ERROR; + GPtrArray *certificates; priv->tls = wocky_tls_session_handshake_finish (sess, res, &error); DEBUG ("completed %s handshake", tla); @@ -1580,7 +1583,34 @@ starttls_handshake_cb (GObject *source, DEBUG ("verifying certificate (peer: %s)", (peer == NULL) ? "-" : peer); - if (wocky_tls_session_verify_peer (sess, peer, flags, &status) != 0) + certificates = wocky_tls_session_verify_peer (sess, peer, flags, &status); + + if (priv->verify_func != NULL) + (* priv->verify_func) (self, certificates, status, priv->verify_func_data); + else + wocky_connector_verify_finish (self, status); +} + +/** + * wocky_connector_verify_finish: + * @self: a #WockyConnector instance. + * @status: a #WockyTLSCertStatus as the verification result. + * + * This completes the verification step in the TLS handshake. If a custom + * verification is used with #wocky_connector_set_verify_func, this function + * must be called with the verification result. + */ +void +wocky_connector_verify_finish (WockyConnector *self, + WockyTLSCertStatus status) +{ + WockyConnectorPrivate *priv = WOCKY_CONNECTOR_GET_PRIVATE (self); + gchar *peer = priv->domain; + + if (priv->legacy_ssl) + peer = priv->xmpp_host; + + if (status != WOCKY_TLS_CERT_OK) { gboolean ok_when_lenient = FALSE; const gchar *msg = NULL; @@ -1614,16 +1644,11 @@ starttls_handshake_cb (GObject *source, default: msg = "SSL Certificate Verification Error for %s"; } + if (!(priv->cert_insecure_ok && ok_when_lenient)) { GError *cert_error = NULL; - if (peer == NULL) - { - if (priv->legacy_ssl) - peer = priv->xmpp_host; - if (peer == NULL) - peer = priv->domain; - } + cert_error = g_error_new (WOCKY_TLS_CERT_ERROR, status, msg, peer); abort_connect_error (self, &cert_error, NULL); g_error_free (cert_error); @@ -1647,9 +1672,6 @@ starttls_handshake_cb (GObject *source, } } - DEBUG ("cert verified %s", - (flags == WOCKY_TLS_VERIFY_LENIENT) ? "LENIENT" : "NORMAL"); - priv->encrypted = TRUE; xmpp_init (self, FALSE); } @@ -2770,6 +2792,27 @@ wocky_connector_add_crl (WockyConnector *self, } /** + * wocky_connector_set_verify_func: + * @self: a #WockyConnector instance. + * @status: a #WockyCertVerifyFunc that will be used for verification. + * @user_data: a #gpointer to pass to the function. + * + * Set a custom verification function for the TLS handshake. The custom + * function should ultimately call #wocky_connector_verify_finish with the + * desired verification result. + */ +void +wocky_connector_set_verify_func (WockyConnector *self, + WockyCertVerifyFunc *verify_func, + gpointer user_data) +{ + WockyConnectorPrivate *priv = WOCKY_CONNECTOR_GET_PRIVATE (self); + + priv->verify_func = verify_func; + priv->verify_func_data = user_data; +} + +/** * wocky_connector_new: * @jid: a JID (user AT domain). * @pass: the password. diff --git a/wocky/wocky-connector.h b/wocky/wocky-connector.h index cdf1440..255bc95 100644 --- a/wocky/wocky-connector.h +++ b/wocky/wocky-connector.h @@ -28,6 +28,7 @@ #include "wocky-xmpp-stanza.h" #include "wocky-tls.h" +#include "wocky-tls-enumtypes.h" G_BEGIN_DECLS @@ -181,6 +182,18 @@ gboolean wocky_connector_add_crl (WockyConnector *self, gboolean wocky_connector_add_ca (WockyConnector *self, const gchar *path); +typedef void (WockyCertVerifyFunc) (WockyConnector *connector, + GPtrArray *certificates, + WockyTLSCertStatus status, + gpointer user_data); + +void wocky_connector_set_verify_func (WockyConnector *self, + WockyCertVerifyFunc *verify_func, + gpointer user_data); + +void wocky_connector_verify_finish (WockyConnector *self, + WockyTLSCertStatus status); + G_END_DECLS #endif /* #ifndef __WOCKY_CONNECTOR_H__*/ diff --git a/wocky/wocky-openssl.c b/wocky/wocky-openssl.c index 6c0e1fc..c53ffc7 100644 --- a/wocky/wocky-openssl.c +++ b/wocky/wocky-openssl.c @@ -916,7 +916,7 @@ check_peer_name (const char *target, X509 *cert) return rval; } -int +GPtrArray * wocky_tls_session_verify_peer (WockyTLSSession *session, const gchar *peername, WockyTLSVerificationLevel level, @@ -927,6 +927,10 @@ wocky_tls_session_verify_peer (WockyTLSSession *session, const gchar *check_level; X509 *cert; gboolean lenient = (level == WOCKY_TLS_VERIFY_LENIENT); + STACK_OF(X509) *cert_chain = NULL; + guint cls = 0; + GPtrArray *certificates = + g_ptr_array_new_with_free_func ((GDestroyNotify) g_byte_array_unref); DEBUG (); g_assert (status != NULL); @@ -954,6 +958,37 @@ wocky_tls_session_verify_peer (WockyTLSSession *session, rval = SSL_get_verify_result (session->ssl); DEBUG ("X509 cert: %p; verified: %d", cert, rval); + cert_chain = SSL_get_peer_cert_chain (session->ssl); + if (cert_chain != NULL) + cls = sk_X509_num (cert_chain); + + for (guint i = 0; i < cls; i++) + { + GByteArray *certificate; + X509 *peer; + gint peer_len; + guchar *peer_buffer; + + if (cert_chain == NULL) + peer = cert; + else + peer = sk_X509_value (cert_chain, i); + + peer_len = i2d_X509 (peer, NULL); + + certificate = g_byte_array_sized_new (peer_len); + + peer_buffer = g_malloc (peer_len); + + i2d_X509 (peer, &peer_buffer); + peer_buffer -= peer_len; + + g_byte_array_append (certificate, peer_buffer, peer_len); + g_ptr_array_add (certificates, certificate); + + g_free (peer_buffer); + } + /* anonymous SSL: no cert to check - rval will be X509_V_OK (0) * * in this case (see openssl documentation) */ if (lenient && cert == NULL) @@ -1025,11 +1060,10 @@ wocky_tls_session_verify_peer (WockyTLSSession *session, case WOCKY_TLS_CERT_MAYBE_DOS: break; default: - rval = X509_V_OK; *status = WOCKY_TLS_CERT_OK; } - return rval; + return certificates; } /* if we get this far, we have a certificate which should be valid: * @@ -1043,12 +1077,9 @@ wocky_tls_session_verify_peer (WockyTLSSession *session, * otherwise we need to figure out which error (if any) our verification * * call failed with: */ if (!peer_name_ok) - { - *status = WOCKY_TLS_CERT_NAME_MISMATCH; - return X509_V_ERR_APPLICATION_VERIFICATION; - } + *status = WOCKY_TLS_CERT_NAME_MISMATCH; - return rval; + return certificates; } static gssize diff --git a/wocky/wocky-tls.c b/wocky/wocky-tls.c index b7eeb52..78d3ee2 100644 --- a/wocky/wocky-tls.c +++ b/wocky/wocky-tls.c @@ -603,7 +603,7 @@ wocky_tls_session_handshake_finish (WockyTLSSession *session, return g_object_new (WOCKY_TYPE_TLS_CONNECTION, "session", session, NULL); } -int +GPtrArray * wocky_tls_session_verify_peer (WockyTLSSession *session, const gchar *peername, WockyTLSVerificationLevel level, @@ -615,6 +615,9 @@ wocky_tls_session_verify_peer (WockyTLSSession *session, gboolean peer_name_ok = TRUE; const gchar *check_level; gnutls_certificate_verify_flags check; + const gnutls_datum_t *peers = NULL; + GPtrArray *certificates = + g_ptr_array_new_with_free_func ((GDestroyNotify) g_byte_array_unref); /* list gnutls cert error conditions in descending order of noteworthiness * * and map them to wocky cert error conditions */ @@ -683,9 +686,18 @@ wocky_tls_session_verify_peer (WockyTLSSession *session, *status = WOCKY_TLS_CERT_UNKNOWN_ERROR; } - return rval; + return certificates; } + peers = gnutls_certificate_get_peers (session->session, &cls); + + for (guint i = 0; i < cls; i++) + { + GByteArray *cert = g_byte_array_sized_new (peers[0].size); + g_byte_array_append (cert, peers[0].data, peers[0].size); + g_ptr_array_add (certificates, cert); + } + /* if we get this far, we have a structurally valid certificate * * signed by _someone_: check the hostname matches the peername */ if (peername != NULL) @@ -695,34 +707,28 @@ wocky_tls_session_verify_peer (WockyTLSSession *session, gnutls_openpgp_crt_t opgp; case GNUTLS_CRT_X509: DEBUG ("checking X509 cert"); - if ((rval = gnutls_x509_crt_init (&x509)) == GNUTLS_E_SUCCESS) + if (gnutls_x509_crt_init (&x509) == GNUTLS_E_SUCCESS) { /* we know these ops must succeed, or verify_peers2 would have * * failed before we got here: We just need to duplicate a bit * * of what it does: */ - const gnutls_datum_t *peers = - gnutls_certificate_get_peers (session->session, &cls); - gnutls_x509_crt_import (x509, &peers[0], GNUTLS_X509_FMT_DER); - rval = gnutls_x509_crt_check_hostname (x509, peername); - DEBUG ("gnutls_x509_crt_check_hostname: %s -> %d", peername, rval); - rval = (rval == 0) ? -1 : GNUTLS_E_SUCCESS; - peer_name_ok = (rval == GNUTLS_E_SUCCESS); + peer_name_ok = + (gnutls_x509_crt_check_hostname (x509, peername) != 0); + DEBUG ("gnutls_x509_crt_check_hostname: %s -> %s", peername, + peer_name_ok ? "success" : "failure"); gnutls_x509_crt_deinit (x509); } break; case GNUTLS_CRT_OPENPGP: DEBUG ("checking PGP cert"); - if ((rval = gnutls_openpgp_crt_init (&opgp)) == GNUTLS_E_SUCCESS) + if (gnutls_openpgp_crt_init (&opgp) == GNUTLS_E_SUCCESS) { - const gnutls_datum_t *peers = - gnutls_certificate_get_peers (session->session, &cls); - gnutls_openpgp_crt_import (opgp, &peers[0], GNUTLS_OPENPGP_FMT_RAW); - rval = gnutls_openpgp_crt_check_hostname (opgp, peername); - DEBUG ("gnutls_openpgp_crt_check_hostname: %s -> %d",peername,rval); - rval = (rval == 0) ? -1 : GNUTLS_E_SUCCESS; - peer_name_ok = (rval == GNUTLS_E_SUCCESS); + peer_name_ok = + (gnutls_openpgp_crt_check_hostname (opgp, peername) != 0); + DEBUG ("gnutls_openpgp_crt_check_hostname: %s -> %s", peername, + peer_name_ok ? "success" : "failure"); gnutls_openpgp_crt_deinit (opgp); } @@ -730,7 +736,6 @@ wocky_tls_session_verify_peer (WockyTLSSession *session, default: /* theoretically, this can't happen if ...verify_peers2 is working: */ DEBUG ("unknown cert type!"); - rval = GNUTLS_E_INVALID_REQUEST; peer_name_ok = FALSE; } @@ -753,13 +758,12 @@ wocky_tls_session_verify_peer (WockyTLSSession *session, { DEBUG ("gnutls error %d set", status_map[x].gnutls); *status = status_map[x].wocky; - rval = GNUTLS_E_CERTIFICATE_ERROR; break; } } } - return rval; + return certificates; } static gssize diff --git a/wocky/wocky-tls.h b/wocky/wocky-tls.h index 8a80e09..5811c99 100644 --- a/wocky/wocky-tls.h +++ b/wocky/wocky-tls.h @@ -73,10 +73,10 @@ typedef enum GType wocky_tls_connection_get_type (void); GType wocky_tls_session_get_type (void); -int wocky_tls_session_verify_peer (WockyTLSSession *session, - const gchar *peername, - WockyTLSVerificationLevel level, - WockyTLSCertStatus *status); +GPtrArray *wocky_tls_session_verify_peer (WockyTLSSession *session, + const gchar *peername, + WockyTLSVerificationLevel level, + WockyTLSCertStatus *status); WockyTLSConnection *wocky_tls_session_handshake (WockyTLSSession *session, GCancellable *cancellable, -- 1.7.0