/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* The following is the mozilla license blurb, as the bodies some of
* these functions were derived from the mozilla source. */
/*
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* 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 the Initial Developer are Copyright (C) 1994-2000
* the Initial Developer. All Rights Reserved.
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*/
/*
* Author: Chris Toshok (toshok@ximian.com)
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <libedataserverui/e-passwords.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include "e-cert-db.h"
#include "e-pkcs12.h"
#include "prmem.h"
#include "nss.h"
#include "ssl.h"
#include "pkcs12.h"
#include "p12plcy.h"
#include "pk11func.h"
#include "secerr.h"
/* static callback functions for the NSS PKCS#12 library */
static SECItem * PR_CALLBACK nickname_collision (SECItem *, PRBool *, gpointer );
static gboolean handle_error (gint myerr);
#define PKCS12_BUFFER_SIZE 2048
#define PKCS12_RESTORE_OK 1
#define PKCS12_BACKUP_OK 2
#define PKCS12_USER_CANCELED 3
#define PKCS12_NOSMARTCARD_EXPORT 4
#define PKCS12_RESTORE_FAILED 5
#define PKCS12_BACKUP_FAILED 6
#define PKCS12_NSS_ERROR 7
G_DEFINE_TYPE (EPKCS12, e_pkcs12, G_TYPE_OBJECT)
static void
e_pkcs12_class_init (EPKCS12Class *class)
{
}
static void
e_pkcs12_init (EPKCS12 *ec)
{
}
EPKCS12 *
e_pkcs12_new (void)
{
return g_object_new (E_TYPE_PKCS12, NULL);
}
static gboolean
input_to_decoder (SEC_PKCS12DecoderContext *dcx,
const gchar *path,
GError **error)
{
/* nsNSSShutDownPreventionLock locker; */
SECStatus srv;
gint amount;
gchar buf[PKCS12_BUFFER_SIZE];
FILE *fp;
/* open path */
fp = g_fopen (path, "rb");
if (!fp) {
/* XXX gerror */
printf ("couldn't open '%s'\n", path);
return FALSE;
}
while (TRUE) {
amount = fread (buf, 1, sizeof (buf), fp);
if (amount < 0) {
fclose (fp);
return FALSE;
}
/* feed the file data into the decoder */
srv = SEC_PKCS12DecoderUpdate (dcx,
(guchar *) buf,
amount);
if (srv) {
/* XXX g_error */
fclose (fp);
return FALSE;
}
if (amount < PKCS12_BUFFER_SIZE)
break;
}
fclose (fp);
return TRUE;
}
/* XXX toshok - this needs to be done using a signal as in the
* e_cert_db_login_to_slot stuff, instead of a direct gui dep here..
* for now, though, it stays. */
static gboolean
prompt_for_password (gchar *title,
gchar *prompt,
SECItem *pwd)
{
gchar *passwd;
passwd = e_passwords_ask_password (
title, NULL, "", prompt,
E_PASSWORDS_REMEMBER_NEVER | E_PASSWORDS_SECRET,
NULL, NULL);
if (passwd) {
gsize len = strlen (passwd);
const gchar *inptr = passwd;
guchar *outptr;
gunichar2 c;
SECITEM_AllocItem (NULL, pwd, sizeof (gunichar2) * (len + 1));
outptr = pwd->data;
while (inptr && (c = (gunichar2) (g_utf8_get_char (inptr) & 0xffff))) {
inptr = g_utf8_next_char (inptr);
c = GUINT16_TO_BE (c);
*outptr++ = ((gchar *) &c)[0];
*outptr++ = ((gchar *) &c)[1];
}
outptr[0] = 0;
outptr[1] = 0;
memset (passwd, 0, strlen (passwd));
g_free (passwd);
}
return TRUE;
}
static gboolean
import_from_file_helper (EPKCS12 *pkcs12,
PK11SlotInfo *slot,
const gchar *path,
gboolean *aWantRetry,
GError **error)
{
/*nsNSSShutDownPreventionLock locker; */
gboolean rv;
SECStatus srv = SECSuccess;
SEC_PKCS12DecoderContext *dcx = NULL;
SECItem passwd;
GError *err = NULL;
*aWantRetry = FALSE;
passwd.data = NULL;
rv = prompt_for_password (
_("PKCS12 File Password"),
_("Enter password for PKCS12 file:"), &passwd);
if (!rv) goto finish;
if (passwd.data == NULL) {
handle_error (PKCS12_USER_CANCELED);
return TRUE;
}
/* initialize the decoder */
dcx = SEC_PKCS12DecoderStart (&passwd,
slot,
/* we specify NULL for all the
funcs + data so it'll use the
default pk11wrap functions */
NULL, NULL, NULL,
NULL, NULL, NULL);
if (!dcx) {
srv = SECFailure;
goto finish;
}
/* read input file and feed it to the decoder */
rv = input_to_decoder (dcx, path, &err);
if (!rv) {
#ifdef notyet
/* XXX we need this to check the gerror */
if (NS_ERROR_ABORT == rv) {
/* inputToDecoder indicated a NSS error */
srv = SECFailure;
}
#else
srv = SECFailure;
#endif
goto finish;
}
/* verify the blob */
srv = SEC_PKCS12DecoderVerify (dcx);
if (srv) goto finish;
/* validate bags */
srv = SEC_PKCS12DecoderValidateBags (dcx, nickname_collision);
if (srv) goto finish;
/* import cert and key */
srv = SEC_PKCS12DecoderImportBags (dcx);
if (srv) goto finish;
/* Later - check to see if this should become default email cert */
handle_error (PKCS12_RESTORE_OK);
finish:
/* If srv != SECSuccess, NSS probably set a specific error code.
* We should use that error code instead of inventing a new one
* for every error possible. */
if (srv != SECSuccess) {
if (SEC_ERROR_BAD_PASSWORD == PORT_GetError ()) {
*aWantRetry = TRUE;
}
handle_error (PKCS12_NSS_ERROR);
} else if (!rv) {
handle_error (PKCS12_RESTORE_FAILED);
}
/* finish the decoder */
if (dcx)
SEC_PKCS12DecoderFinish (dcx);
return TRUE;
}
gboolean
e_pkcs12_import_from_file (EPKCS12 *pkcs12,
const gchar *path,
GError **error)
{
/*nsNSSShutDownPreventionLock locker;*/
gboolean rv = TRUE;
gboolean wantRetry;
PK11SlotInfo *slot;
printf ("importing pkcs12 from '%s'\n", path);
slot = PK11_GetInternalKeySlot ();
if (!e_cert_db_login_to_slot (e_cert_db_peek (), slot))
return FALSE;
do {
rv = import_from_file_helper (pkcs12, slot, path, &wantRetry, error);
} while (rv && wantRetry);
return rv;
}
gboolean
e_pkcs12_export_to_file (EPKCS12 *pkcs12,
const gchar *path,
GList *certs,
GError **error)
{
return FALSE;
}
/* what to do when the nickname collides with one already in the db.
* TODO: not handled, throw a dialog allowing the nick to be changed? */
static SECItem * PR_CALLBACK
nickname_collision (SECItem *oldNick,
PRBool *cancel,
gpointer wincx)
{
/* nsNSSShutDownPreventionLock locker; */
gint count = 1;
gchar *nickname = NULL;
gchar *default_nickname = _("Imported Certificate");
SECItem *new_nick;
*cancel = PR_FALSE;
printf ("nickname_collision\n");
/* The user is trying to import a PKCS#12 file that doesn't have the
* attribute we use to set the nickname. So in order to reduce the
* number of interactions we require with the user, we'll build a nickname
* for the user. The nickname isn't prominently displayed in the UI,
* so it's OK if we generate one on our own here.
* XXX If the NSS API were smarter and actually passed a pointer to
* the CERTCertificate * we're importing we could actually just
* call default_nickname (which is what the issuance code path
* does) and come up with a reasonable nickname. Alas, the NSS
* API limits our ability to produce a useful nickname without
* bugging the user. :(
*/
while (1) {
CERTCertificate *cert;
/* If we've gotten this far, that means there isn't a certificate
* in the database that has the same subject name as the cert we're
* trying to import. So we need to come up with a "nickname" to
* satisfy the NSS requirement or fail in trying to import.
* Basically we use a default nickname from a properties file and
* see if a certificate exists with that nickname. If there isn't, then
* create update the count by one and append the string '#1' Or
* whatever the count currently is, and look for a cert with
* that nickname. Keep updating the count until we find a nickname
* without a corresponding cert.
* XXX If a user imports *many * certs without the 'friendly name'
* attribute, then this may take a long time. :(
*/
if (count > 1) {
g_free (nickname);
nickname = g_strdup_printf ("%s #%d", default_nickname, count);
} else {
g_free (nickname);
nickname = g_strdup (default_nickname);
}
cert = CERT_FindCertByNickname (CERT_GetDefaultCertDB (),
nickname);
if (!cert) {
break;
}
CERT_DestroyCertificate (cert);
count++;
}
new_nick = PR_Malloc (sizeof (SECItem));
new_nick->type = siAsciiString;
new_nick->data = (guchar *) nickname;
new_nick->len = strlen ((gchar *) new_nick->data);
return new_nick;
}
static gboolean
handle_error (gint myerr)
{
printf ("handle_error (%d)\n", myerr);
return FALSE;
}