Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/crypto/krb5/src/plugins/tls/k5tls/openssl.c
34914 views
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(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
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
587