summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbeck <>2023-05-28 09:02:01 +0000
committerbeck <>2023-05-28 09:02:01 +0000
commitfa13c61b67163471b62143c6d6c5bf85974c2914 (patch)
tree7bfc6527c3c57631f84017c3a82e2c993e8e2077
parenta95e36808ea4031670010ec297a8701fa0500aaa (diff)
downloadopenbsd-fa13c61b67163471b62143c6d6c5bf85974c2914.tar.gz
openbsd-fa13c61b67163471b62143c6d6c5bf85974c2914.tar.bz2
openbsd-fa13c61b67163471b62143c6d6c5bf85974c2914.zip
Refactor tls_check_common_name to use lower level API.
X509_NAME_get_text_by_NID is kind of a bad interface that we wish to make safer, and does not give us the visibility we really want here to detect hostile things. Instead call the lower level functions to do some better checking that should be done by X509_NAME_get_text_by_NID, but is not in the OpenSSL version. Specifically we will treat the input as hostile and fail if: 1) The certificate contains more than one CN in the subject. 2) The CN does not decode as UTF-8 3) The CN is of invalid length (must be between 1 and 64 bytes) 4) The CN contains a 0 byte 4) matches the existing logic, 1 and 2, and 3 are new checks. ok tb@
-rw-r--r--src/lib/libtls/tls_verify.c59
-rw-r--r--src/regress/lib/libtls/verify/verifytest.c6
2 files changed, 51 insertions, 14 deletions
diff --git a/src/lib/libtls/tls_verify.c b/src/lib/libtls/tls_verify.c
index 0cb86f686d..420e278c99 100644
--- a/src/lib/libtls/tls_verify.c
+++ b/src/lib/libtls/tls_verify.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: tls_verify.c,v 1.23 2023/05/11 07:35:27 tb Exp $ */ 1/* $OpenBSD: tls_verify.c,v 1.24 2023/05/28 09:02:01 beck Exp $ */
2/* 2/*
3 * Copyright (c) 2014 Jeremie Courreges-Anglas <jca@openbsd.org> 3 * Copyright (c) 2014 Jeremie Courreges-Anglas <jca@openbsd.org>
4 * 4 *
@@ -205,10 +205,13 @@ static int
205tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, 205tls_check_common_name(struct tls *ctx, X509 *cert, const char *name,
206 int *cn_match) 206 int *cn_match)
207{ 207{
208 unsigned char *utf8_bytes = NULL;
208 X509_NAME *subject_name; 209 X509_NAME *subject_name;
209 char *common_name = NULL; 210 char *common_name = NULL;
210 union tls_addr addrbuf; 211 union tls_addr addrbuf;
211 int common_name_len; 212 int common_name_len;
213 ASN1_STRING *data;
214 int lastpos = -1;
212 int rv = -1; 215 int rv = -1;
213 216
214 *cn_match = 0; 217 *cn_match = 0;
@@ -217,29 +220,60 @@ tls_check_common_name(struct tls *ctx, X509 *cert, const char *name,
217 if (subject_name == NULL) 220 if (subject_name == NULL)
218 goto done; 221 goto done;
219 222
220 common_name_len = X509_NAME_get_text_by_NID(subject_name, 223 lastpos = X509_NAME_get_index_by_NID(subject_name,
221 NID_commonName, NULL, 0); 224 NID_commonName, lastpos);
222 if (common_name_len < 0) 225 if (lastpos == -1)
223 goto done; 226 goto done;
227 if (X509_NAME_get_index_by_NID(subject_name, NID_commonName, lastpos)
228 != -1) {
229 /*
230 * Having multiple CN's is possible, and even happened back in
231 * the glory days of mullets and Hammer pants. In anything like
232 * a modern TLS cert, CN is as close to deprecated as it gets,
233 * and having more than one is bad. We therefore fail if we have
234 * more than one CN fed to us in the subject, treating the
235 * certificate as hostile.
236 */
237 tls_set_errorx(ctx, "error verifying name '%s': "
238 "Certificate subject contains mutiple Common Name fields, "
239 "probably a malicious or malformed certificate", name);
240 goto err;
241 }
224 242
225 common_name = calloc(common_name_len + 1, 1); 243 data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name,
226 if (common_name == NULL) { 244 lastpos));
227 tls_set_error(ctx, "out of memory"); 245 /*
246 * Fail if we cannot encode as UTF-8, or if the UTF-8 encoding of the
247 * string contains a 0 byte. We treat any certificate with such data
248 * in the CN as hostile and fail.
249 */
250 if ((common_name_len = ASN1_STRING_to_UTF8(&utf8_bytes, data)) < 0) {
251 tls_set_errorx(ctx, "error verifying name '%s': "
252 "Common Name field cannot be encoded as a UTF-8 string, "
253 "probably a malicious certificate", name);
228 goto err; 254 goto err;
229 } 255 }
230 256
231 X509_NAME_get_text_by_NID(subject_name, NID_commonName, common_name, 257 if (common_name_len < 1 || common_name_len > 64) {
232 common_name_len + 1); 258 tls_set_errorx(ctx, "error verifying name '%s': "
259 "Common Name field has invalid length, "
260 "probably a malicious certificate", name);
261 goto err;
262 }
233 263
234 /* NUL bytes in CN? */ 264 if (memchr(utf8_bytes, 0, common_name_len) != NULL) {
235 if (common_name_len < 0 ||
236 (size_t)common_name_len != strlen(common_name)) {
237 tls_set_errorx(ctx, "error verifying name '%s': " 265 tls_set_errorx(ctx, "error verifying name '%s': "
238 "NUL byte in Common Name field, " 266 "NUL byte in Common Name field, "
239 "probably a malicious certificate", name); 267 "probably a malicious certificate", name);
240 goto err; 268 goto err;
241 } 269 }
242 270
271 common_name = strndup(utf8_bytes, common_name_len);
272 if (common_name == NULL) {
273 tls_set_error(ctx, "out of memory");
274 goto err;
275 }
276
243 /* 277 /*
244 * We don't want to attempt wildcard matching against IP addresses, 278 * We don't want to attempt wildcard matching against IP addresses,
245 * so perform a simple comparison here. 279 * so perform a simple comparison here.
@@ -258,6 +292,7 @@ tls_check_common_name(struct tls *ctx, X509 *cert, const char *name,
258 rv = 0; 292 rv = 0;
259 293
260 err: 294 err:
295 free(utf8_bytes);
261 free(common_name); 296 free(common_name);
262 return rv; 297 return rv;
263} 298}
diff --git a/src/regress/lib/libtls/verify/verifytest.c b/src/regress/lib/libtls/verify/verifytest.c
index b41b62fcfb..57aa992149 100644
--- a/src/regress/lib/libtls/verify/verifytest.c
+++ b/src/regress/lib/libtls/verify/verifytest.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: verifytest.c,v 1.7 2017/04/30 03:53:31 jsing Exp $ */ 1/* $OpenBSD: verifytest.c,v 1.8 2023/05/28 09:02:01 beck Exp $ */
2/* 2/*
3 * Copyright (c) 2014 Joel Sing <jsing@openbsd.org> 3 * Copyright (c) 2014 Joel Sing <jsing@openbsd.org>
4 * 4 *
@@ -40,6 +40,7 @@ struct verify_test {
40 const char name[128]; 40 const char name[128];
41 int want_return; 41 int want_return;
42 int want_match; 42 int want_match;
43 int name_type;
43}; 44};
44 45
45struct verify_test verify_tests[] = { 46struct verify_test verify_tests[] = {
@@ -474,7 +475,8 @@ do_verify_test(int test_no, struct verify_test *vt)
474 if ((name = X509_NAME_new()) == NULL) 475 if ((name = X509_NAME_new()) == NULL)
475 errx(1, "failed to malloc X509_NAME"); 476 errx(1, "failed to malloc X509_NAME");
476 if (X509_NAME_add_entry_by_NID(name, NID_commonName, 477 if (X509_NAME_add_entry_by_NID(name, NID_commonName,
477 MBSTRING_ASC, (unsigned char *)vt->common_name, 478 vt->name_type ? vt->name_type : MBSTRING_ASC,
479 (unsigned char *)vt->common_name,
478 vt->common_name_len, -1, 0) == 0) 480 vt->common_name_len, -1, 0) == 0)
479 errx(1, "failed to add name entry"); 481 errx(1, "failed to add name entry");
480 if (X509_set_subject_name(cert, name) == 0) 482 if (X509_set_subject_name(cert, name) == 0)