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