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
init_openssl(void)52 init_openssl(void)
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
flush_errors(krb5_context context)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
ascii_tolower(char p)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
label_match(const char * presented,size_t plen,const char * expected,size_t elen,krb5_boolean allow_wildcard,krb5_boolean * wildcard)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
domain_match(const char * presented,size_t plen,const char * expected)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 *
get_cert_sans(X509 * x)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
get_cert_cn(X509 * x,char * buf,size_t bufsize)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
check_cert_address(X509 * x,const char * text)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
check_cert_servername(X509 * x,const char * expected)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
check_cert_name_or_ip(X509 * x,const char * expected_name)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
verify_callback(int preverify_ok,X509_STORE_CTX * store_ctx)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
load_anchor_file(X509_STORE * store,const char * path)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
load_anchor_dir(X509_STORE * store,const char * path)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
load_anchor(SSL_CTX * ctx,const char * location)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
load_anchors(krb5_context context,char ** anchors,SSL_CTX * sctx)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
setup(krb5_context context,SOCKET fd,const char * servername,char ** anchors,k5_tls_handle * handle_out)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
write_tls(krb5_context context,k5_tls_handle handle,const void * data,size_t len)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
read_tls(krb5_context context,k5_tls_handle handle,void * data,size_t data_size,size_t * len_out)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
free_handle(krb5_context context,k5_tls_handle handle)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
tls_k5tls_initvt(krb5_context context,int maj_ver,int min_ver,krb5_plugin_vtable vtable)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