From 77a91693fd1ea23c4854bf62c0b20d44cfc56eee Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Wed, 17 Dec 2014 14:31:43 +0000 Subject: [PATCH] Add PKCS#11 support for OpenSSL using libp11 Not that I'm overly worried about feature parity, but it was an interesting exercise in working out how OpenSSL applications can Do The Right Thing with PKCS#11. Perhaps I can turn openssl-pkcs11.c into a generic library function to load EVP_PKEY/X509 from PKCS#11 URIs. Signed-off-by: David Woodhouse --- Makefile.am | 6 +- configure.ac | 7 + library.c | 13 + openconnect-internal.h | 16 ++ openssl-pkcs11.c | 602 +++++++++++++++++++++++++++++++++++++++++ openssl.c | 14 +- www/building.xml | 2 + www/changelog.xml | 2 +- www/features.xml | 2 +- 9 files changed, 654 insertions(+), 10 deletions(-) create mode 100644 openssl-pkcs11.c diff --git a/Makefile.am b/Makefile.am index 0a387859..0f78a751 100644 --- a/Makefile.am +++ b/Makefile.am @@ -21,7 +21,7 @@ openconnect_LDADD = libopenconnect.la $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(INTL_LI library_srcs = ssl.c http.c auth.c library.c compat.c dtls.c cstp.c \ mainloop.c script.c ntlm.c digest.c lib_srcs_gnutls = gnutls.c gnutls_pkcs12.c gnutls_tpm.c -lib_srcs_openssl = openssl.c +lib_srcs_openssl = openssl.c openssl-pkcs11.c lib_srcs_win32 = tun-win32.c sspi.c lib_srcs_posix = tun.c lib_srcs_gssapi = gssapi.c @@ -62,8 +62,8 @@ library_srcs += $(lib_srcs_posix) endif libopenconnect_la_SOURCES = version.c $(library_srcs) -libopenconnect_la_CFLAGS = $(AM_CFLAGS) $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(P11KIT_CFLAGS) $(TSS_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBOATH_CFLAGS) $(LIBPSKC_CFLAGS) $(GSSAPI_CFLAGS) $(INTL_CFLAGS) $(ICONV_CFLAGS) $(LIBPCSCLITE_CFLAGS) -libopenconnect_la_LIBADD = $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(P11KIT_LIBS) $(TSS_LIBS) $(LIBSTOKEN_LIBS) $(LIBOATH_LIBS) $(LIBPSKC_LIBS) $(GSSAPI_LIBS) $(INTL_LIBS) $(ICONV_LIBS) $(LIBPCSCLITE_LIBS) +libopenconnect_la_CFLAGS = $(AM_CFLAGS) $(SSL_CFLAGS) $(DTLS_SSL_CFLAGS) $(LIBXML2_CFLAGS) $(LIBPROXY_CFLAGS) $(ZLIB_CFLAGS) $(P11KIT_CFLAGS) $(TSS_CFLAGS) $(LIBSTOKEN_CFLAGS) $(LIBOATH_CFLAGS) $(LIBPSKC_CFLAGS) $(GSSAPI_CFLAGS) $(INTL_CFLAGS) $(ICONV_CFLAGS) $(LIBPCSCLITE_CFLAGS) $(LIBP11_CFLAGS) +libopenconnect_la_LIBADD = $(SSL_LIBS) $(DTLS_SSL_LIBS) $(LIBXML2_LIBS) $(LIBPROXY_LIBS) $(ZLIB_LIBS) $(P11KIT_LIBS) $(TSS_LIBS) $(LIBSTOKEN_LIBS) $(LIBOATH_LIBS) $(LIBPSKC_LIBS) $(GSSAPI_LIBS) $(INTL_LIBS) $(ICONV_LIBS) $(LIBPCSCLITE_LIBS) $(LIBP11_LIBS) if OPENBSD_LIBTOOL # OpenBSD's libtool doesn't have -version-number, but its -version-info arg # does what GNU libtool's -version-number does. Which arguably is what the diff --git a/configure.ac b/configure.ac index e67ec356..c7201fbd 100644 --- a/configure.ac +++ b/configure.ac @@ -458,6 +458,13 @@ case "$ssl_library" in check_openssl_dtls=no ;; openssl) + PKG_CHECK_MODULES(P11KIT, p11-kit-1, + [PKG_CHECK_MODULES(LIBP11, libp11, + [AC_DEFINE(HAVE_LIBP11, 1, [Have libp11 and p11-kit for OpenSSL]) + AC_SUBST(P11KIT_PC, ["libp11 p11-kit-1"]) + proxy_module="`$PKG_CONFIG --variable=proxy_module p11-kit-1`" + AC_DEFINE_UNQUOTED([DEFAULT_PKCS11_MODULE], "${proxy_module}", [p11-kit proxy])], + [:])], [:]) AC_DEFINE(OPENCONNECT_OPENSSL, 1, [Using OpenSSL]) AC_DEFINE(DTLS_OPENSSL, 1, [Using OpenSSL for DTLS]) AC_SUBST(SSL_DTLS_PC, [openssl]) diff --git a/library.c b/library.c index 5ca14f23..e9c28ffc 100644 --- a/library.c +++ b/library.c @@ -276,6 +276,17 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo) } memset(vpninfo->yubikey_pwhash, 0, sizeof(vpninfo->yubikey_pwhash)); free(vpninfo->yubikey_objname); +#endif +#ifdef HAVE_LIBP11 + if (vpninfo->pkcs11_ctx) { + if (vpninfo->pkcs11_slot_list) + PKCS11_release_all_slots(vpninfo->pkcs11_ctx, + vpninfo->pkcs11_slot_list, + vpninfo->pkcs11_slot_count); + PKCS11_CTX_unload(vpninfo->pkcs11_ctx); + PKCS11_CTX_free(vpninfo->pkcs11_ctx); + } + free(vpninfo->pkcs11_cert_id); #endif /* These check strm->state so they are safe to call multiple times */ inflateEnd(&vpninfo->inflate_strm); @@ -519,6 +530,8 @@ int openconnect_has_pkcs11_support(void) { #if defined(OPENCONNECT_GNUTLS) && defined(HAVE_P11KIT) return 1; +#elif defined(OPENCONNECT_OPENSSL) && defined(HAVE_LIBP11) + return 1; #else return 0; #endif diff --git a/openconnect-internal.h b/openconnect-internal.h index ba68c51b..640671a9 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -89,6 +89,10 @@ #include #endif +#ifdef HAVE_LIBP11 +#include +#endif + #ifdef HAVE_LIBPCSCLITE #ifdef __APPLE__ #include @@ -310,6 +314,14 @@ struct openconnect_info { unsigned pfs; #if defined(OPENCONNECT_OPENSSL) +#ifdef HAVE_LIBP11 + PKCS11_CTX *pkcs11_ctx; + PKCS11_SLOT *pkcs11_slot_list; + unsigned int pkcs11_slot_count; + PKCS11_SLOT *pkcs11_cert_slot; + unsigned char *pkcs11_cert_id; + size_t pkcs11_cert_id_len; + #endif X509 *cert_x509; SSL_CTX *https_ctx; SSL *https_ssl; @@ -636,6 +648,10 @@ FILE *openconnect_fopen_utf8(struct openconnect_info *vpninfo, void openconnect_clear_cookies(struct openconnect_info *vpninfo); +/* openssl-pkcs11.c */ +int load_pkcs11_key(struct openconnect_info *vpninfo); +int load_pkcs11_certificate(struct openconnect_info *vpninfo); + /* {gnutls,openssl}.c */ int openconnect_open_https(struct openconnect_info *vpninfo); void openconnect_close_https(struct openconnect_info *vpninfo, int final); diff --git a/openssl-pkcs11.c b/openssl-pkcs11.c new file mode 100644 index 00000000..1cdb2f77 --- /dev/null +++ b/openssl-pkcs11.c @@ -0,0 +1,602 @@ +/* + * OpenConnect (SSL + DTLS) VPN client + * + * Copyright © 2008-2014 Intel Corporation. + * + * Author: David Woodhouse + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#include + +#include +#include +#include +#include + +#include "openconnect-internal.h" + +#include +#include + +#ifdef HAVE_LIBP11 /* And p11-kit */ + +static PKCS11_CTX *pkcs11_ctx(struct openconnect_info *vpninfo) +{ + PKCS11_CTX *ctx; + + if (!vpninfo->pkcs11_ctx) { + ERR_load_PKCS11_strings(); + + ctx = PKCS11_CTX_new(); + if (!ctx) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to establish libp11 PKCS#11 context:\n")); + openconnect_report_ssl_errors(vpninfo); + return NULL; + } + if (PKCS11_CTX_load(ctx, DEFAULT_PKCS11_MODULE) < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to load PKCS#11 provider module (p11-kit-proxy.so):\n")); + openconnect_report_ssl_errors(vpninfo); + PKCS11_CTX_free(ctx); + return NULL; + } + vpninfo->pkcs11_ctx = ctx; + } + + return vpninfo->pkcs11_ctx; +} + +static int parse_uri_attr(const char *attr, int attrlen, unsigned char **field, + size_t *field_len) +{ + size_t outlen = 0; + unsigned char *out; + int ret = 0; + + out = malloc(attrlen + 1); + if (!out) + return -ENOMEM; + + while (!ret && attrlen) { + if (*attr == '%') { + if (attrlen < 3) { + ret = -EINVAL; + } else { + out[outlen++] = unhex(attr+1); + + attrlen -= 3; + attr += 3; + } + + } else { + out[outlen++] = *(attr++); + attrlen--; + } + } + + if (ret) + free(out); + else { + if (field_len) + *field_len = outlen; + out[outlen] = 0; + *field = out; + } + + return ret; +} + + + +static int parse_pkcs11_uri(const char *uri, PKCS11_TOKEN **p_tok, + unsigned char **id, size_t *id_len, + char **label) +{ + PKCS11_TOKEN *tok; + char *newlabel = NULL; + const char *end, *p; + int ret = 0; + + tok = calloc(1, sizeof(*tok)); + if (!tok) { + fprintf(stderr, "Could not allocate memory for token info\n"); + return -ENOMEM; + } + + /* We are only ever invoked if the string starts with 'pkcs11:' */ + end = uri + 6; + while (!ret && end[0] && end[1]) { + p = end + 1; + end = strchr(p, ';'); + if (!end) + end = p + strlen(p); + + if (!strncmp(p, "model=", 6)) { + p += 6; + ret = parse_uri_attr(p, end - p, (void *)&tok->model, NULL); + } else if (!strncmp(p, "manufacturer=", 13)) { + p += 13; + ret = parse_uri_attr(p, end - p, (void *)&tok->manufacturer, NULL); + } else if (!strncmp(p, "token=", 6)) { + p += 6; + ret = parse_uri_attr(p, end - p, (void *)&tok->label, NULL); + } else if (!strncmp(p, "serial=", 7)) { + p += 7; + ret = parse_uri_attr(p, end - p, (void *)&tok->serialnr, NULL); + } else if (!strncmp(p, "object=", 7)) { + p += 7; + ret = parse_uri_attr(p, end - p, (void *)&newlabel, NULL); + } else if (!strncmp(p, "id=", 3)) { + p += 3; + ret = parse_uri_attr(p, end - p, (void *)id, id_len); + } else if (!strncmp(p, "type=", 5) || !strncmp(p, "object-type=", 12)) { + p = strchr(p, '=') + 1; + + if ((end - p == 4 && !strncmp(p, "cert", 4)) || + (end - p == 7 && !strncmp(p, "private", 7))) { + /* Actually, just ignore it */ + } else + ret = -EINVAL; + /* Ignore object type for now. */ + } else { + ret = -EINVAL; + } + } + + if (!ret) { + *label = newlabel; + *p_tok = tok; + } else { + free(tok); + tok = NULL; + free(newlabel); + } + + return ret; +} + +static int request_pin(struct openconnect_info *vpninfo, struct pin_cache *cache, int retrying) +{ + struct oc_auth_form f; + struct oc_form_opt o; + char message[1024]; + int ret; + + if (!vpninfo || !vpninfo->process_auth_form) + return -EINVAL; + + memset(&f, 0, sizeof(f)); + f.auth_id = (char *)"pkcs11_pin"; + f.opts = &o; + message[sizeof(message)-1] = 0; + snprintf(message, sizeof(message) - 1, _("PIN required for %s"), cache->token); + f.message = message; + if (retrying) + f.error = (char *)_("Wrong PIN"); + o.next = NULL; + o.type = OC_FORM_OPT_PASSWORD; + o.name = (char *)"pkcs11_pin"; + o.label = (char *)_("Enter PIN:"); + o._value = NULL; + + ret = process_auth_form(vpninfo, &f); + if (ret || !o._value) + return -EIO; + + cache->pin = o._value; + return 0; +} + +static int slot_login(struct openconnect_info *vpninfo, PKCS11_CTX *ctx, PKCS11_SLOT *slot) +{ + PKCS11_TOKEN *token = slot->token; + struct pin_cache *cache = vpninfo->pin_cache; + int ret, retrying = 0; + + retry: + ERR_clear_error(); + if (!token->secureLogin) { + if (!cache) { + for (cache = vpninfo->pin_cache; cache; cache = cache->next) + if (!strcmp(slot->description, cache->token)) + break; + } + if (!cache) { + cache = malloc(sizeof(*cache)); + if (!cache) + return -ENOMEM; + cache->pin = NULL; + cache->next = vpninfo->pin_cache; + cache->token = strdup(slot->description); + if (!cache->token) { + free(cache); + return -ENOMEM; + } + vpninfo->pin_cache = cache; + } + if (!cache->pin) { + ret = request_pin(vpninfo, cache, retrying); + if (ret) + return ret; + } + } + ret = PKCS11_login(slot, 0, cache ? cache->pin : NULL); + if (ret) { + unsigned long err = ERR_peek_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PKCS11 && + ERR_GET_FUNC(err) == PKCS11_F_PKCS11_LOGIN) + err = ERR_GET_REASON(err); + else + err = CKR_OK; /* Anything we don't explicitly match */ + + switch (ERR_GET_REASON(err)) { + case CKR_PIN_INCORRECT: + /* They'll be told about it in the next UI prompt */ + if (cache) { + free(cache->pin); + cache->pin = NULL; + } + retrying = 1; + goto retry; + case CKR_PIN_LOCKED: + vpn_progress(vpninfo, PRG_ERR, _("PIN locked\n")); + break; + case CKR_PIN_EXPIRED: + vpn_progress(vpninfo, PRG_ERR, _("PIN expired\n")); + break; + case CKR_USER_ANOTHER_ALREADY_LOGGED_IN: + vpn_progress(vpninfo, PRG_ERR, _("Another user already logged in\n")); + break; + default: + vpn_progress(vpninfo, PRG_ERR, + _("Unknown error logging in to PKCS#11 token\n")); + openconnect_report_ssl_errors(vpninfo); + } + ERR_clear_error(); + return -EPERM; + } + vpn_progress(vpninfo, PRG_TRACE, + _("Logged in to PKCS#11 slot '%s'\n"), + slot->description); + return 0; +} + +static PKCS11_CERT *slot_find_cert(struct openconnect_info *vpninfo, PKCS11_CTX *ctx, + PKCS11_SLOT *slot, const char *cert_label, + unsigned char *cert_id, size_t cert_id_len) +{ + PKCS11_CERT *cert_list = NULL, *cert = NULL; + unsigned int cert_count; + + if (PKCS11_enumerate_certs(slot->token, &cert_list, &cert_count) < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to enumerate certs in PKCS#11 slot '%s'\n"), + slot->description); + return NULL; + } + + vpn_progress(vpninfo, PRG_TRACE, + _("Found %d certs in slot '%s'\n"), + cert_count, slot->description); + + for (cert = cert_list; cert < &cert_list[cert_count]; cert++) { + + if (cert_label && strcmp(cert_label, cert->label)) + continue; + + if (cert_id && (cert_id_len != cert->id_len || + memcmp(cert_id, cert->id, cert_id_len))) + continue; + + return cert; + } + return NULL; +} + +int load_pkcs11_certificate(struct openconnect_info *vpninfo) +{ + PKCS11_CTX *ctx = pkcs11_ctx(vpninfo); + PKCS11_TOKEN *match_tok = NULL; + PKCS11_CERT *cert; + char *cert_label = NULL; + unsigned char *cert_id = NULL; + size_t cert_id_len = 0; + PKCS11_SLOT *slot_list = NULL, *slot, *login_slot = NULL; + unsigned int slot_count, matching_slots = 0; + int ret = 0; + + if (parse_pkcs11_uri(vpninfo->cert, &match_tok, &cert_id, + &cert_id_len, &cert_label) < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to parse PKCS#11 URI '%s'\n"), + vpninfo->cert); + return -EINVAL; + } + + if (PKCS11_enumerate_slots(ctx, &slot_list, &slot_count) < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to enumerate PKCS#11 slots\n")); + openconnect_report_ssl_errors(vpninfo); + ret = -EIO; + goto out; + } + for (slot = slot_list; slot < &slot_list[slot_count] && slot != login_slot; slot++) { + if (!slot->token) + continue; + if (match_tok->label && + strcmp(match_tok->label, slot->token->label)) + continue; + if (match_tok->manufacturer && + strcmp(match_tok->manufacturer, slot->token->manufacturer)) + continue; + if (match_tok->model && + strcmp(match_tok->model, slot->token->model)) + continue; + if (match_tok->serialnr && + strcmp(match_tok->serialnr, slot->token->serialnr)) + continue; + + + cert = slot_find_cert(vpninfo, ctx, slot, cert_label, cert_id, cert_id_len); + if (cert) + goto got_cert; + + login_slot = slot; + matching_slots++; + } + /* If there was precisely one matching slot, and we still didn't find the cert, + try logging in to it. */ + if (matching_slots == 1 && login_slot->token->loginRequired) { + slot = login_slot; + vpn_progress(vpninfo, PRG_INFO, + _("Logging in to PKCS#11 slot '%s'\n"), + slot->description); + if (!slot_login(vpninfo, ctx, slot)) { + cert = slot_find_cert(vpninfo, ctx, slot, cert_label, cert_id, cert_id_len); + if (cert) + goto got_cert; + } + } + ret = -EINVAL; + got_cert: + if (cert) { + /* This happens if the cert is too large for the fixed buffer + in libp11 :( */ + if (!cert->x509) { + vpn_progress(vpninfo, PRG_ERR, + _("Certificate X.509 content not fetched by libp11\n")); + ret = -EIO; + goto out; + } + + vpn_progress(vpninfo, PRG_DEBUG, + _("Using PKCS#11 certificate %s\n"), vpninfo->cert); + + vpninfo->cert_x509 = X509_dup(cert->x509); + if (!SSL_CTX_use_certificate(vpninfo->https_ctx, vpninfo->cert_x509)) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to install certificate in OpenSSL context\n")); + openconnect_report_ssl_errors(vpninfo); + ret = -EIO; + goto out; + } + /* If the key is in PKCS#11 too (which is likely), then keep the slot around. + We might want to know which slot the certificate was found in, so we can + log into it to find the key. */ + if (!strncmp(vpninfo->sslkey, "pkcs11:", 7)) { + vpninfo->pkcs11_slot_list = slot_list; + vpninfo->pkcs11_slot_count = slot_count; + vpninfo->pkcs11_cert_slot = slot; + slot_list = NULL; + } + /* Also remember the ID of the cert, in case it helps us find the matching key */ + vpninfo->pkcs11_cert_id = malloc(cert->id_len); + if (vpninfo->pkcs11_cert_id) { + vpninfo->pkcs11_cert_id_len = cert->id_len; + memcpy(vpninfo->pkcs11_cert_id, cert->id, cert->id_len); + } + } + out: + if (match_tok) { + free(match_tok->model); + free(match_tok->manufacturer); + free(match_tok->serialnr); + free(match_tok->label); + free(match_tok); + } + free(cert_id); + free(cert_label); + if (slot_list) + PKCS11_release_all_slots(ctx, slot_list, slot_count); + + return ret; +} + +static PKCS11_KEY *slot_find_key(struct openconnect_info *vpninfo, PKCS11_CTX *ctx, + PKCS11_SLOT *slot, const char *key_label, + unsigned char *key_id, size_t key_id_len) +{ + PKCS11_KEY *key_list = NULL, *key = NULL; + unsigned int key_count; + + if (PKCS11_enumerate_keys(slot->token, &key_list, &key_count) < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to enumerate keys in PKCS#11 slot '%s'\n"), + slot->description); + return NULL; + } + + vpn_progress(vpninfo, PRG_TRACE, + _("Found %d keys in slot '%s'\n"), + key_count, slot->description); + + for (key = key_list; key < &key_list[key_count]; key++) { + + if (key_label && strcmp(key_label, key->label)) + continue; + + if (key_id && (key_id_len != key->id_len || + memcmp(key_id, key->id, key_id_len))) + continue; + + return key; + } + return NULL; +} + +int load_pkcs11_key(struct openconnect_info *vpninfo) +{ + PKCS11_CTX *ctx = pkcs11_ctx(vpninfo); + PKCS11_TOKEN *match_tok = NULL; + PKCS11_KEY *key = NULL; + EVP_PKEY *pkey = NULL; + char *key_label = NULL; + unsigned char *key_id = NULL; + size_t key_id_len = 0; + PKCS11_SLOT *slot_list = NULL, *slot, *login_slot = NULL; + unsigned int slot_count, matching_slots = 0; + int ret = 0; + + if (parse_pkcs11_uri(vpninfo->sslkey, &match_tok, &key_id, + &key_id_len, &key_label) < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to parse PKCS#11 URI '%s'\n"), + vpninfo->sslkey); + return -EINVAL; + } + + if (vpninfo->pkcs11_slot_list) { + slot_list = vpninfo->pkcs11_slot_list; + slot_count = vpninfo->pkcs11_slot_count; + } else if (PKCS11_enumerate_slots(ctx, &slot_list, &slot_count) < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to enumerate PKCS#11 slots\n")); + openconnect_report_ssl_errors(vpninfo); + ret = -EIO; + goto out; + } + + for (slot = slot_list; slot < &slot_list[slot_count] && slot != login_slot; slot++) { + if (!slot->token) + continue; + if (match_tok->label && + strcmp(match_tok->label, slot->token->label)) + continue; + if (match_tok->manufacturer && + strcmp(match_tok->manufacturer, slot->token->manufacturer)) + continue; + if (match_tok->model && + strcmp(match_tok->model, slot->token->model)) + continue; + if (match_tok->serialnr && + strcmp(match_tok->serialnr, slot->token->serialnr)) + continue; + + key = slot_find_key(vpninfo, ctx, slot, key_label, key_id, key_id_len); + if (key) + goto got_key; + + login_slot = slot; + matching_slots++; + } + /* If there was precisely one matching slot, or if we know which slot + the cert was found in and the key wasn't separately specified, then + try that slot. */ + if (matching_slots != 1 && vpninfo->pkcs11_cert_slot && + vpninfo->sslkey == vpninfo->cert) { + /* Use the slot the cert was found in, if one specifier was given for both */ + matching_slots = 1; + login_slot = vpninfo->pkcs11_cert_slot; + vpninfo->pkcs11_cert_slot = NULL; + } + if (matching_slots == 1 && login_slot->token->loginRequired) { + slot = login_slot; + vpn_progress(vpninfo, PRG_INFO, + _("Logging in to PKCS#11 slot '%s'\n"), + slot->description); + if (!slot_login(vpninfo, ctx, slot)) { + key = slot_find_key(vpninfo, ctx, slot, key_label, key_id, key_id_len); + if (key) + goto got_key; + + /* We still haven't found it. If we weren't explicitly given a URI for + the key and we're inferring the location of the key from the cert, + then drop the label and try matching the CKA_ID of the cert. */ + if (vpninfo->cert == vpninfo->sslkey && vpninfo->pkcs11_cert_id && + (key_label || !key_id)) { + key = slot_find_key(vpninfo, ctx, slot, NULL, vpninfo->pkcs11_cert_id, + vpninfo->pkcs11_cert_id_len); + if (key) + goto got_key; + } + } + } + ret = -EINVAL; + + got_key: + if (key) { + vpn_progress(vpninfo, PRG_DEBUG, + _("Using PKCS#11 key %s\n"), vpninfo->cert); + + pkey = PKCS11_get_private_key(key); + if (!pkey) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to instantiated private key from PKCS#11\n")); + openconnect_report_ssl_errors(vpninfo); + ret = -EIO; + goto out; + } + + if (!SSL_CTX_use_PrivateKey(vpninfo->https_ctx, pkey)) { + vpn_progress(vpninfo, PRG_ERR, _("Add key from PKCS#11 failed\n")); + openconnect_report_ssl_errors(vpninfo); + ret = -EINVAL; + goto out; + } + + /* We have to keep the entire slot list around, because the EVP_PKEY + depends on the one we're using, and we have no way to free the + others. */ + vpninfo->pkcs11_slot_list = slot_list; + vpninfo->pkcs11_slot_count = slot_count; + slot_list = NULL; + } + out: + if (match_tok) { + free(match_tok->model); + free(match_tok->manufacturer); + free(match_tok->serialnr); + free(match_tok->label); + free(match_tok); + } + free(key_id); + free(key_label); + if (slot_list) + PKCS11_release_all_slots(ctx, slot_list, slot_count); + + return ret; +} +#else +int load_pkcs11_key(struct openconnect_info *vpninfo) +{ + vpn_progress(vpninfo, PRG_ERR, + _("This version of OpenConnect was built without PKCS#11 support\n")); + return -EINVAL; +} +int load_pkcs11_certificate(struct openconnect_info *vpninfo) +{ + vpn_progress(vpninfo, PRG_ERR, + _("This version of OpenConnect was built without PKCS#11 support\n")); + return -EINVAL; +} +#endif diff --git a/openssl.c b/openssl.c index d1f6cade..7678cf7c 100644 --- a/openssl.c +++ b/openssl.c @@ -734,11 +734,11 @@ static int load_certificate(struct openconnect_info *vpninfo) FILE *f; char buf[256]; - if (!strncmp(vpninfo->sslkey, "pkcs11:", 7) || - !strncmp(vpninfo->cert, "pkcs11:", 7)) { - vpn_progress(vpninfo, PRG_ERR, - _("This binary built without PKCS#11 support\n")); - return -EINVAL; + if (!strncmp(vpninfo->cert, "pkcs11:", 7)) { + int ret = load_pkcs11_certificate(vpninfo); + if (ret) + return ret; + goto got_cert; } vpn_progress(vpninfo, PRG_DEBUG, @@ -792,6 +792,8 @@ static int load_certificate(struct openconnect_info *vpninfo) if (ret) return ret; } + + got_cert: #ifdef ANDROID_KEYSTORE if (!strncmp(vpninfo->sslkey, "keystore:", 9)) { EVP_PKEY *key; @@ -819,6 +821,8 @@ static int load_certificate(struct openconnect_info *vpninfo) return 0; } #endif /* ANDROID_KEYSTORE */ + if (!strncmp(vpninfo->sslkey, "pkcs11:", 7)) + return load_pkcs11_key(vpninfo); f = openconnect_fopen_utf8(vpninfo, vpninfo->sslkey, "rb"); if (!f) { diff --git a/www/building.xml b/www/building.xml index e1856401..a08b97fc 100644 --- a/www/building.xml +++ b/www/building.xml @@ -28,6 +28,8 @@ libraries and tools installed:

