1 /* $OpenBSD: tls_verify.c,v 1.1 2014/10/31 13:46:17 jsing 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_internal.h" 28 29 int tls_match_hostname(const char *cert_hostname, const char *hostname); 30 int tls_check_subject_altname(X509 *cert, const char *host); 31 int tls_check_common_name(X509 *cert, const char *host); 32 33 int 34 tls_match_hostname(const char *cert_hostname, const char *hostname) 35 { 36 const char *cert_domain, *domain, *next_dot; 37 38 if (strcasecmp(cert_hostname, hostname) == 0) 39 return 0; 40 41 /* Wildcard match? */ 42 if (cert_hostname[0] == '*') { 43 /* 44 * Valid wildcards: 45 * - "*.domain.tld" 46 * - "*.sub.domain.tld" 47 * - etc. 48 * Reject "*.tld". 49 * No attempt to prevent the use of eg. "*.co.uk". 50 */ 51 cert_domain = &cert_hostname[1]; 52 /* Disallow "*" */ 53 if (cert_domain[0] == '\0') 54 return -1; 55 /* Disallow "*foo" */ 56 if (cert_domain[0] != '.') 57 return -1; 58 /* Disallow "*.." */ 59 if (cert_domain[1] == '.') 60 return -1; 61 next_dot = strchr(&cert_domain[1], '.'); 62 /* Disallow "*.bar" */ 63 if (next_dot == NULL) 64 return -1; 65 /* Disallow "*.bar.." */ 66 if (next_dot[1] == '.') 67 return -1; 68 69 domain = strchr(hostname, '.'); 70 71 /* No wildcard match against a hostname 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 int 83 tls_check_subject_altname(X509 *cert, const char *host) 84 { 85 STACK_OF(GENERAL_NAME) *altname_stack = NULL; 86 union { struct in_addr ip4; struct in6_addr ip6; } addrbuf; 87 int addrlen, type; 88 int count, i; 89 int rv = -1; 90 91 altname_stack = X509_get_ext_d2i(cert, NID_subject_alt_name, 92 NULL, NULL); 93 if (altname_stack == NULL) 94 return -1; 95 96 if (inet_pton(AF_INET, host, &addrbuf) == 1) { 97 type = GEN_IPADD; 98 addrlen = 4; 99 } else if (inet_pton(AF_INET6, host, &addrbuf) == 1) { 100 type = GEN_IPADD; 101 addrlen = 16; 102 } else { 103 type = GEN_DNS; 104 addrlen = 0; 105 } 106 107 count = sk_GENERAL_NAME_num(altname_stack); 108 for (i = 0; i < count; i++) { 109 GENERAL_NAME *altname; 110 111 altname = sk_GENERAL_NAME_value(altname_stack, i); 112 113 if (altname->type != type) 114 continue; 115 116 if (type == GEN_DNS) { 117 unsigned char *data; 118 int format; 119 120 format = ASN1_STRING_type(altname->d.dNSName); 121 if (format == V_ASN1_IA5STRING) { 122 data = ASN1_STRING_data(altname->d.dNSName); 123 124 if (ASN1_STRING_length(altname->d.dNSName) != 125 (int)strlen(data)) { 126 fprintf(stdout, "%s: NUL byte in " 127 "subjectAltName, probably a " 128 "malicious certificate.\n", 129 getprogname()); 130 rv = -2; 131 break; 132 } 133 134 if (tls_match_hostname(data, host) == 0) { 135 rv = 0; 136 break; 137 } 138 } else 139 fprintf(stdout, "%s: unhandled subjectAltName " 140 "dNSName encoding (%d)\n", getprogname(), 141 format); 142 143 } else if (type == GEN_IPADD) { 144 unsigned char *data; 145 int datalen; 146 147 datalen = ASN1_STRING_length(altname->d.iPAddress); 148 data = ASN1_STRING_data(altname->d.iPAddress); 149 150 if (datalen == addrlen && 151 memcmp(data, &addrbuf, addrlen) == 0) { 152 rv = 0; 153 break; 154 } 155 } 156 } 157 158 sk_GENERAL_NAME_free(altname_stack); 159 return rv; 160 } 161 162 int 163 tls_check_common_name(X509 *cert, const char *host) 164 { 165 X509_NAME *name; 166 char *common_name = NULL; 167 int common_name_len; 168 int rv = -1; 169 union { struct in_addr ip4; struct in6_addr ip6; } addrbuf; 170 171 name = X509_get_subject_name(cert); 172 if (name == NULL) 173 goto out; 174 175 common_name_len = X509_NAME_get_text_by_NID(name, NID_commonName, 176 NULL, 0); 177 if (common_name_len < 0) 178 goto out; 179 180 common_name = calloc(common_name_len + 1, 1); 181 if (common_name == NULL) 182 goto out; 183 184 X509_NAME_get_text_by_NID(name, NID_commonName, common_name, 185 common_name_len + 1); 186 187 /* NUL bytes in CN? */ 188 if (common_name_len != (int)strlen(common_name)) { 189 fprintf(stdout, "%s: NUL byte in Common Name field, " 190 "probably a malicious certificate.\n", getprogname()); 191 rv = -2; 192 goto out; 193 } 194 195 if (inet_pton(AF_INET, host, &addrbuf) == 1 || 196 inet_pton(AF_INET6, host, &addrbuf) == 1) { 197 /* 198 * We don't want to attempt wildcard matching against IP 199 * addresses, so perform a simple comparison here. 200 */ 201 if (strcmp(common_name, host) == 0) 202 rv = 0; 203 else 204 rv = -1; 205 goto out; 206 } 207 208 if (tls_match_hostname(common_name, host) == 0) 209 rv = 0; 210 out: 211 free(common_name); 212 return rv; 213 } 214 215 int 216 tls_check_hostname(X509 *cert, const char *host) 217 { 218 int rv; 219 220 rv = tls_check_subject_altname(cert, host); 221 if (rv == 0 || rv == -2) 222 return rv; 223 224 return tls_check_common_name(cert, host); 225 } 226