[Spice-devel] [PATCH 6/8] common: add ssl_verify.c common code

Marc-André Lureau marcandre.lureau at redhat.com
Tue May 3 07:37:43 PDT 2011


Code adapter from RedPeer::ssl_verify_callback() and used by
spice-gtk.

Since v1:
 - fixed Makefile.am
 - added config.h include
 - autoconf alloca added in patch series
 - moved int escape inside for loop
 - added a failed case when missing assignment
 - replaced strlen () by -1
 - skip spaces after comma
 - c++ guards

I didn't use bool, because openSSL uses int, and it is more future
proof for error reporting.
---
 common/Makefile.am  |    2 +
 common/ssl_verify.c |  481 +++++++++++++++++++++++++++++++++++++++++++++++++++
 common/ssl_verify.h |   60 +++++++
 3 files changed, 543 insertions(+), 0 deletions(-)
 create mode 100644 common/ssl_verify.c
 create mode 100644 common/ssl_verify.h

diff --git a/common/Makefile.am b/common/Makefile.am
index dff9574..5c38b75 100644
--- a/common/Makefile.am
+++ b/common/Makefile.am
@@ -69,5 +69,7 @@ EXTRA_DIST =				\
 	quic_family_tmpl.c		\
 	quic_rgb_tmpl.c			\
 	quic_tmpl.c			\
+	ssl_verify.h			\
+	ssl_verify.c			\
 	$(NULL)
 
