/* -*- 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;
}