And optionally also:
    +
  • p11-kit (for PKCS#11 support)
  • +
  • libp11 (also needed for PKCS#11 support if using OpenSSL)
  • libproxy
  • trousers (for TPM support if using GnuTLS)
  • libstoken (for SecurID software token support)
  • diff --git a/www/changelog.xml b/www/changelog.xml index 3c2865b3..116ebbcb 100644 --- a/www/changelog.xml +++ b/www/changelog.xml @@ -15,7 +15,7 @@
    • OpenConnect HEAD
        -
      • No changelog entries yet
      • +
      • Add PKCS#11 support for OpenSSL.

    • OpenConnect v7.01 diff --git a/www/features.xml b/www/features.xml index ff22b92f..3ea03710 100644 --- a/www/features.xml +++ b/www/features.xml @@ -14,7 +14,7 @@
    • Connection through SOCKS5 proxy.
    • Automatic detection of IPv4 and IPv6 address, routes.
    • Authentication via HTTP forms.
    • -
    • Authentication using SSL certificates — from local file, Trusted Platform Module and (when built with GnuTLS) PKCS#11 smartcards.
    • +
    • Authentication using SSL certificates — from local file, Trusted Platform Module and PKCS#11 smartcards.
    • Authentication using SecurID software tokens (when built with libstoken)
    • Authentication using OATH TOTP or HOTP software tokens (when built with liboath)
    • Authentication using Yubikey OATH tokens (when built with libpcsclite)