Skip to content

Commit 545e67e

Browse files
committed
Lib: Add robust OpenSSL hostname validation.
Use vetted code from https://wiki.openssl.org/index.php/Hostname_validation and https://github.com/iSECPartners/ssl-conservatory to do hostname validation of SSL certs when using OpenSSL.
1 parent bd37667 commit 545e67e

File tree

4 files changed

+231
-1
lines changed

4 files changed

+231
-1
lines changed

Makefile.am

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ if SSL_OPENSSL
4444
librabbitmq_librabbitmq_la_SOURCES += \
4545
librabbitmq/amqp_hostcheck.c \
4646
librabbitmq/amqp_hostcheck.h \
47-
librabbitmq/amqp_openssl.c
47+
librabbitmq/amqp_openssl.c \
48+
librabbitmq/amqp_openssl_hostname_validation.c \
49+
librabbitmq/amqp_openssl_hostname_validation.h
4850
if OS_APPLE
4951
librabbitmq_librabbitmq_la_CFLAGS += -Wno-deprecated-declarations
5052
endif

librabbitmq/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ if (ENABLE_SSL_SUPPORT)
8282

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

0 commit comments

Comments
 (0)