xref: /freebsd-src/contrib/libevent/sample/openssl_hostname_validation.c (revision b50261e21f39a6c7249a49e7b60aa878c98512a8)
1c43e99fdSEd Maste /* Obtained from: https://github.com/iSECPartners/ssl-conservatory */
2c43e99fdSEd Maste 
3c43e99fdSEd Maste /*
4c43e99fdSEd Maste Copyright (C) 2012, iSEC Partners.
5c43e99fdSEd Maste 
6c43e99fdSEd Maste Permission is hereby granted, free of charge, to any person obtaining a copy of
7c43e99fdSEd Maste this software and associated documentation files (the "Software"), to deal in
8c43e99fdSEd Maste the Software without restriction, including without limitation the rights to
9c43e99fdSEd Maste use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10c43e99fdSEd Maste of the Software, and to permit persons to whom the Software is furnished to do
11c43e99fdSEd Maste so, subject to the following conditions:
12c43e99fdSEd Maste 
13c43e99fdSEd Maste The above copyright notice and this permission notice shall be included in all
14c43e99fdSEd Maste copies or substantial portions of the Software.
15c43e99fdSEd Maste 
16c43e99fdSEd Maste THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17c43e99fdSEd Maste IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18c43e99fdSEd Maste FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19c43e99fdSEd Maste AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20c43e99fdSEd Maste LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21c43e99fdSEd Maste OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22c43e99fdSEd Maste SOFTWARE.
23c43e99fdSEd Maste  */
24c43e99fdSEd Maste 
25c43e99fdSEd Maste /*
26c43e99fdSEd Maste  * Helper functions to perform basic hostname validation using OpenSSL.
27c43e99fdSEd Maste  *
28c43e99fdSEd Maste  * Please read "everything-you-wanted-to-know-about-openssl.pdf" before
29c43e99fdSEd Maste  * attempting to use this code. This whitepaper describes how the code works,
30c43e99fdSEd Maste  * how it should be used, and what its limitations are.
31c43e99fdSEd Maste  *
32c43e99fdSEd Maste  * Author:  Alban Diquet
33c43e99fdSEd Maste  * License: See LICENSE
34c43e99fdSEd Maste  *
35c43e99fdSEd Maste  */
36c43e99fdSEd Maste 
37c43e99fdSEd Maste // Get rid of OSX 10.7 and greater deprecation warnings.
38c43e99fdSEd Maste #if defined(__APPLE__) && defined(__clang__)
39c43e99fdSEd Maste #pragma clang diagnostic ignored "-Wdeprecated-declarations"
40c43e99fdSEd Maste #endif
41c43e99fdSEd Maste 
42c43e99fdSEd Maste #include <openssl/x509v3.h>
43c43e99fdSEd Maste #include <openssl/ssl.h>
44c43e99fdSEd Maste #include <string.h>
45c43e99fdSEd Maste 
46c43e99fdSEd Maste #include "openssl_hostname_validation.h"
47c43e99fdSEd Maste #include "hostcheck.h"
48c43e99fdSEd Maste 
49c43e99fdSEd Maste #define HOSTNAME_MAX_SIZE 255
50c43e99fdSEd Maste 
51*b50261e2SCy Schubert #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
52*b50261e2SCy Schubert 	(defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
53c43e99fdSEd Maste #define ASN1_STRING_get0_data ASN1_STRING_data
54c43e99fdSEd Maste #endif
55c43e99fdSEd Maste 
56c43e99fdSEd Maste /**
57c43e99fdSEd Maste * Tries to find a match for hostname in the certificate's Common Name field.
58c43e99fdSEd Maste *
59c43e99fdSEd Maste * Returns MatchFound if a match was found.
60c43e99fdSEd Maste * Returns MatchNotFound if no matches were found.
61c43e99fdSEd Maste * Returns MalformedCertificate if the Common Name had a NUL character embedded in it.
62c43e99fdSEd Maste * Returns Error if the Common Name could not be extracted.
63c43e99fdSEd Maste */
matches_common_name(const char * hostname,const X509 * server_cert)64c43e99fdSEd Maste static HostnameValidationResult matches_common_name(const char *hostname, const X509 *server_cert) {
65c43e99fdSEd Maste         int common_name_loc = -1;
66c43e99fdSEd Maste         X509_NAME_ENTRY *common_name_entry = NULL;
67c43e99fdSEd Maste         ASN1_STRING *common_name_asn1 = NULL;
68c43e99fdSEd Maste         const char *common_name_str = NULL;
69c43e99fdSEd Maste 
70c43e99fdSEd Maste         // Find the position of the CN field in the Subject field of the certificate
71c43e99fdSEd Maste         common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1);
72c43e99fdSEd Maste         if (common_name_loc < 0) {
73c43e99fdSEd Maste                 return Error;
74c43e99fdSEd Maste         }
75c43e99fdSEd Maste 
76c43e99fdSEd Maste         // Extract the CN field
77c43e99fdSEd Maste         common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) server_cert), common_name_loc);
78c43e99fdSEd Maste         if (common_name_entry == NULL) {
79c43e99fdSEd Maste                 return Error;
80c43e99fdSEd Maste         }
81c43e99fdSEd Maste 
82c43e99fdSEd Maste         // Convert the CN field to a C string
83c43e99fdSEd Maste         common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
84c43e99fdSEd Maste         if (common_name_asn1 == NULL) {
85c43e99fdSEd Maste                 return Error;
86c43e99fdSEd Maste         }
87c43e99fdSEd Maste         common_name_str = (char *) ASN1_STRING_get0_data(common_name_asn1);
88c43e99fdSEd Maste 
89c43e99fdSEd Maste         // Make sure there isn't an embedded NUL character in the CN
90c43e99fdSEd Maste         if ((size_t)ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) {
91c43e99fdSEd Maste                 return MalformedCertificate;
92c43e99fdSEd Maste         }
93c43e99fdSEd Maste 
94c43e99fdSEd Maste         // Compare expected hostname with the CN
95c43e99fdSEd Maste         if (Curl_cert_hostcheck(common_name_str, hostname) == CURL_HOST_MATCH) {
96c43e99fdSEd Maste                 return MatchFound;
97c43e99fdSEd Maste         }
98c43e99fdSEd Maste         else {
99c43e99fdSEd Maste                 return MatchNotFound;
100c43e99fdSEd Maste         }
101c43e99fdSEd Maste }
102c43e99fdSEd Maste 
103c43e99fdSEd Maste 
104c43e99fdSEd Maste /**
105c43e99fdSEd Maste * Tries to find a match for hostname in the certificate's Subject Alternative Name extension.
106c43e99fdSEd Maste *
107c43e99fdSEd Maste * Returns MatchFound if a match was found.
108c43e99fdSEd Maste * Returns MatchNotFound if no matches were found.
109c43e99fdSEd Maste * Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
110c43e99fdSEd Maste * Returns NoSANPresent if the SAN extension was not present in the certificate.
111c43e99fdSEd Maste */
matches_subject_alternative_name(const char * hostname,const X509 * server_cert)112c43e99fdSEd Maste static HostnameValidationResult matches_subject_alternative_name(const char *hostname, const X509 *server_cert) {
113c43e99fdSEd Maste         HostnameValidationResult result = MatchNotFound;
114c43e99fdSEd Maste         int i;
115c43e99fdSEd Maste         int san_names_nb = -1;
116c43e99fdSEd Maste         STACK_OF(GENERAL_NAME) *san_names = NULL;
117c43e99fdSEd Maste 
118c43e99fdSEd Maste         // Try to extract the names within the SAN extension from the certificate
119c43e99fdSEd Maste         san_names = X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL);
120c43e99fdSEd Maste         if (san_names == NULL) {
121c43e99fdSEd Maste                 return NoSANPresent;
122c43e99fdSEd Maste         }
123c43e99fdSEd Maste         san_names_nb = sk_GENERAL_NAME_num(san_names);
124c43e99fdSEd Maste 
125c43e99fdSEd Maste         // Check each name within the extension
126c43e99fdSEd Maste         for (i=0; i<san_names_nb; i++) {
127c43e99fdSEd Maste                 const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
128c43e99fdSEd Maste 
129c43e99fdSEd Maste                 if (current_name->type == GEN_DNS) {
130c43e99fdSEd Maste                         // Current name is a DNS name, let's check it
131c43e99fdSEd Maste                         const char *dns_name = (char *) ASN1_STRING_get0_data(current_name->d.dNSName);
132c43e99fdSEd Maste 
133c43e99fdSEd Maste                         // Make sure there isn't an embedded NUL character in the DNS name
134c43e99fdSEd Maste                         if ((size_t)ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
135c43e99fdSEd Maste                                 result = MalformedCertificate;
136c43e99fdSEd Maste                                 break;
137c43e99fdSEd Maste                         }
138c43e99fdSEd Maste                         else { // Compare expected hostname with the DNS name
139c43e99fdSEd Maste                                 if (Curl_cert_hostcheck(dns_name, hostname)
140c43e99fdSEd Maste                                     == CURL_HOST_MATCH) {
141c43e99fdSEd Maste                                         result = MatchFound;
142c43e99fdSEd Maste                                         break;
143c43e99fdSEd Maste                                 }
144c43e99fdSEd Maste                         }
145c43e99fdSEd Maste                 }
146c43e99fdSEd Maste         }
147c43e99fdSEd Maste         sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
148c43e99fdSEd Maste 
149c43e99fdSEd Maste         return result;
150c43e99fdSEd Maste }
151c43e99fdSEd Maste 
152c43e99fdSEd Maste 
153c43e99fdSEd Maste /**
154c43e99fdSEd Maste * Validates the server's identity by looking for the expected hostname in the
155c43e99fdSEd Maste * server's certificate. As described in RFC 6125, it first tries to find a match
156c43e99fdSEd Maste * in the Subject Alternative Name extension. If the extension is not present in
157c43e99fdSEd Maste * the certificate, it checks the Common Name instead.
158c43e99fdSEd Maste *
159c43e99fdSEd Maste * Returns MatchFound if a match was found.
160c43e99fdSEd Maste * Returns MatchNotFound if no matches were found.
161c43e99fdSEd Maste * Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
162c43e99fdSEd Maste * Returns Error if there was an error.
163c43e99fdSEd Maste */
validate_hostname(const char * hostname,const X509 * server_cert)164c43e99fdSEd Maste HostnameValidationResult validate_hostname(const char *hostname, const X509 *server_cert) {
165c43e99fdSEd Maste         HostnameValidationResult result;
166c43e99fdSEd Maste 
167c43e99fdSEd Maste         if((hostname == NULL) || (server_cert == NULL))
168c43e99fdSEd Maste                 return Error;
169c43e99fdSEd Maste 
170c43e99fdSEd Maste         // First try the Subject Alternative Names extension
171c43e99fdSEd Maste         result = matches_subject_alternative_name(hostname, server_cert);
172c43e99fdSEd Maste         if (result == NoSANPresent) {
173c43e99fdSEd Maste                 // Extension was not found: try the Common Name
174c43e99fdSEd Maste                 result = matches_common_name(hostname, server_cert);
175c43e99fdSEd Maste         }
176c43e99fdSEd Maste 
177c43e99fdSEd Maste         return result;
178c43e99fdSEd Maste }
179