File Coverage

amqp_openssl_hostname_validation.c
Criterion Covered Total %
statement 24 48 50.0
branch 8 26 30.7
condition n/a
subroutine n/a
pod n/a
total 32 74 43.2


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) 2012, iSEC Partners.
3             * Copyright (C) 2015 Alan Antonuk.
4             *
5             * All rights reserved.
6             *
7             * Permission to use, copy, modify, and distribute this software for any
8             * purpose with or without fee is hereby granted, provided that the above
9             * copyright notice and this permission notice appear in all copies.
10             *
11             * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12             * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13             * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
14             * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
15             * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16             * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
17             * USE OR OTHER DEALINGS IN THE SOFTWARE.
18             *
19             * Except as contained in this notice, the name of a copyright holder shall
20             * not be used in advertising or otherwise to promote the sale, use or other
21             * dealings in this Software without prior written authorization of the
22             * copyright holder.
23             */
24              
25             /* Originally from:
26             * https://github.com/iSECPartners/ssl-conservatory
27             * https://wiki.openssl.org/index.php/Hostname_validation
28             */
29              
30             #if defined(_WIN32)
31             #define WIN32_LEAN_AND_MEAN
32             #endif
33              
34             #include
35             #include
36              
37             #include "amqp_hostcheck.h"
38             #include "amqp_openssl_bio.h"
39             #include "amqp_openssl_hostname_validation.h"
40              
41             #include
42              
43             #define HOSTNAME_MAX_SIZE 255
44              
45             /**
46             * Tries to find a match for hostname in the certificate's Common Name field.
47             *
48             * Returns AMQP_HVR_MATCH_FOUND if a match was found.
49             * Returns AMQP_HVR_MATCH_NOT_FOUND if no matches were found.
50             * Returns AMQP_HVR_MALFORMED_CERTIFICATE if the Common Name had a NUL character
51             * embedded in it.
52             * Returns AMQP_HVR_ERROR if the Common Name could not be extracted.
53             */
54 0           static amqp_hostname_validation_result amqp_matches_common_name(
55             const char *hostname, const X509 *server_cert) {
56 0           int common_name_loc = -1;
57 0           X509_NAME_ENTRY *common_name_entry = NULL;
58 0           ASN1_STRING *common_name_asn1 = NULL;
59 0           const char *common_name_str = NULL;
60              
61             // Find the position of the CN field in the Subject field of the certificate
62 0           common_name_loc = X509_NAME_get_index_by_NID(
63             X509_get_subject_name((X509 *)server_cert), NID_commonName, -1);
64 0 0         if (common_name_loc < 0) {
65 0           return AMQP_HVR_ERROR;
66             }
67              
68             // Extract the CN field
69 0           common_name_entry = X509_NAME_get_entry(
70             X509_get_subject_name((X509 *)server_cert), common_name_loc);
71 0 0         if (common_name_entry == NULL) {
72 0           return AMQP_HVR_ERROR;
73             }
74              
75             // Convert the CN field to a C string
76 0           common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
77 0 0         if (common_name_asn1 == NULL) {
78 0           return AMQP_HVR_ERROR;
79             }
80              
81             #ifdef AMQP_OPENSSL_V110
82             common_name_str = (const char *)ASN1_STRING_get0_data(common_name_asn1);
83             #else
84 0           common_name_str = (char *)ASN1_STRING_data(common_name_asn1);
85             #endif
86              
87             // Make sure there isn't an embedded NUL character in the CN
88 0 0         if ((size_t)ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) {
89 0           return AMQP_HVR_MALFORMED_CERTIFICATE;
90             }
91              
92             // Compare expected hostname with the CN
93 0 0         if (amqp_hostcheck(common_name_str, hostname) == AMQP_HCR_MATCH) {
94 0           return AMQP_HVR_MATCH_FOUND;
95             } else {
96 0           return AMQP_HVR_MATCH_NOT_FOUND;
97             }
98             }
99              
100             /**
101             * Tries to find a match for hostname in the certificate's Subject Alternative
102             * Name extension.
103             *
104             * Returns AMQP_HVR_MATCH_FOUND if a match was found.
105             * Returns AMQP_HVR_MATCH_NOT_FOUND if no matches were found.
106             * Returns AMQP_HVR_MALFORMED_CERTIFICATE if any of the hostnames had a NUL
107             * character embedded in it.
108             * Returns AMQP_HVR_NO_SAN_PRESENT if the SAN extension was not present in the
109             * certificate.
110             */
111 1           static amqp_hostname_validation_result amqp_matches_subject_alternative_name(
112             const char *hostname, const X509 *server_cert) {
113 1           amqp_hostname_validation_result result = AMQP_HVR_MATCH_NOT_FOUND;
114             int i;
115 1           int san_names_nb = -1;
116 1           STACK_OF(GENERAL_NAME) *san_names = NULL;
117              
118             // Try to extract the names within the SAN extension from the certificate
119 1           san_names =
120             X509_get_ext_d2i((X509 *)server_cert, NID_subject_alt_name, NULL, NULL);
121 1 50         if (san_names == NULL) {
122 0           return AMQP_HVR_NO_SAN_PRESENT;
123             }
124 1           san_names_nb = sk_GENERAL_NAME_num(san_names);
125              
126             // Check each name within the extension
127 1 50         for (i = 0; i < san_names_nb; i++) {
128 1           const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
129              
130 1 50         if (current_name->type == GEN_DNS) {
131             // Current name is a DNS name, let's check it
132 1           const char *dns_name = (const char *)
133             #ifdef AMQP_OPENSSL_V110
134             ASN1_STRING_get0_data(current_name->d.dNSName);
135             #else
136 1           ASN1_STRING_data(current_name->d.dNSName);
137             #endif
138              
139             // Make sure there isn't an embedded NUL character in the DNS name
140 1 50         if ((size_t)ASN1_STRING_length(current_name->d.dNSName) !=
141 1           strlen(dns_name)) {
142 0           result = AMQP_HVR_MALFORMED_CERTIFICATE;
143 0           break;
144             } else { // Compare expected hostname with the DNS name
145 1 50         if (amqp_hostcheck(dns_name, hostname) == AMQP_HCR_MATCH) {
146 1           result = AMQP_HVR_MATCH_FOUND;
147 1           break;
148             }
149             }
150             }
151             }
152 1           sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
153              
154 1           return result;
155             }
156              
157             /**
158             * Validates the server's identity by looking for the expected hostname in the
159             * server's certificate. As described in RFC 6125, it first tries to find a
160             * match in the Subject Alternative Name extension. If the extension is not
161             * present in the certificate, it checks the Common Name instead.
162             *
163             * Returns AMQP_HVR_MATCH_FOUND if a match was found.
164             * Returns AMQP_HVR_MATCH_NOT_FOUND if no matches were found.
165             * Returns AMQP_HVR_MALFORMED_CERTIFICATE if any of the hostnames had a NUL
166             * character embedded in it.
167             * Returns AMQP_HVR_ERROR if there was an error.
168             */
169 1           amqp_hostname_validation_result amqp_ssl_validate_hostname(
170             const char *hostname, const X509 *server_cert) {
171             amqp_hostname_validation_result result;
172              
173 1 50         if ((hostname == NULL) || (server_cert == NULL)) return AMQP_HVR_ERROR;
    50          
174              
175             // First try the Subject Alternative Names extension
176 1           result = amqp_matches_subject_alternative_name(hostname, server_cert);
177 1 50         if (result == AMQP_HVR_NO_SAN_PRESENT) {
178             // Extension was not found: try the Common Name
179 0           result = amqp_matches_common_name(hostname, server_cert);
180             }
181              
182 1           return result;
183             }