From 6761587671aa4405e6d799b2dc29b8bbb25f4e0e Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Fri, 15 Jun 2012 14:03:03 +0100 Subject: [PATCH] Add Android keystore support Based on a patch from Vilmos Nebehaj Signed-off-by: Vilmos Nebehaj Signed-off-by: David Woodhouse --- gnutls.c | 31 +++++++++++ openconnect-internal.h | 23 ++++++++ openssl.c | 148 ++++++++++++++++++++++++++++++++++++++++--------- www/changelog.xml | 5 +- 4 files changed, 178 insertions(+), 29 deletions(-) diff --git a/gnutls.c b/gnutls.c index 04cd1de..2f3fe79 100644 --- a/gnutls.c +++ b/gnutls.c @@ -247,6 +247,37 @@ static int load_datum(struct openconnect_info *vpninfo, { struct stat st; int fd, err; +#ifdef ANDROID_KEYSTORE + if (!strncmp(fname, "keystore:", 9)) { + char content[KEYSTORE_MESSAGE_SIZE]; + int len; + const char *p = fname + 9; + + /* Skip first two slashes if the user has given it as + keystore://foo ... */ + if (*p == '/') + p++; + if (*p == '/') + p++; + len = keystore_get(p, strlen(p), content); + if (len < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to lead item '%s' from keystore\n"), + p); + return -EINVAL; + } + datum->data = gnutls_malloc(len + 1); + if (!datum->data) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to allocate memory for keystore item\n")); + return -ENOMEM; + } + datum->data[len] = 0; + memcpy(datum->data, content, len); + datum->size = len; + return 0; + } +#endif fd = open(fname, O_RDONLY|O_CLOEXEC); if (fd == -1) { diff --git a/openconnect-internal.h b/openconnect-internal.h index 664ac5e..0683ddd 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -394,4 +394,27 @@ int add_securid_pin(char *token, char *pin); /* version.c */ extern const char *openconnect_version_str; +#ifdef ANDROID_KEYSTORE +#include +#elif defined (FAKE_ANDROID_KEYSTORE) /* For testing */ +#define ANDROID_KEYSTORE +#define KEYSTORE_MESSAGE_SIZE 16384 +#include +#include +#include + +static inline int keystore_get(const char *p, int plen, char *content) +{ + int fd = open(p, O_RDONLY); + int len; + if (fd == -1) + return fd; + len = read(fd, content, KEYSTORE_MESSAGE_SIZE); + close(fd); + if (len <= 0) + return 0; + return len; +} +#endif /* FAKE_ANDROID_KEYSTORE */ + #endif /* __OPENCONNECT_INTERNAL_H__ */ diff --git a/openssl.c b/openssl.c index 47ed9cc..efd342c 100644 --- a/openssl.c +++ b/openssl.c @@ -600,6 +600,61 @@ static int reload_pem_cert(struct openconnect_info *vpninfo) return 0; } +#ifdef ANDROID_KEYSTORE +static BIO *BIO_from_keystore(struct openconnect_info *vpninfo, const char *item) +{ + char content[KEYSTORE_MESSAGE_SIZE]; + BIO *b; + int len; + const char *p = item + 9; + + /* Skip first two slashes if the user has given it as + keystore://foo ... */ + if (*p == '/') + p++; + if (*p == '/') + p++; + len = keystore_get(p, strlen(p), content); + if (len < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to lead item '%s' from keystore\n"), + p); + return NULL; + } + if (!(b = BIO_new(BIO_s_mem())) || BIO_write(b, content, len) != len) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to create BIO for keystore item '%s'\n"), + p); + BIO_free(b); + return NULL; + } + return b; +} +#endif + +static int is_pem_password_error(struct openconnect_info *vpninfo) +{ + unsigned long err = ERR_peek_error(); + + openconnect_report_ssl_errors(vpninfo); + +#ifndef EVP_F_EVP_DECRYPTFINAL_EX +#define EVP_F_EVP_DECRYPTFINAL_EX EVP_F_EVP_DECRYPTFINAL +#endif + /* If the user fat-fingered the passphrase, try again */ + if (ERR_GET_LIB(err) == ERR_LIB_EVP && + ERR_GET_FUNC(err) == EVP_F_EVP_DECRYPTFINAL_EX && + ERR_GET_REASON(err) == EVP_R_BAD_DECRYPT) { + vpn_progress(vpninfo, PRG_ERR, + _("Loading private key failed (wrong passphrase?)\n")); + return 1; + } + + vpn_progress(vpninfo, PRG_ERR, + _("Loading private key failed (see above errors)\n")); + return 0; +} + static int load_certificate(struct openconnect_info *vpninfo) { if (!strncmp(vpninfo->sslkey, "pkcs11:", 7) || @@ -612,8 +667,9 @@ static int load_certificate(struct openconnect_info *vpninfo) vpn_progress(vpninfo, PRG_TRACE, _("Using certificate file %s\n"), vpninfo->cert); - if (vpninfo->cert_type == CERT_TYPE_PKCS12 || - vpninfo->cert_type == CERT_TYPE_UNKNOWN) { + if (strncmp(vpninfo->cert, "keystore:", 9) && + (vpninfo->cert_type == CERT_TYPE_PKCS12 || + vpninfo->cert_type == CERT_TYPE_UNKNOWN)) { FILE *f; PKCS12 *p12; @@ -640,16 +696,70 @@ static int load_certificate(struct openconnect_info *vpninfo) } /* It's PEM or TPM now, and either way we need to load the plain cert: */ - if (!SSL_CTX_use_certificate_chain_file(vpninfo->https_ctx, - vpninfo->cert)) { - vpn_progress(vpninfo, PRG_ERR, - _("Loading certificate failed\n")); - openconnect_report_ssl_errors(vpninfo); - return -EINVAL; +#ifdef ANDROID_KEYSTORE + if (!strncmp(vpninfo->cert, "keystore:", 9)) { + BIO *b = BIO_from_keystore(vpninfo, vpninfo->cert); + if (!b) + return -EINVAL; + vpninfo->cert_x509 = PEM_read_bio_X509_AUX(b, NULL, pem_pw_cb, vpninfo); + BIO_free(b); + if (!vpninfo->cert_x509) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to load X509 certificate from keystore\n")); + openconnect_report_ssl_errors(vpninfo); + BIO_free(b); + return -EINVAL; + } + if (!SSL_CTX_use_certificate(vpninfo->https_ctx, vpninfo->cert_x509)) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to use X509 certificate from keystore\n")); + openconnect_report_ssl_errors(vpninfo); + X509_free(vpninfo->cert_x509); + vpninfo->cert_x509 = NULL; + return -EINVAL; + } + } else +#endif /* ANDROID_KEYSTORE */ + { + if (!SSL_CTX_use_certificate_chain_file(vpninfo->https_ctx, + vpninfo->cert)) { + vpn_progress(vpninfo, PRG_ERR, + _("Loading certificate failed\n")); + openconnect_report_ssl_errors(vpninfo); + return -EINVAL; + } + + /* Ew, we can't get it back from the OpenSSL CTX in any sane fashion */ + reload_pem_cert(vpninfo); } - /* Ew, we can't get it back from the OpenSSL CTX in any sane fashion */ - reload_pem_cert(vpninfo); +#ifdef ANDROID_KEYSTORE + if (!strncmp(vpninfo->sslkey, "keystore:", 9)) { + EVP_PKEY *key; + BIO *b; + + again_android: + b = BIO_from_keystore(vpninfo, vpninfo->sslkey); + if (!b) + return -EINVAL; + key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb, vpninfo); + BIO_free(b); + if (!key) { + if (is_pem_password_error(vpninfo)) + goto again_android; + return -EINVAL; + } + if (!SSL_CTX_use_PrivateKey(vpninfo->https_ctx, key)) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to use private key from keystore\n")); + EVP_PKEY_free(key); + X509_free(vpninfo->cert_x509); + vpninfo->cert_x509 = NULL; + return -EINVAL; + } + return 0; + } +#endif /* ANDROID */ if (vpninfo->cert_type == CERT_TYPE_UNKNOWN) { FILE *f = fopen(vpninfo->sslkey, "r"); @@ -692,24 +802,8 @@ static int load_certificate(struct openconnect_info *vpninfo) again: if (!SSL_CTX_use_RSAPrivateKey_file(vpninfo->https_ctx, vpninfo->sslkey, SSL_FILETYPE_PEM)) { - unsigned long err = ERR_peek_error(); - - openconnect_report_ssl_errors(vpninfo); - -#ifndef EVP_F_EVP_DECRYPTFINAL_EX -#define EVP_F_EVP_DECRYPTFINAL_EX EVP_F_EVP_DECRYPTFINAL -#endif - /* If the user fat-fingered the passphrase, try again */ - if (ERR_GET_LIB(err) == ERR_LIB_EVP && - ERR_GET_FUNC(err) == EVP_F_EVP_DECRYPTFINAL_EX && - ERR_GET_REASON(err) == EVP_R_BAD_DECRYPT) { - vpn_progress(vpninfo, PRG_ERR, - _("Loading private key failed (wrong passphrase?)\n")); + if (is_pem_password_error(vpninfo)) goto again; - } - - vpn_progress(vpninfo, PRG_ERR, - _("Loading private key failed (see above errors)\n")); return -EINVAL; } return 0; diff --git a/www/changelog.xml b/www/changelog.xml index c511b79..0a0d1e2 100644 --- a/www/changelog.xml +++ b/www/changelog.xml @@ -17,8 +17,9 @@
  • OpenConnect HEAD
      -
    • Support TPM, and also additional checks on PKCS#11 certs, even with GnuTLS 2.12
    • -
    • Fix library references to OpenSSL's ERR_print_errors_cb() when built against GnuTLS v2.12
    • +
    • Add keystore support for Android.
    • +
    • Support TPM, and also additional checks on PKCS#11 certs, even with GnuTLS 2.12.
    • +
    • Fix library references to OpenSSL's ERR_print_errors_cb() when built against GnuTLS v2.12.

  • OpenConnect v3.99 -- 2.7.4