xref: /freebsd/crypto/krb5/src/plugins/tls/k5tls/openssl.c (revision 328110da2661a8841f12000b99fea27ceacdd5b2)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/tls/k5tls/openssl.c - OpenSSL TLS module implementation */
3 /*
4  * Copyright 2013,2014 Red Hat, Inc.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *    1. Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *
12  *    2. Redistributions in binary form must reproduce the above copyright
13  *       notice, this list of conditions and the following disclaimer in
14  *       the documentation and/or other materials provided with the
15  *       distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "k5-int.h"
31 #include "k5-utf8.h"
32 #include "k5-tls.h"
33 
34 #ifdef TLS_IMPL_OPENSSL
35 #include <openssl/err.h>
36 #include <openssl/ssl.h>
37 #include <openssl/x509.h>
38 #include <openssl/x509v3.h>
39 #include <dirent.h>
40 
41 struct k5_tls_handle_st {
42     SSL *ssl;
43     char *servername;
44 };
45 
46 static int ex_context_id = -1;
47 static int ex_handle_id = -1;
48 
49 MAKE_INIT_FUNCTION(init_openssl);
50 
51 int
52 init_openssl()
53 {
54     SSL_library_init();
55     SSL_load_error_strings();
56     OpenSSL_add_all_algorithms();
57     ex_context_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
58     ex_handle_id = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
59     return 0;
60 }
61 
62 static void
63 flush_errors(krb5_context context)
64 {
65     unsigned long err;
66     char buf[128];
67 
68     while ((err = ERR_get_error()) != 0) {
69         ERR_error_string_n(err, buf, sizeof(buf));
70         TRACE_TLS_ERROR(context, buf);
71     }
72 }
73 
74 /* Return the passed-in character, lower-cased if it's an ASCII character. */
75 static inline char
76 ascii_tolower(char p)
77 {
78     if (KRB5_UPPER(p))
79         return p + ('a' - 'A');
80     return p;
81 }
82 
83 /*
84  * Check a single label.  If allow_wildcard is true, and the presented name
85  * includes a wildcard, return true and note that we matched a wildcard.
86  * Otherwise, for both the presented and expected values, do a case-insensitive
87  * comparison of ASCII characters, and a case-sensitive comparison of
88  * everything else.
89  */
90 static krb5_boolean
91 label_match(const char *presented, size_t plen, const char *expected,
92             size_t elen, krb5_boolean allow_wildcard, krb5_boolean *wildcard)
93 {
94     unsigned int i;
95 
96     if (allow_wildcard && plen == 1 && presented[0] == '*') {
97         *wildcard = TRUE;
98         return TRUE;
99     }
100 
101     if (plen != elen)
102         return FALSE;
103 
104     for (i = 0; i < elen; i++) {
105         if (ascii_tolower(presented[i]) != ascii_tolower(expected[i]))
106             return FALSE;
107     }
108     return TRUE;
109 }
110 
111 /* Break up the two names and check them, label by label. */
112 static krb5_boolean
113 domain_match(const char *presented, size_t plen, const char *expected)
114 {
115     const char *p, *q, *r, *s;
116     int n_label;
117     krb5_boolean used_wildcard = FALSE;
118 
119     n_label = 0;
120     p = presented;
121     r = expected;
122     while (p < presented + plen && *r != '\0') {
123         q = memchr(p, '.', plen - (p - presented));
124         if (q == NULL)
125             q = presented + plen;
126         s = r + strcspn(r, ".");
127         if (!label_match(p, q - p, r, s - r, n_label == 0, &used_wildcard))
128             return FALSE;
129         p = q < presented + plen ? q + 1 : q;
130         r = *s ? s + 1 : s;
131         n_label++;
132     }
133     if (used_wildcard && n_label <= 2)
134         return FALSE;
135     if (p == presented + plen && *r == '\0')
136         return TRUE;
137     return FALSE;
138 }
139 
140 /* Fetch the list of subjectAltNames from a certificate. */
141 static GENERAL_NAMES *
142 get_cert_sans(X509 *x)
143 {
144     int ext;
145     X509_EXTENSION *san_ext;
146 
147     ext = X509_get_ext_by_NID(x, NID_subject_alt_name, -1);
148     if (ext < 0)
149         return NULL;
150     san_ext = X509_get_ext(x, ext);
151     if (san_ext == NULL)
152         return NULL;
153     return X509V3_EXT_d2i(san_ext);
154 }
155 
156 /* Fetch a CN value from the subjct name field, returning its length, or -1 if
157  * there is no subject name or it contains no CN value. */
158 static int
159 get_cert_cn(X509 *x, char *buf, size_t bufsize)
160 {
161     X509_NAME *name;
162 
163     name = X509_get_subject_name(x);
164     if (name == NULL)
165         return -1;
166     return X509_NAME_get_text_by_NID(name, NID_commonName, buf, bufsize);
167 }
168 
169 /* Return true if text matches any of the addresses we can recover from x. */
170 static krb5_boolean
171 check_cert_address(X509 *x, const char *text)
172 {
173     char buf[1024];
174     GENERAL_NAMES *sans;
175     GENERAL_NAME *san = NULL;
176     ASN1_OCTET_STRING *ip;
177     krb5_boolean found_ip_san = FALSE, matched = FALSE;
178     int n_sans, i;
179     int name_length;
180     struct in_addr sin;
181     struct in6_addr sin6;
182 
183     /* Parse the IP address into an octet string. */
184     ip = ASN1_OCTET_STRING_new();
185     if (ip == NULL)
186         return FALSE;
187     if (inet_pton(AF_INET, text, &sin)) {
188         ASN1_OCTET_STRING_set(ip, (unsigned char *)&sin, sizeof(sin));
189     } else if (inet_pton(AF_INET6, text, &sin6)) {
190         ASN1_OCTET_STRING_set(ip, (unsigned char *)&sin6, sizeof(sin6));
191     } else {
192         ASN1_OCTET_STRING_free(ip);
193         return FALSE;
194     }
195 
196     /* Check for matches in ipaddress subjectAltName values. */
197     sans = get_cert_sans(x);
198     if (sans != NULL) {
199         n_sans = sk_GENERAL_NAME_num(sans);
200         for (i = 0; i < n_sans; i++) {
201             san = sk_GENERAL_NAME_value(sans, i);
202             if (san->type != GEN_IPADD)
203                 continue;
204             found_ip_san = TRUE;
205             matched = (ASN1_OCTET_STRING_cmp(ip, san->d.iPAddress) == 0);
206             if (matched)
207                 break;
208         }
209         sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free);
210     }
211     ASN1_OCTET_STRING_free(ip);
212 
213     if (found_ip_san)
214         return matched;
215 
216     /* Check for a match against the CN value in the peer's subject name. */
217     name_length = get_cert_cn(x, buf, sizeof(buf));
218     if (name_length >= 0) {
219         /* Do a string compare to check if it's an acceptable value. */
220         return strlen(text) == (size_t)name_length &&
221                strncmp(text, buf, name_length) == 0;
222     }
223 
224     /* We didn't find a match. */
225     return FALSE;
226 }
227 
228 /* Return true if expected matches any of the names we can recover from x. */
229 static krb5_boolean
230 check_cert_servername(X509 *x, const char *expected)
231 {
232     char buf[1024];
233     GENERAL_NAMES *sans;
234     GENERAL_NAME *san = NULL;
235     unsigned char *dnsname;
236     krb5_boolean found_dns_san = FALSE, matched = FALSE;
237     int name_length, n_sans, i;
238 
239     /* Check for matches in dnsname subjectAltName values. */
240     sans = get_cert_sans(x);
241     if (sans != NULL) {
242         n_sans = sk_GENERAL_NAME_num(sans);
243         for (i = 0; i < n_sans; i++) {
244             san = sk_GENERAL_NAME_value(sans, i);
245             if (san->type != GEN_DNS)
246                 continue;
247             found_dns_san = TRUE;
248             dnsname = NULL;
249             name_length = ASN1_STRING_to_UTF8(&dnsname, san->d.dNSName);
250             if (dnsname == NULL)
251                 continue;
252             matched = domain_match((char *)dnsname, name_length, expected);
253             OPENSSL_free(dnsname);
254             if (matched)
255                 break;
256         }
257         sk_GENERAL_NAME_pop_free(sans, GENERAL_NAME_free);
258     }
259 
260     if (matched)
261         return TRUE;
262     if (found_dns_san)
263         return matched;
264 
265     /* Check for a match against the CN value in the peer's subject name. */
266     name_length = get_cert_cn(x, buf, sizeof(buf));
267     if (name_length >= 0)
268         return domain_match(buf, name_length, expected);
269 
270     /* We didn't find a match. */
271     return FALSE;
272 }
273 
274 static krb5_boolean
275 check_cert_name_or_ip(X509 *x, const char *expected_name)
276 {
277     struct in_addr in;
278     struct in6_addr in6;
279 
280     if (inet_pton(AF_INET, expected_name, &in) != 0 ||
281         inet_pton(AF_INET6, expected_name, &in6) != 0) {
282         return check_cert_address(x, expected_name);
283     } else {
284         return check_cert_servername(x, expected_name);
285     }
286 }
287 
288 static int
289 verify_callback(int preverify_ok, X509_STORE_CTX *store_ctx)
290 {
291     X509 *x;
292     SSL *ssl;
293     BIO *bio;
294     krb5_context context;
295     int err, depth;
296     k5_tls_handle handle;
297     const char *cert = NULL, *errstr, *expected_name;
298     size_t count;
299 
300     ssl = X509_STORE_CTX_get_ex_data(store_ctx,
301                                      SSL_get_ex_data_X509_STORE_CTX_idx());
302     context = SSL_get_ex_data(ssl, ex_context_id);
303     handle = SSL_get_ex_data(ssl, ex_handle_id);
304     assert(context != NULL && handle != NULL);
305     /* We do have the peer's cert, right? */
306     x = X509_STORE_CTX_get_current_cert(store_ctx);
307     if (x == NULL) {
308         TRACE_TLS_NO_REMOTE_CERTIFICATE(context);
309         return 0;
310     }
311     /* Figure out where we are. */
312     depth = X509_STORE_CTX_get_error_depth(store_ctx);
313     if (depth < 0)
314         return 0;
315     /* If there's an error at this level that we're not ignoring, fail. */
316     err = X509_STORE_CTX_get_error(store_ctx);
317     if (err != X509_V_OK) {
318         bio = BIO_new(BIO_s_mem());
319         if (bio != NULL) {
320             X509_NAME_print_ex(bio, X509_get_subject_name(x), 0, 0);
321             count = BIO_get_mem_data(bio, &cert);
322             errstr = X509_verify_cert_error_string(err);
323             TRACE_TLS_CERT_ERROR(context, depth, count, cert, err, errstr);
324             BIO_free(bio);
325         }
326         return 0;
327     }
328     /* If we're not looking at the peer, we're done and everything's ok. */
329     if (depth != 0)
330         return 1;
331     /* Check if the name we expect to find is in the certificate. */
332     expected_name = handle->servername;
333     if (check_cert_name_or_ip(x, expected_name)) {
334         TRACE_TLS_SERVER_NAME_MATCH(context, expected_name);
335         return 1;
336     } else {
337         TRACE_TLS_SERVER_NAME_MISMATCH(context, expected_name);
338     }
339     /* The name didn't match. */
340     return 0;
341 }
342 
343 static krb5_error_code
344 load_anchor_file(X509_STORE *store, const char *path)
345 {
346     FILE *fp;
347     STACK_OF(X509_INFO) *sk = NULL;
348     X509_INFO *xi;
349     int i;
350 
351     fp = fopen(path, "r");
352     if (fp == NULL)
353         return errno;
354     sk = PEM_X509_INFO_read(fp, NULL, NULL, NULL);
355     fclose(fp);
356     if (sk == NULL)
357         return ENOENT;
358     for (i = 0; i < sk_X509_INFO_num(sk); i++) {
359         xi = sk_X509_INFO_value(sk, i);
360         if (xi->x509 != NULL)
361             X509_STORE_add_cert(store, xi->x509);
362     }
363     sk_X509_INFO_pop_free(sk, X509_INFO_free);
364     return 0;
365 }
366 
367 static krb5_error_code
368 load_anchor_dir(X509_STORE *store, const char *path)
369 {
370     DIR *d = NULL;
371     struct dirent *dentry = NULL;
372     char filename[1024];
373     krb5_boolean found_any = FALSE;
374 
375     d = opendir(path);
376     if (d == NULL)
377         return ENOENT;
378     while ((dentry = readdir(d)) != NULL) {
379         if (dentry->d_name[0] != '.') {
380             snprintf(filename, sizeof(filename), "%s/%s",
381                      path, dentry->d_name);
382             if (load_anchor_file(store, filename) == 0)
383                 found_any = TRUE;
384         }
385     }
386     closedir(d);
387     return found_any ? 0 : ENOENT;
388 }
389 
390 static krb5_error_code
391 load_anchor(SSL_CTX *ctx, const char *location)
392 {
393     X509_STORE *store;
394     const char *envloc;
395 
396     store = SSL_CTX_get_cert_store(ctx);
397     if (strncmp(location, "FILE:", 5) == 0) {
398         return load_anchor_file(store, location + 5);
399     } else if (strncmp(location, "DIR:", 4) == 0) {
400         return load_anchor_dir(store, location + 4);
401     } else if (strncmp(location, "ENV:", 4) == 0) {
402         envloc = secure_getenv(location + 4);
403         if (envloc == NULL)
404             return ENOENT;
405         return load_anchor(ctx, envloc);
406     }
407     return EINVAL;
408 }
409 
410 static krb5_error_code
411 load_anchors(krb5_context context, char **anchors, SSL_CTX *sctx)
412 {
413     unsigned int i;
414     krb5_error_code ret;
415 
416     if (anchors != NULL) {
417         for (i = 0; anchors[i] != NULL; i++) {
418             ret = load_anchor(sctx, anchors[i]);
419             if (ret)
420                 return ret;
421         }
422     } else {
423         /* Use the library defaults. */
424         if (SSL_CTX_set_default_verify_paths(sctx) != 1)
425             return ENOENT;
426     }
427 
428     return 0;
429 }
430 
431 static krb5_error_code
432 setup(krb5_context context, SOCKET fd, const char *servername,
433       char **anchors, k5_tls_handle *handle_out)
434 {
435     int e;
436     long options = SSL_OP_NO_SSLv2;
437     SSL_CTX *ctx = NULL;
438     SSL *ssl = NULL;
439     k5_tls_handle handle = NULL;
440 
441     *handle_out = NULL;
442 
443     (void)CALL_INIT_FUNCTION(init_openssl);
444     if (ex_context_id == -1 || ex_handle_id == -1)
445         return KRB5_PLUGIN_OP_NOTSUPP;
446 
447     /* Do general SSL library setup. */
448     ctx = SSL_CTX_new(SSLv23_client_method());
449     if (ctx == NULL)
450         goto error;
451 
452 #ifdef SSL_OP_IGNORE_UNEXPECTED_EOF
453     /*
454      * For OpenSSL 3 and later, mark close_notify alerts as optional.  We don't
455      * need to worry about truncation attacks because the protocols this module
456      * is used with (Kerberos and change-password) receive a single
457      * length-delimited message from the server.  For prior versions of OpenSSL
458      * we check for SSL_ERROR_SYSCALL when reading instead (this error changes
459      * to SSL_ERROR_SSL in OpenSSL 3).
460      */
461     options |= SSL_OP_IGNORE_UNEXPECTED_EOF;
462 #endif
463     SSL_CTX_set_options(ctx, options);
464 
465     SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
466     X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), 0);
467     e = load_anchors(context, anchors, ctx);
468     if (e != 0)
469         goto error;
470 
471     ssl = SSL_new(ctx);
472     if (ssl == NULL)
473         goto error;
474 
475     if (!SSL_set_fd(ssl, fd))
476         goto error;
477 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
478     if (!SSL_set_tlsext_host_name(ssl, servername))
479         goto error;
480 #endif
481     SSL_set_connect_state(ssl);
482 
483     /* Create a handle and allow verify_callback to access it. */
484     handle = malloc(sizeof(*handle));
485     if (handle == NULL || !SSL_set_ex_data(ssl, ex_handle_id, handle))
486         goto error;
487 
488     handle->ssl = ssl;
489     handle->servername = strdup(servername);
490     if (handle->servername == NULL)
491         goto error;
492     *handle_out = handle;
493     SSL_CTX_free(ctx);
494     return 0;
495 
496 error:
497     flush_errors(context);
498     free(handle);
499     SSL_free(ssl);
500     SSL_CTX_free(ctx);
501     return KRB5_PLUGIN_OP_NOTSUPP;
502 }
503 
504 static k5_tls_status
505 write_tls(krb5_context context, k5_tls_handle handle, const void *data,
506           size_t len)
507 {
508     int nwritten, e;
509 
510     /* Try to transmit our request; allow verify_callback to access context. */
511     if (!SSL_set_ex_data(handle->ssl, ex_context_id, context))
512         return ERROR_TLS;
513     nwritten = SSL_write(handle->ssl, data, len);
514     (void)SSL_set_ex_data(handle->ssl, ex_context_id, NULL);
515     if (nwritten > 0)
516         return DONE;
517 
518     e = SSL_get_error(handle->ssl, nwritten);
519     if (e == SSL_ERROR_WANT_READ)
520         return WANT_READ;
521     else if (e == SSL_ERROR_WANT_WRITE)
522         return WANT_WRITE;
523     flush_errors(context);
524     return ERROR_TLS;
525 }
526 
527 static k5_tls_status
528 read_tls(krb5_context context, k5_tls_handle handle, void *data,
529          size_t data_size, size_t *len_out)
530 {
531     ssize_t nread;
532     int e;
533 
534     *len_out = 0;
535 
536     /* Try to read response data; allow verify_callback to access context. */
537     if (!SSL_set_ex_data(handle->ssl, ex_context_id, context))
538         return ERROR_TLS;
539     nread = SSL_read(handle->ssl, data, data_size);
540     (void)SSL_set_ex_data(handle->ssl, ex_context_id, NULL);
541     if (nread > 0) {
542         *len_out = nread;
543         return DATA_READ;
544     }
545 
546     e = SSL_get_error(handle->ssl, nread);
547     if (e == SSL_ERROR_WANT_READ)
548         return WANT_READ;
549     else if (e == SSL_ERROR_WANT_WRITE)
550         return WANT_WRITE;
551 
552     if (e == SSL_ERROR_ZERO_RETURN || (e == SSL_ERROR_SYSCALL && nread == 0))
553         return DONE;
554 
555     flush_errors(context);
556     return ERROR_TLS;
557 }
558 
559 static void
560 free_handle(krb5_context context, k5_tls_handle handle)
561 {
562     SSL_free(handle->ssl);
563     free(handle->servername);
564     free(handle);
565 }
566 
567 krb5_error_code
568 tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver,
569                  krb5_plugin_vtable vtable);
570 
571 krb5_error_code
572 tls_k5tls_initvt(krb5_context context, int maj_ver, int min_ver,
573                  krb5_plugin_vtable vtable)
574 {
575     k5_tls_vtable vt;
576 
577     vt = (k5_tls_vtable)vtable;
578     vt->setup = setup;
579     vt->write = write_tls;
580     vt->read = read_tls;
581     vt->free_handle = free_handle;
582     return 0;
583 }
584 
585 #endif /* TLS_IMPL_OPENSSL */
586