Commit 441df990b4e68459eb98c10445478d0fece30b83

Carlos Martín Nieto 2012-05-17T23:57:30

ssl: look up the last CN the alternative names don't match

diff --git a/src/netops.c b/src/netops.c
index 6341fb8..bd76d30 100644
--- a/src/netops.c
+++ b/src/netops.c
@@ -219,12 +219,15 @@ static int verify_server_cert(git_transport *t, const char *host)
 {
 	X509 *cert;
 	X509_NAME *peer_name;
-	char buf[1024];
+	ASN1_STRING *str;
+	unsigned char *peer_cn = NULL;
 	int matched = -1, type = GEN_DNS;
 	GENERAL_NAMES *alts;
 	struct in6_addr addr6;
 	struct in_addr addr4;
 	void *addr;
+	int i = -1,j;
+
 
 	/* Try to parse the host as an IP address to see if it is */
 	if (inet_pton(AF_INET, host, &addr4)) {
@@ -243,7 +246,7 @@ static int verify_server_cert(git_transport *t, const char *host)
 	/* Check the alternative names */
 	alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
 	if (alts) {
-		int num, i;
+		int num;
 
 		num = sk_GENERAL_NAME_num(alts);
 		for (i = 0; i < num && matched != 1; i++) {
@@ -257,7 +260,7 @@ static int verify_server_cert(git_transport *t, const char *host)
 
 			if (type == GEN_DNS) {
 				/* If it contains embedded NULs, don't even try */
-				if (namelen != strnlen(name, namelen))
+				if (memchr(name, '\0', namelen))
 					continue;
 
 				if (check_host_name(name, host) < 0)
@@ -272,22 +275,62 @@ static int verify_server_cert(git_transport *t, const char *host)
 	}
 	GENERAL_NAMES_free(alts);
 
-	if (matched == 0) {
-		giterr_set(GITERR_SSL, "Certificate host name check failed");
-		return -1;
-	}
+	if (matched == 0)
+		goto on_error;
+
 	if (matched == 1)
 		return 0;
 
 	/* If no alternative names are available, check the common name */
 	peer_name = X509_get_subject_name(cert);
-	X509_NAME_get_text_by_NID(peer_name, NID_commonName, buf, sizeof(buf));
-	if (strcasecmp(host, buf)) {
-		giterr_set(GITERR_NET, "CN %s doesn't match host %s\n", buf, host);
-		return -1;
+	if (peer_name == NULL)
+		goto on_error;
+
+	if (peer_name) {
+		/* Get the index of the last CN entry */
+		while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0)
+			i = j;
+	}
+
+	if (i < 0)
+		goto on_error;
+
+	str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i));
+	if (str == NULL)
+		goto on_error;
+
+	/* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */
+	if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) {
+		int size = ASN1_STRING_length(str);
+
+		if (size > 0) {
+			peer_cn = OPENSSL_malloc(size + 1);
+			GITERR_CHECK_ALLOC(peer_cn);
+			memcpy(peer_cn, ASN1_STRING_data(str), size);
+			peer_cn[size] = '\0';
+		}
+	} else {
+		int size = ASN1_STRING_to_UTF8(&peer_cn, str);
+		GITERR_CHECK_ALLOC(peer_cn);
+		if (memchr(peer_cn, '\0', size))
+			goto cert_fail;
 	}
 
+	if (check_host_name((char *)peer_cn, host) < 0)
+		goto cert_fail;
+
+	OPENSSL_free(peer_cn);
+
 	return 0;
+
+on_error:
+	OPENSSL_free(peer_cn);
+	return ssl_set_error(&t->ssl, 0);
+
+cert_fail:
+	OPENSSL_free(peer_cn);
+	giterr_set(GITERR_SSL, "Certificate host name check failed");
+	return -1;
 }
 
 static int ssl_setup(git_transport *t, const char *host)