uploaded original spice-server-0.12.4 and celt-0.5.1.3
[sdk/emulator/libs/spice-server.git] / spice-common / common / ssl_verify.c
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 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include "mem.h"
24 #include "ssl_verify.h"
25 #include "log.h"
26
27 #ifndef WIN32
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #endif
32 #include <ctype.h>
33 #include <string.h>
34
35 #ifdef WIN32
36 static int inet_aton(const char* ip, struct in_addr* in_addr)
37 {
38     unsigned long addr = inet_addr(ip);
39
40     if (addr == INADDR_NONE) {
41         return 0;
42     }
43     in_addr->S_un.S_addr = addr;
44     return 1;
45 }
46 #endif
47
48 static int verify_pubkey(X509* cert, const char *key, size_t key_size)
49 {
50     EVP_PKEY* cert_pubkey = NULL;
51     EVP_PKEY* orig_pubkey = NULL;
52     BIO* bio = NULL;
53     int ret = 0;
54
55     if (!key || key_size == 0)
56         return 0;
57
58     if (!cert) {
59         spice_debug("warning: no cert!");
60         return 0;
61     }
62
63     cert_pubkey = X509_get_pubkey(cert);
64     if (!cert_pubkey) {
65         spice_debug("warning: reading public key from certificate failed");
66         goto finish;
67     }
68
69     bio = BIO_new_mem_buf((void*)key, key_size);
70     if (!bio) {
71         spice_debug("creating BIO failed");
72         goto finish;
73     }
74
75     orig_pubkey = d2i_PUBKEY_bio(bio, NULL);
76     if (!orig_pubkey) {
77         spice_debug("reading pubkey from bio failed");
78         goto finish;
79     }
80
81     ret = EVP_PKEY_cmp(orig_pubkey, cert_pubkey);
82
83     if (ret == 1) {
84         spice_debug("public keys match");
85     } else if (ret == 0) {
86         spice_debug("public keys mismatch");
87     } else {
88         spice_debug("public keys types mismatch");
89     }
90
91 finish:
92     if (bio)
93         BIO_free(bio);
94
95     if (orig_pubkey)
96         EVP_PKEY_free(orig_pubkey);
97
98     if (cert_pubkey)
99         EVP_PKEY_free(cert_pubkey);
100
101     return ret;
102 }
103
104 /* from gnutls
105  * compare hostname against certificate, taking account of wildcards
106  * return 1 on success or 0 on error
107  *
108  * note: certnamesize is required as X509 certs can contain embedded NULs in
109  * the strings such as CN or subjectAltName
110  */
111 static int _gnutls_hostname_compare(const char *certname,
112                                     size_t certnamesize, const char *hostname)
113 {
114     /* find the first different character */
115     for (; *certname && *hostname && toupper (*certname) == toupper (*hostname);
116          certname++, hostname++, certnamesize--)
117         ;
118
119     /* the strings are the same */
120     if (certnamesize == 0 && *hostname == '\0')
121         return 1;
122
123     if (*certname == '*')
124         {
125             /* a wildcard certificate */
126
127             certname++;
128             certnamesize--;
129
130             while (1)
131                 {
132                     /* Use a recursive call to allow multiple wildcards */
133                     if (_gnutls_hostname_compare (certname, certnamesize, hostname))
134                         return 1;
135
136                     /* wildcards are only allowed to match a single domain
137                        component or component fragment */
138                     if (*hostname == '\0' || *hostname == '.')
139                         break;
140                     hostname++;
141                 }
142
143             return 0;
144         }
145
146     return 0;
147 }
148
149 /**
150  * From gnutls and spice red_peer.c
151  * TODO: switch to gnutls and get rid of this
152  *
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.
157  *
158  * Returns: 1 for a successful match, and 0 on failure.
159  **/
160 static int verify_hostname(X509* cert, const char *hostname)
161 {
162     GENERAL_NAMES* subject_alt_names;
163     int found_dns_name = 0;
164     struct in_addr addr;
165     int addr_len = 0;
166     int cn_match = 0;
167     X509_NAME* subject;
168
169     spice_return_val_if_fail(hostname != NULL, 0);
170
171     if (!cert) {
172         spice_debug("warning: no cert!");
173         return 0;
174     }
175
176     // only IpV4 supported
177     if (inet_aton(hostname, &addr)) {
178         addr_len = sizeof(struct in_addr);
179     }
180
181     /* try matching against:
182      *  1) a DNS name as an alternative name (subjectAltName) extension
183      *     in the certificate
184      *  2) the common name (CN) in the certificate
185      *
186      *  either of these may be of the form: *.domain.tld
187      *
188      *  only try (2) if there is no subjectAltName extension of
189      *  type dNSName
190      */
191
192     /* Check through all included subjectAltName extensions, comparing
193      * against all those of type dNSName.
194      */
195     subject_alt_names = (GENERAL_NAMES*)X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
196
197     if (subject_alt_names) {
198         int num_alts = sk_GENERAL_NAME_num(subject_alt_names);
199         int i;
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) {
203                 found_dns_name = 1;
204                 if (_gnutls_hostname_compare((char *)ASN1_STRING_data(name->d.dNSName),
205                                              ASN1_STRING_length(name->d.dNSName),
206                                              hostname)) {
207                     spice_debug("alt name match=%s", ASN1_STRING_data(name->d.dNSName));
208                     GENERAL_NAMES_free(subject_alt_names);
209                     return 1;
210                 }
211             } else if (name->type == GEN_IPADD) {
212                 int alt_ip_len = ASN1_STRING_length(name->d.iPAddress);
213                 found_dns_name = 1;
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);
219                     return 1;
220                 }
221             }
222         }
223         GENERAL_NAMES_free(subject_alt_names);
224     }
225
226     if (found_dns_name) {
227         spice_debug("warning: SubjectAltName mismatch");
228         return 0;
229     }
230
231     /* extracting commonNames */
232     subject = X509_get_subject_name(cert);
233     if (subject) {
234         int pos = -1;
235         X509_NAME_ENTRY* cn_entry;
236         ASN1_STRING* cn_asn1;
237
238         while ((pos = X509_NAME_get_index_by_NID(subject, NID_commonName, pos)) != -1) {
239             cn_entry = X509_NAME_get_entry(subject, pos);
240             if (!cn_entry) {
241                 continue;
242             }
243             cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry);
244             if (!cn_asn1) {
245                 continue;
246             }
247
248             if (_gnutls_hostname_compare((char*)ASN1_STRING_data(cn_asn1),
249                                          ASN1_STRING_length(cn_asn1),
250                                          hostname)) {
251                 spice_debug("common name match=%s", (char*)ASN1_STRING_data(cn_asn1));
252                 cn_match = 1;
253                 break;
254             }
255         }
256     }
257
258     if (!cn_match) {
259         spice_debug("warning: common name mismatch");
260     }
261
262     return cn_match;
263 }
264
265 static X509_NAME* subject_to_x509_name(const char *subject, int *nentries)
266 {
267     X509_NAME* in_subject;
268     const char *p;
269     char *key, *val, *k, *v = NULL;
270     enum {
271         KEY,
272         VALUE
273     } state;
274
275     spice_return_val_if_fail(subject != NULL, NULL);
276     spice_return_val_if_fail(nentries != NULL, NULL);
277
278     key = (char*)alloca(strlen(subject));
279     val = (char*)alloca(strlen(subject));
280     in_subject = X509_NAME_new();
281
282     if (!in_subject || !key || !val) {
283         spice_debug("failed to allocate");
284         return NULL;
285     }
286
287     *nentries = 0;
288
289     k = key;
290     state = KEY;
291     for (p = subject;; ++p) {
292         int escape = 0;
293         if (*p == '\\') {
294             ++p;
295             if (*p != '\\' && *p != ',') {
296                 spice_debug("Invalid character after \\");
297                 goto fail;
298             }
299             escape = 1;
300         }
301
302         switch (state) {
303         case KEY:
304             if (*p == ' ' && k == key) {
305                 continue; /* skip spaces before key */
306             } if (*p == 0) {
307                 if (k == key) /* empty key, ending */
308                     goto success;
309                 goto fail;
310             } else if (*p == ',' && !escape) {
311                 goto fail; /* assignment is missing */
312             } else if (*p == '=' && !escape) {
313                 state = VALUE;
314                 *k = 0;
315                 v = val;
316             } else
317                 *k++ = *p;
318             break;
319         case VALUE:
320             if (*p == 0 || (*p == ',' && !escape)) {
321                 if (v == val) /* empty value */
322                     goto fail;
323
324                 *v = 0;
325
326                 if (!X509_NAME_add_entry_by_txt(in_subject, key,
327                                                 MBSTRING_UTF8,
328                                                 (const unsigned char*)val,
329                                                 -1, -1, 0)) {
330                     spice_debug("warning: failed to add entry %s=%s to X509_NAME",
331                                 key, val);
332                     goto fail;
333                 }
334                 *nentries += 1;
335
336                 if (*p == 0)
337                     goto success;
338
339                 state = KEY;
340                 k = key;
341             } else
342                 *v++ = *p;
343             break;
344         }
345     }
346
347 success:
348     return in_subject;
349
350 fail:
351     if (in_subject)
352         X509_NAME_free(in_subject);
353
354     return NULL;
355 }
356
357 static int verify_subject(X509* cert, SpiceOpenSSLVerify* verify)
358 {
359     X509_NAME *cert_subject = NULL;
360     int ret;
361     int in_entries;
362
363     if (!cert) {
364         spice_debug("warning: no cert!");
365         return 0;
366     }
367
368     cert_subject = X509_get_subject_name(cert);
369     if (!cert_subject) {
370         spice_debug("warning: reading certificate subject failed");
371         return 0;
372     }
373
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!");
378             return 0;
379         }
380     }
381
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);
386         return 0;
387     }
388
389     ret = X509_NAME_cmp(cert_subject, verify->in_subject);
390
391     if (ret == 0) {
392         spice_debug("subjects match");
393     } else {
394         char *p;
395         spice_debug("subjects mismatch");
396
397         p = X509_NAME_oneline(cert_subject, NULL, 0);
398         spice_debug("cert_subject: %s", p);
399         free(p);
400
401         p = X509_NAME_oneline(verify->in_subject, NULL, 0);
402         spice_debug("in_subject:   %s", p);
403         free(p);
404     }
405
406     return !ret;
407 }
408
409 static int openssl_verify(int preverify_ok, X509_STORE_CTX *ctx)
410 {
411     int depth, err;
412     SpiceOpenSSLVerify *v;
413     SSL *ssl;
414     X509* cert;
415     char buf[256];
416     unsigned int failed_verifications;
417
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);
420
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);
425     if (depth > 0) {
426         if (!preverify_ok) {
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;
430
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)
435                 return 1;
436
437             if (err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)
438                 spice_debug("server certificate not being signed by the provided CA");
439
440             return 0;
441         } else
442             return 1;
443     }
444
445     /* depth == 0 */
446     if (!cert) {
447         spice_debug("failed to get server certificate");
448         return 0;
449     }
450
451     failed_verifications = 0;
452     if (v->verifyop & SPICE_SSL_VERIFY_OP_PUBKEY) {
453         if (verify_pubkey(cert, v->pubkey, v->pubkey_size))
454             return 1;
455         else
456             failed_verifications |= SPICE_SSL_VERIFY_OP_PUBKEY;
457     }
458
459     if (!v->all_preverify_ok || !preverify_ok)
460         return 0;
461
462     if (v->verifyop & SPICE_SSL_VERIFY_OP_HOSTNAME) {
463        if (verify_hostname(cert, v->hostname))
464            return 1;
465         else
466             failed_verifications |= SPICE_SSL_VERIFY_OP_HOSTNAME;
467     }
468
469
470     if (v->verifyop & SPICE_SSL_VERIFY_OP_SUBJECT) {
471         if (verify_subject(cert, v))
472             return 1;
473         else
474             failed_verifications |= SPICE_SSL_VERIFY_OP_SUBJECT;
475     }
476
477     /* If we reach this code, this means all the tests failed, thus
478      * verification failed
479      */
480     if (failed_verifications & SPICE_SSL_VERIFY_OP_PUBKEY)
481         spice_warning("ssl: pubkey verification failed");
482
483     if (failed_verifications & SPICE_SSL_VERIFY_OP_HOSTNAME)
484         spice_warning("ssl: hostname '%s' verification failed", v->hostname);
485
486     if (failed_verifications & SPICE_SSL_VERIFY_OP_SUBJECT)
487         spice_warning("ssl: subject '%s' verification failed", v->subject);
488
489     spice_warning("ssl: verification failed");
490
491     return 0;
492 }
493
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,
497                                              const char *subject)
498 {
499     SpiceOpenSSLVerify *v;
500
501     if (!verifyop)
502         return NULL;
503
504     v = spice_new0(SpiceOpenSSLVerify, 1);
505
506     v->ssl              = ssl;
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);
512
513     v->all_preverify_ok = 1;
514
515     SSL_set_app_data(ssl, v);
516     SSL_set_verify(ssl,
517                    SSL_VERIFY_PEER, openssl_verify);
518
519     return v;
520 }
521
522 void spice_openssl_verify_free(SpiceOpenSSLVerify* verify)
523 {
524     if (!verify)
525         return;
526
527     free(verify->pubkey);
528     free(verify->subject);
529     free(verify->hostname);
530
531     if (verify->in_subject)
532         X509_NAME_free(verify->in_subject);
533
534     if (verify->ssl)
535         SSL_set_app_data(verify->ssl, NULL);
536     free(verify);
537 }