--- 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 @@
/** List holding pointers to all registered private key schemes */
static GList *pkcs12_schemes = NULL;
@@ -40,15 +41,14 @@
purple_pkcs12_import(PurplePkcs12Scheme *scheme,
const gchar *filename, const gchar *password,
- GList **crts, PurplePrivateKey **key)
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); @@ -67,10 +67,12 @@
purple_pkcs12_import_to_pool(PurplePkcs12Scheme *scheme, const gchar *filename, const gchar *password,
PurpleCertificatePool *crtpool, PurplePrivateKeyPool *keypool)
+ gboolean result = FALSE; 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)) - 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 (!purple_certificate_pool_store(crtpool, id, crt)) + id = purple_privatekey_get_unique_id(cred->key);
- if (!purple_certificate_pool_store(crtpool, id, crt))
+ if (!purple_privatekey_pool_store(keypool, id, key, password)) - id = purple_privatekey_get_unique_id(key);
- if (!purple_privatekey_pool_store(keypool, id, key, password))
- purple_certificate_destroy_list(crts);
- purple_privatekey_destroy(key);
+ purple_credential_destroy_list(creds); @@ -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
@@ -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);
* 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
- * 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
purple_pkcs12_import(PurplePkcs12Scheme *scheme, const gchar *filename, const gchar *password,
- GList **crts, PurplePrivateKey **key);
* 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 @@
@@ -715,6 +716,7 @@
x509_crtdata_addref(x509_crtdata_t *cd)
+ purple_debug_info("gnutls", "crtdata_addref: cd = %p, refcount = %d\n", cd, cd->refcount); @@ -722,6 +724,7 @@
x509_crtdata_delref(x509_crtdata_t *cd)
+ purple_debug_info("gnutls", "crtdata_delref: cd = %p, refcount = %d\n", cd, cd->refcount); g_critical("Refcount of x509_crtdata_t is %d, which is less "
@@ -729,6 +732,7 @@
/* If the refcount reaches zero, kill the structure */
+ 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)
+ purple_debug_info("gnutls", "keydata_addref: kd = %p, refcount = %d\n", kd, kd->refcount); @@ -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)
@@ -1514,14 +1519,15 @@
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)); /* TODO: Again we seem to randomly get a "just not quite big enough" size above. */
out_buf = g_new0(gchar, out_size);
ret = gnutls_x509_privkey_export_pkcs8(key_dat, GNUTLS_X509_FMT_PEM,
@@ -1748,12 +1754,13 @@
case GNUTLS_BAG_PKCS8_ENCRYPTED_KEY:
case GNUTLS_BAG_PKCS8_KEY:
if (privkey_ok == 1) { /* too simple to continue */
purple_debug_error("gnutls/pkcs12",
"Already found a key.\n");
ret = gnutls_x509_privkey_init(&key);
purple_debug_error("gnutls/pkcs12",
@@ -1794,9 +1801,10 @@
gnutls_pkcs12_bag_deinit(bag);
if (privkey_ok != 0) /* private key was found */
if (privkey_ok == 0) {/* no private key */
@@ -1971,6 +1979,15 @@
gnutls_pkcs12_bag_deinit (bag);
+ 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); @@ -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); + pkey = g_new0(PurplePrivateKey, 1); + pkey->scheme = &x509_key_gnutls; + pkey->data = x509_keydata_addref(keydat); +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; + 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 */ + 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); + 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 */ + 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); + if (0 == g_strcmp0(issuer_dn, subject_dn)) { + * 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. +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; + /* 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; + 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)); + purple_debug_info("gnutls", "Added crt %p to chain\n", issuer); + 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"); + * Create from the given list of certificates a certificate chain for + * @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. +get_crt_chain_for_key(const PurplePrivateKey *pkey, GList* pcrts) + 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); + unsigned char* keyid = NULL; + unsigned char *crtid = NULL; + 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); + 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); + 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); + 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); + 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); 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)
+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 + * 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. +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);
- pkey = g_new0(PurplePrivateKey, 1);
- pkey->scheme = &x509_key_gnutls;
- pkey->data = x509_keydata_addref(keydat);
+ PurpleCredential *cred = NULL; + PurplePrivateKey *pkey = 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 = 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); + purple_debug_error("gnutls", + "Failed to find cert chain for key\n"); + cred = g_new0(PurpleCredential, 1); + 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); @@ -2055,20 +2372,20 @@
x509_import_pkcs12_from_file(const gchar* filename,
- GList **pcrts, /* PurpleCertificate */
- PurplePrivateKey **pkey)
+ GList** creds /* PurpleCredential */) gnutls_x509_crt_fmt_t fmt;
- gnutls_x509_privkey_t key;
+ 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",
@@ -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 (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);
- 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 (key && (ret = _gnutls_check_key_cert_match (res)) < 0) {
+ *creds = collect_credentials(crts, keys); + 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);
@@ -2362,15 +2677,14 @@
pkcs12_import(const gchar *filename, const gchar *password,
- GList **crts, PurplePrivateKey **key)
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); --- 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 @@
@@ -681,12 +683,31 @@
+dump_pkcs12_import_data(const char* msg, pkcs12_import_data *data) + 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); pkcs12_import_key_password_ok_cb(gboolean result, pkcs12_import_data *data)
+ 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 @@
pkcs12_import_name_ok_cb(pkcs12_import_data *data, char* name)
+ dump_pkcs12_import_data(__func__, data); data->name = g_strdup(name);
@@ -733,10 +755,9 @@
PurpleCertificateScheme *x509_crts;
PurplePrivateKeyScheme *x509_keys;
- PurplePrivateKey *key = NULL;
pkcs12_import_data *data;
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"); - /* 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);
+ 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 = g_new0(pkcs12_import_data, 1);
+ dump_pkcs12_import_data(__func__, data); - purple_privatekey_pool_store_request(
- G_CALLBACK(pkcs12_import_key_password_ok_cb),
- G_CALLBACK(pkcs12_import_key_password_cancel_cb),
+ purple_privatekey_pool_store_request( + G_CALLBACK(pkcs12_import_key_password_ok_cb), + G_CALLBACK(pkcs12_import_key_password_cancel_cb), + purple_debug_info("gtkcertmgr/user_mgmt", "Done saving imported credentials\n"); /* TODO: Perhaps find a way to be specific about what just
- 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"),