/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* * gtlsdatabase-openssl.c * * Copyright (C) 2015 NICE s.r.l. * * This file is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * In addition, when the library is used with OpenSSL, a special * exception applies. Refer to the LICENSE_EXCEPTION file for details. * * Authors: Ignacio Casal Quinteiro */ #include "config.h" #include "gtlsdatabase-openssl.h" #include #include #include "openssl-include.h" /* * SecTrustCopyAnchorCertificates is only available on macOS, so we check for * SEC_OS_OSX: https://github.com/Apple-FOSS-Mirror/Security/blob/master/base/SecBase.h */ #ifdef __APPLE__ #include #else #define SEC_OS_OSX 0 #endif #ifdef G_OS_WIN32 #include #endif typedef struct { /* * This class is protected by mutex because the default GTlsDatabase * is a global singleton, accessible via the default GTlsBackend. */ GMutex mutex; /* read-only after construct */ X509_STORE *store; } GTlsDatabaseOpensslPrivate; static void g_tls_database_openssl_initable_interface_init (GInitableIface *iface); G_DEFINE_TYPE_WITH_CODE (GTlsDatabaseOpenssl, g_tls_database_openssl, G_TYPE_TLS_DATABASE, G_ADD_PRIVATE (GTlsDatabaseOpenssl) G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, g_tls_database_openssl_initable_interface_init)) static void g_tls_database_openssl_finalize (GObject *object) { GTlsDatabaseOpenssl *self = G_TLS_DATABASE_OPENSSL (object); GTlsDatabaseOpensslPrivate *priv; priv = g_tls_database_openssl_get_instance_private (self); if (priv->store) X509_STORE_free (priv->store); g_mutex_clear (&priv->mutex); G_OBJECT_CLASS (g_tls_database_openssl_parent_class)->finalize (object); } static void g_tls_database_openssl_init (GTlsDatabaseOpenssl *self) { GTlsDatabaseOpensslPrivate *priv; priv = g_tls_database_openssl_get_instance_private (self); g_mutex_init (&priv->mutex); } static STACK_OF(X509) * convert_certificate_chain_to_openssl (GTlsCertificateOpenssl *chain) { GTlsCertificate *cert; STACK_OF(X509) *openssl_chain; openssl_chain = sk_X509_new_null (); for (cert = G_TLS_CERTIFICATE (chain); cert; cert = g_tls_certificate_get_issuer (cert)) sk_X509_push (openssl_chain, g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL (cert))); return openssl_chain; } static GTlsCertificateFlags g_tls_database_openssl_verify_chain (GTlsDatabase *database, GTlsCertificate *chain, const gchar *purpose, GSocketConnectable *identity, GTlsInteraction *interaction, GTlsDatabaseVerifyFlags flags, GCancellable *cancellable, GError **error) { GTlsDatabaseOpenssl *self = G_TLS_DATABASE_OPENSSL (database); GTlsDatabaseOpensslPrivate *priv; STACK_OF(X509) *certs; X509_STORE_CTX *csc; X509 *x; GTlsCertificateFlags result = 0; g_return_val_if_fail (G_IS_TLS_CERTIFICATE_OPENSSL (chain), G_TLS_CERTIFICATE_GENERIC_ERROR); priv = g_tls_database_openssl_get_instance_private (self); if (g_cancellable_set_error_if_cancelled (cancellable, error)) return G_TLS_CERTIFICATE_GENERIC_ERROR; certs = convert_certificate_chain_to_openssl (G_TLS_CERTIFICATE_OPENSSL (chain)); csc = X509_STORE_CTX_new (); x = g_tls_certificate_openssl_get_cert (G_TLS_CERTIFICATE_OPENSSL (chain)); if (!X509_STORE_CTX_init (csc, priv->store, x, certs)) { X509_STORE_CTX_free (csc); sk_X509_free (certs); return G_TLS_CERTIFICATE_GENERIC_ERROR; } if (X509_verify_cert (csc) <= 0) result = g_tls_certificate_openssl_convert_error (X509_STORE_CTX_get_error (csc)); X509_STORE_CTX_free (csc); sk_X509_free (certs); if (g_cancellable_set_error_if_cancelled (cancellable, error)) return G_TLS_CERTIFICATE_GENERIC_ERROR; if (identity) result |= g_tls_certificate_openssl_verify_identity (G_TLS_CERTIFICATE_OPENSSL (chain), identity); return result; } #if SEC_OS_OSX static gboolean populate_store (X509_STORE *store, GError **error) { CFArrayRef anchors; OSStatus ret; CFIndex i; ret = SecTrustCopyAnchorCertificates (&anchors); if (ret != errSecSuccess) { g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC, _("Could not get trusted anchors from Keychain")); return FALSE; } for (i = 0; i < CFArrayGetCount (anchors); i++) { SecCertificateRef cert; CFDataRef data; cert = (SecCertificateRef)CFArrayGetValueAtIndex (anchors, i); data = SecCertificateCopyData (cert); if (data) { X509 *x; const unsigned char *pdata; pdata = (const unsigned char *)CFDataGetBytePtr (data); x = d2i_X509 (NULL, &pdata, CFDataGetLength (data)); if (x) X509_STORE_add_cert (store, x); CFRelease (data); } } CFRelease (anchors); return TRUE; } #elif defined(G_OS_WIN32) static gboolean add_certs_from_store (const gunichar2 *source_cert_store_name, X509_STORE *store) { HANDLE store_handle; PCCERT_CONTEXT cert_context = NULL; store_handle = CertOpenSystemStoreW (0, source_cert_store_name); if (store_handle == NULL) return FALSE; while (cert_context = CertEnumCertificatesInStore (store_handle, cert_context)) { X509 *x; const unsigned char *pdata; pdata = (const unsigned char *)cert_context->pbCertEncoded; x = d2i_X509 (NULL, &pdata, cert_context->cbCertEncoded); if (x) X509_STORE_add_cert (store, x); } CertCloseStore (store_handle, 0); return TRUE; } static gboolean populate_store (X509_STORE *store, GError **error) { if (!add_certs_from_store (L"ROOT", store)) { g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC, _("Could not get root certificate store")); return FALSE; } if (!add_certs_from_store (L"CA", store)) { g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC, _("Could not get CA certificate store")); return FALSE; } return TRUE; } #else static gboolean populate_store (X509_STORE *store, GError **error) { if (!X509_STORE_set_default_paths (store)) { char error_buffer[256]; ERR_error_string_n (ERR_get_error (), error_buffer, sizeof (error_buffer)); g_set_error (error, G_TLS_ERROR, G_TLS_ERROR_MISC, _("Failed to load system trust store: %s"), error_buffer); return FALSE; } return TRUE; } #endif static gboolean g_tls_database_openssl_populate_trust_list (GTlsDatabaseOpenssl *self, X509_STORE *store, GError **error) { return populate_store (store, error); } static void g_tls_database_openssl_class_init (GTlsDatabaseOpensslClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GTlsDatabaseClass *database_class = G_TLS_DATABASE_CLASS (klass); gobject_class->finalize = g_tls_database_openssl_finalize; database_class->verify_chain = g_tls_database_openssl_verify_chain; klass->populate_trust_list = g_tls_database_openssl_populate_trust_list; } static gboolean g_tls_database_openssl_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { GTlsDatabaseOpenssl *self = G_TLS_DATABASE_OPENSSL (initable); GTlsDatabaseOpensslPrivate *priv; X509_STORE *store; gboolean result = TRUE; priv = g_tls_database_openssl_get_instance_private (self); if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; store = X509_STORE_new (); if (store == NULL) { g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_MISC, _("Could not create CA store")); result = FALSE; goto out; } g_assert (G_TLS_DATABASE_OPENSSL_GET_CLASS (self)->populate_trust_list); if (!G_TLS_DATABASE_OPENSSL_GET_CLASS (self)->populate_trust_list (self, store, error)) { result = FALSE; goto out; } if (g_cancellable_set_error_if_cancelled (cancellable, error)) result = FALSE; if (result) { g_mutex_lock (&priv->mutex); if (!priv->store) { priv->store = store; store = NULL; } g_mutex_unlock (&priv->mutex); } out: if (store) X509_STORE_free (store); return result; } static void g_tls_database_openssl_initable_interface_init (GInitableIface *iface) { iface->init = g_tls_database_openssl_initable_init; } GTlsDatabaseOpenssl * g_tls_database_openssl_new (GError **error) { g_return_val_if_fail (!error || !*error, NULL); return g_initable_new (G_TYPE_TLS_DATABASE_OPENSSL, NULL, error, NULL); } static gboolean check_for_ocsp_must_staple (X509 *cert) { int idx = -1; /* We ignore the return of this as we only expect one extension. */ STACK_OF(ASN1_INTEGER) *features = X509_get_ext_d2i (cert, NID_tlsfeature, NULL, &idx); if (!features) return FALSE; for (guint i = 0; i < sk_ASN1_INTEGER_num (features); i++) { const long feature_id = ASN1_INTEGER_get (sk_ASN1_INTEGER_value (features, i)); if (feature_id == 5 || feature_id == 17) /* status_request, status_request_v2 */ { sk_ASN1_INTEGER_pop_free (features, ASN1_INTEGER_free); return TRUE; } } sk_ASN1_INTEGER_pop_free (features, ASN1_INTEGER_free); return FALSE; } GTlsCertificateFlags g_tls_database_openssl_verify_ocsp_response (GTlsDatabaseOpenssl *self, GTlsCertificate *chain, OCSP_RESPONSE *resp) { GTlsCertificateFlags errors = 0; GTlsDatabaseOpensslPrivate *priv; STACK_OF(X509) *chain_openssl = NULL; OCSP_BASICRESP *basic_resp = NULL; int ocsp_status = 0; int i; chain_openssl = convert_certificate_chain_to_openssl (G_TLS_CERTIFICATE_OPENSSL (chain)); priv = g_tls_database_openssl_get_instance_private (self); if ((chain_openssl == NULL) || (priv->store == NULL)) { errors = G_TLS_CERTIFICATE_GENERIC_ERROR; goto end; } /* OpenSSL doesn't provide an API to determine if the chain requires * an OCSP response (known as Must-Staple) using the status_request * X509v3 extension. We also seem to have no way of correctly knowing the * final certificate path that OpenSSL will internally use, so can't do it * ourselves. So for now we will check only the server certificate to see if * it sets Must-Staple. This is inconsistent with GnuTLS's behavior, but it * seems to be the best we can do. Checking *every* certificate for Must- * Staple would be wrong because we don't want to check certificates that * OpenSSL does not actually use as part of its final certification path. */ if (resp == NULL) { if (check_for_ocsp_must_staple (sk_X509_value (chain_openssl, 0))) errors = G_TLS_CERTIFICATE_GENERIC_ERROR; goto end; } ocsp_status = OCSP_response_status (resp); if (ocsp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { errors = G_TLS_CERTIFICATE_GENERIC_ERROR; goto end; } basic_resp = OCSP_response_get1_basic (resp); if (basic_resp == NULL) { errors = G_TLS_CERTIFICATE_GENERIC_ERROR; goto end; } if (OCSP_basic_verify (basic_resp, chain_openssl, priv->store, 0) <= 0) { errors = G_TLS_CERTIFICATE_GENERIC_ERROR; goto end; } for (i = 0; i < OCSP_resp_count (basic_resp); i++) { OCSP_SINGLERESP *single_resp = OCSP_resp_get0 (basic_resp, i); ASN1_GENERALIZEDTIME *revocation_time = NULL; ASN1_GENERALIZEDTIME *this_update_time = NULL; ASN1_GENERALIZEDTIME *next_update_time = NULL; int crl_reason = 0; int cert_status = 0; if (single_resp == NULL) continue; cert_status = OCSP_single_get0_status (single_resp, &crl_reason, &revocation_time, &this_update_time, &next_update_time); if (!OCSP_check_validity (this_update_time, next_update_time, 300L, -1L)) { errors = G_TLS_CERTIFICATE_GENERIC_ERROR; goto end; } switch (cert_status) { case V_OCSP_CERTSTATUS_GOOD: break; case V_OCSP_CERTSTATUS_REVOKED: errors = G_TLS_CERTIFICATE_REVOKED; goto end; case V_OCSP_CERTSTATUS_UNKNOWN: errors = G_TLS_CERTIFICATE_GENERIC_ERROR; goto end; } } end: if (chain_openssl) sk_X509_free (chain_openssl); if (basic_resp) OCSP_BASICRESP_free (basic_resp); if (resp) OCSP_RESPONSE_free (resp); return errors; }