diff options
Diffstat (limited to 'src/lib/libtls/tls_verify.c')
| -rw-r--r-- | src/lib/libtls/tls_verify.c | 394 |
1 files changed, 0 insertions, 394 deletions
diff --git a/src/lib/libtls/tls_verify.c b/src/lib/libtls/tls_verify.c deleted file mode 100644 index 2935278383..0000000000 --- a/src/lib/libtls/tls_verify.c +++ /dev/null | |||
| @@ -1,394 +0,0 @@ | |||
| 1 | /* $OpenBSD: tls_verify.c,v 1.32 2024/12/10 08:40:30 tb Exp $ */ | ||
| 2 | /* | ||
| 3 | * Copyright (c) 2014 Jeremie Courreges-Anglas <jca@openbsd.org> | ||
| 4 | * | ||
| 5 | * Permission to use, copy, modify, and distribute this software for any | ||
| 6 | * purpose with or without fee is hereby granted, provided that the above | ||
| 7 | * copyright notice and this permission notice appear in all copies. | ||
| 8 | * | ||
| 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
| 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
| 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
| 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
| 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
| 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
| 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
| 16 | */ | ||
| 17 | |||
| 18 | #include <sys/socket.h> | ||
| 19 | |||
| 20 | #include <arpa/inet.h> | ||
| 21 | #include <netinet/in.h> | ||
| 22 | |||
| 23 | #include <string.h> | ||
| 24 | |||
| 25 | #include <openssl/x509v3.h> | ||
| 26 | |||
| 27 | #include <tls.h> | ||
| 28 | #include "tls_internal.h" | ||
| 29 | |||
| 30 | static int | ||
| 31 | tls_match_name(const char *cert_name, const char *name) | ||
| 32 | { | ||
| 33 | const char *cert_domain, *domain, *next_dot; | ||
| 34 | |||
| 35 | if (strcasecmp(cert_name, name) == 0) | ||
| 36 | return 0; | ||
| 37 | |||
| 38 | /* Wildcard match? */ | ||
| 39 | if (cert_name[0] == '*') { | ||
| 40 | /* | ||
| 41 | * Valid wildcards: | ||
| 42 | * - "*.domain.tld" | ||
| 43 | * - "*.sub.domain.tld" | ||
| 44 | * - etc. | ||
| 45 | * Reject "*.tld". | ||
| 46 | * No attempt to prevent the use of eg. "*.co.uk". | ||
| 47 | */ | ||
| 48 | cert_domain = &cert_name[1]; | ||
| 49 | /* Disallow "*" */ | ||
| 50 | if (cert_domain[0] == '\0') | ||
| 51 | return -1; | ||
| 52 | /* Disallow "*foo" */ | ||
| 53 | if (cert_domain[0] != '.') | ||
| 54 | return -1; | ||
| 55 | /* Disallow "*.." */ | ||
| 56 | if (cert_domain[1] == '.') | ||
| 57 | return -1; | ||
| 58 | next_dot = strchr(&cert_domain[1], '.'); | ||
| 59 | /* Disallow "*.bar" */ | ||
| 60 | if (next_dot == NULL) | ||
| 61 | return -1; | ||
| 62 | /* Disallow "*.bar.." */ | ||
| 63 | if (next_dot[1] == '.') | ||
| 64 | return -1; | ||
| 65 | |||
| 66 | domain = strchr(name, '.'); | ||
| 67 | |||
| 68 | /* No wildcard match against a name with no host part. */ | ||
| 69 | if (name[0] == '.') | ||
| 70 | return -1; | ||
| 71 | /* No wildcard match against a name with no domain part. */ | ||
| 72 | if (domain == NULL || strlen(domain) == 1) | ||
| 73 | return -1; | ||
| 74 | |||
| 75 | if (strcasecmp(cert_domain, domain) == 0) | ||
| 76 | return 0; | ||
| 77 | } | ||
| 78 | |||
| 79 | return -1; | ||
| 80 | } | ||
| 81 | |||
| 82 | /* | ||
| 83 | * See RFC 5280 section 4.2.1.6 for SubjectAltName details. | ||
| 84 | * alt_match is set to 1 if a matching alternate name is found. | ||
| 85 | * alt_exists is set to 1 if any known alternate name exists in the certificate. | ||
| 86 | */ | ||
| 87 | static int | ||
| 88 | tls_check_subject_altname(struct tls *ctx, X509 *cert, const char *name, | ||
| 89 | int *alt_match, int *alt_exists) | ||
| 90 | { | ||
| 91 | STACK_OF(GENERAL_NAME) *altname_stack = NULL; | ||
| 92 | union tls_addr addrbuf; | ||
| 93 | int addrlen, type; | ||
| 94 | int count, i; | ||
| 95 | int critical = 0; | ||
| 96 | int rv = -1; | ||
| 97 | |||
| 98 | *alt_match = 0; | ||
| 99 | *alt_exists = 0; | ||
| 100 | |||
| 101 | altname_stack = X509_get_ext_d2i(cert, NID_subject_alt_name, &critical, | ||
| 102 | NULL); | ||
| 103 | if (altname_stack == NULL) { | ||
| 104 | if (critical != -1) { | ||
| 105 | tls_set_errorx(ctx, TLS_ERROR_UNKNOWN, | ||
| 106 | "error decoding subjectAltName"); | ||
| 107 | goto err; | ||
| 108 | } | ||
| 109 | goto done; | ||
| 110 | } | ||
| 111 | |||
| 112 | if (inet_pton(AF_INET, name, &addrbuf) == 1) { | ||
| 113 | type = GEN_IPADD; | ||
| 114 | addrlen = 4; | ||
| 115 | } else if (inet_pton(AF_INET6, name, &addrbuf) == 1) { | ||
| 116 | type = GEN_IPADD; | ||
| 117 | addrlen = 16; | ||
| 118 | } else { | ||
| 119 | type = GEN_DNS; | ||
| 120 | addrlen = 0; | ||
| 121 | } | ||
| 122 | |||
| 123 | count = sk_GENERAL_NAME_num(altname_stack); | ||
| 124 | for (i = 0; i < count; i++) { | ||
| 125 | GENERAL_NAME *altname; | ||
| 126 | |||
| 127 | altname = sk_GENERAL_NAME_value(altname_stack, i); | ||
| 128 | |||
| 129 | if (altname->type == GEN_DNS || altname->type == GEN_IPADD) | ||
| 130 | *alt_exists = 1; | ||
| 131 | |||
| 132 | if (altname->type != type) | ||
| 133 | continue; | ||
| 134 | |||
| 135 | if (type == GEN_DNS) { | ||
| 136 | const unsigned char *data; | ||
| 137 | int format, len; | ||
| 138 | |||
| 139 | format = ASN1_STRING_type(altname->d.dNSName); | ||
| 140 | if (format == V_ASN1_IA5STRING) { | ||
| 141 | data = ASN1_STRING_get0_data(altname->d.dNSName); | ||
| 142 | len = ASN1_STRING_length(altname->d.dNSName); | ||
| 143 | |||
| 144 | if (len < 0 || (size_t)len != strlen(data)) { | ||
| 145 | tls_set_errorx(ctx, TLS_ERROR_UNKNOWN, | ||
| 146 | "error verifying name '%s': " | ||
| 147 | "NUL byte in subjectAltName, " | ||
| 148 | "probably a malicious certificate", | ||
| 149 | name); | ||
| 150 | goto err; | ||
| 151 | } | ||
| 152 | |||
| 153 | /* | ||
| 154 | * Per RFC 5280 section 4.2.1.6: | ||
| 155 | * " " is a legal domain name, but that | ||
| 156 | * dNSName must be rejected. | ||
| 157 | */ | ||
| 158 | if (strcmp(data, " ") == 0) { | ||
| 159 | tls_set_errorx(ctx, TLS_ERROR_UNKNOWN, | ||
| 160 | "error verifying name '%s': " | ||
| 161 | "a dNSName of \" \" must not be " | ||
| 162 | "used", name); | ||
| 163 | goto err; | ||
| 164 | } | ||
| 165 | |||
| 166 | if (tls_match_name(data, name) == 0) { | ||
| 167 | *alt_match = 1; | ||
| 168 | goto done; | ||
| 169 | } | ||
| 170 | } else { | ||
| 171 | #ifdef DEBUG | ||
| 172 | fprintf(stdout, "%s: unhandled subjectAltName " | ||
| 173 | "dNSName encoding (%d)\n", getprogname(), | ||
| 174 | format); | ||
| 175 | #endif | ||
| 176 | } | ||
| 177 | |||
| 178 | } else if (type == GEN_IPADD) { | ||
| 179 | const unsigned char *data; | ||
| 180 | int datalen; | ||
| 181 | |||
| 182 | datalen = ASN1_STRING_length(altname->d.iPAddress); | ||
| 183 | data = ASN1_STRING_get0_data(altname->d.iPAddress); | ||
| 184 | |||
| 185 | if (datalen < 0) { | ||
| 186 | tls_set_errorx(ctx, TLS_ERROR_UNKNOWN, | ||
| 187 | "Unexpected negative length for an " | ||
| 188 | "IP address: %d", datalen); | ||
| 189 | goto err; | ||
| 190 | } | ||
| 191 | |||
| 192 | /* | ||
| 193 | * Per RFC 5280 section 4.2.1.6: | ||
| 194 | * IPv4 must use 4 octets and IPv6 must use 16 octets. | ||
| 195 | */ | ||
| 196 | if (datalen == addrlen && | ||
| 197 | memcmp(data, &addrbuf, addrlen) == 0) { | ||
| 198 | *alt_match = 1; | ||
| 199 | goto done; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | done: | ||
| 205 | rv = 0; | ||
| 206 | |||
| 207 | err: | ||
| 208 | sk_GENERAL_NAME_pop_free(altname_stack, GENERAL_NAME_free); | ||
| 209 | return rv; | ||
| 210 | } | ||
| 211 | |||
| 212 | static int | ||
| 213 | tls_get_common_name_internal(X509 *cert, char **out_common_name, | ||
| 214 | unsigned int *out_tlserr, const char **out_errstr) | ||
| 215 | { | ||
| 216 | unsigned char *utf8_bytes = NULL; | ||
| 217 | X509_NAME *subject_name; | ||
| 218 | char *common_name = NULL; | ||
| 219 | int common_name_len; | ||
| 220 | ASN1_STRING *data; | ||
| 221 | int lastpos = -1; | ||
| 222 | int rv = -1; | ||
| 223 | |||
| 224 | *out_tlserr = TLS_ERROR_UNKNOWN; | ||
| 225 | *out_errstr = "unknown"; | ||
| 226 | |||
| 227 | free(*out_common_name); | ||
| 228 | *out_common_name = NULL; | ||
| 229 | |||
| 230 | subject_name = X509_get_subject_name(cert); | ||
| 231 | if (subject_name == NULL) | ||
| 232 | goto err; | ||
| 233 | |||
| 234 | lastpos = X509_NAME_get_index_by_NID(subject_name, | ||
| 235 | NID_commonName, lastpos); | ||
| 236 | if (lastpos == -1) | ||
| 237 | goto done; | ||
| 238 | if (lastpos < 0) | ||
| 239 | goto err; | ||
| 240 | if (X509_NAME_get_index_by_NID(subject_name, NID_commonName, lastpos) | ||
| 241 | != -1) { | ||
| 242 | /* | ||
| 243 | * Having multiple CN's is possible, and even happened back in | ||
| 244 | * the glory days of mullets and Hammer pants. In anything like | ||
| 245 | * a modern TLS cert, CN is as close to deprecated as it gets, | ||
| 246 | * and having more than one is bad. We therefore fail if we have | ||
| 247 | * more than one CN fed to us in the subject, treating the | ||
| 248 | * certificate as hostile. | ||
| 249 | */ | ||
| 250 | *out_tlserr = TLS_ERROR_UNKNOWN; | ||
| 251 | *out_errstr = "error getting common name: " | ||
| 252 | "Certificate subject contains multiple Common Name fields, " | ||
| 253 | "probably a malicious or malformed certificate"; | ||
| 254 | goto err; | ||
| 255 | } | ||
| 256 | |||
| 257 | data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subject_name, | ||
| 258 | lastpos)); | ||
| 259 | /* | ||
| 260 | * Fail if we cannot encode the CN bytes as UTF-8. | ||
| 261 | */ | ||
| 262 | if ((common_name_len = ASN1_STRING_to_UTF8(&utf8_bytes, data)) < 0) { | ||
| 263 | *out_tlserr = TLS_ERROR_UNKNOWN; | ||
| 264 | *out_errstr = "error getting common name: " | ||
| 265 | "Common Name field cannot be encoded as a UTF-8 string, " | ||
| 266 | "probably a malicious certificate"; | ||
| 267 | goto err; | ||
| 268 | } | ||
| 269 | /* | ||
| 270 | * Fail if the CN is of invalid length. RFC 5280 specifies that a CN | ||
| 271 | * must be between 1 and 64 bytes long. | ||
| 272 | */ | ||
| 273 | if (common_name_len < 1 || common_name_len > 64) { | ||
| 274 | *out_tlserr = TLS_ERROR_UNKNOWN; | ||
| 275 | *out_errstr = "error getting common name: " | ||
| 276 | "Common Name field has invalid length, " | ||
| 277 | "probably a malicious certificate"; | ||
| 278 | goto err; | ||
| 279 | } | ||
| 280 | /* | ||
| 281 | * Fail if the resulting text contains a NUL byte. | ||
| 282 | */ | ||
| 283 | if (memchr(utf8_bytes, 0, common_name_len) != NULL) { | ||
| 284 | *out_tlserr = TLS_ERROR_UNKNOWN; | ||
| 285 | *out_errstr = "error getting common name: " | ||
| 286 | "NUL byte in Common Name field, " | ||
| 287 | "probably a malicious certificate"; | ||
| 288 | goto err; | ||
| 289 | } | ||
| 290 | |||
| 291 | common_name = strndup(utf8_bytes, common_name_len); | ||
| 292 | if (common_name == NULL) { | ||
| 293 | *out_tlserr = TLS_ERROR_OUT_OF_MEMORY; | ||
| 294 | *out_errstr = "out of memory"; | ||
| 295 | goto err; | ||
| 296 | } | ||
| 297 | |||
| 298 | *out_common_name = common_name; | ||
| 299 | common_name = NULL; | ||
| 300 | |||
| 301 | done: | ||
| 302 | if (*out_common_name == NULL) | ||
| 303 | *out_common_name = strdup(""); | ||
| 304 | if (*out_common_name == NULL) { | ||
| 305 | *out_tlserr = TLS_ERROR_OUT_OF_MEMORY; | ||
| 306 | *out_errstr = "out of memory"; | ||
| 307 | goto err; | ||
| 308 | } | ||
| 309 | |||
| 310 | rv = 0; | ||
| 311 | |||
| 312 | err: | ||
| 313 | free(utf8_bytes); | ||
| 314 | free(common_name); | ||
| 315 | return rv; | ||
| 316 | } | ||
| 317 | |||
| 318 | int | ||
| 319 | tls_get_common_name(struct tls *ctx, X509 *cert, const char *in_name, | ||
| 320 | char **out_common_name) | ||
| 321 | { | ||
| 322 | unsigned int errcode = TLS_ERROR_UNKNOWN; | ||
| 323 | const char *errstr = "unknown"; | ||
| 324 | |||
| 325 | if (tls_get_common_name_internal(cert, out_common_name, &errcode, | ||
| 326 | &errstr) == -1) { | ||
| 327 | const char *name = in_name; | ||
| 328 | const char *space = " "; | ||
| 329 | |||
| 330 | if (name == NULL) | ||
| 331 | name = space = ""; | ||
| 332 | |||
| 333 | tls_set_errorx(ctx, errcode, "%s%s%s", name, space, errstr); | ||
| 334 | return -1; | ||
| 335 | } | ||
| 336 | |||
| 337 | return 0; | ||
| 338 | } | ||
| 339 | |||
| 340 | static int | ||
| 341 | tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, | ||
| 342 | int *cn_match) | ||
| 343 | { | ||
| 344 | char *common_name = NULL; | ||
| 345 | union tls_addr addrbuf; | ||
| 346 | int rv = -1; | ||
| 347 | |||
| 348 | if (tls_get_common_name(ctx, cert, name, &common_name) == -1) | ||
| 349 | goto err; | ||
| 350 | if (strlen(common_name) == 0) | ||
| 351 | goto done; | ||
| 352 | |||
| 353 | /* | ||
| 354 | * We don't want to attempt wildcard matching against IP addresses, | ||
| 355 | * so perform a simple comparison here. | ||
| 356 | */ | ||
| 357 | if (inet_pton(AF_INET, name, &addrbuf) == 1 || | ||
| 358 | inet_pton(AF_INET6, name, &addrbuf) == 1) { | ||
| 359 | if (strcmp(common_name, name) == 0) | ||
| 360 | *cn_match = 1; | ||
| 361 | goto done; | ||
| 362 | } | ||
| 363 | |||
| 364 | if (tls_match_name(common_name, name) == 0) | ||
| 365 | *cn_match = 1; | ||
| 366 | |||
| 367 | done: | ||
| 368 | rv = 0; | ||
| 369 | |||
| 370 | err: | ||
| 371 | free(common_name); | ||
| 372 | return rv; | ||
| 373 | } | ||
| 374 | |||
| 375 | int | ||
| 376 | tls_check_name(struct tls *ctx, X509 *cert, const char *name, int *match) | ||
| 377 | { | ||
| 378 | int alt_exists; | ||
| 379 | |||
| 380 | *match = 0; | ||
| 381 | |||
| 382 | if (tls_check_subject_altname(ctx, cert, name, match, | ||
| 383 | &alt_exists) == -1) | ||
| 384 | return -1; | ||
| 385 | |||
| 386 | /* | ||
| 387 | * As per RFC 6125 section 6.4.4, if any known alternate name existed | ||
| 388 | * in the certificate, we do not attempt to match on the CN. | ||
| 389 | */ | ||
| 390 | if (*match || alt_exists) | ||
| 391 | return 0; | ||
| 392 | |||
| 393 | return tls_check_common_name(ctx, cert, name, match); | ||
| 394 | } | ||
