1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3 Copyright (C) 2011 Red Hat, Inc.
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.
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.
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/>.
24 #include "ssl_verify.h"
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
36 static int inet_aton(const char* ip, struct in_addr* in_addr)
38 unsigned long addr = inet_addr(ip);
40 if (addr == INADDR_NONE) {
43 in_addr->S_un.S_addr = addr;
48 static int verify_pubkey(X509* cert, const char *key, size_t key_size)
50 EVP_PKEY* cert_pubkey = NULL;
51 EVP_PKEY* orig_pubkey = NULL;
55 if (!key || key_size == 0)
59 spice_debug("warning: no cert!");
63 cert_pubkey = X509_get_pubkey(cert);
65 spice_debug("warning: reading public key from certificate failed");
69 bio = BIO_new_mem_buf((void*)key, key_size);
71 spice_debug("creating BIO failed");
75 orig_pubkey = d2i_PUBKEY_bio(bio, NULL);
77 spice_debug("reading pubkey from bio failed");
81 ret = EVP_PKEY_cmp(orig_pubkey, cert_pubkey);
84 spice_debug("public keys match");
85 } else if (ret == 0) {
86 spice_debug("public keys mismatch");
88 spice_debug("public keys types mismatch");
96 EVP_PKEY_free(orig_pubkey);
99 EVP_PKEY_free(cert_pubkey);
105 * compare hostname against certificate, taking account of wildcards
106 * return 1 on success or 0 on error
108 * note: certnamesize is required as X509 certs can contain embedded NULs in
109 * the strings such as CN or subjectAltName
111 static int _gnutls_hostname_compare(const char *certname,
112 size_t certnamesize, const char *hostname)
114 /* find the first different character */
115 for (; *certname && *hostname && toupper (*certname) == toupper (*hostname);
116 certname++, hostname++, certnamesize--)
119 /* the strings are the same */
120 if (certnamesize == 0 && *hostname == '\0')
123 if (*certname == '*')
125 /* a wildcard certificate */
132 /* Use a recursive call to allow multiple wildcards */
133 if (_gnutls_hostname_compare (certname, certnamesize, hostname))
136 /* wildcards are only allowed to match a single domain
137 component or component fragment */
138 if (*hostname == '\0' || *hostname == '.')
150 * From gnutls and spice red_peer.c
151 * TODO: switch to gnutls and get rid of this
153 * This function will check if the given certificate's subject matches
154 * the given hostname. This is a basic implementation of the matching
155 * described in RFC2818 (HTTPS), which takes into account wildcards,
156 * and the DNSName/IPAddress subject alternative name PKIX extension.
158 * Returns: 1 for a successful match, and 0 on failure.
160 static int verify_hostname(X509* cert, const char *hostname)
162 GENERAL_NAMES* subject_alt_names;
163 int found_dns_name = 0;
169 spice_return_val_if_fail(hostname != NULL, 0);
172 spice_debug("warning: no cert!");
176 // only IpV4 supported
177 if (inet_aton(hostname, &addr)) {
178 addr_len = sizeof(struct in_addr);
181 /* try matching against:
182 * 1) a DNS name as an alternative name (subjectAltName) extension
184 * 2) the common name (CN) in the certificate
186 * either of these may be of the form: *.domain.tld
188 * only try (2) if there is no subjectAltName extension of
192 /* Check through all included subjectAltName extensions, comparing
193 * against all those of type dNSName.
195 subject_alt_names = (GENERAL_NAMES*)X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
197 if (subject_alt_names) {
198 int num_alts = sk_GENERAL_NAME_num(subject_alt_names);
200 for (i = 0; i < num_alts; i++) {
201 const GENERAL_NAME* name = sk_GENERAL_NAME_value(subject_alt_names, i);
202 if (name->type == GEN_DNS) {
204 if (_gnutls_hostname_compare((char *)ASN1_STRING_data(name->d.dNSName),
205 ASN1_STRING_length(name->d.dNSName),
207 spice_debug("alt name match=%s", ASN1_STRING_data(name->d.dNSName));
208 GENERAL_NAMES_free(subject_alt_names);
211 } else if (name->type == GEN_IPADD) {
212 int alt_ip_len = ASN1_STRING_length(name->d.iPAddress);
214 if ((addr_len == alt_ip_len)&&
215 !memcmp(ASN1_STRING_data(name->d.iPAddress), &addr, addr_len)) {
216 spice_debug("alt name IP match=%s",
217 inet_ntoa(*((struct in_addr*)ASN1_STRING_data(name->d.dNSName))));
218 GENERAL_NAMES_free(subject_alt_names);
223 GENERAL_NAMES_free(subject_alt_names);
226 if (found_dns_name) {
227 spice_debug("warning: SubjectAltName mismatch");
231 /* extracting commonNames */
232 subject = X509_get_subject_name(cert);
235 X509_NAME_ENTRY* cn_entry;
236 ASN1_STRING* cn_asn1;
238 while ((pos = X509_NAME_get_index_by_NID(subject, NID_commonName, pos)) != -1) {
239 cn_entry = X509_NAME_get_entry(subject, pos);
243 cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
248 if (_gnutls_hostname_compare((char*)ASN1_STRING_data(cn_asn1),
249 ASN1_STRING_length(cn_asn1),
251 spice_debug("common name match=%s", (char*)ASN1_STRING_data(cn_asn1));
259 spice_debug("warning: common name mismatch");
265 static X509_NAME* subject_to_x509_name(const char *subject, int *nentries)
267 X509_NAME* in_subject;
269 char *key, *val, *k, *v = NULL;
275 spice_return_val_if_fail(subject != NULL, NULL);
276 spice_return_val_if_fail(nentries != NULL, NULL);
278 key = (char*)alloca(strlen(subject));
279 val = (char*)alloca(strlen(subject));
280 in_subject = X509_NAME_new();
282 if (!in_subject || !key || !val) {
283 spice_debug("failed to allocate");
291 for (p = subject;; ++p) {
295 if (*p != '\\' && *p != ',') {
296 spice_debug("Invalid character after \\");
304 if (*p == ' ' && k == key) {
305 continue; /* skip spaces before key */
307 if (k == key) /* empty key, ending */
310 } else if (*p == ',' && !escape) {
311 goto fail; /* assignment is missing */
312 } else if (*p == '=' && !escape) {
320 if (*p == 0 || (*p == ',' && !escape)) {
321 if (v == val) /* empty value */
326 if (!X509_NAME_add_entry_by_txt(in_subject, key,
328 (const unsigned char*)val,
330 spice_debug("warning: failed to add entry %s=%s to X509_NAME",
352 X509_NAME_free(in_subject);
357 static int verify_subject(X509* cert, SpiceOpenSSLVerify* verify)
359 X509_NAME *cert_subject = NULL;
364 spice_debug("warning: no cert!");
368 cert_subject = X509_get_subject_name(cert);
370 spice_debug("warning: reading certificate subject failed");
374 if (!verify->in_subject) {
375 verify->in_subject = subject_to_x509_name(verify->subject, &in_entries);
376 if (!verify->in_subject) {
377 spice_debug("warning: no in_subject!");
382 /* Note: this check is redundant with the pre-condition in X509_NAME_cmp */
383 if (X509_NAME_entry_count(cert_subject) != in_entries) {
384 spice_debug("subject mismatch: #entries cert=%d, input=%d",
385 X509_NAME_entry_count(cert_subject), in_entries);
389 ret = X509_NAME_cmp(cert_subject, verify->in_subject);
392 spice_debug("subjects match");
395 spice_debug("subjects mismatch");
397 p = X509_NAME_oneline(cert_subject, NULL, 0);
398 spice_debug("cert_subject: %s", p);
401 p = X509_NAME_oneline(verify->in_subject, NULL, 0);
402 spice_debug("in_subject: %s", p);
409 static int openssl_verify(int preverify_ok, X509_STORE_CTX *ctx)
412 SpiceOpenSSLVerify *v;
416 unsigned int failed_verifications;
418 ssl = (SSL*)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
419 v = (SpiceOpenSSLVerify*)SSL_get_app_data(ssl);
421 cert = X509_STORE_CTX_get_current_cert(ctx);
422 X509_NAME_oneline(X509_get_subject_name(cert), buf, 256);
423 depth = X509_STORE_CTX_get_error_depth(ctx);
424 err = X509_STORE_CTX_get_error(ctx);
427 spice_warning("openssl verify:num=%d:%s:depth=%d:%s", err,
428 X509_verify_cert_error_string(err), depth, buf);
429 v->all_preverify_ok = 0;
431 /* if certificate verification failed, we can still authorize the server */
432 /* if its public key matches the one we hold in the peer_connect_options. */
433 if (err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN &&
434 v->verifyop & SPICE_SSL_VERIFY_OP_PUBKEY)
437 if (err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)
438 spice_debug("server certificate not being signed by the provided CA");
447 spice_debug("failed to get server certificate");
451 failed_verifications = 0;
452 if (v->verifyop & SPICE_SSL_VERIFY_OP_PUBKEY) {
453 if (verify_pubkey(cert, v->pubkey, v->pubkey_size))
456 failed_verifications |= SPICE_SSL_VERIFY_OP_PUBKEY;
459 if (!v->all_preverify_ok || !preverify_ok)
462 if (v->verifyop & SPICE_SSL_VERIFY_OP_HOSTNAME) {
463 if (verify_hostname(cert, v->hostname))
466 failed_verifications |= SPICE_SSL_VERIFY_OP_HOSTNAME;
470 if (v->verifyop & SPICE_SSL_VERIFY_OP_SUBJECT) {
471 if (verify_subject(cert, v))
474 failed_verifications |= SPICE_SSL_VERIFY_OP_SUBJECT;
477 /* If we reach this code, this means all the tests failed, thus
478 * verification failed
480 if (failed_verifications & SPICE_SSL_VERIFY_OP_PUBKEY)
481 spice_warning("ssl: pubkey verification failed");
483 if (failed_verifications & SPICE_SSL_VERIFY_OP_HOSTNAME)
484 spice_warning("ssl: hostname '%s' verification failed", v->hostname);
486 if (failed_verifications & SPICE_SSL_VERIFY_OP_SUBJECT)
487 spice_warning("ssl: subject '%s' verification failed", v->subject);
489 spice_warning("ssl: verification failed");
494 SpiceOpenSSLVerify* spice_openssl_verify_new(SSL *ssl, SPICE_SSL_VERIFY_OP verifyop,
495 const char *hostname,
496 const char *pubkey, size_t pubkey_size,
499 SpiceOpenSSLVerify *v;
504 v = spice_new0(SpiceOpenSSLVerify, 1);
507 v->verifyop = verifyop;
508 v->hostname = spice_strdup(hostname);
509 v->pubkey = (char*)spice_memdup(pubkey, pubkey_size);
510 v->pubkey_size = pubkey_size;
511 v->subject = spice_strdup(subject);
513 v->all_preverify_ok = 1;
515 SSL_set_app_data(ssl, v);
517 SSL_VERIFY_PEER, openssl_verify);
522 void spice_openssl_verify_free(SpiceOpenSSLVerify* verify)
527 free(verify->pubkey);
528 free(verify->subject);
529 free(verify->hostname);
531 if (verify->in_subject)
532 X509_NAME_free(verify->in_subject);
535 SSL_set_app_data(verify->ssl, NULL);