pidgin/ljfisher-ssl-client-auth

Parents 96ae7c4fe9ec
Children e638594f7bb6
Collect related crts and keys into a new PurpleCredential to better handle multiple keys in PKCS12 files.
--- a/libpurple/Makefile.am Mon Oct 08 17:59:34 2012 -0400
+++ b/libpurple/Makefile.am Mon Oct 08 18:02:10 2012 -0400
@@ -46,6 +46,7 @@
connection.c \
conversation.c \
core.c \
+ credential.c \
debug.c \
desktopitem.c \
eventloop.c \
@@ -114,6 +115,7 @@
connection.h \
conversation.h \
core.h \
+ credential.h \
dbus-maybe.h \
debug.h \
desktopitem.h \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/credential.c Mon Oct 08 18:02:10 2012 -0400
@@ -0,0 +1,43 @@
+/**
+ * @file credential.c CREDENTIAL API
+ * @ingroup core
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#include "credential.h"
+
+void purple_credential_destroy(PurpleCredential *cred)
+{
+ g_return_if_fail(NULL == cred);
+ purple_certificate_destroy_list(cred->crts);
+ purple_privatekey_destroy(cred->key);
+}
+
+void purple_credential_destroy_list(GList *creds)
+{
+ g_return_if_fail(NULL == creds);
+ g_list_foreach(creds, (GFunc)purple_credential_destroy, NULL);
+ g_list_free(creds);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/credential.h Mon Oct 08 18:02:10 2012 -0400
@@ -0,0 +1,63 @@
+/**
+ * @file credential.h CREDENTIAL API
+ * @ingroup core
+ * @see
+ * @since 3.0.0
+ *
+ * Purple credentials are basically a convienent means to carry around a
+ * key and its associated certificates in one object.
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *a
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
+ */
+
+#ifndef _PURPLE_CREDENTIAL_H
+#define _PURPLE_CREDENTIAL_H
+
+#include <glib.h>
+#include "privatekey.h"
+#include "certificate.h"
+
+typedef struct _PurpleCredential PurpleCredential;
+
+struct _PurpleCredential
+{
+ GList *crts; /* certificate chain starting with end point cert */
+ PurplePrivateKey *key; /* private key for end point cert */
+};
+
+/**
+ * Destroy a credential and all certificates and keys referenced by it.
+ *
+ * @param cred PurpleCredential to destroy.
+ */
+void purple_credential_destroy(PurpleCredential *cred);
+
+/**
+ * Destroy a list of credentials.
+ *
+ * @param List of credentials.
+ */
+void purple_credential_destroy_list(GList *creds);
+
+#endif /* _PURPLE_CREDENTIAL_H*/
--- a/libpurple/pkcs12.c Mon Oct 08 17:59:34 2012 -0400
+++ b/libpurple/pkcs12.c Mon Oct 08 18:02:10 2012 -0400
@@ -33,6 +33,7 @@
#include "request.h"
#include "signals.h"
#include "util.h"
+#include "credential.h"
/** List holding pointers to all registered private key schemes */
static GList *pkcs12_schemes = NULL;
@@ -40,15 +41,14 @@
gboolean
purple_pkcs12_import(PurplePkcs12Scheme *scheme,
const gchar *filename, const gchar *password,
- GList **crts, PurplePrivateKey **key)
+ GList **credentials)
{
g_return_val_if_fail(scheme, FALSE);
g_return_val_if_fail(filename, FALSE);
g_return_val_if_fail(password, FALSE);
- g_return_val_if_fail(crts, FALSE);
- g_return_val_if_fail(key, FALSE);
+ g_return_val_if_fail(credentials, FALSE);
- return (scheme->import_pkcs12)(filename, password, crts, key);
+ return (scheme->import_pkcs12)(filename, password, credentials);
}
gboolean
@@ -67,10 +67,12 @@
purple_pkcs12_import_to_pool(PurplePkcs12Scheme *scheme, const gchar *filename, const gchar *password,
PurpleCertificatePool *crtpool, PurplePrivateKeyPool *keypool)
{
- GList *crts;
+ GList *creds;
PurplePrivateKey *key;
GList *i;
gchar *id;
+ gboolean result = FALSE;
+ GList *creditem;
g_return_val_if_fail(scheme, FALSE);
g_return_val_if_fail(filename, FALSE);
@@ -79,33 +81,37 @@
g_return_val_if_fail(keypool, FALSE);
- if (!purple_pkcs12_import(scheme, filename, password, &crts, &key))
+ if (!purple_pkcs12_import(scheme, filename, password, &creds))
return FALSE;
- for (i = g_list_first(crts); NULL != g_list_next(i); i = g_list_next(i)) {
- PurpleCertificate *crt = (PurpleCertificate*)i->data;
+ for (creditem = g_list_first(creds);
+ NULL != creditem; creditem = g_list_next(creditem)) {
+ PurpleCredential *cred = (PurpleCredential*)creditem->data;
+
+ for (i = g_list_first(cred->crts); NULL != i; i = g_list_next(i)) {
+ PurpleCertificate *crt = (PurpleCertificate*)i->data;
- id = purple_certificate_get_unique_id(crt);
+ id = purple_certificate_get_unique_id(crt);
+ if (NULL == id)
+ goto done;
+
+ if (!purple_certificate_pool_store(crtpool, id, crt))
+ goto done;
+ }
+
+ id = purple_privatekey_get_unique_id(cred->key);
if (NULL == id)
- goto error;
+ goto done;
- if (!purple_certificate_pool_store(crtpool, id, crt))
- goto error;
+ if (!purple_privatekey_pool_store(keypool, id, key, password))
+ goto done;
}
- id = purple_privatekey_get_unique_id(key);
- if (NULL == id)
- goto error;
-
- if (!purple_privatekey_pool_store(keypool, id, key, password))
- goto error;
+ result = TRUE;
- return TRUE;
-
-error:
- purple_certificate_destroy_list(crts);
- purple_privatekey_destroy(key);
- return FALSE;
+done:
+ purple_credential_destroy_list(creds);
+ return result;
}
void
@@ -144,7 +150,7 @@
_("Cancel"), cancel_cb, /* cancel text and callback */
NULL, NULL, NULL, /* account, who, conv */
user_data); /* callback data */
- g_free(primary); /* TODO: not right */
+ g_free(primary); /* TODO: not right ?? */
}
/****************************************************************************/
--- a/libpurple/pkcs12.h Mon Oct 08 17:59:34 2012 -0400
+++ b/libpurple/pkcs12.h Mon Oct 08 18:02:10 2012 -0400
@@ -2,7 +2,7 @@
* @file pkcs12.h PKCS12 API
* @ingroup core
* @see
- * @since 2.2.0
+ * @since 3.0.0
*/
/*
@@ -71,16 +71,12 @@
*
* @param filename File path to import from
* @param password Password protecting the PKCS12 file
- * @param crts List of ptrs to PurpleCertificates from the PKCS12 file.
+ * @param credentials List of PurpleCredentials from the PKCS12 file.
* Must be free'd by caller.
- * @param key PurplePrivateKey from the PKCS12 file.
- * Must be free'd by caller.
- * We only support one private key per PKCS12 file since we
- * are otherwise unable to match the key with its certificate.
* @return TRUE if at least one certificate and key were imported, and FALSE on failure
*/
gboolean (*import_pkcs12)(const gchar *filename, const gchar *password,
- GList **crts, PurplePrivateKey **key);
+ GList **credentials);
/**
* Exports PurpleCertificates and PurplePrivateKey to a file
@@ -112,22 +108,20 @@
* @param scheme Scheme to import under
* @param filename File path to import from
* @param password Password protecting the PKCS12 file
- * @param crts Certificate chain from the PKCS12 file in the form of a list
+ * @param credentials List of PurpleCredentials. Each credentials contains:
+ * Certificate chain from the PKCS12 file in the form of a list
* of ptrs to PurpleCertificates. The chain must be in order.
* The first certificate must be the certificate corresponding to
* key. Each certificate should be followed by the issuer's
* certificate and end at the root CA certificate. The whole chain
* need not be present.
- * Must be free'd by caller.
- * @param key PurplePrivateKey from the PKCS12 file.
+ * The PurplePrivateKey from the PKCS12 file for the certificate chain.
* Must be free'd by caller.
- * We only support one private key per PKCS12 file since we are
- * otherwise unable to match up the key with its certificate.
* @return TRUE if at least one certificate and key were imported, and FALSE on failure
*/
gboolean
purple_pkcs12_import(PurplePkcs12Scheme *scheme, const gchar *filename, const gchar *password,
- GList **crts, PurplePrivateKey **key);
+ GList **credentials);
/**
* Exports PurpleCertificates and PurplePrivateKey to a file
--- a/libpurple/plugins/ssl/ssl-gnutls.c Mon Oct 08 17:59:34 2012 -0400
+++ b/libpurple/plugins/ssl/ssl-gnutls.c Mon Oct 08 18:02:10 2012 -0400
@@ -23,6 +23,7 @@
#include "debug.h"
#include "certificate.h"
#include "privatekey.h"
+#include "credential.h"
#include "pkcs12.h"
#include "plugin.h"
#include "sslconn.h"
@@ -715,6 +716,7 @@
x509_crtdata_addref(x509_crtdata_t *cd)
{
(cd->refcount)++;
+ purple_debug_info("gnutls", "crtdata_addref: cd = %p, refcount = %d\n", cd, cd->refcount);
return cd;
}
@@ -722,6 +724,7 @@
x509_crtdata_delref(x509_crtdata_t *cd)
{
(cd->refcount)--;
+ purple_debug_info("gnutls", "crtdata_delref: cd = %p, refcount = %d\n", cd, cd->refcount);
if (cd->refcount < 0)
g_critical("Refcount of x509_crtdata_t is %d, which is less "
@@ -729,6 +732,7 @@
/* If the refcount reaches zero, kill the structure */
if (cd->refcount <= 0) {
+ purple_debug_info("gnutls", "destroying crtdata %p\n", cd);
/* Kill the internal data */
gnutls_x509_crt_deinit( cd->crt );
/* And kill the struct */
@@ -1388,6 +1392,7 @@
x509_keydata_addref(x509_keydata_t *kd)
{
(kd->refcount)++;
+ purple_debug_info("gnutls", "keydata_addref: kd = %p, refcount = %d\n", kd, kd->refcount);
return kd;
}
@@ -1413,7 +1418,7 @@
}
}
-/** Helper macro to retrieve the GnuTLS crt_t from a PurplePrivateKey */
+/** Helper macro to retrieve the GnuTLS key from a PurplePrivateKey */
#define X509_GET_GNUTLS_KEYDATA(pkey) ( ((x509_keydata_t *) (pkey->data))->key)
static gboolean
@@ -1514,14 +1519,15 @@
flags,
NULL, /* Provide no buffer yet */
&out_size /* Put size here */);
- purple_debug_error("gnutls/x509key", "querying for size and export pkcs8 returned (%d) %s with size %zd\n",
- ret, gnutls_strerror(ret), out_size);
- g_return_val_if_fail(ret == GNUTLS_E_SHORT_MEMORY_BUFFER, FALSE);
-
- /* Now allocate a buffer and *really* export it */
+
+ if (GNUTLS_E_SHORT_MEMORY_BUFFER != ret) {
+ purple_debug_error("gnutls/x509key", "Unexpected error getting pkcs8 size: (%d) %s\n",
+ ret, gnutls_strerror(ret));
+ return FALSE;
+ }
/* TODO: Again we seem to randomly get a "just not quite big enough" size above. */
- out_size += 100;
+ //out_size += 100;
out_buf = g_new0(gchar, out_size);
ret = gnutls_x509_privkey_export_pkcs8(key_dat, GNUTLS_X509_FMT_PEM,
@@ -1748,12 +1754,13 @@
switch (type) {
case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY:
case GNUTLS_BAG_PKCS8_KEY:
+#if 0
if (privkey_ok == 1) { /* too simple to continue */
purple_debug_error("gnutls/pkcs12",
"Already found a key.\n");
break;
}
-
+#endif
ret = gnutls_x509_privkey_init(&key);
if (ret < 0) {
purple_debug_error("gnutls/pkcs12",
@@ -1794,9 +1801,10 @@
idx++;
gnutls_pkcs12_bag_deinit(bag);
-
+#if 0
if (privkey_ok != 0) /* private key was found */
break;
+#endif
}
if (privkey_ok == 0) {/* no private key */
@@ -1971,6 +1979,15 @@
if (bag)
gnutls_pkcs12_bag_deinit (bag);
+ if (ret < 0) {
+ g_list_free_full(*keys, (GDestroyNotify)gnutls_x509_privkey_deinit);
+ g_list_free_full(*crts, (GDestroyNotify)gnutls_x509_crt_deinit);
+ g_list_free_full(*crls, (GDestroyNotify)gnutls_x509_crl_deinit);
+ keys = NULL;
+ crts = NULL;
+ crls = NULL;
+ }
+
return ret;
}
@@ -2007,6 +2024,256 @@
}
+static PurplePrivateKey*
+create_purple_privatekey_from_privkey(gnutls_x509_privkey_t key)
+{
+ x509_keydata_t *keydat;
+ PurplePrivateKey *pkey;
+
+ g_return_val_if_fail(NULL != key, NULL);
+
+ keydat = g_new0(x509_keydata_t, 1);
+ keydat->key = key;
+ keydat->refcount = 0;
+
+ pkey = g_new0(PurplePrivateKey, 1);
+ pkey->scheme = &x509_key_gnutls;
+ pkey->data = x509_keydata_addref(keydat);
+
+ return pkey;
+}
+
+static gboolean
+is_self_signed(gnutls_x509_crt_t crt)
+{
+ char* subject_dn = NULL;
+ char* issuer_dn = NULL;
+ size_t subject_size = 0;
+ size_t issuer_size = 0;
+ int ret;
+ gboolean result = FALSE;
+
+ ret = gnutls_x509_crt_get_dn(crt, NULL, &subject_size);
+ if (GNUTLS_E_SHORT_MEMORY_BUFFER != ret) {
+ purple_debug_error("gnutls",
+ "Failed to get size for crt's dn: %s(%d)\n",
+ gnutls_strerror(ret), ret);
+ result = TRUE; /* prevent endless loop */
+ goto done;
+ }
+
+ subject_dn = g_new0(char, subject_size);
+ ret = gnutls_x509_crt_get_dn(crt, subject_dn, &subject_size);
+ if (GNUTLS_E_SUCCESS != ret) {
+ purple_debug_error("gnutls",
+ "Failed to get crt's dn: %s(%d); size=%zd\n",
+ gnutls_strerror(ret), ret, subject_size);
+ result = TRUE;
+ goto done;
+ }
+
+
+ ret = gnutls_x509_crt_get_issuer_dn(crt, NULL, &issuer_size);
+ if (GNUTLS_E_SHORT_MEMORY_BUFFER != ret) {
+ purple_debug_error("gnutls",
+ "Failed to get size for crt's issuer dn: %s(%d)\n",
+ gnutls_strerror(ret), ret);
+ result = TRUE; /* prevent endless loop */
+ goto done;
+ }
+
+ issuer_dn = g_new0(char, issuer_size);
+ ret = gnutls_x509_crt_get_issuer_dn(crt, issuer_dn, &issuer_size);
+ if (GNUTLS_E_SUCCESS != ret) {
+ purple_debug_error("gnutls",
+ "Failed to get crt's dn: %s(%d), size=%zd\n",
+ gnutls_strerror(ret), ret, issuer_size);
+ result = TRUE;
+ goto done;
+ }
+
+ if (0 == g_strcmp0(issuer_dn, subject_dn)) {
+ result = TRUE;
+ }
+
+done:
+ if (subject_dn)
+ g_free(subject_dn);
+ if (issuer_dn)
+ g_free(issuer_dn);
+ return result;
+}
+
+/**
+ * Build the certificate chain for pcrt from the given list
+ * of certificates. This will build as much of the chain as
+ * possible given the available certificates. It will stop when it
+ * finds a self-signed certificate.
+ *
+ * We are passing PurpleCertificate and PurplePrivateKey here instead
+ * of the native gnutls objects since we could hold references to
+ * certificates in multiple chains. GNUTLS does not a copy function
+ * for gnutls_x509_crt_t (oddly it does for gnutls_x509_privkey_t),
+ * but we already have reference counting in the PurpleCertificate
+ * and PurplePrivateKey.
+ *
+ * TODO: Perhaps this should get migrated to the libpurple level
+ * but we will also need to add means to PurpleCertificate and
+ * PurplePrivateKey to get a key id that allows us to match the
+ * key with the cert. That API doesn't exist.
+ *
+ * @param pcrt End-point certificate to start building chain from. Not modified.
+ * @param pcrts List of PurpleCertificates to build chain from. Not modified.
+ * @returns The certificate chain for pcrt. This will always return
+ * at least a list containing pcrt. A list of PurpleCertificates.
+ */
+static GList*
+get_chain_for_crt(PurpleCertificate *pcrt, GList* pcrts)
+{
+ gnutls_x509_crt_t crt = X509_GET_GNUTLS_DATA(pcrt);
+ PurpleCertificate *pissuer = NULL;
+ gnutls_x509_crt_t issuer = NULL;
+ x509_crtdata_t *issuer_crtdata = NULL;
+ GList *node = NULL;
+ GList *chain = NULL;
+ gboolean found = TRUE;
+
+ /* We modify the list locally so make a
+ copy so we don't affect the caller */
+ pcrts = g_list_copy(pcrts);
+
+ chain = g_list_append(chain, x509_copy_certificate(pcrt));
+
+ while (found && !is_self_signed(crt)) {
+
+ node = g_list_first(pcrts);
+ pissuer = (PurpleCertificate*)node->data;
+ issuer_crtdata = (x509_crtdata_t *)pissuer->data;
+ issuer = issuer_crtdata->crt;
+
+ found = FALSE;
+ while (NULL != node) {
+ if (gnutls_x509_crt_check_issuer(crt, issuer)) {
+ /* Remove from list so we don't get tricked
+ into an infinite loop */
+ pcrts = g_list_delete_link(pcrts, node);
+
+
+ /* add to the cert chain */
+ chain = g_list_append(chain, x509_copy_certificate(pissuer));
+
+ crt = issuer;
+ found = TRUE;
+ purple_debug_info("gnutls", "Added crt %p to chain\n", issuer);
+ break;
+ }
+ node = g_list_next(node);
+ pissuer = (PurpleCertificate*)node->data;
+ issuer_crtdata = (x509_crtdata_t *)pissuer->data;
+ issuer = issuer_crtdata->crt;
+ }
+ }
+
+ if (!is_self_signed(crt)) {
+ /* TODO: This should be a warning box */
+ purple_debug_warning("gnutls", "Certificate chain incomplete.\n");
+ }
+
+ return chain;
+}
+
+/**
+ * Create from the given list of certificates a certificate chain for
+ * the given key.
+ *
+ * @param pcrts List of PurpleCertificates. Not modified.
+ * @param pkey Create the certificate chain for this key. Not modified.
+ * @return List of PurpleCertificates constiting the chain ordered from client
+ * to the top-level CA. NULL for error.
+ */
+static GList*
+get_crt_chain_for_key(const PurplePrivateKey *pkey, GList* pcrts)
+{
+ GList *node = NULL;
+ GList *chain = NULL; /* NULL for error */
+ PurpleCertificate *pcrt = NULL;
+ gnutls_x509_crt_t crt = NULL;
+ gnutls_x509_privkey_t key = X509_GET_GNUTLS_KEYDATA(pkey);
+ size_t keyid_size = 0;
+ unsigned char* keyid = NULL;
+ size_t crtid_size = 0;
+ unsigned char *crtid = NULL;
+ int ret = 0;
+
+
+ ret = gnutls_x509_privkey_get_key_id(key, 0, NULL, &keyid_size);
+ if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ purple_debug_error("gnutls",
+ "Failed to get size for keyid: %s(%d)\n",
+ gnutls_strerror(ret), ret);
+ goto done;
+ }
+
+ keyid = g_new0(unsigned char, keyid_size);
+ ret = gnutls_x509_privkey_get_key_id(key, 0, keyid, &keyid_size);
+ if (GNUTLS_E_SUCCESS != ret) {
+ purple_debug_error("gnutls",
+ "Failed to get key id: %s(%d)\n",
+ gnutls_strerror(ret), ret);
+ goto done;
+ }
+
+ purple_debug_info("gnutls",
+ "Finding cert chain for key with id %02x %02x %02x %02x\n",
+ keyid[0], keyid[1], keyid[2], keyid[3]);
+
+ for (node = g_list_first(pcrts); node != NULL;
+ node = g_list_next(node)) {
+ pcrt = (PurpleCertificate*)node->data;
+ crt = X509_GET_GNUTLS_DATA(pcrt);
+
+ ret = gnutls_x509_crt_get_key_id(crt, 0, NULL, &crtid_size);
+ if (ret != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ purple_debug_error("gnutls",
+ "Failed to get size for crtid: %s(%d)\n",
+ gnutls_strerror(ret), ret);
+ goto done;
+ }
+
+ crtid = g_new0(unsigned char, crtid_size);
+ ret = gnutls_x509_crt_get_key_id(crt, 0, crtid, &crtid_size);
+ if (GNUTLS_E_SUCCESS != ret) {
+ purple_debug_error("gnutls",
+ "Failed to get crt id with error %s(%d)\n",
+ gnutls_strerror(ret), ret);
+ goto done;
+ }
+
+ purple_debug_info("gnutls",
+ "Examining cert (%p) with id: %02x %02x %02x %02x\n",
+ crt, crtid[0], crtid[1], crtid[2], crtid[3]);
+
+ if (keyid_size == crtid_size
+ && 0 == memcmp(keyid, crtid, keyid_size)) {
+ purple_debug_info("gnutls",
+ "Found key's cert (%p) with id: %02x %02x %02x %02x\n",
+ crt, crtid[0], crtid[1], crtid[2], crtid[3]);
+
+ chain = get_chain_for_crt(pcrt, pcrts);
+
+ break;
+ }
+ }
+
+done:
+ if (keyid)
+ g_free(keyid);
+ if (crtid)
+ g_free(crtid);
+
+ return chain;
+}
+
static void
add_to_purple_crt_list(gpointer data, gpointer user_data)
{
@@ -2028,23 +2295,73 @@
*pcrts = g_list_append(*pcrts, pcrt);
}
-static PurplePrivateKey*
-create_purple_privatekey_from_privkey(gnutls_x509_privkey_t key)
+static void
+free_unused_pcrts(gpointer data, gpointer user_data)
+{
+ PurpleCertificate *pcrt = (PurpleCertificate*)data;
+ x509_destroy_certificate(pcrt);
+}
+
+
+/**
+ * Collect sets of credentials from the given list of GnuTLS certificates
+ * and private keys.
+ *
+ * This is the conversion point from GnuTLS to Purple objects. We will
+ * traverse the lists matching up private keys with certificates based on
+ * the GnuTLS key id (specific to GnuTLS) for the key and certificate. We
+ * will also create the certificate chain for the client certificate.
+ * THis was motivated by the fact that we can't really assume that PKCS12
+ * files have any logical order and it is up to us to figure out what is
+ * in there. You really start to understand why PKI sucks here.
+ *
+ * @param crts List of gnutls_x509_crt_t. Not modified.
+ * @param keys List of gnutls_x509_privkey_t. Not modified.
+ * @returns List of PurpleCredentials generated from the lists of crts and keys.
+ */
+static GList*
+collect_credentials(GList *crts, GList *keys)
{
- x509_keydata_t *keydat;
- PurplePrivateKey *pkey;
-
- g_return_val_if_fail(NULL != key, NULL);
-
- keydat = g_new0(x509_keydata_t, 1);
- keydat->key = key;
- keydat->refcount = 0;
-
- pkey = g_new0(PurplePrivateKey, 1);
- pkey->scheme = &x509_key_gnutls;
- pkey->data = x509_keydata_addref(keydat);
-
- return pkey;
+ GList *node = NULL;
+ GList *creds = NULL;
+ PurpleCredential *cred = NULL;
+ PurplePrivateKey *pkey = NULL;
+ GList *pcrts = NULL;
+ GList *chain = NULL;
+
+ /* Convert list of gnutls_x509_crt to list of PurpleCertificates */
+ /* Only work with PurpleCertificates from now on */
+ g_list_foreach(crts, add_to_purple_crt_list, &pcrts);
+
+ for (node = g_list_first(keys);
+ node != NULL;
+ node = g_list_next(node)) {
+ /* Convert gnutls_x509_privkey to PurplePrivateKey */
+ gnutls_x509_privkey_t key = (gnutls_x509_privkey_t)node->data;
+ pkey = create_purple_privatekey_from_privkey(key);
+
+ chain = get_crt_chain_for_key(pkey, pcrts);
+
+ if (NULL == chain) {
+ purple_debug_error("gnutls",
+ "Failed to find cert chain for key\n");
+ }
+ else {
+ cred = g_new0(PurpleCredential, 1);
+ cred->crts = chain;
+ cred->key = pkey;
+ creds = g_list_append(creds, cred);
+ }
+ }
+
+ /* Free any certificates not used for credentials. When we create
+ * the pcrts list above we add a reference to each crt. Each crt
+ * added to a chain also increases the crt's ref cnt. Anything with
+ * ref cnt of 1 (not used in a chain) will get freed here.
+ */
+ g_list_foreach(pcrts, free_unused_pcrts, NULL);
+
+ return creds;
}
/**
@@ -2055,20 +2372,20 @@
static gboolean
x509_import_pkcs12_from_file(const gchar* filename,
const gchar* password,
- GList **pcrts, /* PurpleCertificate */
- PurplePrivateKey **pkey)
+ GList** creds /* PurpleCredential */)
{
gnutls_pkcs12_t p12;
gnutls_datum_t dt;
gnutls_x509_crt_fmt_t fmt;
GList *crts = NULL;
GList *keys = NULL;
- gnutls_x509_privkey_t key;
int rv;
+ purple_debug_info("gnutls/pkcs12", "Loading pkcs12 from %s\n", filename);
+
if (!read_pkcs12_file(filename, &dt, &fmt)) {
- purple_debug_error("gnutls",
+ purple_debug_error("gnutls/pkcs12",
"Failed to load PKCS12 file from %s\n",
filename);
g_free(dt.data);
@@ -2115,7 +2432,7 @@
purple_debug_info("gnutls/x509",
"Found %d keys and %d certs in pkcs12\n",
g_list_length(keys), g_list_length(crts));
-
+#if 0
if (g_list_length(keys) != 1) {
purple_debug_error("gnutls/x509",
"Only support one private key in pkcs12 file. Found %d\n",
@@ -2124,18 +2441,16 @@
g_list_free_full(crts, (GDestroyNotify)gnutls_x509_crt_deinit);
return FALSE;
}
-
- key = (gnutls_x509_privkey_t)(g_list_first(keys)->data);
- *pkey = create_purple_privatekey_from_privkey(key);
- g_list_foreach(crts, add_to_purple_crt_list, pcrts);
-
- /* check if the key and certificate found match */
-#if 0 /* TODO ljf */
- if (key && (ret = _gnutls_check_key_cert_match (res)) < 0) {
- gnutls_assert ();
- to done;
+#endif
+
+ *creds = collect_credentials(crts, keys);
+ if (NULL == creds) {
+ purple_debug_error("gnutls/x509",
+ "Failed to collect credentials from pkcs12 file\n");
+ g_list_free_full(keys, (GDestroyNotify)gnutls_x509_privkey_deinit);
+ g_list_free_full(crts, (GDestroyNotify)gnutls_x509_crt_deinit);
+ return FALSE;
}
-#endif
return TRUE;
}
@@ -2362,15 +2677,14 @@
static gboolean
pkcs12_import(const gchar *filename, const gchar *password,
- GList **crts, PurplePrivateKey **key)
+ GList **credentials)
{
g_return_val_if_fail(filename, FALSE);
g_return_val_if_fail(password, FALSE);
- g_return_val_if_fail(crts, FALSE);
- g_return_val_if_fail(key, FALSE);
-
-
- return x509_import_pkcs12_from_file(filename, password, crts, key);
+ g_return_val_if_fail(credentials, FALSE);
+
+
+ return x509_import_pkcs12_from_file(filename, password, credentials);
}
static gboolean
--- a/libpurple/privatekey.h Mon Oct 08 17:59:34 2012 -0400
+++ b/libpurple/privatekey.h Mon Oct 08 18:02:10 2012 -0400
@@ -93,6 +93,7 @@
#include <time.h>
#include <glib.h>
+#include <glib-object.h>
#ifdef __cplusplus
extern "C" {
--- a/pidgin/gtkcertmgr.c Mon Oct 08 17:59:34 2012 -0400
+++ b/pidgin/gtkcertmgr.c Mon Oct 08 18:02:10 2012 -0400
@@ -31,7 +31,9 @@
#include "pidginstock.h"
#include "certificate.h"
+#include "privatekey.h"
#include "pkcs12.h"
+#include "credential.h"
#include "debug.h"
#include "notify.h"
#include "request.h"
@@ -681,12 +683,31 @@
}
static void
+dump_pkcs12_import_data(const char* msg, pkcs12_import_data *data)
+{
+ GList *node;
+
+ purple_debug_info("gtkcertmgr/user_mgmt", "%s: pkcs12_import_data %p\n", msg, data);
+ purple_debug_info("gtkcertmgr/user_mgmt", "\tkey = %p\n", data->key);
+ purple_debug_info("gtkcertmgr/user_mgmt", "\tcrts = %p\n", data->crts);
+
+ for (node = g_list_first(data->crts); NULL != node; node = g_list_next(node)) {
+ purple_debug_info("gtkcertmgr/user_mgmt", "\t\tcrt = %p\n", node->data);
+ purple_debug_info("gtkcertmgr/user_mgmt", "\t\t\tscheme = %p\n", ((PurpleCertificate*)node->data)->scheme);
+ purple_debug_info("gtkcertmgr/user_mgmt", "\t\t\tdata = %p\n", ((PurpleCertificate*)node->data)->data);
+ }
+
+}
+
+static void
pkcs12_import_key_password_ok_cb(gboolean result, pkcs12_import_data *data)
{
GList *i = NULL;
gchar *id = NULL;
PurpleCertificate *crt;
+ dump_pkcs12_import_data(__func__, data);
+
i = g_list_first(data->crts);
crt = (PurpleCertificate*)i->data;
id = purple_certificate_get_unique_id(crt);
@@ -708,6 +729,7 @@
static void
pkcs12_import_name_ok_cb(pkcs12_import_data *data, char* name)
{
+ dump_pkcs12_import_data(__func__, data);
g_free(data->name);
data->name = g_strdup(name);
@@ -733,10 +755,9 @@
{
PurpleCertificateScheme *x509_crts;
PurplePrivateKeyScheme *x509_keys;
- GList *crts = NULL;
- PurplePrivateKey *key = NULL;
pkcs12_import_data *data;
gboolean result;
+ GList *creds = NULL;
purple_debug_info("gtkcertmgr/user_mgmt", "Importing pkcs12 file %s with password XXXXXX\n", filename);
@@ -747,37 +768,44 @@
g_return_if_fail(x509_keys);
/* Now load the certificate/keys from disk */
- result = purple_pkcs12_import(um_dat->pkcs12, filename, password, &crts, &key);
+ result = purple_pkcs12_import(um_dat->pkcs12, filename, password, &creds);
+
+ purple_debug_info("gtkcertmgr/user_mgmt", "Importing pkcs12 succeeded\n");
/* Did it work? */
if (result) {
- /* key id must be the same as the corresponding cert */
- /* We will only add all the certs to the pool if the key add is ok */
- PurpleCertificate *crt = (PurpleCertificate*)(g_list_first(crts)->data);
- gchar *id = purple_certificate_get_unique_id(crt);
- gchar *name = purple_certificate_get_subject_name(crt);
+ GList *i;
+ for (i = g_list_first(creds); NULL != i; i = g_list_next(i)) {
+ PurpleCredential *cred = (PurpleCredential*)i->data;
+
+ /* We will only add all the certs to the pool if the key add is ok */
+ PurpleCertificate *crt = (PurpleCertificate*)(g_list_first(cred->crts)->data);
+ gchar *id = purple_certificate_get_unique_id(crt);
+ gchar *name = purple_certificate_get_subject_name(crt);
+
+ data = g_new0(pkcs12_import_data, 1);
+ data->crts = cred->crts;
+ data->key = cred->key;
- data = g_new0(pkcs12_import_data, 1);
- data->crts = crts;
- data->key = key;
+ dump_pkcs12_import_data(__func__, data);
- purple_privatekey_pool_store_request(
- um_dat->user_keys,
- name,
- id,
- key,
- G_CALLBACK(pkcs12_import_key_password_ok_cb),
- G_CALLBACK(pkcs12_import_key_password_cancel_cb),
- data);
+ purple_privatekey_pool_store_request(
+ um_dat->user_keys,
+ name,
+ id,
+ cred->key,
+ G_CALLBACK(pkcs12_import_key_password_ok_cb),
+ G_CALLBACK(pkcs12_import_key_password_cancel_cb),
+ data);
+ }
+
+ purple_debug_info("gtkcertmgr/user_mgmt", "Done saving imported credentials\n");
} else {
/* Errors! Oh no! */
/* TODO: Perhaps find a way to be specific about what just
went wrong? */
gchar * secondary;
- purple_certificate_destroy_list(crts);
- purple_privatekey_destroy(key);
-
secondary = g_strdup_printf(_("File %s could not be imported.\nMake sure that the file is readable, in PKCS12 format and you used the correct password.\n"), filename);
purple_notify_message(NULL, PURPLE_NOTIFY_MSG_ERROR,
_("Certificate Import Error"),