dovecot-2.0: login proxy: Verify SSL certificate hostname when c...

dovecot at dovecot.org dovecot at dovecot.org
Wed Nov 16 23:31:57 EET 2011


details:   http://hg.dovecot.org/dovecot-2.0/rev/5e9eaf63a6b1
changeset: 12977:5e9eaf63a6b1
user:      Timo Sirainen <tss at iki.fi>
date:      Wed Nov 16 23:31:46 2011 +0200
description:
login proxy: Verify SSL certificate hostname when connecting to remote server.

diffstat:

 src/login-common/login-proxy.c       |  18 +++++--
 src/login-common/ssl-proxy-openssl.c |  82 ++++++++++++++++++++++++++++++++++++
 src/login-common/ssl-proxy.c         |   6 ++
 src/login-common/ssl-proxy.h         |   1 +
 4 files changed, 101 insertions(+), 6 deletions(-)

diffs (160 lines):

diff -r e4aa32586f17 -r 5e9eaf63a6b1 src/login-common/login-proxy.c
--- a/src/login-common/login-proxy.c	Wed Nov 16 22:59:36 2011 +0200
+++ b/src/login-common/login-proxy.c	Wed Nov 16 23:31:46 2011 +0200
@@ -505,18 +505,24 @@
 {
 	struct login_proxy *proxy = context;
 
-	if ((proxy->ssl_flags & PROXY_SSL_FLAG_ANY_CERT) != 0 ||
-	    ssl_proxy_has_valid_client_cert(proxy->ssl_server_proxy))
+	if ((proxy->ssl_flags & PROXY_SSL_FLAG_ANY_CERT) != 0)
 		return 0;
 
-	if (!ssl_proxy_has_broken_client_cert(proxy->ssl_server_proxy)) {
+	if (ssl_proxy_has_broken_client_cert(proxy->ssl_server_proxy)) {
+		client_log_err(proxy->client, t_strdup_printf(
+			"proxy: Received invalid SSL certificate from %s:%u",
+			proxy->host, proxy->port));
+	} else if (!ssl_proxy_has_valid_client_cert(proxy->ssl_server_proxy)) {
 		client_log_err(proxy->client, t_strdup_printf(
 			"proxy: SSL certificate not received from %s:%u",
 			proxy->host, proxy->port));
+	} else if (ssl_proxy_cert_match_name(proxy->ssl_server_proxy,
+					     proxy->host) < 0) {
+		client_log_err(proxy->client, t_strdup_printf(
+			"proxy: hostname doesn't match SSL certificate at %s:%u",
+			proxy->host, proxy->port));
 	} else {
-		client_log_err(proxy->client, t_strdup_printf(
-			"proxy: Received invalid SSL certificate from %s:%u",
-			proxy->host, proxy->port));
+		return 0;
 	}
 	proxy->disconnecting = TRUE;
 	return -1;
diff -r e4aa32586f17 -r 5e9eaf63a6b1 src/login-common/ssl-proxy-openssl.c
--- a/src/login-common/ssl-proxy-openssl.c	Wed Nov 16 22:59:36 2011 +0200
+++ b/src/login-common/ssl-proxy-openssl.c	Wed Nov 16 23:31:46 2011 +0200
@@ -21,6 +21,7 @@
 
 #include <openssl/crypto.h>
 #include <openssl/x509.h>
+#include <openssl/x509v3.h>
 #include <openssl/pem.h>
 #include <openssl/ssl.h>
 #include <openssl/err.h>
@@ -661,6 +662,87 @@
 	return proxy->cert_received && proxy->cert_broken;
 }
 
+static const char *asn1_string_to_c(ASN1_STRING *asn_str)
+{
+	const char *cstr;
+	unsigned int len;
+
+	len = ASN1_STRING_length(asn_str);
+	cstr = t_strndup(ASN1_STRING_data(asn_str), len);
+	if (strlen(cstr) != len) {
+		/* NULs in the name - could be some MITM attack.
+		   never allow. */
+		return "";
+	}
+	return cstr;
+}
+
+static const char *get_general_dns_name(const GENERAL_NAME *name)
+{
+	if (ASN1_STRING_type(name->d.ia5) != V_ASN1_IA5STRING)
+		return "";
+
+	return asn1_string_to_c(name->d.ia5);
+}
+
+static const char *get_cname(X509 *cert)
+{
+	X509_NAME *name;
+	X509_NAME_ENTRY *entry;
+	ASN1_STRING *str;
+	int cn_idx;
+
+	name = X509_get_subject_name(cert);
+	if (name == NULL)
+		return "";
+	cn_idx = X509_NAME_get_index_by_NID(name, NID_commonName, -1);
+	if (cn_idx == -1)
+		return "";
+	entry = X509_NAME_get_entry(name, cn_idx);
+	i_assert(entry != NULL);
+	str = X509_NAME_ENTRY_get_data(entry);
+	i_assert(str != NULL);
+	return asn1_string_to_c(str);
+}
+
+static int openssl_cert_match_name(SSL *ssl, const char *verify_name)
+{
+	X509 *cert;
+	STACK_OF(GENERAL_NAME) *gnames;
+	const GENERAL_NAME *gn;
+	const char *dnsname;
+	bool dns_names = FALSE;
+	unsigned int i, count;
+
+	cert = SSL_get_peer_certificate(ssl);
+	i_assert(cert != NULL);
+
+	/* verify against SubjectAltNames */
+	gnames = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+	count = gnames == NULL ? 0 : sk_GENERAL_NAME_num(gnames);
+	for (i = 0; i < count; i++) {
+		gn = sk_GENERAL_NAME_value(gnames, i);
+		if (gn->type == GEN_DNS) {
+			dns_names = TRUE;
+			dnsname = get_general_dns_name(gn);
+			if (strcmp(dnsname, verify_name) == 0)
+				break;
+		}
+	}
+	sk_GENERAL_NAME_pop_free(gnames, GENERAL_NAME_free);
+	/* verify against CommonName only when there wasn't any DNS
+	   SubjectAltNames */
+	if (dns_names)
+		return i < count ? 0 : -1;
+
+	return strcmp(get_cname(cert), verify_name) == 0 ? 0 : -1;
+}
+
+int ssl_proxy_cert_match_name(struct ssl_proxy *proxy, const char *verify_name)
+{
+	return openssl_cert_match_name(proxy->ssl, verify_name);
+}
+
 const char *ssl_proxy_get_peer_name(struct ssl_proxy *proxy)
 {
 	X509 *x509;
diff -r e4aa32586f17 -r 5e9eaf63a6b1 src/login-common/ssl-proxy.c
--- a/src/login-common/ssl-proxy.c	Wed Nov 16 22:59:36 2011 +0200
+++ b/src/login-common/ssl-proxy.c	Wed Nov 16 23:31:46 2011 +0200
@@ -46,6 +46,12 @@
 	return FALSE;
 }
 
+int ssl_proxy_cert_match_name(struct ssl_proxy *proxy ATTR_UNUSED,
+			      const char *verify_name ATTR_UNUSED)
+{
+	return -1;
+}
+
 const char *ssl_proxy_get_peer_name(struct ssl_proxy *proxy ATTR_UNUSED)
 {
 	return NULL;
diff -r e4aa32586f17 -r 5e9eaf63a6b1 src/login-common/ssl-proxy.h
--- a/src/login-common/ssl-proxy.h	Wed Nov 16 22:59:36 2011 +0200
+++ b/src/login-common/ssl-proxy.h	Wed Nov 16 23:31:46 2011 +0200
@@ -24,6 +24,7 @@
 void ssl_proxy_set_client(struct ssl_proxy *proxy, struct client *client);
 bool ssl_proxy_has_valid_client_cert(const struct ssl_proxy *proxy) ATTR_PURE;
 bool ssl_proxy_has_broken_client_cert(struct ssl_proxy *proxy);
+int ssl_proxy_cert_match_name(struct ssl_proxy *proxy, const char *verify_name);
 const char *ssl_proxy_get_peer_name(struct ssl_proxy *proxy);
 bool ssl_proxy_is_handshaked(const struct ssl_proxy *proxy) ATTR_PURE;
 const char *ssl_proxy_get_last_error(const struct ssl_proxy *proxy) ATTR_PURE;


More information about the dovecot-cvs mailing list