diff options
author | beck <> | 2023-05-28 09:02:01 +0000 |
---|---|---|
committer | beck <> | 2023-05-28 09:02:01 +0000 |
commit | fa13c61b67163471b62143c6d6c5bf85974c2914 (patch) | |
tree | 7bfc6527c3c57631f84017c3a82e2c993e8e2077 | |
parent | a95e36808ea4031670010ec297a8701fa0500aaa (diff) | |
download | openbsd-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.c | 59 | ||||
-rw-r--r-- | src/regress/lib/libtls/verify/verifytest.c | 6 |
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 | |||
205 | tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, | 205 | tls_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 | ||
45 | struct verify_test verify_tests[] = { | 46 | struct 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) |