new upstream release (3.3.0); modify package compatibility for Stretch
[ossec-hids.git] / src / os_auth / check_cert.c
1 /* Copyright (C) 2014 Trend Micro Inc.
2  * All rights reserved.
3  *
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
7  * Foundation
8  *
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
13  * including the two.
14  *
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.
22  *
23  */
24
25 #ifdef LIBOPENSSL_ENABLED
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <ctype.h>
31
32 #include "shared.h"
33 #include "check_cert.h"
34
35
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.
40  */
41 int check_x509_cert(const SSL *ssl, const char *manager)
42 {
43     X509 *cert = NULL;
44     int verified = VERIFY_FALSE;
45
46     if (!(cert = SSL_get_peer_certificate(ssl))) {
47         goto CERT_CHECK_ERROR;
48     }
49
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.
52      */
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;
56     }
57
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;
62         }
63     }
64
65     X509_free(cert);
66
67     return verified;
68
69 CERT_CHECK_ERROR:
70     if (cert) {
71         X509_free(cert);
72     }
73
74     return VERIFY_ERROR;
75 }
76
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.
80  */
81 int check_subject_alt_names(X509 *cert, const char *manager)
82 {
83     GENERAL_NAMES *names = NULL;
84     int result = VERIFY_FALSE;
85     int i = 0;
86
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);
90
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);
95             }
96         }
97
98         GENERAL_NAMES_free(names);
99     }
100
101     return result;
102 }
103
104 /* Loop through all the common name entries until we find a match or
105  * an error occurs.
106  */
107 int check_subject_cn(X509 *cert, const char *manager)
108 {
109     X509_NAME *name = NULL;
110     int result = VERIFY_FALSE;
111     int i = 0;
112
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);
117         }
118     }
119
120     return result;
121 }
122
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
128  * is supported.
129  */
130 int check_hostname(ASN1_STRING *cert_astr, const char *manager)
131 {
132     label c_labels[DNS_MAX_LABELS];
133     label m_labels[DNS_MAX_LABELS];
134     int c_label_num = 0;
135     int m_label_num = 0;
136     int wildcard_cert = 0;
137     int i = 0;
138     char *cert_cstr = NULL;
139
140     if (!(cert_cstr = asn1_to_cstr(cert_astr))) {
141         return VERIFY_FALSE;
142     }
143
144     /* Convert domain names to arrays of labels separated by '.'
145      */
146     c_label_num = label_array(cert_cstr, c_labels);
147     m_label_num = label_array(manager, m_labels);
148     free(cert_cstr);
149
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.
153      */
154     if (m_label_num <= 0 || c_label_num <= 0) {
155         return VERIFY_FALSE;
156     }
157
158     if (m_label_num != c_label_num) {
159         return VERIFY_FALSE;
160     }
161
162     /* Wildcards are accepted in the first label only. Partial wildcard
163      * matching is not supported.
164      */
165     if (label_valid(&m_labels[0]) && !strcmp(c_labels[0].text, "*")) {
166         wildcard_cert = 1;
167     }
168
169     /* Validate and match all labels.
170      */
171     for (i = wildcard_cert; i < m_label_num; i++) {
172         if (!label_valid(&m_labels[i])) {
173             return VERIFY_FALSE;
174         }
175
176         if (!label_match(&m_labels[i], &c_labels[i])) {
177             return VERIFY_FALSE;
178         }
179     }
180
181     return VERIFY_TRUE;
182 }
183
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.
186  */
187 int check_ipaddr(const ASN1_STRING *cert_astr, const char *manager)
188 {
189     struct sockaddr_in iptest;
190     struct sockaddr_in6 iptest6;
191
192     memset(&iptest, 0, sizeof(iptest));
193     memset(&iptest6, 0, sizeof(iptest6));
194
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)) {
197             return VERIFY_TRUE;
198         }
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)) {
201             return VERIFY_TRUE;
202         }
203     }
204
205     return VERIFY_FALSE;
206 }
207
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
210  * length zero.
211  */
212 int label_array(const char *domain_name, label result[DNS_MAX_LABELS])
213 {
214     int label_count = 0;
215     const char *label_start = domain_name;
216     const char *label_end = domain_name;
217
218     do {
219         if (label_count == DNS_MAX_LABELS) {
220             return VERIFY_FALSE;
221         }
222
223         if (*label_end == '.' || *label_end == '\0') {
224             label *new_label = &result[label_count];
225
226             if ((new_label->len = (size_t)(label_end - label_start)) > DNS_MAX_LABEL_LEN) {
227                 return VERIFY_FALSE;
228             }
229
230             strncpy(new_label->text, label_start, new_label->len);
231             new_label->text[new_label->len] = '\0';
232
233             label_start = label_end + 1;
234             label_count++;
235         }
236     } while (*label_end++ != '\0');
237
238     if (label_count == 0) {
239         return VERIFY_FALSE;
240     }
241
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
244      * is given.
245      */
246     return (result[label_count - 1].len > 0) ? label_count : label_count - 1;
247 }
248
249 /* Validate a label according to the guidelines in RFC 1035. This could
250  * be relaxed if necessary.
251  */
252 int label_valid(const label *l)
253 {
254     size_t i;
255
256     if (l->len <= 0 || l->len > DNS_MAX_LABEL_LEN) {
257         return VERIFY_FALSE;
258     }
259
260     if (!isalpha(l->text[0]) || !isalnum(l->text[l->len - 1])) {
261         return VERIFY_FALSE;
262     }
263
264     for (i = 0; i < l->len; i++) {
265         if (!isalnum(l->text[i]) && l->text[i] != '-') {
266             return VERIFY_FALSE;
267         }
268     }
269
270     return VERIFY_TRUE;
271 }
272
273 /* Compare two labels and determine whether they match.
274  */
275 int label_match(const label *label1, const label *label2)
276 {
277     size_t i;
278
279     if (label1->len != label2->len) {
280         return VERIFY_FALSE;
281     }
282
283     for (i = 0; i < label1->len; i++) {
284         if (tolower(label1->text[i]) != tolower(label2->text[i])) {
285             return VERIFY_FALSE;
286         }
287     }
288
289     return VERIFY_TRUE;
290 }
291
292 /* Convert an ASN1 string which may not be null terminated into a
293  * standard null terminated string. Also check for embedded null
294  * characters.
295  */
296 char *asn1_to_cstr(ASN1_STRING *astr)
297 {
298     unsigned int astr_len = 0;
299     char *tmp = NULL;
300     char *cstr = NULL;
301
302     if (!(astr_len = (unsigned int) ASN1_STRING_length(astr))) {
303         return NULL;
304     }
305
306     if (!(tmp = (char *)ASN1_STRING_data(astr))) {
307         return NULL;
308     }
309
310     /* Verify that the string does not contain embedded null characters.
311      */
312     if (memchr(tmp, '\0', astr_len)) {
313         return NULL;
314     }
315
316     if ((cstr = (char *) malloc(astr_len + 1)) == NULL) {
317         return NULL;
318     }
319
320     memcpy(cstr, tmp, astr_len);
321     cstr[astr_len] = '\0';
322
323     return cstr;
324 }
325
326 #endif /* LIBOPENSSL_ENABLED */
327