1 /* Copyright (C) 2014 Trend Micro Inc.
4 * This program is a free software; you can redistribute it
5 * and/or modify it under the terms of the GNU General Public
6 * License (version 2) as published by the FSF - Free Software
9 * In addition, as a special exception, the copyright holders give
10 * permission to link the code of portions of this program with the
11 * OpenSSL library under certain conditions as described in each
12 * individual source file, and distribute linked combinations
15 * You must obey the GNU General Public License in all respects
16 * for all of the code used other than OpenSSL. If you modify
17 * file(s) with this exception, you may extend this exception to your
18 * version of the file(s), but you are not obligated to do so. If you
19 * do not wish to do so, delete this exception statement from your
20 * version. If you delete this exception statement from all source
21 * files in the program, then also delete it here.
25 #ifdef LIBOPENSSL_ENABLED
33 #include "check_cert.h"
36 /* Compare the manager's name or IP address given on the command line with the
37 * subject alternative names and common names present in a received certificate.
38 * This could be replaced with X509_check_host() in future but this is only
39 * available in openssl 1.0.2.
41 int check_x509_cert(const SSL *ssl, const char *manager)
44 int verified = VERIFY_FALSE;
46 if (!(cert = SSL_get_peer_certificate(ssl))) {
47 goto CERT_CHECK_ERROR;
50 /* Check for a matching subject alt name entry in the extensions first and
51 * if no match is found there then check the subject CN.
53 debug1("%s: DEBUG: Checking certificate's subject alternative names.", ARGV0);
54 if ((verified = check_subject_alt_names(cert, manager)) == VERIFY_ERROR) {
55 goto CERT_CHECK_ERROR;
58 if (verified == VERIFY_FALSE) {
59 debug1("%s: DEBUG: No matching subject alternative names found. Checking common name.", ARGV0);
60 if ((verified = check_subject_cn(cert, manager)) == VERIFY_ERROR) {
61 goto CERT_CHECK_ERROR;
77 /* Loop through all the subject_alt_name entries until we find a match or
78 * an error occurs. Only entries containing a normal domain name or IP
79 * address are considered.
81 int check_subject_alt_names(X509 *cert, const char *manager)
83 GENERAL_NAMES *names = NULL;
84 int result = VERIFY_FALSE;
87 if ((names = (GENERAL_NAMES *) X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL))) {
88 for (i = 0; i < sk_GENERAL_NAME_num(names) && result == VERIFY_FALSE; i++) {
89 GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i);
91 if (name->type == GEN_DNS) {
92 result = check_hostname(name->d.dNSName, manager);
93 } else if (name->type == GEN_IPADD) {
94 result = check_ipaddr(name->d.iPAddress, manager);
98 GENERAL_NAMES_free(names);
104 /* Loop through all the common name entries until we find a match or
107 int check_subject_cn(X509 *cert, const char *manager)
109 X509_NAME *name = NULL;
110 int result = VERIFY_FALSE;
113 if ((name = X509_get_subject_name(cert))) {
114 while ((i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0 && result == VERIFY_FALSE) {
115 X509_NAME_ENTRY *ne = X509_NAME_get_entry(name, i);
116 result = check_hostname(X509_NAME_ENTRY_get_data(ne), manager);
123 /* Determine whether a string found in a subject alt name or common name
124 * field matches the manager's name specified on the command line. The
125 * domain name from the certificate and the domain name from the command
126 * line are broken down into a sequence of labels and each label is validated
127 * and compared. Matching is case insensitive and basic wildcard matching
130 int check_hostname(ASN1_STRING *cert_astr, const char *manager)
132 label c_labels[DNS_MAX_LABELS];
133 label m_labels[DNS_MAX_LABELS];
136 int wildcard_cert = 0;
138 char *cert_cstr = NULL;
140 if (!(cert_cstr = asn1_to_cstr(cert_astr))) {
144 /* Convert domain names to arrays of labels separated by '.'
146 c_label_num = label_array(cert_cstr, c_labels);
147 m_label_num = label_array(manager, m_labels);
150 /* Check that we have an appropriate number of labels and that the name
151 * from the certificate and the name given on the command line have
152 * the same number of labels.
154 if (m_label_num <= 0 || c_label_num <= 0) {
158 if (m_label_num != c_label_num) {
162 /* Wildcards are accepted in the first label only. Partial wildcard
163 * matching is not supported.
165 if (label_valid(&m_labels[0]) && !strcmp(c_labels[0].text, "*")) {
169 /* Validate and match all labels.
171 for (i = wildcard_cert; i < m_label_num; i++) {
172 if (!label_valid(&m_labels[i])) {
176 if (!label_match(&m_labels[i], &c_labels[i])) {
184 /* Determine whether a string found in a subject alt name or common name
185 * field matches the manager's IP address specified on the command line.
187 int check_ipaddr(const ASN1_STRING *cert_astr, const char *manager)
189 struct sockaddr_in iptest;
190 struct sockaddr_in6 iptest6;
192 memset(&iptest, 0, sizeof(iptest));
193 memset(&iptest6, 0, sizeof(iptest6));
195 if (inet_pton(AF_INET, manager, &iptest.sin_addr) == 1) {
196 if (cert_astr->length == 4 && !memcmp(cert_astr->data, (const void *)&iptest.sin_addr, 4)) {
199 } else if (inet_pton(AF_INET6, manager, &iptest6.sin6_addr) == 1) {
200 if (cert_astr->length == 16 && !memcmp(cert_astr->data, (const void *)&iptest6.sin6_addr, 16)) {
208 /* Separate a domain name into a sequence of labels and return the number
209 * of labels found. strtok() is not used as we want to detect labels with
212 int label_array(const char *domain_name, label result[DNS_MAX_LABELS])
215 const char *label_start = domain_name;
216 const char *label_end = domain_name;
219 if (label_count == DNS_MAX_LABELS) {
223 if (*label_end == '.' || *label_end == '\0') {
224 label *new_label = &result[label_count];
226 if ((new_label->len = (size_t)(label_end - label_start)) > DNS_MAX_LABEL_LEN) {
230 strncpy(new_label->text, label_start, new_label->len);
231 new_label->text[new_label->len] = '\0';
233 label_start = label_end + 1;
236 } while (*label_end++ != '\0');
238 if (label_count == 0) {
242 /* If the length of the last label is zero ignore it. This is the only
243 * valid position for a label of length zero which occurs when a FQDN
246 return (result[label_count - 1].len > 0) ? label_count : label_count - 1;
249 /* Validate a label according to the guidelines in RFC 1035. This could
250 * be relaxed if necessary.
252 int label_valid(const label *l)
256 if (l->len <= 0 || l->len > DNS_MAX_LABEL_LEN) {
260 if (!isalpha(l->text[0]) || !isalnum(l->text[l->len - 1])) {
264 for (i = 0; i < l->len; i++) {
265 if (!isalnum(l->text[i]) && l->text[i] != '-') {
273 /* Compare two labels and determine whether they match.
275 int label_match(const label *label1, const label *label2)
279 if (label1->len != label2->len) {
283 for (i = 0; i < label1->len; i++) {
284 if (tolower(label1->text[i]) != tolower(label2->text[i])) {
292 /* Convert an ASN1 string which may not be null terminated into a
293 * standard null terminated string. Also check for embedded null
296 char *asn1_to_cstr(ASN1_STRING *astr)
298 unsigned int astr_len = 0;
302 if (!(astr_len = (unsigned int) ASN1_STRING_length(astr))) {
306 if (!(tmp = (char *)ASN1_STRING_data(astr))) {
310 /* Verify that the string does not contain embedded null characters.
312 if (memchr(tmp, '\0', astr_len)) {
316 if ((cstr = (char *) malloc(astr_len + 1)) == NULL) {
320 memcpy(cstr, tmp, astr_len);
321 cstr[astr_len] = '\0';
326 #endif /* LIBOPENSSL_ENABLED */