xref: /netbsd-src/external/bsd/libevent/dist/sample/openssl_hostname_validation.c (revision 657871a79c9a2060a6255a242fa1a1ef76b56ec6)
1*657871a7Schristos /*	$NetBSD: openssl_hostname_validation.c,v 1.1.1.2 2021/04/07 02:43:15 christos Exp $	*/
2805a1ce9Schristos /* Obtained from: https://github.com/iSECPartners/ssl-conservatory */
3805a1ce9Schristos 
4805a1ce9Schristos /*
5805a1ce9Schristos Copyright (C) 2012, iSEC Partners.
6805a1ce9Schristos 
7805a1ce9Schristos Permission is hereby granted, free of charge, to any person obtaining a copy of
8805a1ce9Schristos this software and associated documentation files (the "Software"), to deal in
9805a1ce9Schristos the Software without restriction, including without limitation the rights to
10805a1ce9Schristos use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
11805a1ce9Schristos of the Software, and to permit persons to whom the Software is furnished to do
12805a1ce9Schristos so, subject to the following conditions:
13805a1ce9Schristos 
14805a1ce9Schristos The above copyright notice and this permission notice shall be included in all
15805a1ce9Schristos copies or substantial portions of the Software.
16805a1ce9Schristos 
17805a1ce9Schristos THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18805a1ce9Schristos IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19805a1ce9Schristos FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20805a1ce9Schristos AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21805a1ce9Schristos LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22805a1ce9Schristos OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23805a1ce9Schristos SOFTWARE.
24805a1ce9Schristos  */
25805a1ce9Schristos 
26805a1ce9Schristos /*
27805a1ce9Schristos  * Helper functions to perform basic hostname validation using OpenSSL.
28805a1ce9Schristos  *
29805a1ce9Schristos  * Please read "everything-you-wanted-to-know-about-openssl.pdf" before
30805a1ce9Schristos  * attempting to use this code. This whitepaper describes how the code works,
31805a1ce9Schristos  * how it should be used, and what its limitations are.
32805a1ce9Schristos  *
33805a1ce9Schristos  * Author:  Alban Diquet
34805a1ce9Schristos  * License: See LICENSE
35805a1ce9Schristos  *
36805a1ce9Schristos  */
37805a1ce9Schristos 
38805a1ce9Schristos // Get rid of OSX 10.7 and greater deprecation warnings.
39805a1ce9Schristos #if defined(__APPLE__) && defined(__clang__)
40805a1ce9Schristos #pragma clang diagnostic ignored "-Wdeprecated-declarations"
41805a1ce9Schristos #endif
42805a1ce9Schristos 
43805a1ce9Schristos #include <openssl/x509v3.h>
44805a1ce9Schristos #include <openssl/ssl.h>
45805a1ce9Schristos #include <string.h>
46805a1ce9Schristos 
47805a1ce9Schristos #include "openssl_hostname_validation.h"
48805a1ce9Schristos #include "hostcheck.h"
49805a1ce9Schristos 
50805a1ce9Schristos #define HOSTNAME_MAX_SIZE 255
51805a1ce9Schristos 
52*657871a7Schristos #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
53*657871a7Schristos 	(defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
54805a1ce9Schristos #define ASN1_STRING_get0_data ASN1_STRING_data
55805a1ce9Schristos #endif
56805a1ce9Schristos 
57805a1ce9Schristos /**
58805a1ce9Schristos * Tries to find a match for hostname in the certificate's Common Name field.
59805a1ce9Schristos *
60805a1ce9Schristos * Returns MatchFound if a match was found.
61805a1ce9Schristos * Returns MatchNotFound if no matches were found.
62805a1ce9Schristos * Returns MalformedCertificate if the Common Name had a NUL character embedded in it.
63805a1ce9Schristos * Returns Error if the Common Name could not be extracted.
64805a1ce9Schristos */
matches_common_name(const char * hostname,const X509 * server_cert)65805a1ce9Schristos static HostnameValidationResult matches_common_name(const char *hostname, const X509 *server_cert) {
66805a1ce9Schristos         int common_name_loc = -1;
67805a1ce9Schristos         X509_NAME_ENTRY *common_name_entry = NULL;
68805a1ce9Schristos         ASN1_STRING *common_name_asn1 = NULL;
69805a1ce9Schristos         const char *common_name_str = NULL;
70805a1ce9Schristos 
71805a1ce9Schristos         // Find the position of the CN field in the Subject field of the certificate
72805a1ce9Schristos         common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1);
73805a1ce9Schristos         if (common_name_loc < 0) {
74805a1ce9Schristos                 return Error;
75805a1ce9Schristos         }
76805a1ce9Schristos 
77805a1ce9Schristos         // Extract the CN field
78805a1ce9Schristos         common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) server_cert), common_name_loc);
79805a1ce9Schristos         if (common_name_entry == NULL) {
80805a1ce9Schristos                 return Error;
81805a1ce9Schristos         }
82805a1ce9Schristos 
83805a1ce9Schristos         // Convert the CN field to a C string
84805a1ce9Schristos         common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
85805a1ce9Schristos         if (common_name_asn1 == NULL) {
86805a1ce9Schristos                 return Error;
87805a1ce9Schristos         }
88805a1ce9Schristos         common_name_str = (char *) ASN1_STRING_get0_data(common_name_asn1);
89805a1ce9Schristos 
90805a1ce9Schristos         // Make sure there isn't an embedded NUL character in the CN
91805a1ce9Schristos         if ((size_t)ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) {
92805a1ce9Schristos                 return MalformedCertificate;
93805a1ce9Schristos         }
94805a1ce9Schristos 
95805a1ce9Schristos         // Compare expected hostname with the CN
96805a1ce9Schristos         if (Curl_cert_hostcheck(common_name_str, hostname) == CURL_HOST_MATCH) {
97805a1ce9Schristos                 return MatchFound;
98805a1ce9Schristos         }
99805a1ce9Schristos         else {
100805a1ce9Schristos                 return MatchNotFound;
101805a1ce9Schristos         }
102805a1ce9Schristos }
103805a1ce9Schristos 
104805a1ce9Schristos 
105805a1ce9Schristos /**
106805a1ce9Schristos * Tries to find a match for hostname in the certificate's Subject Alternative Name extension.
107805a1ce9Schristos *
108805a1ce9Schristos * Returns MatchFound if a match was found.
109805a1ce9Schristos * Returns MatchNotFound if no matches were found.
110805a1ce9Schristos * Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
111805a1ce9Schristos * Returns NoSANPresent if the SAN extension was not present in the certificate.
112805a1ce9Schristos */
matches_subject_alternative_name(const char * hostname,const X509 * server_cert)113805a1ce9Schristos static HostnameValidationResult matches_subject_alternative_name(const char *hostname, const X509 *server_cert) {
114805a1ce9Schristos         HostnameValidationResult result = MatchNotFound;
115805a1ce9Schristos         int i;
116805a1ce9Schristos         int san_names_nb = -1;
117805a1ce9Schristos         STACK_OF(GENERAL_NAME) *san_names = NULL;
118805a1ce9Schristos 
119805a1ce9Schristos         // Try to extract the names within the SAN extension from the certificate
120805a1ce9Schristos         san_names = X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL);
121805a1ce9Schristos         if (san_names == NULL) {
122805a1ce9Schristos                 return NoSANPresent;
123805a1ce9Schristos         }
124805a1ce9Schristos         san_names_nb = sk_GENERAL_NAME_num(san_names);
125805a1ce9Schristos 
126805a1ce9Schristos         // Check each name within the extension
127805a1ce9Schristos         for (i=0; i<san_names_nb; i++) {
128805a1ce9Schristos                 const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
129805a1ce9Schristos 
130805a1ce9Schristos                 if (current_name->type == GEN_DNS) {
131805a1ce9Schristos                         // Current name is a DNS name, let's check it
132805a1ce9Schristos                         const char *dns_name = (char *) ASN1_STRING_get0_data(current_name->d.dNSName);
133805a1ce9Schristos 
134805a1ce9Schristos                         // Make sure there isn't an embedded NUL character in the DNS name
135805a1ce9Schristos                         if ((size_t)ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
136805a1ce9Schristos                                 result = MalformedCertificate;
137805a1ce9Schristos                                 break;
138805a1ce9Schristos                         }
139805a1ce9Schristos                         else { // Compare expected hostname with the DNS name
140805a1ce9Schristos                                 if (Curl_cert_hostcheck(dns_name, hostname)
141805a1ce9Schristos                                     == CURL_HOST_MATCH) {
142805a1ce9Schristos                                         result = MatchFound;
143805a1ce9Schristos                                         break;
144805a1ce9Schristos                                 }
145805a1ce9Schristos                         }
146805a1ce9Schristos                 }
147805a1ce9Schristos         }
148805a1ce9Schristos         sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
149805a1ce9Schristos 
150805a1ce9Schristos         return result;
151805a1ce9Schristos }
152805a1ce9Schristos 
153805a1ce9Schristos 
154805a1ce9Schristos /**
155805a1ce9Schristos * Validates the server's identity by looking for the expected hostname in the
156805a1ce9Schristos * server's certificate. As described in RFC 6125, it first tries to find a match
157805a1ce9Schristos * in the Subject Alternative Name extension. If the extension is not present in
158805a1ce9Schristos * the certificate, it checks the Common Name instead.
159805a1ce9Schristos *
160805a1ce9Schristos * Returns MatchFound if a match was found.
161805a1ce9Schristos * Returns MatchNotFound if no matches were found.
162805a1ce9Schristos * Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
163805a1ce9Schristos * Returns Error if there was an error.
164805a1ce9Schristos */
validate_hostname(const char * hostname,const X509 * server_cert)165805a1ce9Schristos HostnameValidationResult validate_hostname(const char *hostname, const X509 *server_cert) {
166805a1ce9Schristos         HostnameValidationResult result;
167805a1ce9Schristos 
168805a1ce9Schristos         if((hostname == NULL) || (server_cert == NULL))
169805a1ce9Schristos                 return Error;
170805a1ce9Schristos 
171805a1ce9Schristos         // First try the Subject Alternative Names extension
172805a1ce9Schristos         result = matches_subject_alternative_name(hostname, server_cert);
173805a1ce9Schristos         if (result == NoSANPresent) {
174805a1ce9Schristos                 // Extension was not found: try the Common Name
175805a1ce9Schristos                 result = matches_common_name(hostname, server_cert);
176805a1ce9Schristos         }
177805a1ce9Schristos 
178805a1ce9Schristos         return result;
179805a1ce9Schristos }
180