diff options
author | cvs2svn <admin@example.com> | 2025-04-14 17:32:06 +0000 |
---|---|---|
committer | cvs2svn <admin@example.com> | 2025-04-14 17:32:06 +0000 |
commit | eb8dd9dca1228af0cd132f515509051ecfabf6f6 (patch) | |
tree | edb6da6af7e865d488dc1a29309f1e1ec226e603 /src/lib/libtls/tls_verify.c | |
parent | 247f0352e0ed72a4f476db9dc91f4d982bc83eb2 (diff) | |
download | openbsd-eb8dd9dca1228af0cd132f515509051ecfabf6f6.tar.gz openbsd-eb8dd9dca1228af0cd132f515509051ecfabf6f6.tar.bz2 openbsd-eb8dd9dca1228af0cd132f515509051ecfabf6f6.zip |
This commit was manufactured by cvs2git to create tag 'tb_20250414'.tb_20250414
Diffstat (limited to '')
-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 | } | ||