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;
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) {
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);
}
}
}
+ 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"),
--- /dev/null
+/*
+ * 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;
+}