diff --git a/common/ssl_verify.c b/common/ssl_verify.c
new file mode 100644
index 0000000..5e3722e
--- /dev/null
+++ b/common/ssl_verify.c
@@ -0,0 +1,481 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 Red Hat, Inc.
+
+   This library 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 library 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 library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "mem.h"
+#include "ssl_verify.h"
+
+#ifndef WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#include <ctype.h>
+
+#ifndef SPICE_DEBUG
+# define SPICE_DEBUG(format, args...)
+#endif
+
+#ifdef WIN32
+static int inet_aton(const char* ip, struct in_addr* in_addr)
+{
+    unsigned long addr = inet_addr(ip);
+
+    if (addr == INADDR_NONE) {
+        return 0;
+    }
+    in_addr->S_un.S_addr = addr;
+    return 1;
+}
+#endif
+
+static int verify_pubkey(X509* cert, const char *key, size_t key_size)
+{
+    EVP_PKEY* cert_pubkey = NULL;
+    EVP_PKEY* orig_pubkey = NULL;
+    BIO* bio = NULL;
+    int ret = 0;
+
+    if (!key || key_size == 0)
+        return 0;
+
+    if (!cert) {
+        SPICE_DEBUG("warning: no cert!");
+        return 0;
+    }
+
+    cert_pubkey = X509_get_pubkey(cert);
+    if (!cert_pubkey) {
+        SPICE_DEBUG("warning: reading public key from certificate failed");
+        goto finish;
+    }
+
+    bio = BIO_new_mem_buf((void*)key, key_size);
+    if (!bio) {
+        SPICE_DEBUG("creating BIO failed");
+        goto finish;
+    }
+
+    orig_pubkey = d2i_PUBKEY_bio(bio, NULL);
+    if (!orig_pubkey) {
+        SPICE_DEBUG("reading pubkey from bio failed");
+        goto finish;
+    }
+
+    ret = EVP_PKEY_cmp(orig_pubkey, cert_pubkey);
+
+    if (ret == 1)
+        SPICE_DEBUG("public keys match");
+    else if (ret == 0)
+        SPICE_DEBUG("public keys mismatch");
+    else
+        SPICE_DEBUG("public keys types mismatch");
+
+finish:
+    if (bio)
+        BIO_free(bio);
+
+    if (orig_pubkey)
+        EVP_PKEY_free(orig_pubkey);
+
+    if (cert_pubkey)
+        EVP_PKEY_free(cert_pubkey);
+
+    return ret;
+}
+
+/* from gnutls
+ * compare hostname against certificate, taking account of wildcards
+ * return 1 on success or 0 on error
+ *
+ * note: certnamesize is required as X509 certs can contain embedded NULs in
+ * the strings such as CN or subjectAltName
+ */
+static int _gnutls_hostname_compare(const char *certname,
+                                    size_t certnamesize, const char *hostname)
+{
+    /* find the first different character */
+    for (; *certname && *hostname && toupper (*certname) == toupper (*hostname);
+         certname++, hostname++, certnamesize--)
+        ;
+
+    /* the strings are the same */
+    if (certnamesize == 0 && *hostname == '\0')
+        return 1;
+
+    if (*certname == '*')
+        {
+            /* a wildcard certificate */
+
+            certname++;
+            certnamesize--;
+
+            while (1)
+                {
+                    /* Use a recursive call to allow multiple wildcards */
+                    if (_gnutls_hostname_compare (certname, certnamesize, hostname))
+                        return 1;
+
+                    /* wildcards are only allowed to match a single domain
+                       component or component fragment */
+                    if (*hostname == '\0' || *hostname == '.')
+                        break;
+                    hostname++;
+                }
+
+            return 0;
+        }
+
+    return 0;
+}
+
+/**
+ * From gnutls and spice red_peer.c
+ * TODO: switch to gnutls and get rid of this
+ *
+ * This function will check if the given certificate's subject matches
+ * the given hostname.  This is a basic implementation of the matching
+ * described in RFC2818 (HTTPS), which takes into account wildcards,
+ * and the DNSName/IPAddress subject alternative name PKIX extension.
+ *
+ * Returns: 1 for a successful match, and 0 on failure.
+ **/
+static int verify_hostname(X509* cert, const char *hostname)
+{
+    GENERAL_NAMES* subject_alt_names;
+    int found_dns_name = 0;
+    struct in_addr addr;
+    int addr_len = 0;
+    int cn_match = 0;
+
+    if (!cert) {
+        SPICE_DEBUG("warning: no cert!");
+        return 0;
+    }
+
+    // only IpV4 supported
+    if (inet_aton(hostname, &addr)) {
+        addr_len = sizeof(struct in_addr);
+    }
+
+    /* try matching against:
+     *  1) a DNS name as an alternative name (subjectAltName) extension
+     *     in the certificate
+     *  2) the common name (CN) in the certificate
+     *
+     *  either of these may be of the form: *.domain.tld
+     *
+     *  only try (2) if there is no subjectAltName extension of
+     *  type dNSName
+     */
+
+    /* Check through all included subjectAltName extensions, comparing
+     * against all those of type dNSName.
+     */
+    subject_alt_names = (GENERAL_NAMES*)X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+
+    if (subject_alt_names) {
+        int num_alts = sk_GENERAL_NAME_num(subject_alt_names);
+        int i;
+        for (i = 0; i < num_alts; i++) {
+            const GENERAL_NAME* name = sk_GENERAL_NAME_value(subject_alt_names, i);
+            if (name->type == GEN_DNS) {
+                found_dns_name = 1;
+                if (_gnutls_hostname_compare((char *)ASN1_STRING_data(name->d.dNSName),
+                                             ASN1_STRING_length(name->d.dNSName),
+                                             hostname)) {
+                    SPICE_DEBUG("alt name match=%s", ASN1_STRING_data(name->d.dNSName));
+                    GENERAL_NAMES_free(subject_alt_names);
+                    return 1;
+                }
+            } else if (name->type == GEN_IPADD) {
+                int alt_ip_len = ASN1_STRING_length(name->d.iPAddress);
+                found_dns_name = 1;
+                if ((addr_len == alt_ip_len)&&
+                    !memcmp(ASN1_STRING_data(name->d.iPAddress), &addr, addr_len)) {
+                    SPICE_DEBUG("alt name IP match=%s",
+                                inet_ntoa(*((struct in_addr*)ASN1_STRING_data(name->d.dNSName))));
+                    GENERAL_NAMES_free(subject_alt_names);
+                    return 1;
+                }
+            }
+        }
+        GENERAL_NAMES_free(subject_alt_names);
+    }
+
+    if (found_dns_name) {
+        SPICE_DEBUG("warning: SubjectAltName mismatch");
+        return 0;
+    }
+
+    /* extracting commonNames */
+    X509_NAME* subject = X509_get_subject_name(cert);
+    if (subject) {
+        int pos = -1;
+        X509_NAME_ENTRY* cn_entry;
+        ASN1_STRING* cn_asn1;
+
+        while ((pos = X509_NAME_get_index_by_NID(subject, NID_commonName, pos)) != -1) {
+            cn_entry = X509_NAME_get_entry(subject, pos);
+            if (!cn_entry) {
+                continue;
+            }
+            cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
+            if (!cn_asn1) {
+                continue;
+            }
+
+            if (_gnutls_hostname_compare((char*)ASN1_STRING_data(cn_asn1),
+                                         ASN1_STRING_length(cn_asn1),
+                                         hostname)) {
+                SPICE_DEBUG("common name match=%s", (char*)ASN1_STRING_data(cn_asn1));
+                cn_match = 1;
+                break;
+            }
+        }
+    }
+
+    if (!cn_match)
+        SPICE_DEBUG("warning: common name mismatch");
+
+    return cn_match;
+}
+
+X509_NAME* subject_to_x509_name(const char *subject, int *nentries)
+{
+    X509_NAME* in_subject;
+    const char *p;
+    char *key, *val, *k, *v = NULL;
+    enum {
+        KEY,
+        VALUE
+    } state;
+
+    key = (char*)alloca(strlen(subject));
+    val = (char*)alloca(strlen(subject));
+    in_subject = X509_NAME_new();
+
+    if (!in_subject || !key || !val) {
+        SPICE_DEBUG("failed to allocate");
+        return NULL;
+    }
+
+    *nentries = 0;
+
+    k = key;
+    state = KEY;
+    for (p = subject;; ++p) {
+        int escape = 0;
+        if (*p == '\\') {
+            ++p;
+            if (*p != '\\' && *p != ',') {
+                SPICE_DEBUG("Invalid character after \\");
+                goto fail;
+            }
+            escape = 1;
+        }
+
+        switch (state) {
+        case KEY:
+            if (*p == ' ' && k == key) {
+                continue; /* skip spaces before key */
+            } if (*p == 0) {
+                if (k == key) /* empty key, ending */
+                    goto success;
+                goto fail;
+            } else if (*p == ',' && !escape) {
+                goto fail; /* assignment is missing */
+            } else if (*p == '=' && !escape) {
+                state = VALUE;
+                *k = 0;
+                v = val;
+            } else
+                *k++ = *p;
+            break;
+        case VALUE:
+            if (*p == 0 || (*p == ',' && !escape)) {
+                if (v == val) /* empty value */
+                    goto fail;
+
+                *v = 0;
+
+                if (!X509_NAME_add_entry_by_txt(in_subject, key,
+                                                MBSTRING_UTF8,
+                                                (const unsigned char*)val,
+                                                -1, -1, 0)) {
+                    SPICE_DEBUG("warning: failed to add entry %s=%s to X509_NAME",
+                                key, val);
+                    goto fail;
+                }
+                *nentries += 1;
+
+                if (*p == 0)
+                    goto success;
+
+                state = KEY;
+                k = key;
+            } else
+                *v++ = *p;
+            break;
+        }
+    }
+
+success:
+    return in_subject;
+
+fail:
+    if (in_subject)
+        X509_NAME_free(in_subject);
+
+    return NULL;
+}
+
+int verify_subject(X509* cert, SpiceOpenSSLVerify* verify)
+{
+    X509_NAME *cert_subject = NULL;
+    int ret;
+    int in_entries;
+
+    if (!cert) {
+        SPICE_DEBUG("warning: no cert!");
+        return 0;
+    }
+
+    cert_subject = X509_get_subject_name(cert);
+    if (!cert_subject) {
+        SPICE_DEBUG("warning: reading certificate subject failed");
+        return 0;
+    }
+
+    if (!verify->in_subject) {
+        verify->in_subject = subject_to_x509_name(verify->subject, &in_entries);
+        if (!verify->in_subject) {
+            SPICE_DEBUG("warning: no in_subject!");
+            return 0;
+        }
+    }
+
+    /* Note: this check is redundant with the pre-condition in X509_NAME_cmp */
+    if (X509_NAME_entry_count(cert_subject) != in_entries) {
+        SPICE_DEBUG("subject mismatch: #entries cert=%d, input=%d",
+            X509_NAME_entry_count(cert_subject), in_entries);
+        return 0;
+    }
+
+    ret = X509_NAME_cmp(cert_subject, verify->in_subject);
+
+    if (ret == 0)
+        SPICE_DEBUG("subjects match");
+    else
+        SPICE_DEBUG("subjects mismatch");
+
+    return !ret;
+}
+
+static int openssl_verify(int preverify_ok, X509_STORE_CTX *ctx)
+{
+    int depth;
+    SpiceOpenSSLVerify *v;
+    SSL *ssl;
+    X509* cert;
+
+    ssl = (SSL*)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+    v = (SpiceOpenSSLVerify*)SSL_get_app_data(ssl);
+
+    depth = X509_STORE_CTX_get_error_depth(ctx);
+    if (depth > 0) {
+        if (!preverify_ok) {
+            SPICE_DEBUG("openssl verify failed at depth=%d", depth);
+            v->all_preverify_ok = 0;
+            return 0;
+        } else
+            return 1;
+    }
+
+    /* depth == 0 */
+    cert = X509_STORE_CTX_get_current_cert(ctx);
+    if (!cert) {
+        SPICE_DEBUG("failed to get server certificate");
+        return 0;
+    }
+
+    if (v->verifyop & SPICE_SSL_VERIFY_OP_PUBKEY &&
+        verify_pubkey(cert, v->pubkey, v->pubkey_size))
+        return 1;
+
+    if (!v->all_preverify_ok || !preverify_ok)
+        return 0;
+
+    if (v->verifyop & SPICE_SSL_VERIFY_OP_HOSTNAME &&
+        verify_hostname(cert, v->hostname))
+        return 1;
+
+    if (v->verifyop & SPICE_SSL_VERIFY_OP_SUBJECT &&
+        verify_subject(cert, v))
+        return 1;
+
+    return 0;
+}
+
+SpiceOpenSSLVerify* spice_openssl_verify_new(SSL *ssl, SPICE_SSL_VERIFY_OP verifyop,
+                                             const char *hostname,
+                                             const char *pubkey, size_t pubkey_size,
+                                             const char *subject)
+{
+    SpiceOpenSSLVerify *v;
+
+    if (!verifyop)
+        return NULL;
+
+    v = spice_new0(SpiceOpenSSLVerify, 1);
+
+    v->ssl              = ssl;
+    v->verifyop         = verifyop;
+    v->hostname         = spice_strdup(hostname);
+    v->pubkey           = (char*)spice_memdup(pubkey, pubkey_size);
+    v->pubkey_size      = pubkey_size;
+    v->subject          = spice_strdup(subject);
+
+    v->all_preverify_ok = 1;
+
+    SSL_set_app_data(ssl, v);
+    SSL_set_verify(ssl,
+                   SSL_VERIFY_PEER, openssl_verify);
+
+    return v;
+}
+
+void spice_openssl_verify_free(SpiceOpenSSLVerify* verify)
+{
+    if (!verify)
+        return;
+
+    free(verify->pubkey);
+    free(verify->subject);
+    free(verify->hostname);
+
+    if (verify->in_subject)
+        X509_NAME_free(verify->in_subject);
+
+    if (verify->ssl)
+        SSL_set_app_data(verify->ssl, NULL);
+    free(verify);
+}
diff --git a/common/ssl_verify.h b/common/ssl_verify.h
new file mode 100644
index 0000000..f0b97d8
--- /dev/null
+++ b/common/ssl_verify.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2011 Red Hat, Inc.
+
+   This library 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 library 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 library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SSL_VERIFY_H
+#define SSL_VERIFY_H
+
+#include <openssl/rsa.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509v3.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+  SPICE_SSL_VERIFY_OP_NONE     = 0,
+  SPICE_SSL_VERIFY_OP_PUBKEY   = (1 << 0),
+  SPICE_SSL_VERIFY_OP_HOSTNAME = (1 << 1),
+  SPICE_SSL_VERIFY_OP_SUBJECT  = (1 << 2),
+} SPICE_SSL_VERIFY_OP;
+
+typedef struct {
+    SSL                 *ssl;
+    SPICE_SSL_VERIFY_OP verifyop;
+    int                 all_preverify_ok;
+    char                *hostname;
+    char                *pubkey;
+    size_t              pubkey_size;
+    char                *subject;
+    X509_NAME           *in_subject;
+} SpiceOpenSSLVerify;
+
+SpiceOpenSSLVerify* spice_openssl_verify_new(SSL *ssl, SPICE_SSL_VERIFY_OP verifyop,
+                                             const char *hostname,
+                                             const char *pubkey, size_t pubkey_size,
+                                             const char *subject);
+void spice_openssl_verify_free(SpiceOpenSSLVerify* verify);
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+#endif // SSL_VERIFY_H
-- 
1.7.4



More information about the Spice-devel mailing list