blob: e3a4188f85bc772f1da9ee2b2e0cd51136254398 [file] [log] [blame]
From 28bea7d86e2f5f83ff02a2d92c349c8d3af6bb1c Mon Sep 17 00:00:00 2001
From: Kevin Cernekee <cernekee@gmail.com>
Date: Sun, 10 Apr 2016 23:13:08 -0700
Subject: [PATCH 7/8] library: Add openconnect_get_peer_cert_chain()
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@gmail.com>
---
gnutls.c | 39 ++++++++++++++-
java/src/com/example/LibTest.java | 2 +
.../infradead/libopenconnect/LibOpenConnect.java | 1 +
jni.c | 49 +++++++++++++++++++
libopenconnect.map.in | 2 +
openconnect-internal.h | 2 +
openconnect.h | 18 +++++++
openssl.c | 55 ++++++++++++++++++++--
8 files changed, 163 insertions(+), 5 deletions(-)
diff --git a/gnutls.c b/gnutls.c
index 2a93dac..338f7a7 100644
--- a/gnutls.c
+++ b/gnutls.c
@@ -1948,6 +1948,38 @@ void openconnect_free_cert_info(struct openconnect_info *vpninfo,
gnutls_free(buf);
}
+int openconnect_get_peer_cert_chain(struct openconnect_info *vpninfo,
+ struct oc_cert **chainp)
+{
+ struct oc_cert *chain, *p;
+ const gnutls_datum_t *cert_list = vpninfo->cert_list_handle;
+ int i, cert_list_size = vpninfo->cert_list_size;
+
+ if (!cert_list)
+ return -EINVAL;
+
+ if (cert_list_size <= 0)
+ return -EIO;
+
+ p = chain = calloc(cert_list_size, sizeof(struct oc_cert));
+ if (!chain)
+ return -ENOMEM;
+
+ for (i = 0; i < cert_list_size; i++, p++) {
+ p->der_data = (unsigned char *)cert_list[i].data;
+ p->der_len = cert_list[i].size;
+ }
+
+ *chainp = chain;
+ return cert_list_size;
+}
+
+void openconnect_free_peer_cert_chain(struct openconnect_info *vpninfo,
+ struct oc_cert *chain)
+{
+ free(chain);
+}
+
static int verify_peer(gnutls_session_t session)
{
struct openconnect_info *vpninfo = gnutls_session_get_ptr(session);
@@ -2079,10 +2111,13 @@ static int verify_peer(gnutls_session_t session)
vpn_progress(vpninfo, PRG_INFO,
_("Server certificate verify failed: %s\n"),
reason);
- if (vpninfo->validate_peer_cert)
+ if (vpninfo->validate_peer_cert) {
+ vpninfo->cert_list_handle = (void *)cert_list;
+ vpninfo->cert_list_size = cert_list_size;
err = vpninfo->validate_peer_cert(vpninfo->cbdata,
reason) ? GNUTLS_E_CERTIFICATE_ERROR : 0;
- else
+ vpninfo->cert_list_handle = NULL;
+ } else
err = GNUTLS_E_CERTIFICATE_ERROR;
}
diff --git a/java/src/com/example/LibTest.java b/java/src/com/example/LibTest.java
index da073b7..78e77b5 100644
--- a/java/src/com/example/LibTest.java
+++ b/java/src/com/example/LibTest.java
@@ -49,6 +49,8 @@ public final class LibTest {
byte der[] = getPeerCertDER();
System.out.println("DER is " + der.length + " bytes long");
+ byte chain[][] = getPeerCertChain();
+ System.out.println("Chain has " + chain.length + " certs");
System.out.print("\nAccept this certificate? [n] ");
String s = getline();
diff --git a/java/src/org/infradead/libopenconnect/LibOpenConnect.java b/java/src/org/infradead/libopenconnect/LibOpenConnect.java
index 3f70b2b..ce4ffcc 100644
--- a/java/src/org/infradead/libopenconnect/LibOpenConnect.java
+++ b/java/src/org/infradead/libopenconnect/LibOpenConnect.java
@@ -156,6 +156,7 @@ public abstract class LibOpenConnect {
public synchronized native String getPeerCertHash();
public synchronized native String getPeerCertDetails();
public synchronized native byte[] getPeerCertDER();
+ public synchronized native byte[][] getPeerCertChain();
/* library info */
diff --git a/jni.c b/jni.c
index bfcdaa5..d72ac2e 100644
--- a/jni.c
+++ b/jni.c
@@ -785,6 +785,55 @@ JNIEXPORT jbyteArray JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_ge
return jresult;
}
+/* special handling: callee-allocated, caller-freed binary buffer */
+JNIEXPORT jbyteArray JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_getPeerCertChain(
+ JNIEnv *jenv, jobject jobj)
+{
+ struct libctx *ctx = getctx(jenv, jobj);
+ struct oc_cert *chain = NULL, *p;
+ int cert_list_size, i;
+ jobjectArray jresult = NULL;
+ jclass jcls;
+
+ if (!ctx)
+ goto err;
+ cert_list_size = openconnect_get_peer_cert_chain(ctx->vpninfo, &chain);
+ if (cert_list_size <= 0)
+ goto err;
+
+ jcls = (*ctx->jenv)->FindClass(ctx->jenv, "[B");
+ if (!jcls)
+ goto err;
+
+ jresult = (*ctx->jenv)->NewObjectArray(ctx->jenv, cert_list_size, jcls, NULL);
+ if (!jresult)
+ goto err;
+
+ if ((*ctx->jenv)->PushLocalFrame(ctx->jenv, 256) < 0)
+ goto err;
+
+ for (i = 0, p = chain; i < cert_list_size; i++, p++) {
+ jbyteArray cert = (*ctx->jenv)->NewByteArray(ctx->jenv, p->der_len);
+ if (!cert)
+ goto err2;
+ (*ctx->jenv)->SetByteArrayRegion(ctx->jenv, cert, 0, p->der_len, (jbyte *)p->der_data);
+ (*ctx->jenv)->SetObjectArrayElement(ctx->jenv, jresult, i, cert);
+ }
+
+ (*ctx->jenv)->PopLocalFrame(ctx->jenv, NULL);
+ openconnect_free_peer_cert_chain(ctx->vpninfo, chain);
+ return jresult;
+
+err2:
+ (*ctx->jenv)->PopLocalFrame(ctx->jenv, NULL);
+err:
+ if (jresult)
+ (*ctx->jenv)->DeleteLocalRef(ctx->jenv, jresult);
+ if (chain)
+ openconnect_free_peer_cert_chain(ctx->vpninfo, chain);
+ return NULL;
+}
+
/* special handling: two string arguments */
JNIEXPORT void JNICALL Java_org_infradead_libopenconnect_LibOpenConnect_setClientCert(
JNIEnv *jenv, jobject jobj, jstring jcert, jstring jsslkey)
diff --git a/libopenconnect.map.in b/libopenconnect.map.in
index deaf058..f832dda 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..4ded761 100644
--- a/openconnect-internal.h
+++ b/openconnect-internal.h
@@ -443,6 +443,8 @@ struct openconnect_info {
void *peer_cert;
char *peer_cert_hash;
+ void *cert_list_handle;
+ int cert_list_size;
char *cookie; /* Pointer to within cookies list */
struct oc_vpn_option *cookies;
diff --git a/openconnect.h b/openconnect.h
index d34aae0..904a92a 100644
--- a/openconnect.h
+++ b/openconnect.h
@@ -46,6 +46,8 @@ extern "C" {
* - Add openconnect_set_setup_tun_handler().
* - Add openconnect_set_reconnected_handler().
* - Add openconnect_get_dnsname().
+ * - Add openconnect_get_peer_cert_chain() and
+ * openconnect_free_peer_cert_chain().
*
* API version 5.2 (v7.05; 2015-03-10):
* - Add openconnect_set_http_auth(), openconnect_set_protocol().
@@ -273,6 +275,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 +375,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 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. The caller should free the chain,
+ but should not modify the contents. */
+int openconnect_get_peer_cert_chain(struct openconnect_info *vpninfo,
+ struct oc_cert **chain);
+void openconnect_free_peer_cert_chain(struct openconnect_info *vpninfo,
+ 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..8a5ef56 100644
--- a/openssl.c
+++ b/openssl.c
@@ -1325,6 +1325,48 @@ 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_list_handle;
+ int i, cert_list_size;
+
+ if (!ctx)
+ return -EINVAL;
+
+ cert_list_size = sk_X509_num(ctx->untrusted);
+ if (!cert_list_size)
+ return -EIO;
+
+ p = chain = calloc(cert_list_size, sizeof(struct oc_cert));
+ if (!chain)
+ return -ENOMEM;
+
+ for (i = 0; i < cert_list_size; 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, chain);
+ return -ENOMEM;
+ }
+ }
+
+ *chainp = chain;
+ return cert_list_size;
+}
+
+void openconnect_free_peer_cert_chain(struct openconnect_info *vpninfo,
+ struct oc_cert *chain)
+{
+ int i;
+
+ for (i = 0; i < vpninfo->cert_list_size; 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 +1417,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_list_handle = ctx;
+ ret = vpninfo->validate_peer_cert(vpninfo->cbdata, err_string);
+ vpninfo->cert_list_handle = NULL;
+
+ if (!ret)
+ return 1;
+ }
return 0;
}
--
1.9.1