From 886a95fb74930421db7c72de68c7e4de744f9025 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Thu, 31 May 2012 15:07:31 +0100 Subject: [PATCH] Import pkcs12_parse() function from GnuTLS to fix PKCS#12 handling An immediate effect is that this fixes the checking of cert expiry for PKCS#12 certificates. But it also means we can include the full supporting chain of intermediate CAs (which has to be pre-assembled before we ever call gnutls_certificate_set_x509_key() and can't be appended later), and we can use the extra certs from the PKCS#12 file too, which parse_pkcs12() currently doesn't bother to give us. The plan is to fix parse_pkcs12(), submit the changes back upstream and make it an exported function there, then stick a version-conditional on our local copy and look forward to the day when we can rip it out again. Signed-off-by: David Woodhouse --- gnutls.c | 49 +++++--- gnutls_pkcs12.c | 341 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 374 insertions(+), 16 deletions(-) create mode 100644 gnutls_pkcs12.c diff --git a/gnutls.c b/gnutls.c index 3e6bec5..2c5891b 100644 --- a/gnutls.c +++ b/gnutls.c @@ -310,12 +310,18 @@ static int load_datum(struct openconnect_info *vpninfo, return 0; } +/* Pull in our local copy of GnuTLS's parse_pkcs12() function, for now */ +#include "gnutls_pkcs12.c" + /* A non-zero, non-error return to make load_certificate() continue and interpreting the file as other types */ #define NOT_PKCS12 1 static int load_pkcs12_certificate(struct openconnect_info *vpninfo, - gnutls_datum_t *datum) + gnutls_datum_t *datum, + gnutls_x509_privkey_t *key, + gnutls_x509_crt_t *cert, + gnutls_x509_crl_t * crl) { gnutls_pkcs12_t p12; char *pass; @@ -377,28 +383,23 @@ static int load_pkcs12_certificate(struct openconnect_info *vpninfo, return ret; } - /* We can't actually *use* this gnutls_pkcs12_t, AFAICT. - We have to let GnuTLS re-import it all again. */ + err = parse_pkcs12(vpninfo->https_cred, p12, pass, key, cert, crl); gnutls_pkcs12_deinit(p12); - - err = gnutls_certificate_set_x509_simple_pkcs12_mem(vpninfo->https_cred, datum, - GNUTLS_X509_FMT_DER, pass); - if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to load PKCS#12 certificate: %s\n"), gnutls_strerror(err)); return -EINVAL; } - /* FIXME: We haven't checked the certificate expiry */ return 0; } static int load_certificate(struct openconnect_info *vpninfo) { gnutls_datum_t fdata; - gnutls_x509_crt_t cert; - gnutls_x509_privkey_t key; + gnutls_x509_privkey_t key = NULL; + gnutls_x509_crt_t cert = NULL; + gnutls_x509_crl_t crl = NULL; int err; if (vpninfo->cert_type == CERT_TYPE_TPM) { @@ -433,13 +434,15 @@ static int load_certificate(struct openconnect_info *vpninfo) if (vpninfo->cert_type == CERT_TYPE_PKCS12 || vpninfo->cert_type == CERT_TYPE_UNKNOWN) { - err = load_pkcs12_certificate(vpninfo, &fdata); - /* Either it's printed and error and failed, or it's succeeded */ - if (err <= 0) { + err = load_pkcs12_certificate(vpninfo, &fdata, &key, &cert, &crl); + if (!err) + goto got_cert; + else if (err <= 0) { gnutls_free(fdata.data); return err; } - /* ... or it falls through to try PEM formats */ + /* It returned NOT_PKCS12. + Fall through to try PEM formats. */ } gnutls_x509_crt_init(&cert); @@ -506,14 +509,28 @@ static int load_certificate(struct openconnect_info *vpninfo) } } } + got_cert: + gnutls_free(fdata.data); + check_certificate_expiry(vpninfo, cert); + + if (crl) { + err = gnutls_certificate_set_x509_crl(vpninfo->https_cred, &crl, 1); + gnutls_x509_crl_deinit(crl); + if (err) { + vpn_progress(vpninfo, PRG_ERR, + _("Setting certificate recovation list failed: %s\n"), + gnutls_strerror(err)); + gnutls_x509_privkey_deinit(key); + gnutls_x509_crt_deinit(cert); + return -EIO; + } + } /* FIXME: We need to work around OpenSSL RT#1942 on the server, by including as much of the chain of issuer certificates as we can. */ err = gnutls_certificate_set_x509_key(vpninfo->https_cred, &cert, 1, key); gnutls_x509_privkey_deinit(key); - check_certificate_expiry(vpninfo, cert); gnutls_x509_crt_deinit(cert); - gnutls_free(fdata.data); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Setting certificate failed: %s\n"), diff --git a/gnutls_pkcs12.c b/gnutls_pkcs12.c new file mode 100644 index 0000000..623f47c --- /dev/null +++ b/gnutls_pkcs12.c @@ -0,0 +1,341 @@ +/* + * Ick. This is (or at least started off as) a straight copy of + * parse_pkcs12() from GnuTLS lib/gnutls_x509.c, as of commit ID + * 77670476814c078bbad56ce8772b192a3b5736b6 on the gnutls_2_12_x + * branch. + * + * We need to *see* the cert so that we can check its expiry, and + * we'll also want to get all the other certs in the PKCS#12 file + * rather than only the leaf node. Hopefully these changes can be + * merged back into GnuTLS as soon as possible, it can be made a + * public function, and this copy can die. + */ +#define opaque unsigned char +#define gnutls_assert() do {} while(0) + +/* + * Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Free Software Foundation, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file WAS part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA + * + */ + + +static int +parse_pkcs12 (gnutls_certificate_credentials_t res, + gnutls_pkcs12_t p12, + const char *password, + gnutls_x509_privkey_t * key, + gnutls_x509_crt_t * cert, gnutls_x509_crl_t * crl) +{ + gnutls_pkcs12_bag_t bag = NULL; + int idx = 0; + int ret; + size_t cert_id_size = 0; + size_t key_id_size = 0; + opaque cert_id[20]; + opaque key_id[20]; + int privkey_ok = 0; + + *cert = NULL; + *key = NULL; + *crl = NULL; + + /* find the first private key */ + for (;;) + { + int elements_in_bag; + int i; + + ret = gnutls_pkcs12_bag_init (&bag); + if (ret < 0) + { + bag = NULL; + gnutls_assert (); + goto done; + } + + ret = gnutls_pkcs12_get_bag (p12, idx, bag); + if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + break; + if (ret < 0) + { + gnutls_assert (); + goto done; + } + + ret = gnutls_pkcs12_bag_get_type (bag, 0); + if (ret < 0) + { + gnutls_assert (); + goto done; + } + + if (ret == GNUTLS_BAG_ENCRYPTED) + { + ret = gnutls_pkcs12_bag_decrypt (bag, password); + if (ret < 0) + { + gnutls_assert (); + goto done; + } + } + + elements_in_bag = gnutls_pkcs12_bag_get_count (bag); + if (elements_in_bag < 0) + { + gnutls_assert (); + goto done; + } + + for (i = 0; i < elements_in_bag; i++) + { + int type; + gnutls_datum_t data; + + type = gnutls_pkcs12_bag_get_type (bag, i); + if (type < 0) + { + gnutls_assert (); + goto done; + } + + ret = gnutls_pkcs12_bag_get_data (bag, i, &data); + if (ret < 0) + { + gnutls_assert (); + goto done; + } + + switch (type) + { + case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY: + case GNUTLS_BAG_PKCS8_KEY: + if (*key != NULL) /* too simple to continue */ + { + gnutls_assert (); + break; + } + + ret = gnutls_x509_privkey_init (key); + if (ret < 0) + { + gnutls_assert (); + goto done; + } + + ret = gnutls_x509_privkey_import_pkcs8 + (*key, &data, GNUTLS_X509_FMT_DER, password, + type == GNUTLS_BAG_PKCS8_KEY ? GNUTLS_PKCS_PLAIN : 0); + if (ret < 0) + { + gnutls_assert (); + gnutls_x509_privkey_deinit (*key); + goto done; + } + + key_id_size = sizeof (key_id); + ret = + gnutls_x509_privkey_get_key_id (*key, 0, key_id, + &key_id_size); + if (ret < 0) + { + gnutls_assert (); + gnutls_x509_privkey_deinit (*key); + goto done; + } + + privkey_ok = 1; /* break */ + break; + default: + break; + } + } + + idx++; + gnutls_pkcs12_bag_deinit (bag); + + if (privkey_ok != 0) /* private key was found */ + break; + } + + if (privkey_ok == 0) /* no private key */ + { + gnutls_assert (); + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + } + + /* now find the corresponding certificate + */ + idx = 0; + bag = NULL; + for (;;) + { + int elements_in_bag; + int i; + + ret = gnutls_pkcs12_bag_init (&bag); + if (ret < 0) + { + bag = NULL; + gnutls_assert (); + goto done; + } + + ret = gnutls_pkcs12_get_bag (p12, idx, bag); + if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + break; + if (ret < 0) + { + gnutls_assert (); + goto done; + } + + ret = gnutls_pkcs12_bag_get_type (bag, 0); + if (ret < 0) + { + gnutls_assert (); + goto done; + } + + if (ret == GNUTLS_BAG_ENCRYPTED) + { + ret = gnutls_pkcs12_bag_decrypt (bag, password); + if (ret < 0) + { + gnutls_assert (); + goto done; + } + } + + elements_in_bag = gnutls_pkcs12_bag_get_count (bag); + if (elements_in_bag < 0) + { + gnutls_assert (); + goto done; + } + + for (i = 0; i < elements_in_bag; i++) + { + int type; + gnutls_datum_t data; + + type = gnutls_pkcs12_bag_get_type (bag, i); + if (type < 0) + { + gnutls_assert (); + goto done; + } + + ret = gnutls_pkcs12_bag_get_data (bag, i, &data); + if (ret < 0) + { + gnutls_assert (); + goto done; + } + + switch (type) + { + case GNUTLS_BAG_CERTIFICATE: + if (*cert != NULL) /* no need to set it again */ + { + gnutls_assert (); + break; + } + + ret = gnutls_x509_crt_init (cert); + if (ret < 0) + { + gnutls_assert (); + goto done; + } + + ret = + gnutls_x509_crt_import (*cert, &data, GNUTLS_X509_FMT_DER); + if (ret < 0) + { + gnutls_assert (); + gnutls_x509_crt_deinit (*cert); + goto done; + } + + /* check if the key id match */ + cert_id_size = sizeof (cert_id); + ret = + gnutls_x509_crt_get_key_id (*cert, 0, cert_id, &cert_id_size); + if (ret < 0) + { + gnutls_assert (); + gnutls_x509_crt_deinit (*cert); + goto done; + } + + if (memcmp (cert_id, key_id, cert_id_size) != 0) + { /* they don't match - skip the certificate */ + gnutls_x509_crt_deinit (*cert); + *cert = NULL; + } + break; + + case GNUTLS_BAG_CRL: + if (*crl != NULL) + { + gnutls_assert (); + break; + } + + ret = gnutls_x509_crl_init (crl); + if (ret < 0) + { + gnutls_assert (); + goto done; + } + + ret = gnutls_x509_crl_import (*crl, &data, GNUTLS_X509_FMT_DER); + if (ret < 0) + { + gnutls_assert (); + gnutls_x509_crl_deinit (*crl); + goto done; + } + break; + + case GNUTLS_BAG_ENCRYPTED: + /* XXX Bother to recurse one level down? Unlikely to + use the same password anyway. */ + case GNUTLS_BAG_EMPTY: + default: + break; + } + } + + idx++; + gnutls_pkcs12_bag_deinit (bag); + } + + ret = 0; + +done: + if (bag) + gnutls_pkcs12_bag_deinit (bag); + + return ret; +} -- 2.7.4