diff --git a/tls/gnutls/gtlsdatabase-gnutls.c b/tls/gnutls/gtlsdatabase-gnutls.c index 3825a351710b1ea280064fd664e407e60a24cbb3..85b771fb7472755322f42a4917ba07527b94216e 100644 --- a/tls/gnutls/gtlsdatabase-gnutls.c +++ b/tls/gnutls/gtlsdatabase-gnutls.c @@ -66,6 +66,9 @@ typedef struct * string handles. This array is populated on demand. */ GHashTable *handles; + + /* Unowned. This is only set temporarily, during certificate verification. */ + GCancellable *verify_chain_cancellable; } GTlsDatabaseGnutlsPrivate; static void g_tls_database_gnutls_initable_interface_init (GInitableIface *iface); @@ -220,6 +223,8 @@ g_tls_database_gnutls_finalize (GObject *object) GTlsDatabaseGnutls *self = G_TLS_DATABASE_GNUTLS (object); GTlsDatabaseGnutlsPrivate *priv = g_tls_database_gnutls_get_instance_private (self); + g_assert (!priv->verify_chain_cancellable); + g_clear_pointer (&priv->subjects, g_hash_table_destroy); g_clear_pointer (&priv->issuers, g_hash_table_destroy); g_clear_pointer (&priv->complete, g_hash_table_destroy); @@ -496,10 +501,13 @@ g_tls_database_gnutls_verify_chain (GTlsDatabase *database, if (g_cancellable_set_error_if_cancelled (cancellable, error)) return G_TLS_CERTIFICATE_GENERIC_ERROR; + g_assert (!priv->verify_chain_cancellable); + priv->verify_chain_cancellable = cancellable; gnutls_chain = convert_certificate_chain_to_gnutls (G_TLS_CERTIFICATE_GNUTLS (chain)); gerr = gnutls_x509_trust_list_verify_crt (priv->trust_list, gnutls_chain->chain, gnutls_chain->length, 0, &gnutls_result, NULL); + priv->verify_chain_cancellable = NULL; if (gerr != 0 || g_cancellable_set_error_if_cancelled (cancellable, error)) { @@ -575,6 +583,131 @@ g_tls_database_gnutls_populate_trust_list (GTlsDatabaseGnutls *self, return gerr >= 0; } +#if GNUTLS_VERSION_MAJOR > 3 || GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR >= 7 +static int +issuer_missing_cb (gnutls_x509_trust_list_t tlist, + const gnutls_x509_crt_t crt, + gnutls_x509_crt_t **issuers, + guint *issuers_size) +{ + GTlsDatabaseGnutls *self = gnutls_x509_trust_list_get_ptr (tlist); + GTlsDatabaseGnutlsPrivate *priv = g_tls_database_gnutls_get_instance_private (self); + gnutls_datum_t datum; + GFile *file = NULL; + GFileInputStream *istream = NULL; + char *aia = NULL; + char *scheme = NULL; + int gerr; + int ret = -1; + guchar buffer[2048]; + gssize n_read; + GByteArray *der = NULL; + GError *error = NULL; + + /* The server sent an incomplete certificate chain, but we may be able to + * download the missing certificate to allow verification to proceed. See + * Authority Information Access, RFC 5280 ยง4.2.2.1. Also see: + * https://blogs.gnome.org/mcatanzaro/2015/01/30/mozilla-is-responsible-for-the-redhat-corpmerchandise-com-fiasco/ + */ + + for (int i = 0; ; i++) + { + gerr = gnutls_x509_crt_get_authority_info_access (crt, i, GNUTLS_IA_CAISSUERS_URI, &datum, NULL); + if (gerr == GNUTLS_E_UNKNOWN_ALGORITHM) + continue; + + if (gerr == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + return -1; + + if (gerr < 0) + { + g_warning ("Failed to read Authority Information Access from certificate: %s", gnutls_strerror (gerr)); + return -1; + } + + /* Success */ + break; + } + g_assert (gerr == GNUTLS_E_SUCCESS); + + aia = g_malloc0 (datum.size + 1); + memcpy (aia, datum.data, datum.size); + + if (!g_uri_is_valid (aia, G_URI_FLAGS_NONE, &error)) + { + g_warning ("Authority Information Access URI %s is not a valid URI: %s", aia, error->message); + goto out; + } + + /* We support only HTTP. Notably, HTTPS is not supported because (a) it is + * not specified by RFC 5280, and (b) since we have no way to break a + * recursive loop if the connection to retrieve the certificate itself also + * requires a missing certificate. We could easily support FTP, but we don't, + * because that's silly. Also note that we don't support "certs-only" CMS + * messages, we only support directly retrieving a DER certificate. Finally, + * we don't support the case where accessLocation is a directoryName, so no + * private DAP or LDAP. + */ + scheme = g_uri_parse_scheme (aia); + if (!scheme || strcmp (scheme, "http") != 0) + { + g_warning ("Authority Information Access URI %s uses unsupported URI scheme '%s'", scheme, aia); + goto out; + } + + file = g_file_new_for_uri (aia); + istream = g_file_read (file, priv->verify_chain_cancellable, &error); + if (!istream) + { + g_warning ("Failed to download missing issuer certificate from Authority Information Access URI %s: failed g_file_read (do you need to install gvfs?): %s", + aia, error->message); + goto out; + } + + der = g_byte_array_sized_new (sizeof (buffer)); + do + { + n_read = g_input_stream_read (G_INPUT_STREAM (istream), buffer, sizeof (buffer), + priv->verify_chain_cancellable, &error); + if (n_read == -1) + { + g_warning ("Failed to download missing issuer certificate from Authority Information Access URI %s: failed g_input_stream_read: %s", + aia, error->message); + goto out; + } + g_byte_array_append (der, buffer, n_read); + } while (n_read > 0); + + gnutls_free (datum.data); + datum.size = der->len; + datum.data = (unsigned char *)g_byte_array_free (der, FALSE); + der = NULL; + + gerr = gnutls_x509_crt_list_import2 (issuers, issuers_size, &datum, GNUTLS_X509_FMT_DER, 0); + if (gerr < 0) + { + g_warning ("Failed to download missing issuer certificate from Authority Information Access URI %s: failed gnutls_x509_crt_import: %s", + aia, gnutls_strerror (gerr)); + goto out; + } + + ret = 0; + +out: + if (error) + g_error_free (error); + if (file) + g_object_unref (file); + if (istream) + g_object_unref (istream); + if (der) + g_byte_array_unref (der); + gnutls_free (datum.data); + g_free (aia); + return ret; +} +#endif + static void g_tls_database_gnutls_class_init (GTlsDatabaseGnutlsClass *klass) { @@ -610,6 +743,10 @@ g_tls_database_gnutls_initable_init (GInitable *initable, return FALSE; gnutls_x509_trust_list_init (&trust_list, 0); +#if GNUTLS_VERSION_MAJOR > 3 || GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR >= 7 + gnutls_x509_trust_list_set_getissuer_function (trust_list, issuer_missing_cb); + gnutls_x509_trust_list_set_ptr (trust_list, self); +#endif g_assert (G_TLS_DATABASE_GNUTLS_GET_CLASS (self)->populate_trust_list); if (!G_TLS_DATABASE_GNUTLS_GET_CLASS (self)->populate_trust_list (self, trust_list, error))