diff options
Diffstat (limited to 'smime/lib/e-cert-db.c')
-rw-r--r-- | smime/lib/e-cert-db.c | 822 |
1 files changed, 822 insertions, 0 deletions
diff --git a/smime/lib/e-cert-db.c b/smime/lib/e-cert-db.c new file mode 100644 index 0000000000..8e98124a14 --- /dev/null +++ b/smime/lib/e-cert-db.c @@ -0,0 +1,822 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* e-cert-db.c + * + * Copyright (C) 2003 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Chris Toshok (toshok@ximian.com) + */ + +/* The following is the mozilla license blurb, as the bodies of most + of these functions were derived from the mozilla source. */ + +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape security libraries. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + * + */ + +#include "e-cert-db.h" + +#include "nss.h" +#include "pk11func.h" +#include "certdb.h" +#include <e-util/e-dialog-utils.h> +#include <gtk/gtkmessagedialog.h> +#include <libgnome/gnome-i18n.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +struct _ECertDBPrivate { +}; + +#define PARENT_TYPE G_TYPE_OBJECT +static GObjectClass *parent_class; + +static CERTDERCerts* e_cert_db_get_certs_from_package (PRArenaPool *arena, char *data, guint32 length); + + + +static void +e_cert_db_dispose (GObject *object) +{ + ECertDB *ec = E_CERT_DB (object); + + if (!ec->priv) + return; + + /* XXX free instance specific data */ + + g_free (ec->priv); + ec->priv = NULL; + + if (G_OBJECT_CLASS (parent_class)->dispose) + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +e_cert_db_class_init (ECertDBClass *klass) +{ + GObjectClass *object_class; + char *evolution_dir_path; + gboolean success; + + object_class = G_OBJECT_CLASS(klass); + + parent_class = g_type_class_ref (PARENT_TYPE); + + object_class->dispose = e_cert_db_dispose; + + evolution_dir_path = g_build_path ("/", g_get_home_dir (), ".evolution", NULL); + + /* we initialize NSS here to make sure it only happens once */ + success = (SECSuccess == NSS_InitReadWrite (evolution_dir_path)); + if (!success) { + success = (SECSuccess == NSS_Init (evolution_dir_path)); + if (success) + g_warning ("opening cert databases read-only"); + } + if (!success) { + success = (SECSuccess == NSS_NoDB_Init (evolution_dir_path)); + if (success) + g_warning ("initializing security library without cert databases."); + } + g_free (evolution_dir_path); + + if (!success) { + g_warning ("Failed all methods for initializing NSS"); + } +} + +static void +e_cert_db_init (ECertDB *ec) +{ + ec->priv = g_new0 (ECertDBPrivate, 1); +} + +GType +e_cert_db_get_type (void) +{ + static GType cert_type = 0; + + if (!cert_type) { + static const GTypeInfo cert_info = { + sizeof (ECertDBClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) e_cert_db_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (ECertDB), + 0, /* n_preallocs */ + (GInstanceInitFunc) e_cert_db_init, + }; + + cert_type = g_type_register_static (PARENT_TYPE, "ECertDB", &cert_info, 0); + } + + return cert_type; +} + + + +GStaticMutex init_mutex = G_STATIC_MUTEX_INIT; +static ECertDB *cert_db = NULL; + +ECertDB* +e_cert_db_peek (void) +{ + g_static_mutex_lock (&init_mutex); + if (!cert_db) + cert_db = g_object_new (E_TYPE_CERT_DB, NULL); + g_static_mutex_unlock (&init_mutex); + + return cert_db; +} + +void +e_cert_db_shutdown (void) +{ + /* XXX */ +} + +/* searching for certificates */ +ECert* +e_cert_db_find_cert_by_nickname (ECertDB *certdb, + const char *nickname, + GError **error) +{ + // nsNSSShutDownPreventionLock locker; + CERTCertificate *cert = NULL; + + //PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Getting \"%s\"\n", asciiname)); +#if 0 + // what it should be, but for now... + if (aToken) { + cert = PK11_FindCertFromNickname(asciiname, NULL); + } else { + cert = CERT_FindCertByNickname(CERT_GetDefaultCertDB(), asciiname); + } +#endif + cert = PK11_FindCertFromNickname((char*)nickname, NULL); + if (!cert) { + cert = CERT_FindCertByNickname(CERT_GetDefaultCertDB(), (char*)nickname); + } + + + if (cert) { + // PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("got it\n")); + ECert *ecert = e_cert_new (cert); + return ecert; + } + else { + /* XXX gerror */ + return NULL; + } +} + +ECert* +e_cert_db_find_cert_by_key (ECertDB *certdb, + const char *db_key, + GError **error) +{ +#if 0 + // nsNSSShutDownPreventionLock locker; + SECItem keyItem = {siBuffer, NULL, 0}; + SECItem *dummy; + CERTIssuerAndSN issuerSN; + unsigned long moduleID,slotID; + CERTCertificate *cert; + + if (!db_key) { + /* XXX gerror */ + return NULL; + } + + dummy = NSSBase64_DecodeBuffer(NULL, &keyItem, db_key, + (PRUint32)PL_strlen(db_key)); + + // someday maybe we can speed up the search using the moduleID and slotID + moduleID = NS_NSS_GET_LONG(keyItem.data); + slotID = NS_NSS_GET_LONG(&keyItem.data[NS_NSS_LONG]); + + // build the issuer/SN structure + issuerSN.serialNumber.len = NS_NSS_GET_LONG(&keyItem.data[NS_NSS_LONG*2]); + issuerSN.derIssuer.len = NS_NSS_GET_LONG(&keyItem.data[NS_NSS_LONG*3]); + issuerSN.serialNumber.data= &keyItem.data[NS_NSS_LONG*4]; + issuerSN.derIssuer.data= &keyItem.data[NS_NSS_LONG*4+ + issuerSN.serialNumber.len]; + + cert = CERT_FindCertByIssuerAndSN(CERT_GetDefaultCertDB(), &issuerSN); + PR_FREEIF(keyItem.data); + if (cert) { + ECert *ecert = e_cert_new (cert); + return e_cert; + } + + /* XXX gerror */ + return NULL; +#endif +} + +GList* +e_cert_db_get_cert_nicknames (ECertDB *certdb, + ECertType cert_type, + GError **error) +{ +} + +ECert* +e_cert_db_find_email_encryption_cert (ECertDB *certdb, + const char *nickname, + GError **error) +{ +} + +ECert* +e_cert_db_find_email_signing_cert (ECertDB *certdb, + const char *nickname, + GError **error) +{ +} + +ECert* +e_cert_db_find_cert_by_email_address (ECertDB *certdb, + const char *email, + GError **error) +{ + /* nsNSSShutDownPreventionLock locker; */ + ECert *cert; + CERTCertificate *any_cert = CERT_FindCertByNicknameOrEmailAddr(CERT_GetDefaultCertDB(), + (char*)email); + CERTCertList *certlist; + + if (!any_cert) { + /* XXX gerror */ + return NULL; + } + + /* any_cert now contains a cert with the right subject, but it might not have the correct usage */ + certlist = CERT_CreateSubjectCertList(NULL, + CERT_GetDefaultCertDB(), + &any_cert->derSubject, + PR_Now(), PR_TRUE); + if (!certlist) { + /* XXX gerror */ + /* XXX free any_cert */ + return NULL; + } + + if (SECSuccess != CERT_FilterCertListByUsage(certlist, certUsageEmailRecipient, PR_FALSE)) { + /* XXX gerror */ + /* XXX free any_cert */ + /* XXX free certlist */ + return NULL; + } + + if (CERT_LIST_END(CERT_LIST_HEAD(certlist), certlist)) { + /* XXX gerror */ + /* XXX free any_cert */ + /* XXX free certlist */ + return NULL; + } + + cert = e_cert_new (CERT_LIST_HEAD(certlist)->cert); + + return cert; +} + +static gboolean +_confirm_download_ca_cert (ECert *cert, guint32 *trustBits, gboolean *allow) +{ + /* right now just allow it and set the trustBits to 0 */ + *trustBits = 0; + *allow = TRUE; + return TRUE; +} + +static gboolean +handle_ca_cert_download(GList *certs, GError **error) +{ + ECert *certToShow; + SECItem der; + CERTCertificate *tmpCert; + + /* First thing we have to do is figure out which certificate + we're gonna present to the user. The CA may have sent down + a list of certs which may or may not be a chained list of + certs. Until the day we can design some solid UI for the + general case, we'll code to the > 90% case. That case is + where a CA sends down a list that is a chain up to its root + in either ascending or descending order. What we're gonna + do is compare the first 2 entries, if the first was signed + by the second, we assume the leaf cert is the first cert + and display it. If the second cert was signed by the first + cert, then we assume the first cert is the root and the + last cert in the array is the leaf. In this case we + display the last cert. + */ + + /* nsNSSShutDownPreventionLock locker;*/ + + if (certs == NULL) { + g_warning ("Didn't get any certs to import."); + return TRUE; + } + else if (certs->next == NULL) { + /* there's 1 cert */ + certToShow = E_CERT (certs->data); + } + else { + /* there are multiple certs */ + ECert *cert0; + ECert *cert1; + const char* cert0SubjectName; + const char* cert0IssuerName; + const char* cert1SubjectName; + const char* cert1IssuerName; + + cert0 = E_CERT (certs->data); + cert1 = E_CERT (certs->next->data); + + cert0IssuerName = e_cert_get_issuer_name (cert0); + cert0SubjectName = e_cert_get_subject_name (cert0); + + cert1IssuerName = e_cert_get_issuer_name (cert1); + cert1SubjectName = e_cert_get_subject_name (cert1); + + if (!strcmp(cert1IssuerName, cert0SubjectName)) { + /* In this case, the first cert in the list signed the second, + so the first cert is the root. Let's display the last cert + in the list. */ + certToShow = E_CERT (g_list_last (certs)->data); + } + else if (!strcmp(cert0IssuerName, cert1SubjectName)) { + /* In this case the second cert has signed the first cert. The + first cert is the leaf, so let's display it. */ + certToShow = cert0; + } else { + /* It's not a chain, so let's just show the first one in the + downloaded list. */ + certToShow = cert0; + } + } + + if (!certToShow) { + /* XXX gerror */ + return FALSE; + } + + if (!e_cert_get_raw_der (certToShow, (char**)&der.data, &der.len)) { + /* XXX gerror */ + return FALSE; + } + + { + /*PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Creating temp cert\n"));*/ + CERTCertDBHandle *certdb = CERT_GetDefaultCertDB(); + tmpCert = CERT_FindCertByDERCert(certdb, &der); + if (!tmpCert) { + tmpCert = CERT_NewTempCertificate(certdb, &der, + NULL, PR_FALSE, PR_TRUE); + } + if (!tmpCert) { + g_warning ("Couldn't create cert from DER blob"); + return FALSE; + } + } + +#if 0 + CERTCertificateCleaner tmpCertCleaner(tmpCert); +#endif + + if (tmpCert->isperm) { + e_notice (NULL, GTK_MESSAGE_WARNING, _("Certificate already exists")); + /* XXX gerror */ + return FALSE; + } + else { + guint32 trustBits; + gboolean allow; + char *nickname; + SECStatus srv; + + if (!_confirm_download_ca_cert (certToShow, &trustBits, &allow)) { + /* XXX gerror */ + return FALSE; + } + + if (!allow) { + /* XXX gerror */ + return FALSE; + } + + //PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("trust is %d\n", trustBits)); + + nickname = CERT_MakeCANickname(tmpCert); + + //PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Created nick \"%s\"\n", nickname.get())); + +#if 0 + nsNSSCertTrust trust; + trust.SetValidCA(); + trust.AddCATrust(trustBits & nsIX509CertDB::TRUSTED_SSL, + trustBits & nsIX509CertDB::TRUSTED_EMAIL, + trustBits & nsIX509CertDB::TRUSTED_OBJSIGN); +#endif + + srv = CERT_AddTempCertToPerm(tmpCert, + nickname, + /*XXX trust.GetTrust()*/ 0); + + if (srv != SECSuccess) { + /* XXX gerror */ + return FALSE; + } + +#if 0 + /* Now it's time to add the rest of the certs we just downloaded. + Since we didn't prompt the user about any of these certs, we + won't set any trust bits for them. */ + nsNSSCertTrust defaultTrust; + defaultTrust.SetValidCA(); + defaultTrust.AddCATrust(0,0,0); + for (PRUint32 i=0; i<numCerts; i++) { + if (i == selCertIndex) + continue; + + certToShow = do_QueryElementAt(x509Certs, i); + certToShow->GetRawDER(&der.len, (PRUint8 **)&der.data); + + CERTCertificate *tmpCert2 = + CERT_NewTempCertificate(certdb, &der, nsnull, PR_FALSE, PR_TRUE); + + if (!tmpCert2) { + NS_ASSERTION(0, "Couldn't create temp cert from DER blob\n"); + continue; // Let's try to import the rest of 'em + } + nickname.Adopt(CERT_MakeCANickname(tmpCert2)); + CERT_AddTempCertToPerm(tmpCert2, NS_CONST_CAST(char*,nickname.get()), + defaultTrust.GetTrust()); + CERT_DestroyCertificate(tmpCert2); + } +#endif + return TRUE; + } +} + +/* deleting certificates */ +gboolean +e_cert_db_delete_cert (ECertDB *certdb, + ECert *ecert) +{ + // nsNSSShutDownPreventionLock locker; + // nsNSSCertificate *nssCert = NS_STATIC_CAST(nsNSSCertificate*, aCert); + + CERTCertificate *cert; + SECStatus srv = SECSuccess; + if (!e_cert_mark_for_deletion (ecert)) { + return FALSE; + } + + cert = e_cert_get_internal_cert (ecert); + if (cert->slot && e_cert_get_cert_type (ecert) != E_CERT_USER) { + /* To delete a cert of a slot (builtin, most likely), mark it as + completely untrusted. This way we keep a copy cached in the + local database, and next time we try to load it off of the + external token/slot, we'll know not to trust it. We don't + want to do that with user certs, because a user may re-store + the cert onto the card again at which point we *will* want to + trust that cert if it chains up properly. */ +#if 0 + nsNSSCertTrust trust(0, 0, 0); + srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), + cert, trust.GetTrust()); +#endif + } + +#if 0 + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("cert deleted: %d", srv)); +#endif + return (srv) ? FALSE : TRUE; +} + +/* importing certificates */ +gboolean +e_cert_db_import_certs (ECertDB *certdb, + char *data, guint32 length, + ECertType cert_type, + GError **error) +{ + /*nsNSSShutDownPreventionLock locker;*/ + PRArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + GList *certs = NULL; + CERTDERCerts *certCollection = e_cert_db_get_certs_from_package (arena, data, length); + int i; + gboolean rv; + + if (!certCollection) { + /* XXX gerror */ + PORT_FreeArena(arena, PR_FALSE); + return FALSE; + } + + /* Now let's create some certs to work with */ + for (i=0; i<certCollection->numcerts; i++) { + SECItem *currItem = &certCollection->rawCerts[i]; + ECert *cert; + + cert = e_cert_new_from_der ((char*)currItem->data, currItem->len); + if (!cert) { + /* XXX gerror */ + g_list_foreach (certs, (GFunc)g_object_unref, NULL); + g_list_free (certs); + PORT_FreeArena(arena, PR_FALSE); + return FALSE; + } + certs = g_list_append (certs, cert); + } + switch (cert_type) { + case E_CERT_CA: + rv = handle_ca_cert_download(certs, error); + break; + default: + // We only deal with import CA certs in this method currently. + /* XXX gerror */ + PORT_FreeArena(arena, PR_FALSE); + rv = FALSE; + } + + g_list_foreach (certs, (GFunc)g_object_unref, NULL); + g_list_free (certs); + PORT_FreeArena(arena, PR_FALSE); + return rv; +} + +gboolean +e_cert_db_import_email_cert (ECertDB *certdb, + char *data, guint32 length, + GError **error) +{ +} + +gboolean +e_cert_db_import_user_cert (ECertDB *certdb, + char *data, guint32 length, + GError **error) +{ +#if 0 + /* nsNSSShutDownPreventionLock locker;*/ + PK11SlotInfo *slot; + char * nickname = NULL; + gboolean rv = FALSE; + int numCACerts; + SECItem *CACerts; + CERTDERCerts * collectArgs; + CERTCertificate * cert=NULL; + + collectArgs = e_cert_db_get_certs_from_package(data, length); + if (!collectArgs) { + goto loser; + } + + cert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), collectArgs->rawCerts, + (char *)NULL, PR_FALSE, PR_TRUE); + if (!cert) { + goto loser; + } + + slot = PK11_KeyForCertExists(cert, NULL, NULL); + if ( slot == NULL ) { + goto loser; + } + PK11_FreeSlot(slot); + + /* pick a nickname for the cert */ + if (cert->nickname) { + /* sigh, we need a call to look up other certs with this subject and + * identify nicknames from them. We can no longer walk down internal + * database structures rjr */ + nickname = cert->nickname; + } + else { + g_assert_not_reached (); + /* nickname = default_nickname(cert, NULL); */ + } + + /* user wants to import the cert */ + slot = PK11_ImportCertForKey(cert, nickname, NULL); + if (!slot) { + goto loser; + } + PK11_FreeSlot(slot); + numCACerts = collectArgs->numcerts - 1; + + if (numCACerts) { + CACerts = collectArgs->rawCerts+1; + if ( ! CERT_ImportCAChain(CACerts, numCACerts, certUsageUserCertImport) ) { + rv = TRUE; + } + } + + loser: + if ( cert ) { + CERT_DestroyCertificate(cert); + } + return rv; +#endif +} + +gboolean +e_cert_db_import_server_cert (ECertDB *certdb, + char *data, guint32 length, + GError **error) +{ +} + +gboolean +e_cert_db_import_certs_from_file (ECertDB *cert_db, + const char *file_path, + ECertType cert_type, + GError **error) +{ + gboolean rv; + int fd; + struct stat sb; + char *buf; + int bytes_read; + + switch (cert_type) { + case E_CERT_CA: + case E_CERT_CONTACT: + case E_CERT_SITE: + /* good */ + break; + + default: + /* not supported (yet) */ + /* XXX gerror */ + return FALSE; + } + + fd = open (file_path, O_RDONLY); + if (fd == -1) { + /* XXX gerror */ + return FALSE; + } + + if (-1 == fstat (fd, &sb)) { + /* XXX gerror */ + close (fd); + return FALSE; + } + + buf = g_malloc (sb.st_size); + if (!buf) { + /* XXX gerror */ + close (fd); + return FALSE; + } + + bytes_read = read (fd, buf, sb.st_size); + + close (fd); + + if (bytes_read != sb.st_size) { + /* XXX gerror */ + rv = FALSE; + } + else { + printf ("importing %d bytes from `%s'\n", bytes_read, file_path); + + switch (cert_type) { + case E_CERT_CA: + rv = e_cert_db_import_certs (cert_db, buf, bytes_read, cert_type, error); + break; + + case E_CERT_SITE: + rv = e_cert_db_import_server_cert (cert_db, buf, bytes_read, error); + break; + + case E_CERT_CONTACT: + rv = e_cert_db_import_email_cert (cert_db, buf, bytes_read, error); + break; + + default: + rv = FALSE; + break; + } + } + + g_free (buf); + return rv; +} + +gboolean +e_cert_db_import_pkcs12_file (ECertDB *cert_db, + const char *file_path, + GError **error) +{ +} + +gboolean +e_cert_db_export_pkcs12_file (ECertDB *cert_db, + const char *file_path, + GList *certs, + GError **error) +{ +} + + + +static SECStatus PR_CALLBACK +collect_certs(void *arg, SECItem **certs, int numcerts) +{ + CERTDERCerts *collectArgs; + SECItem *cert; + SECStatus rv; + + collectArgs = (CERTDERCerts *)arg; + + collectArgs->numcerts = numcerts; + collectArgs->rawCerts = (SECItem *) PORT_ArenaZAlloc(collectArgs->arena, sizeof(SECItem) * numcerts); + if ( collectArgs->rawCerts == NULL ) + return(SECFailure); + + cert = collectArgs->rawCerts; + + while ( numcerts-- ) { + rv = SECITEM_CopyItem(collectArgs->arena, cert, *certs); + if ( rv == SECFailure ) + return(SECFailure); + cert++; + certs++; + } + + return (SECSuccess); +} + +static CERTDERCerts* +e_cert_db_get_certs_from_package (PRArenaPool *arena, + char *data, + guint32 length) +{ + /*nsNSSShutDownPreventionLock locker;*/ + CERTDERCerts *collectArgs = + (CERTDERCerts *)PORT_ArenaZAlloc(arena, sizeof(CERTDERCerts)); + SECStatus sec_rv; + + if (!collectArgs) + return NULL; + + collectArgs->arena = arena; + sec_rv = CERT_DecodeCertPackage(data, + length, collect_certs, + (void *)collectArgs); + + if (sec_rv != SECSuccess) + return NULL; + + return collectArgs; +} |