[PATCH 6/6] [RFC] library: Add openconnect_get_peer_cert_chain()

Kevin Cernekee cernekee at gmail.com
Thu Apr 14 23:06:05 PDT 2016


Allow external validation of the entire certificate chain, not just the
peer_cert.  Tested using a letsencrypt cert on Chrome OS.

Signed-off-by: Kevin Cernekee <cernekee at gmail.com>
---
 libopenconnect.map.in  |  2 ++
 openconnect-internal.h |  1 +
 openconnect.h          | 16 +++++++++++++++
 openssl.c              | 56 +++++++++++++++++++++++++++++++++++++++++++++++---
 4 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/libopenconnect.map.in b/libopenconnect.map.in
index 1e33389..8c48283 100644
--- a/libopenconnect.map.in
+++ b/libopenconnect.map.in
@@ -3,6 +3,7 @@ OPENCONNECT_5.0 {
 	openconnect_check_peer_cert_hash;
 	openconnect_clear_cookie;
 	openconnect_free_cert_info;
+	openconnect_free_peer_cert_chain;
 	openconnect_get_cookie;
 	openconnect_get_cstp_cipher;
 	openconnect_get_dnsname;
@@ -11,6 +12,7 @@ OPENCONNECT_5.0 {
 	openconnect_get_ifname;
 	openconnect_get_ip_info;
 	openconnect_get_peer_cert_DER;
+	openconnect_get_peer_cert_chain;
 	openconnect_get_peer_cert_details;
 	openconnect_get_peer_cert_hash;
 	openconnect_get_port;
diff --git a/openconnect-internal.h b/openconnect-internal.h
index b339ef6..27f9df8 100644
--- a/openconnect-internal.h
+++ b/openconnect-internal.h
@@ -443,6 +443,7 @@ struct openconnect_info {
 
 	void *peer_cert;
 	char *peer_cert_hash;
+	void *cert_chain_handle;
 
 	char *cookie; /* Pointer to within cookies list */
 	struct oc_vpn_option *cookies;
diff --git a/openconnect.h b/openconnect.h
index f7d4fe2..bcbcb74 100644
--- a/openconnect.h
+++ b/openconnect.h
@@ -273,6 +273,12 @@ struct oc_stats {
 	uint64_t rx_bytes;
 };
 
+struct oc_cert {
+	int der_len;
+	unsigned char *der_data;
+	void *reserved;
+};
+
 /****************************************************************************/
 
 #define PRG_ERR		0
@@ -367,6 +373,16 @@ int openconnect_get_peer_cert_DER(struct openconnect_info *vpninfo,
 				  unsigned char **buf);
 void openconnect_free_cert_info(struct openconnect_info *vpninfo,
 				void *buf);
+
+/* Creates a linked list of all certs in the peer's chain, returning the
+   number of certs in the chain (or <0 on error). Only valid inside the
+   validate_peer_cert callback. */
+int openconnect_get_peer_cert_chain(struct openconnect_info *vpninfo,
+				    struct oc_cert **chain);
+void openconnect_free_peer_cert_chain(struct openconnect_info *vpninfo,
+				      int ncerts,
+				      struct oc_cert *chain);
+
 /* Contains a comma-separated list of authentication methods to enabled.
    Currently supported: Negotiate,NTLM,Digest,Basic */
 int openconnect_set_http_auth(struct openconnect_info *vpninfo,
diff --git a/openssl.c b/openssl.c
index 007809b..05cc973 100644
--- a/openssl.c
+++ b/openssl.c
@@ -1325,6 +1325,49 @@ static void workaround_openssl_certchain_bug(struct openconnect_info *vpninfo,
 	X509_STORE_CTX_cleanup(&ctx);
 }
 
+int openconnect_get_peer_cert_chain(struct openconnect_info *vpninfo,
+				    struct oc_cert **chainp)
+{
+	struct oc_cert *chain, *p;
+	X509_STORE_CTX *ctx = vpninfo->cert_chain_handle;
+	int i, ncerts;
+
+	if (!ctx)
+		return -EINVAL;
+
+	ncerts = sk_X509_num(ctx->untrusted);
+	if (!ncerts)
+		return -EIO;
+
+	p = chain = calloc(ncerts, sizeof(struct oc_cert));
+	if (!chain)
+		return -ENOMEM;
+
+	for (i = 0; i < ncerts; i++, p++) {
+		X509 *cert = sk_X509_value(ctx->untrusted, i);
+
+		p->der_len = i2d_X509(cert, &p->der_data);
+		if (p->der_len < 0) {
+			openconnect_free_peer_cert_chain(vpninfo, ncerts, chain);
+			return -ENOMEM;
+		}
+	}
+
+	*chainp = chain;
+	return ncerts;
+}
+
+void openconnect_free_peer_cert_chain(struct openconnect_info *vpninfo,
+				      int ncerts,
+				      struct oc_cert *chain)
+{
+	int i;
+
+	for (i = 0; i < ncerts; i++)
+		OPENSSL_free(chain[i].der_data);
+	free(chain);
+}
+
 static int ssl_app_verify_callback(X509_STORE_CTX *ctx, void *arg)
 {
 	struct openconnect_info *vpninfo = arg;
@@ -1375,9 +1418,16 @@ static int ssl_app_verify_callback(X509_STORE_CTX *ctx, void *arg)
 		     _("Server certificate verify failed: %s\n"),
 		     err_string);
 
-	if (vpninfo->validate_peer_cert &&
-	    !vpninfo->validate_peer_cert(vpninfo->cbdata, err_string))
-		return 1;
+	if (vpninfo->validate_peer_cert) {
+		int ret;
+
+		vpninfo->cert_chain_handle = ctx;
+		ret = vpninfo->validate_peer_cert(vpninfo->cbdata, err_string);
+		vpninfo->cert_chain_handle = NULL;
+
+		if (!ret)
+			return 1;
+	}
 
 	return 0;
 }
-- 
1.9.1




More information about the openconnect-devel mailing list