LCOV - code coverage report
Current view: top level - common - ssl_verify.c (source / functions) Hit Total Coverage
Test: out.info Lines: 54 272 19.9 %
Date: 2022-01-27 10:43:00 Functions: 1 8 12.5 %

          Line data    Source code
       1             : /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
       2             : /*
       3             :    Copyright (C) 2011 Red Hat, Inc.
       4             : 
       5             :    This library is free software; you can redistribute it and/or
       6             :    modify it under the terms of the GNU Lesser General Public
       7             :    License as published by the Free Software Foundation; either
       8             :    version 2.1 of the License, or (at your option) any later version.
       9             : 
      10             :    This library is distributed in the hope that it will be useful,
      11             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13             :    Lesser General Public License for more details.
      14             : 
      15             :    You should have received a copy of the GNU Lesser General Public
      16             :    License along with this library; if not, see <http://www.gnu.org/licenses/>.
      17             : */
      18             : 
      19             : #include <config.h>
      20             : 
      21             : #include "mem.h"
      22             : #include "ssl_verify.h"
      23             : #include "log.h"
      24             : 
      25             : #ifndef WIN32
      26             : #include <sys/socket.h>
      27             : #include <netinet/in.h>
      28             : #include <arpa/inet.h>
      29             : #endif
      30             : #include <ctype.h>
      31             : #include <string.h>
      32             : #include <gio/gio.h>
      33             : 
      34             : #if OPENSSL_VERSION_NUMBER < 0x10100000 || \
      35             :     (defined (LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000)
      36             : static const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1)
      37             : {
      38             :     return M_ASN1_STRING_data(asn1);
      39             : }
      40             : #endif
      41             : 
      42           0 : static int verify_pubkey(X509* cert, const char *key, size_t key_size)
      43             : {
      44           0 :     EVP_PKEY* cert_pubkey = NULL;
      45           0 :     EVP_PKEY* orig_pubkey = NULL;
      46           0 :     BIO* bio = NULL;
      47           0 :     int ret = 0;
      48             : 
      49           0 :     if (!key || key_size == 0)
      50           0 :         return 0;
      51             : 
      52           0 :     if (!cert) {
      53           0 :         spice_debug("warning: no cert!");
      54           0 :         return 0;
      55             :     }
      56             : 
      57           0 :     cert_pubkey = X509_get_pubkey(cert);
      58           0 :     if (!cert_pubkey) {
      59           0 :         spice_debug("warning: reading public key from certificate failed");
      60           0 :         goto finish;
      61             :     }
      62             : 
      63           0 :     bio = BIO_new_mem_buf((void*)key, key_size);
      64           0 :     if (!bio) {
      65           0 :         spice_debug("creating BIO failed");
      66           0 :         goto finish;
      67             :     }
      68             : 
      69           0 :     orig_pubkey = d2i_PUBKEY_bio(bio, NULL);
      70           0 :     if (!orig_pubkey) {
      71           0 :         spice_debug("reading pubkey from bio failed");
      72           0 :         goto finish;
      73             :     }
      74             : 
      75           0 :     ret = EVP_PKEY_cmp(orig_pubkey, cert_pubkey);
      76             : 
      77           0 :     if (ret == 1) {
      78           0 :         spice_debug("public keys match");
      79           0 :     } else if (ret == 0) {
      80           0 :         spice_debug("public keys mismatch");
      81             :     } else {
      82           0 :         spice_debug("public keys types mismatch");
      83             :     }
      84             : 
      85           0 : finish:
      86           0 :     if (bio)
      87           0 :         BIO_free(bio);
      88             : 
      89           0 :     if (orig_pubkey)
      90           0 :         EVP_PKEY_free(orig_pubkey);
      91             : 
      92           0 :     if (cert_pubkey)
      93           0 :         EVP_PKEY_free(cert_pubkey);
      94             : 
      95           0 :     return ret;
      96             : }
      97             : 
      98             : /* from gnutls
      99             :  * compare hostname against certificate, taking account of wildcards
     100             :  * return 1 on success or 0 on error
     101             :  *
     102             :  * note: certnamesize is required as X509 certs can contain embedded NULs in
     103             :  * the strings such as CN or subjectAltName
     104             :  */
     105           0 : static int _gnutls_hostname_compare(const char *certname,
     106             :                                     size_t certnamesize, const char *hostname)
     107             : {
     108             :     /* find the first different character */
     109           0 :     for (; *certname && *hostname && toupper (*certname) == toupper (*hostname);
     110           0 :          certname++, hostname++, certnamesize--)
     111             :         ;
     112             : 
     113             :     /* the strings are the same */
     114           0 :     if (certnamesize == 0 && *hostname == '\0')
     115           0 :         return 1;
     116             : 
     117           0 :     if (*certname == '*')
     118             :         {
     119             :             /* a wildcard certificate */
     120             : 
     121           0 :             certname++;
     122           0 :             certnamesize--;
     123             : 
     124             :             while (1)
     125             :                 {
     126             :                     /* Use a recursive call to allow multiple wildcards */
     127           0 :                     if (_gnutls_hostname_compare (certname, certnamesize, hostname))
     128           0 :                         return 1;
     129             : 
     130             :                     /* wildcards are only allowed to match a single domain
     131             :                        component or component fragment */
     132           0 :                     if (*hostname == '\0' || *hostname == '.')
     133             :                         break;
     134           0 :                     hostname++;
     135             :                 }
     136             : 
     137           0 :             return 0;
     138             :         }
     139             : 
     140           0 :     return 0;
     141             : }
     142             : 
     143             : /**
     144             :  * From gnutls and spice red_peer.c
     145             :  * TODO: switch to gnutls and get rid of this
     146             :  *
     147             :  * This function will check if the given certificate's subject matches
     148             :  * the given hostname.  This is a basic implementation of the matching
     149             :  * described in RFC2818 (HTTPS), which takes into account wildcards,
     150             :  * and the DNSName/IPAddress subject alternative name PKIX extension.
     151             :  *
     152             :  * Returns: 1 for a successful match, and 0 on failure.
     153             :  **/
     154           0 : static int verify_hostname(X509* cert, const char *hostname)
     155             : {
     156             :     GENERAL_NAMES* subject_alt_names;
     157           0 :     int found_dns_name = 0;
     158           0 :     int cn_match = 0;
     159             :     X509_NAME* subject;
     160             : 
     161           0 :     spice_return_val_if_fail(hostname != NULL, 0);
     162             : 
     163           0 :     if (!cert) {
     164           0 :         spice_debug("warning: no cert!");
     165           0 :         return 0;
     166             :     }
     167             : 
     168             :     /* try matching against:
     169             :      *  1) a DNS name as an alternative name (subjectAltName) extension
     170             :      *     in the certificate
     171             :      *  2) the common name (CN) in the certificate
     172             :      *
     173             :      *  either of these may be of the form: *.domain.tld
     174             :      *
     175             :      *  only try (2) if there is no subjectAltName extension of
     176             :      *  type dNSName
     177             :      */
     178             : 
     179             :     /* Check through all included subjectAltName extensions, comparing
     180             :      * against all those of type dNSName.
     181             :      */
     182           0 :     subject_alt_names = (GENERAL_NAMES*)X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
     183             : 
     184           0 :     if (subject_alt_names) {
     185           0 :         int num_alts = sk_GENERAL_NAME_num(subject_alt_names);
     186             :         int i;
     187           0 :         for (i = 0; i < num_alts; i++) {
     188           0 :             const GENERAL_NAME* name = sk_GENERAL_NAME_value(subject_alt_names, i);
     189           0 :             if (name->type == GEN_DNS) {
     190           0 :                 found_dns_name = 1;
     191           0 :                 if (_gnutls_hostname_compare((const char *)ASN1_STRING_get0_data(name->d.dNSName),
     192           0 :                                              ASN1_STRING_length(name->d.dNSName),
     193             :                                              hostname)) {
     194           0 :                     spice_debug("alt name match=%s", ASN1_STRING_get0_data(name->d.dNSName));
     195           0 :                     GENERAL_NAMES_free(subject_alt_names);
     196           0 :                     return 1;
     197             :                 }
     198           0 :             } else if (name->type == GEN_IPADD) {
     199             :                 GInetAddress * ip;
     200             :                 const guint8 * ip_binary;
     201             :                 int alt_ip_len;
     202             :                 int ip_len;
     203             : 
     204           0 :                 found_dns_name = 1;
     205             : 
     206           0 :                 ip = g_inet_address_new_from_string(hostname);
     207           0 :                 if (ip == NULL) {
     208           0 :                     spice_warning("Could not parse hostname: %s", hostname);
     209           0 :                     continue;
     210             :                 }
     211             : 
     212           0 :                 ip_len = g_inet_address_get_native_size(ip);
     213           0 :                 ip_binary = g_inet_address_to_bytes(ip);
     214             : 
     215           0 :                 alt_ip_len = ASN1_STRING_length(name->d.iPAddress);
     216             : 
     217           0 :                 if ((ip_len == alt_ip_len) &&
     218           0 :                    (memcmp(ASN1_STRING_get0_data(name->d.iPAddress), ip_binary, ip_len)) == 0) {
     219           0 :                     GInetAddress * alt_ip = NULL;
     220           0 :                     gchar * alt_ip_string = NULL;
     221             : 
     222           0 :                     alt_ip = g_inet_address_new_from_bytes(ASN1_STRING_get0_data(name->d.iPAddress),
     223             :                                                            g_inet_address_get_family(ip));
     224           0 :                     alt_ip_string = g_inet_address_to_string(alt_ip);
     225           0 :                     spice_debug("alt name IP match=%s", alt_ip_string);
     226             : 
     227           0 :                     g_free(alt_ip_string);
     228           0 :                     g_object_unref(alt_ip);
     229           0 :                     g_object_unref(ip);
     230           0 :                     GENERAL_NAMES_free(subject_alt_names);
     231           0 :                     return 1;
     232             :                 }
     233           0 :                 g_object_unref(ip);
     234             :             }
     235             :         }
     236           0 :         GENERAL_NAMES_free(subject_alt_names);
     237             :     }
     238             : 
     239           0 :     if (found_dns_name) {
     240           0 :         spice_debug("warning: SubjectAltName mismatch");
     241           0 :         return 0;
     242             :     }
     243             : 
     244             :     /* extracting commonNames */
     245           0 :     subject = X509_get_subject_name(cert);
     246           0 :     if (subject) {
     247           0 :         int pos = -1;
     248             :         X509_NAME_ENTRY* cn_entry;
     249             :         ASN1_STRING* cn_asn1;
     250             : 
     251           0 :         while ((pos = X509_NAME_get_index_by_NID(subject, NID_commonName, pos)) != -1) {
     252           0 :             cn_entry = X509_NAME_get_entry(subject, pos);
     253           0 :             if (!cn_entry) {
     254           0 :                 continue;
     255             :             }
     256           0 :             cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
     257           0 :             if (!cn_asn1) {
     258           0 :                 continue;
     259             :             }
     260             : 
     261           0 :             if (_gnutls_hostname_compare((const char*)ASN1_STRING_get0_data(cn_asn1),
     262           0 :                                          ASN1_STRING_length(cn_asn1),
     263             :                                          hostname)) {
     264           0 :                 spice_debug("common name match=%s", (char*)ASN1_STRING_get0_data(cn_asn1));
     265           0 :                 cn_match = 1;
     266           0 :                 break;
     267             :             }
     268             :         }
     269             :     }
     270             : 
     271           0 :     if (!cn_match) {
     272           0 :         spice_debug("warning: common name mismatch");
     273             :     }
     274             : 
     275           0 :     return cn_match;
     276             : }
     277             : 
     278          16 : static X509_NAME* subject_to_x509_name(const char *subject, int *nentries)
     279             : {
     280             :     X509_NAME* in_subject;
     281             :     const char *p;
     282          16 :     char *key, *val = NULL, *k, *v = NULL;
     283             :     enum {
     284             :         KEY,
     285             :         VALUE
     286             :     } state;
     287             : 
     288          16 :     spice_return_val_if_fail(subject != NULL, NULL);
     289          16 :     spice_return_val_if_fail(nentries != NULL, NULL);
     290             : 
     291          16 :     key = (char*)alloca(strlen(subject)+1);
     292          16 :     in_subject = X509_NAME_new();
     293             : 
     294          16 :     if (!in_subject || !key) {
     295           0 :         spice_debug("failed to allocate");
     296           0 :         return NULL;
     297             :     }
     298             : 
     299          16 :     *nentries = 0;
     300             : 
     301          16 :     k = key;
     302          16 :     state = KEY;
     303          79 :     for (p = subject;; ++p) {
     304          79 :         int escape = 0;
     305          79 :         if (*p == '\\') {
     306           4 :             ++p;
     307           4 :             if (*p != '\\' && *p != ',') {
     308           1 :                 spice_debug("Invalid character after \\");
     309           1 :                 goto fail;
     310             :             }
     311           3 :             escape = 1;
     312             :         }
     313             : 
     314          78 :         switch (state) {
     315          49 :         case KEY:
     316          49 :             if (*p == ' ' && k == key) {
     317          13 :                 continue; /* skip spaces before key */
     318          36 :             } if (*p == 0) {
     319           5 :                 if (k == key) /* empty key, ending */
     320           4 :                     goto success;
     321           1 :                 goto fail;
     322          31 :             } else if (*p == ',' && !escape) {
     323           2 :                 goto fail; /* assignment is missing */
     324          29 :             } else if (*p == '=' && !escape) {
     325          13 :                 state = VALUE;
     326          13 :                 *k = 0;
     327          13 :                 val = k + 1;
     328          13 :                 v = val;
     329             :             } else
     330          16 :                 *k++ = *p;
     331          29 :             break;
     332          29 :         case VALUE:
     333          29 :             if (*p == 0 || (*p == ',' && !escape)) {
     334          13 :                 if (v == val) /* empty value */
     335           1 :                     goto fail;
     336             : 
     337          12 :                 *v = 0;
     338             : 
     339          12 :                 if (!X509_NAME_add_entry_by_txt(in_subject, key,
     340             :                                                 MBSTRING_UTF8,
     341             :                                                 (const unsigned char*)val,
     342             :                                                 -1, -1, 0)) {
     343           0 :                     spice_debug("warning: failed to add entry %s=%s to X509_NAME",
     344             :                                 key, val);
     345           0 :                     goto fail;
     346             :                 }
     347          12 :                 *nentries += 1;
     348             : 
     349          12 :                 if (*p == 0)
     350           7 :                     goto success;
     351             : 
     352           5 :                 state = KEY;
     353           5 :                 k = key;
     354             :             } else
     355          16 :                 *v++ = *p;
     356          21 :             break;
     357             :         }
     358             :     }
     359             : 
     360          11 : success:
     361          11 :     return in_subject;
     362             : 
     363           5 : fail:
     364           5 :     if (in_subject)
     365           5 :         X509_NAME_free(in_subject);
     366             : 
     367           5 :     return NULL;
     368             : }
     369             : 
     370           0 : static int verify_subject(X509* cert, SpiceOpenSSLVerify* verify)
     371             : {
     372           0 :     X509_NAME *cert_subject = NULL;
     373             :     X509_NAME* in_subject;
     374             :     int ret;
     375             :     int in_entries;
     376             : 
     377           0 :     if (!cert) {
     378           0 :         spice_debug("warning: no cert!");
     379           0 :         return 0;
     380             :     }
     381             : 
     382           0 :     cert_subject = X509_get_subject_name(cert);
     383           0 :     if (!cert_subject) {
     384           0 :         spice_debug("warning: reading certificate subject failed");
     385           0 :         return 0;
     386             :     }
     387             : 
     388           0 :     in_subject = subject_to_x509_name(verify->subject, &in_entries);
     389           0 :     if (!in_subject) {
     390           0 :         spice_debug("warning: no in_subject!");
     391           0 :         return 0;
     392             :     }
     393             : 
     394             :     /* Note: this check is redundant with the pre-condition in X509_NAME_cmp */
     395           0 :     if (X509_NAME_entry_count(cert_subject) != in_entries) {
     396           0 :         spice_debug("subject mismatch: #entries cert=%d, input=%d",
     397             :             X509_NAME_entry_count(cert_subject), in_entries);
     398           0 :         X509_NAME_free(in_subject);
     399           0 :         return 0;
     400             :     }
     401             : 
     402           0 :     ret = X509_NAME_cmp(cert_subject, in_subject);
     403             : 
     404           0 :     if (ret == 0) {
     405           0 :         spice_debug("subjects match");
     406             :     } else {
     407             :         char *p;
     408           0 :         spice_debug("subjects mismatch");
     409             : 
     410           0 :         p = X509_NAME_oneline(cert_subject, NULL, 0);
     411           0 :         spice_debug("cert_subject: %s", p);
     412           0 :         free(p);
     413             : 
     414           0 :         p = X509_NAME_oneline(in_subject, NULL, 0);
     415           0 :         spice_debug("in_subject:   %s", p);
     416           0 :         free(p);
     417             :     }
     418           0 :     X509_NAME_free(in_subject);
     419             : 
     420           0 :     return !ret;
     421             : }
     422             : 
     423           0 : static int openssl_verify(int preverify_ok, X509_STORE_CTX *ctx)
     424             : {
     425             :     int depth, err;
     426             :     SpiceOpenSSLVerify *v;
     427             :     SSL *ssl;
     428             :     X509* cert;
     429             :     char buf[256];
     430             :     unsigned int failed_verifications;
     431             : 
     432           0 :     ssl = (SSL*)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
     433           0 :     v = (SpiceOpenSSLVerify*)SSL_get_app_data(ssl);
     434             : 
     435           0 :     cert = X509_STORE_CTX_get_current_cert(ctx);
     436           0 :     X509_NAME_oneline(X509_get_subject_name(cert), buf, 256);
     437           0 :     depth = X509_STORE_CTX_get_error_depth(ctx);
     438           0 :     err = X509_STORE_CTX_get_error(ctx);
     439           0 :     if (depth > 0) {
     440           0 :         if (!preverify_ok) {
     441           0 :             spice_warning("Error in certificate chain verification: %s (num=%d:depth%d:%s)",
     442             :                           X509_verify_cert_error_string(err), err, depth, buf);
     443           0 :             v->all_preverify_ok = 0;
     444             : 
     445             :             /* if certificate verification failed, we can still authorize the server */
     446             :             /* if its public key matches the one we hold in the peer_connect_options. */
     447           0 :             if (err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN &&
     448           0 :                 v->verifyop & SPICE_SSL_VERIFY_OP_PUBKEY)
     449           0 :                 return 1;
     450             : 
     451           0 :             if (err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)
     452           0 :                 spice_debug("server certificate not being signed by the provided CA");
     453             : 
     454           0 :             return 0;
     455             :         } else
     456           0 :             return 1;
     457             :     }
     458             : 
     459             :     /* depth == 0 */
     460           0 :     if (!cert) {
     461           0 :         spice_debug("failed to get server certificate");
     462           0 :         return 0;
     463             :     }
     464             : 
     465           0 :     failed_verifications = 0;
     466           0 :     if (v->verifyop & SPICE_SSL_VERIFY_OP_PUBKEY) {
     467           0 :         if (verify_pubkey(cert, v->pubkey, v->pubkey_size))
     468           0 :             return 1;
     469             :         else
     470           0 :             failed_verifications |= SPICE_SSL_VERIFY_OP_PUBKEY;
     471             :     }
     472             : 
     473           0 :     if (!preverify_ok) {
     474           0 :         err = X509_STORE_CTX_get_error(ctx);
     475           0 :         depth = X509_STORE_CTX_get_error_depth(ctx);
     476           0 :         spice_warning("Error in server certificate verification: %s (num=%d:depth%d:%s)",
     477             :                       X509_verify_cert_error_string(err), err, depth, buf);
     478           0 :         return 0;
     479             :     }
     480           0 :     if (!v->all_preverify_ok) {
     481           0 :         return 0;
     482             :     }
     483             : 
     484           0 :     if (v->verifyop & SPICE_SSL_VERIFY_OP_SUBJECT) {
     485           0 :         if (verify_subject(cert, v))
     486           0 :             return 1;
     487             :         else
     488           0 :             failed_verifications |= SPICE_SSL_VERIFY_OP_SUBJECT;
     489           0 :     } else if (v->verifyop & SPICE_SSL_VERIFY_OP_HOSTNAME) {
     490           0 :        if (verify_hostname(cert, v->hostname))
     491           0 :            return 1;
     492             :         else
     493           0 :             failed_verifications |= SPICE_SSL_VERIFY_OP_HOSTNAME;
     494             :     }
     495             : 
     496             :     /* If we reach this code, this means all the tests failed, thus
     497             :      * verification failed
     498             :      */
     499           0 :     if (failed_verifications & SPICE_SSL_VERIFY_OP_PUBKEY)
     500           0 :         spice_warning("ssl: pubkey verification failed");
     501             : 
     502           0 :     if (failed_verifications & SPICE_SSL_VERIFY_OP_HOSTNAME)
     503           0 :         spice_warning("ssl: hostname '%s' verification failed", v->hostname);
     504             : 
     505           0 :     if (failed_verifications & SPICE_SSL_VERIFY_OP_SUBJECT)
     506           0 :         spice_warning("ssl: subject '%s' verification failed", v->subject);
     507             : 
     508           0 :     spice_warning("ssl: verification failed");
     509             : 
     510           0 :     return 0;
     511             : }
     512             : 
     513           0 : SpiceOpenSSLVerify* spice_openssl_verify_new(SSL *ssl, SPICE_SSL_VERIFY_OP verifyop,
     514             :                                              const char *hostname,
     515             :                                              const char *pubkey, size_t pubkey_size,
     516             :                                              const char *subject)
     517             : {
     518             :     SpiceOpenSSLVerify *v;
     519             : 
     520           0 :     if (!verifyop)
     521           0 :         return NULL;
     522             : 
     523           0 :     v = spice_new0(SpiceOpenSSLVerify, 1);
     524             : 
     525           0 :     v->ssl              = ssl;
     526           0 :     v->verifyop         = verifyop;
     527           0 :     v->hostname         = spice_strdup(hostname);
     528           0 :     v->pubkey           = (char*)spice_memdup(pubkey, pubkey_size);
     529           0 :     v->pubkey_size      = pubkey_size;
     530           0 :     v->subject          = spice_strdup(subject);
     531             : 
     532           0 :     v->all_preverify_ok = 1;
     533             : 
     534           0 :     SSL_set_app_data(ssl, v);
     535           0 :     SSL_set_verify(ssl,
     536             :                    SSL_VERIFY_PEER, openssl_verify);
     537             : 
     538           0 :     return v;
     539             : }
     540             : 
     541           0 : void spice_openssl_verify_free(SpiceOpenSSLVerify* verify)
     542             : {
     543           0 :     if (!verify)
     544           0 :         return;
     545             : 
     546           0 :     free(verify->pubkey);
     547           0 :     free(verify->subject);
     548           0 :     free(verify->hostname);
     549             : 
     550           0 :     if (verify->ssl)
     551           0 :         SSL_set_app_data(verify->ssl, NULL);
     552           0 :     free(verify);
     553             : }

Generated by: LCOV version 1.14