Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
  • Loading branch information
cernekee authored and David Woodhouse committed May 6, 2016
1 parent 2e89d13 commit 1da6649
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 5 deletions.
39 changes: 37 additions & 2 deletions gnutls.c
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand Down
2 changes: 2 additions & 0 deletions java/src/com/example/LibTest.java
Expand Up @@ -49,6 +49,8 @@ public int onValidatePeerCert(String msg) {

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();
Expand Down
1 change: 1 addition & 0 deletions java/src/org/infradead/libopenconnect/LibOpenConnect.java
Expand Up @@ -156,6 +156,7 @@ public synchronized native void setMobileInfo(String mobilePlatformVersion,
public synchronized native String getPeerCertHash();
public synchronized native String getPeerCertDetails();
public synchronized native byte[] getPeerCertDER();
public synchronized native byte[][] getPeerCertChain();

/* library info */

Expand Down
49 changes: 49 additions & 0 deletions jni.c
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions libopenconnect.map.in
Expand Up @@ -77,9 +77,11 @@ OPENCONNECT_5_2 {
OPENCONNECT_5_3 {
global:
openconnect_override_getaddrinfo;
openconnect_free_peer_cert_chain;
openconnect_get_cstp_compression;
openconnect_get_dnsname;
openconnect_get_dtls_compression;
openconnect_get_peer_cert_chain;
openconnect_disable_ipv6;
openconnect_set_localname;
openconnect_set_reconnected_handler;
Expand Down
2 changes: 2 additions & 0 deletions openconnect-internal.h
Expand Up @@ -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;
Expand Down
18 changes: 18 additions & 0 deletions openconnect.h
Expand Up @@ -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().
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
55 changes: 52 additions & 3 deletions openssl.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit 1da6649

Please sign in to comment.