172c33676SMaxim Ag /* $OpenBSD: tls_verify.c,v 1.20 2018/02/05 00:52:24 jsing Exp $ */
2f5b1c8a1SJohn Marino /*
3f5b1c8a1SJohn Marino * Copyright (c) 2014 Jeremie Courreges-Anglas <jca@openbsd.org>
4f5b1c8a1SJohn Marino *
5f5b1c8a1SJohn Marino * Permission to use, copy, modify, and distribute this software for any
6f5b1c8a1SJohn Marino * purpose with or without fee is hereby granted, provided that the above
7f5b1c8a1SJohn Marino * copyright notice and this permission notice appear in all copies.
8f5b1c8a1SJohn Marino *
9f5b1c8a1SJohn Marino * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10f5b1c8a1SJohn Marino * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11f5b1c8a1SJohn Marino * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12f5b1c8a1SJohn Marino * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13f5b1c8a1SJohn Marino * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14f5b1c8a1SJohn Marino * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15f5b1c8a1SJohn Marino * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16f5b1c8a1SJohn Marino */
17f5b1c8a1SJohn Marino
18f5b1c8a1SJohn Marino #include <sys/socket.h>
19f5b1c8a1SJohn Marino
20f5b1c8a1SJohn Marino #include <arpa/inet.h>
21f5b1c8a1SJohn Marino #include <netinet/in.h>
22f5b1c8a1SJohn Marino
23f5b1c8a1SJohn Marino #include <string.h>
24f5b1c8a1SJohn Marino
25f5b1c8a1SJohn Marino #include <openssl/x509v3.h>
26f5b1c8a1SJohn Marino
2772c33676SMaxim Ag #include <tls.h>
28f5b1c8a1SJohn Marino #include "tls_internal.h"
29f5b1c8a1SJohn Marino
30f5b1c8a1SJohn Marino static int
tls_match_name(const char * cert_name,const char * name)31f5b1c8a1SJohn Marino tls_match_name(const char *cert_name, const char *name)
32f5b1c8a1SJohn Marino {
33f5b1c8a1SJohn Marino const char *cert_domain, *domain, *next_dot;
34f5b1c8a1SJohn Marino
35f5b1c8a1SJohn Marino if (strcasecmp(cert_name, name) == 0)
36f5b1c8a1SJohn Marino return 0;
37f5b1c8a1SJohn Marino
38f5b1c8a1SJohn Marino /* Wildcard match? */
39f5b1c8a1SJohn Marino if (cert_name[0] == '*') {
40f5b1c8a1SJohn Marino /*
41f5b1c8a1SJohn Marino * Valid wildcards:
42f5b1c8a1SJohn Marino * - "*.domain.tld"
43f5b1c8a1SJohn Marino * - "*.sub.domain.tld"
44f5b1c8a1SJohn Marino * - etc.
45f5b1c8a1SJohn Marino * Reject "*.tld".
46f5b1c8a1SJohn Marino * No attempt to prevent the use of eg. "*.co.uk".
47f5b1c8a1SJohn Marino */
48f5b1c8a1SJohn Marino cert_domain = &cert_name[1];
49f5b1c8a1SJohn Marino /* Disallow "*" */
50f5b1c8a1SJohn Marino if (cert_domain[0] == '\0')
51f5b1c8a1SJohn Marino return -1;
52f5b1c8a1SJohn Marino /* Disallow "*foo" */
53f5b1c8a1SJohn Marino if (cert_domain[0] != '.')
54f5b1c8a1SJohn Marino return -1;
55f5b1c8a1SJohn Marino /* Disallow "*.." */
56f5b1c8a1SJohn Marino if (cert_domain[1] == '.')
57f5b1c8a1SJohn Marino return -1;
58f5b1c8a1SJohn Marino next_dot = strchr(&cert_domain[1], '.');
59f5b1c8a1SJohn Marino /* Disallow "*.bar" */
60f5b1c8a1SJohn Marino if (next_dot == NULL)
61f5b1c8a1SJohn Marino return -1;
62f5b1c8a1SJohn Marino /* Disallow "*.bar.." */
63f5b1c8a1SJohn Marino if (next_dot[1] == '.')
64f5b1c8a1SJohn Marino return -1;
65f5b1c8a1SJohn Marino
66f5b1c8a1SJohn Marino domain = strchr(name, '.');
67f5b1c8a1SJohn Marino
68f5b1c8a1SJohn Marino /* No wildcard match against a name with no host part. */
69f5b1c8a1SJohn Marino if (name[0] == '.')
70f5b1c8a1SJohn Marino return -1;
71f5b1c8a1SJohn Marino /* No wildcard match against a name with no domain part. */
72f5b1c8a1SJohn Marino if (domain == NULL || strlen(domain) == 1)
73f5b1c8a1SJohn Marino return -1;
74f5b1c8a1SJohn Marino
75f5b1c8a1SJohn Marino if (strcasecmp(cert_domain, domain) == 0)
76f5b1c8a1SJohn Marino return 0;
77f5b1c8a1SJohn Marino }
78f5b1c8a1SJohn Marino
79f5b1c8a1SJohn Marino return -1;
80f5b1c8a1SJohn Marino }
81f5b1c8a1SJohn Marino
8272c33676SMaxim Ag /*
8372c33676SMaxim Ag * See RFC 5280 section 4.2.1.6 for SubjectAltName details.
8472c33676SMaxim Ag * alt_match is set to 1 if a matching alternate name is found.
8572c33676SMaxim Ag * alt_exists is set to 1 if any known alternate name exists in the certificate.
8672c33676SMaxim Ag */
87f5b1c8a1SJohn Marino static int
tls_check_subject_altname(struct tls * ctx,X509 * cert,const char * name,int * alt_match,int * alt_exists)8872c33676SMaxim Ag tls_check_subject_altname(struct tls *ctx, X509 *cert, const char *name,
8972c33676SMaxim Ag int *alt_match, int *alt_exists)
90f5b1c8a1SJohn Marino {
91f5b1c8a1SJohn Marino STACK_OF(GENERAL_NAME) *altname_stack = NULL;
92f5b1c8a1SJohn Marino union tls_addr addrbuf;
93f5b1c8a1SJohn Marino int addrlen, type;
94f5b1c8a1SJohn Marino int count, i;
9572c33676SMaxim Ag int rv = 0;
9672c33676SMaxim Ag
9772c33676SMaxim Ag *alt_match = 0;
9872c33676SMaxim Ag *alt_exists = 0;
99f5b1c8a1SJohn Marino
100f5b1c8a1SJohn Marino altname_stack = X509_get_ext_d2i(cert, NID_subject_alt_name,
101f5b1c8a1SJohn Marino NULL, NULL);
102f5b1c8a1SJohn Marino if (altname_stack == NULL)
10372c33676SMaxim Ag return 0;
104f5b1c8a1SJohn Marino
105f5b1c8a1SJohn Marino if (inet_pton(AF_INET, name, &addrbuf) == 1) {
106f5b1c8a1SJohn Marino type = GEN_IPADD;
107f5b1c8a1SJohn Marino addrlen = 4;
108f5b1c8a1SJohn Marino } else if (inet_pton(AF_INET6, name, &addrbuf) == 1) {
109f5b1c8a1SJohn Marino type = GEN_IPADD;
110f5b1c8a1SJohn Marino addrlen = 16;
111f5b1c8a1SJohn Marino } else {
112f5b1c8a1SJohn Marino type = GEN_DNS;
113f5b1c8a1SJohn Marino addrlen = 0;
114f5b1c8a1SJohn Marino }
115f5b1c8a1SJohn Marino
116f5b1c8a1SJohn Marino count = sk_GENERAL_NAME_num(altname_stack);
117f5b1c8a1SJohn Marino for (i = 0; i < count; i++) {
118f5b1c8a1SJohn Marino GENERAL_NAME *altname;
119f5b1c8a1SJohn Marino
120f5b1c8a1SJohn Marino altname = sk_GENERAL_NAME_value(altname_stack, i);
121f5b1c8a1SJohn Marino
12272c33676SMaxim Ag if (altname->type == GEN_DNS || altname->type == GEN_IPADD)
12372c33676SMaxim Ag *alt_exists = 1;
12472c33676SMaxim Ag
125f5b1c8a1SJohn Marino if (altname->type != type)
126f5b1c8a1SJohn Marino continue;
127f5b1c8a1SJohn Marino
128f5b1c8a1SJohn Marino if (type == GEN_DNS) {
129f5b1c8a1SJohn Marino unsigned char *data;
130f5b1c8a1SJohn Marino int format, len;
131f5b1c8a1SJohn Marino
132f5b1c8a1SJohn Marino format = ASN1_STRING_type(altname->d.dNSName);
133f5b1c8a1SJohn Marino if (format == V_ASN1_IA5STRING) {
134f5b1c8a1SJohn Marino data = ASN1_STRING_data(altname->d.dNSName);
135f5b1c8a1SJohn Marino len = ASN1_STRING_length(altname->d.dNSName);
136f5b1c8a1SJohn Marino
13772c33676SMaxim Ag if (len < 0 || (size_t)len != strlen(data)) {
138f5b1c8a1SJohn Marino tls_set_errorx(ctx,
139f5b1c8a1SJohn Marino "error verifying name '%s': "
140f5b1c8a1SJohn Marino "NUL byte in subjectAltName, "
141f5b1c8a1SJohn Marino "probably a malicious certificate",
142f5b1c8a1SJohn Marino name);
14372c33676SMaxim Ag rv = -1;
144f5b1c8a1SJohn Marino break;
145f5b1c8a1SJohn Marino }
146f5b1c8a1SJohn Marino
147f5b1c8a1SJohn Marino /*
148f5b1c8a1SJohn Marino * Per RFC 5280 section 4.2.1.6:
149f5b1c8a1SJohn Marino * " " is a legal domain name, but that
150f5b1c8a1SJohn Marino * dNSName must be rejected.
151f5b1c8a1SJohn Marino */
152f5b1c8a1SJohn Marino if (strcmp(data, " ") == 0) {
15372c33676SMaxim Ag tls_set_errorx(ctx,
154f5b1c8a1SJohn Marino "error verifying name '%s': "
155f5b1c8a1SJohn Marino "a dNSName of \" \" must not be "
156f5b1c8a1SJohn Marino "used", name);
15772c33676SMaxim Ag rv = -1;
158f5b1c8a1SJohn Marino break;
159f5b1c8a1SJohn Marino }
160f5b1c8a1SJohn Marino
161f5b1c8a1SJohn Marino if (tls_match_name(data, name) == 0) {
16272c33676SMaxim Ag *alt_match = 1;
163f5b1c8a1SJohn Marino break;
164f5b1c8a1SJohn Marino }
165f5b1c8a1SJohn Marino } else {
166f5b1c8a1SJohn Marino #ifdef DEBUG
167f5b1c8a1SJohn Marino fprintf(stdout, "%s: unhandled subjectAltName "
168f5b1c8a1SJohn Marino "dNSName encoding (%d)\n", getprogname(),
169f5b1c8a1SJohn Marino format);
170f5b1c8a1SJohn Marino #endif
171f5b1c8a1SJohn Marino }
172f5b1c8a1SJohn Marino
173f5b1c8a1SJohn Marino } else if (type == GEN_IPADD) {
174f5b1c8a1SJohn Marino unsigned char *data;
175f5b1c8a1SJohn Marino int datalen;
176f5b1c8a1SJohn Marino
177f5b1c8a1SJohn Marino datalen = ASN1_STRING_length(altname->d.iPAddress);
178f5b1c8a1SJohn Marino data = ASN1_STRING_data(altname->d.iPAddress);
179f5b1c8a1SJohn Marino
180f5b1c8a1SJohn Marino if (datalen < 0) {
181f5b1c8a1SJohn Marino tls_set_errorx(ctx,
182f5b1c8a1SJohn Marino "Unexpected negative length for an "
183f5b1c8a1SJohn Marino "IP address: %d", datalen);
18472c33676SMaxim Ag rv = -1;
185f5b1c8a1SJohn Marino break;
186f5b1c8a1SJohn Marino }
187f5b1c8a1SJohn Marino
188f5b1c8a1SJohn Marino /*
189f5b1c8a1SJohn Marino * Per RFC 5280 section 4.2.1.6:
190f5b1c8a1SJohn Marino * IPv4 must use 4 octets and IPv6 must use 16 octets.
191f5b1c8a1SJohn Marino */
192f5b1c8a1SJohn Marino if (datalen == addrlen &&
193f5b1c8a1SJohn Marino memcmp(data, &addrbuf, addrlen) == 0) {
19472c33676SMaxim Ag *alt_match = 1;
195f5b1c8a1SJohn Marino break;
196f5b1c8a1SJohn Marino }
197f5b1c8a1SJohn Marino }
198f5b1c8a1SJohn Marino }
199f5b1c8a1SJohn Marino
200f5b1c8a1SJohn Marino sk_GENERAL_NAME_pop_free(altname_stack, GENERAL_NAME_free);
201f5b1c8a1SJohn Marino return rv;
202f5b1c8a1SJohn Marino }
203f5b1c8a1SJohn Marino
204f5b1c8a1SJohn Marino static int
tls_check_common_name(struct tls * ctx,X509 * cert,const char * name,int * cn_match)20572c33676SMaxim Ag tls_check_common_name(struct tls *ctx, X509 *cert, const char *name,
20672c33676SMaxim Ag int *cn_match)
207f5b1c8a1SJohn Marino {
208f5b1c8a1SJohn Marino X509_NAME *subject_name;
209f5b1c8a1SJohn Marino char *common_name = NULL;
210f5b1c8a1SJohn Marino union tls_addr addrbuf;
211f5b1c8a1SJohn Marino int common_name_len;
21272c33676SMaxim Ag int rv = 0;
21372c33676SMaxim Ag
21472c33676SMaxim Ag *cn_match = 0;
215f5b1c8a1SJohn Marino
216f5b1c8a1SJohn Marino subject_name = X509_get_subject_name(cert);
217f5b1c8a1SJohn Marino if (subject_name == NULL)
21872c33676SMaxim Ag goto done;
219f5b1c8a1SJohn Marino
220f5b1c8a1SJohn Marino common_name_len = X509_NAME_get_text_by_NID(subject_name,
221f5b1c8a1SJohn Marino NID_commonName, NULL, 0);
222f5b1c8a1SJohn Marino if (common_name_len < 0)
22372c33676SMaxim Ag goto done;
224f5b1c8a1SJohn Marino
225f5b1c8a1SJohn Marino common_name = calloc(common_name_len + 1, 1);
226f5b1c8a1SJohn Marino if (common_name == NULL)
22772c33676SMaxim Ag goto done;
228f5b1c8a1SJohn Marino
229f5b1c8a1SJohn Marino X509_NAME_get_text_by_NID(subject_name, NID_commonName, common_name,
230f5b1c8a1SJohn Marino common_name_len + 1);
231f5b1c8a1SJohn Marino
232f5b1c8a1SJohn Marino /* NUL bytes in CN? */
23372c33676SMaxim Ag if (common_name_len < 0 ||
23472c33676SMaxim Ag (size_t)common_name_len != strlen(common_name)) {
235f5b1c8a1SJohn Marino tls_set_errorx(ctx, "error verifying name '%s': "
236f5b1c8a1SJohn Marino "NUL byte in Common Name field, "
237f5b1c8a1SJohn Marino "probably a malicious certificate", name);
23872c33676SMaxim Ag rv = -1;
23972c33676SMaxim Ag goto done;
240f5b1c8a1SJohn Marino }
241f5b1c8a1SJohn Marino
24272c33676SMaxim Ag /*
24372c33676SMaxim Ag * We don't want to attempt wildcard matching against IP addresses,
24472c33676SMaxim Ag * so perform a simple comparison here.
24572c33676SMaxim Ag */
246f5b1c8a1SJohn Marino if (inet_pton(AF_INET, name, &addrbuf) == 1 ||
247f5b1c8a1SJohn Marino inet_pton(AF_INET6, name, &addrbuf) == 1) {
248f5b1c8a1SJohn Marino if (strcmp(common_name, name) == 0)
24972c33676SMaxim Ag *cn_match = 1;
25072c33676SMaxim Ag goto done;
251f5b1c8a1SJohn Marino }
252f5b1c8a1SJohn Marino
253f5b1c8a1SJohn Marino if (tls_match_name(common_name, name) == 0)
25472c33676SMaxim Ag *cn_match = 1;
25572c33676SMaxim Ag
25672c33676SMaxim Ag done:
257f5b1c8a1SJohn Marino free(common_name);
258f5b1c8a1SJohn Marino return rv;
259f5b1c8a1SJohn Marino }
260f5b1c8a1SJohn Marino
261f5b1c8a1SJohn Marino int
tls_check_name(struct tls * ctx,X509 * cert,const char * name,int * match)26272c33676SMaxim Ag tls_check_name(struct tls *ctx, X509 *cert, const char *name, int *match)
263f5b1c8a1SJohn Marino {
26472c33676SMaxim Ag int alt_exists;
265f5b1c8a1SJohn Marino
26672c33676SMaxim Ag *match = 0;
267f5b1c8a1SJohn Marino
26872c33676SMaxim Ag if (tls_check_subject_altname(ctx, cert, name, match,
26972c33676SMaxim Ag &alt_exists) == -1)
27072c33676SMaxim Ag return -1;
27172c33676SMaxim Ag
27272c33676SMaxim Ag /*
27372c33676SMaxim Ag * As per RFC 6125 section 6.4.4, if any known alternate name existed
27472c33676SMaxim Ag * in the certificate, we do not attempt to match on the CN.
27572c33676SMaxim Ag */
27672c33676SMaxim Ag if (*match || alt_exists)
27772c33676SMaxim Ag return 0;
27872c33676SMaxim Ag
27972c33676SMaxim Ag return tls_check_common_name(ctx, cert, name, match);
280f5b1c8a1SJohn Marino }
281