aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--camel/ChangeLog15
-rw-r--r--camel/Makefile.am4
-rw-r--r--camel/camel-folder.h2
-rw-r--r--camel/camel-private.h20
-rw-r--r--camel/camel-tcp-stream-openssl.c202
-rw-r--r--camel/camel-tcp-stream-ssl.c44
-rw-r--r--camel/camel.c27
7 files changed, 242 insertions, 72 deletions
diff --git a/camel/ChangeLog b/camel/ChangeLog
index b979b8466e..e17722a7c5 100644
--- a/camel/ChangeLog
+++ b/camel/ChangeLog
@@ -1,3 +1,18 @@
+2002-07-30 Jeffrey Stedfast <fejj@ximian.com>
+
+ * camel-certdb.c: New source file implementing a very basic
+ certificate database. This is mostly just here because the Mozilla
+ NSS certdb seems to not be working for everyone's Evolution
+ install (works fine for me and Ettore but not many other people).
+
+ * camel-tcp-stream-ssl.c (ssl_bad_cert): If we have this
+ certificate in our own CamelCertDB, then get the trust value from
+ that and only prompt the user if the trust is unknown.
+
+ * camel-tcp-stream-openssl.c (ssl_verify): Same.
+
+ * camel.c (camel_init): Create our default certdb.
+
2002-07-30 Peter Williams <peterw@ximian.com>
* providers/imap/camel-imap-folder.c (imap_transfer_offline): Use
diff --git a/camel/Makefile.am b/camel/Makefile.am
index 71fbe008fa..642641e12f 100644
--- a/camel/Makefile.am
+++ b/camel/Makefile.am
@@ -22,6 +22,8 @@ libcamel_la_SOURCES = \
camel-address.c \
camel-arg.c \
camel-block-file.c \
+ camel-certdb.c \
+ camel-charset-map.c \
camel-cipher-context.c \
camel-cms-context.c \
camel-data-cache.c \
@@ -110,7 +112,6 @@ libcamel_la_SOURCES = \
camel-vee-folder.c \
camel-vee-store.c \
camel-vtrash-folder.c \
- camel-charset-map.c \
camel.c \
gstring-util.c \
hash-table-utils.c \
@@ -121,6 +122,7 @@ libcamelinclude_HEADERS = \
camel-address.h \
camel-arg.h \
camel-block-file.h \
+ camel-certdb.h \
camel-charset-map.h \
camel-cipher-context.h \
camel-cms-context.h \
diff --git a/camel/camel-folder.h b/camel/camel-folder.h
index ebaf080457..a1bb7baf66 100644
--- a/camel/camel-folder.h
+++ b/camel/camel-folder.h
@@ -29,7 +29,7 @@
#ifdef __cplusplus
extern "C" {
#pragma }
-#endif /* __cplusplus }*/
+#endif /* __cplusplus */
#include <glib.h>
#include <camel/camel-object.h>
diff --git a/camel/camel-private.h b/camel/camel-private.h
index 5c978da9ac..0530c27fe7 100644
--- a/camel/camel-private.h
+++ b/camel/camel-private.h
@@ -215,6 +215,26 @@ struct _CamelDataWrapperPrivate {
#define CAMEL_DATA_WRAPPER_UNLOCK(dw, l)
#endif
+/* most of this stuff really is private, but the lock can be used by subordinate classes */
+struct _CamelCertDBPrivate {
+#ifdef ENABLE_THREADS
+ GMutex *db_lock; /* for the db hashtable/array */
+ GMutex *io_lock; /* load/save lock, for access to saved_count, etc */
+ GMutex *alloc_lock; /* for setting up and using allocators */
+ GMutex *ref_lock; /* for reffing/unreffing certs */
+#else
+ gpointer dummy;
+#endif
+};
+
+#ifdef ENABLE_THREADS
+#define CAMEL_CERTDB_LOCK(db, l) (g_mutex_lock (((CamelCertDB *) db)->priv->l))
+#define CAMEL_CERTDB_UNLOCK(db, l) (g_mutex_unlock (((CamelCertDB *) db)->priv->l))
+#else
+#define CAMEL_CERTDB_LOCK(db, l)
+#define CAMEL_CERTDB_UNLOCK(db, l)
+#endif
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/camel/camel-tcp-stream-openssl.c b/camel/camel-tcp-stream-openssl.c
index 785f5ad74b..e9880f3507 100644
--- a/camel/camel-tcp-stream-openssl.c
+++ b/camel/camel-tcp-stream-openssl.c
@@ -20,6 +20,7 @@
*
*/
+
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
@@ -539,92 +540,161 @@ socket_connect (struct hostent *h, int port)
return fd;
}
-static void
-save_ssl_cert (const char *certid)
-{
- char *path, *filename;
- struct stat st;
- int fd;
-
- path = g_strdup_printf ("%s/.camel_certs", getenv ("HOME"));
- if (mkdir (path, 0700) == -1) {
- if (errno != EEXIST)
- return;
-
- if (stat (path, &st) == -1)
- return;
-
- if (!S_ISDIR (st.st_mode))
- return;
- }
-
- filename = g_strdup_printf ("%s/%s", path, certid);
- g_free (path);
-
- fd = open (filename, O_WRONLY | O_CREAT, 0600);
- if (fd != -1)
- close (fd);
-
- g_free (filename);
-}
-
-static gboolean
-ssl_cert_is_saved (const char *certid)
+static const char *
+x509_strerror (int err)
{
- char *filename;
- struct stat st;
-
- filename = g_strdup_printf ("%s/.camel_certs/%s", getenv ("HOME"), certid);
-
- if (stat (filename, &st) == -1) {
- g_free (filename);
- return FALSE;
+ switch (err) {
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+ return _("Unable to get issuer's certificate");
+ case X509_V_ERR_UNABLE_TO_GET_CRL:
+ return _("Unable to get Certificate Revocation List");
+ case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
+ return _("Unable to decrypt certificate signature");
+ case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
+ return _("Unable to decrypt Certificate Revocation List signature");
+ case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
+ return _("Unable to decode issuer's public key");
+ case X509_V_ERR_CERT_SIGNATURE_FAILURE:
+ return _("Certificate signature failure");
+ case X509_V_ERR_CRL_SIGNATURE_FAILURE:
+ return _("Certificate Revocation List signature failure");
+ case X509_V_ERR_CERT_NOT_YET_VALID:
+ return _("Certificate not yet valid");
+ case X509_V_ERR_CERT_HAS_EXPIRED:
+ return _("Certificate has expired");
+ case X509_V_ERR_CRL_NOT_YET_VALID:
+ return _("CRL not yet valid");
+ case X509_V_ERR_CRL_HAS_EXPIRED:
+ return _("CRL has expired");
+ case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+ case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
+ case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
+ return _("Error in CRL");
+ case X509_V_ERR_OUT_OF_MEM:
+ return _("Out of memory");
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ return _("Zero-depth self-signed certificate");
+ case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+ return _("Self-signed certificate in chain");
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+ return _("Unable to get issuer's certificate locally");
+ case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
+ return _("Unable to verify leaf signature");
+ case X509_V_ERR_CERT_CHAIN_TOO_LONG:
+ return _("Certificate chain too long");
+ case X509_V_ERR_CERT_REVOKED:
+ return _("Certificate Revoked");
+ case X509_V_ERR_INVALID_CA:
+ return _("Invalid Certificate Authority (CA)");
+ case X509_V_ERR_PATH_LENGTH_EXCEEDED:
+ return _("Path length exceeded");
+ case X509_V_ERR_INVALID_PURPOSE:
+ return _("Invalid purpose");
+ case X509_V_ERR_CERT_UNTRUSTED:
+ return _("Certificate untrusted");
+ case X509_V_ERR_CERT_REJECTED:
+ return _("Certificate rejected");
+ /* These are 'informational' when looking for issuer cert */
+ case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
+ return _("Subject/Issuer mismatch");
+ case X509_V_ERR_AKID_SKID_MISMATCH:
+ return _("AKID/SKID mismatch");
+ case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
+ return _("AKID/Issuer serial mismatch");
+ case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
+ return _("Key usage does not support certificate signing");
+ /* The application is not happy */
+ case X509_V_ERR_APPLICATION_VERIFICATION:
+ return _("Error in application verification");
+ default:
+ return _("Unknown");
}
-
- g_free (filename);
-
- return st.st_uid == getuid ();
}
static int
ssl_verify (int ok, X509_STORE_CTX *ctx)
{
+ unsigned char md5sum[16], fingerprint[40], *f;
CamelTcpStreamSSL *stream;
+ CamelService *service;
+ CamelCertDB *certdb = NULL;
+ CamelCert *ccert = NULL;
+ char *prompt, *cert_str;
+ char buf[257];
X509 *cert;
SSL *ssl;
- int err;
+ int i, err;
+
+ if (ok)
+ return TRUE;
ssl = X509_STORE_CTX_get_ex_data (ctx, SSL_get_ex_data_X509_STORE_CTX_idx ());
stream = SSL_CTX_get_app_data (ssl->ctx);
+ if (!stream)
+ return FALSE;
+
+ service = stream->priv->service;
cert = X509_STORE_CTX_get_current_cert (ctx);
err = X509_STORE_CTX_get_error (ctx);
- if (stream)
- ok = ssl_cert_is_saved (stream->priv->expected_host);
+ /* calculate the MD5 hash of the raw certificate */
+ X509_digest (cert, EVP_md5 (), md5sum, sizeof (md5sum));
+ for (i = 0, f = fingerprint; i < 16; i++, f += 3)
+ sprintf (f, "%.2x%c", md5sum[i], i != 15 ? ':' : '\0');
- if (!ok && stream) {
- CamelService *service = stream->priv->service;
- char *prompt, *cert_str;
- char buf[257];
-
#define GET_STRING(name) X509_NAME_oneline (name, buf, 256)
-
- cert_str = g_strdup_printf (_("Issuer: %s\n"
- "Subject: %s"),
- GET_STRING (X509_get_issuer_name (cert)),
- GET_STRING (X509_get_subject_name (cert)));
-
- prompt = g_strdup_printf (_("Bad certificate from %s:\n\n%s\n\n"
- "Do you wish to accept anyway?"),
- service->url->host, cert_str);
-
- ok = camel_session_alert_user (service->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE);
- g_free (prompt);
-
- if (ok)
- save_ssl_cert (stream->priv->expected_host);
+
+ certdb = camel_certdb_get_default ();
+ if (certdb) {
+ ccert = camel_certdb_get_cert (certdb, fingerprint);
+ if (ccert) {
+ if (ccert->trust != CAMEL_CERT_TRUST_UNKNOWN) {
+ accept = ccert->trust != CAMEL_CERT_TRUST_NEVER;
+ camel_certdb_cert_unref (certdb, ccert);
+ camel_object_unref (certdb);
+
+ return accept;
+ }
+ } else {
+ /* create a new camel-cert */
+ ccert = camel_certdb_cert_new (certdb);
+ camel_cert_set_issuer (certdb, ccert, GET_STRING (X509_get_issuer_name (cert)));
+ camel_cert_set_subject (certdb, ccert, GET_STRING (X509_get_subject_name (cert)));
+ camel_cert_set_hostname (certdb, ccert, stream->priv->expected_host);
+ camel_cert_set_fingerprint (certdb, ccert, fingerprint);
+ camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
+
+ /* Add the certificate to our db */
+ camel_certdb_add (certdb, ccert);
+ }
+ }
+
+ cert_str = g_strdup_printf (_("Issuer: %s\n"
+ "Subject: %s\n"
+ "Fingerprint: %s\n"
+ "Signature: %s"),
+ GET_STRING (X509_get_issuer_name (cert)),
+ GET_STRING (X509_get_subject_name (cert)),
+ fingerprint, cert->valid ? _("GOOD") : _("BAD"));
+
+ prompt = g_strdup_printf (_("Bad certificate from %s:\n\n%s\n\n%s\n\n"
+ "Do you wish to accept anyway?"),
+ service->url->host, cert_str, x509_strerror (err));
+
+ ok = camel_session_alert_user (service->session, CAMEL_SESSION_ALERT_WARNING, prompt, TRUE);
+ g_free (prompt);
+
+ if (ok && ccert) {
+ camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_FULLY);
+ camel_certdb_touch (certdb);
+ }
+
+ if (certdb) {
+ camel_certdb_cert_unref (certdb, ccert);
+ camel_object_unref (certdb);
}
return ok;
diff --git a/camel/camel-tcp-stream-ssl.c b/camel/camel-tcp-stream-ssl.c
index 31a69eb371..15b3dcd8fb 100644
--- a/camel/camel-tcp-stream-ssl.c
+++ b/camel/camel-tcp-stream-ssl.c
@@ -27,6 +27,7 @@
* will be used instead.
*/
+
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
@@ -55,7 +56,7 @@
#include "camel-tcp-stream-ssl.h"
#include "camel-session.h"
-
+#include "camel-certdb.h"
/* from md5-utils.h */
void md5_get_digest (const char *buffer, int buffer_size, unsigned char digest[16]);
@@ -468,6 +469,8 @@ ssl_bad_cert (void *data, PRFileDesc *sockfd)
{
unsigned char md5sum[16], fingerprint[40], *f;
gboolean accept, valid_cert;
+ CamelCertDB *certdb = NULL;
+ CamelCert *ccert = NULL;
char *prompt, *cert_str;
CamelTcpStreamSSL *ssl;
CERTCertificate *cert;
@@ -492,6 +495,32 @@ ssl_bad_cert (void *data, PRFileDesc *sockfd)
/*issuer = CERT_FindCertByName (CERT_GetDefaultCertDB (), &cert->derIssuer);
valid_cert = issuer && CERT_VerifySignedData (&cert->signatureWrap, issuer, PR_Now (), NULL);*/
+ /* first check our own certificate database to see if we accepted the cert (nss's certdb seems to not work) */
+ certdb = camel_certdb_get_default ();
+ if (certdb) {
+ ccert = camel_certdb_get_cert (certdb, fingerprint);
+ if (ccert) {
+ if (ccert->trust != CAMEL_CERT_TRUST_UNKNOWN) {
+ accept = ccert->trust != CAMEL_CERT_TRUST_NEVER;
+ camel_certdb_cert_unref (certdb, ccert);
+ camel_object_unref (certdb);
+
+ return accept ? SECSuccess : SECFailure;
+ }
+ } else {
+ /* create a new camel-cert */
+ ccert = camel_certdb_cert_new (certdb);
+ camel_cert_set_issuer (certdb, ccert, CERT_NameToAscii (&cert->issuer));
+ camel_cert_set_subject (certdb, ccert, CERT_NameToAscii (&cert->subject));
+ camel_cert_set_hostname (certdb, ccert, ssl->priv->expected_host);
+ camel_cert_set_fingerprint (certdb, ccert, fingerprint);
+ camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_UNKNOWN);
+
+ /* Add the certificate to our db */
+ camel_certdb_add (certdb, ccert);
+ }
+ }
+
cert_str = g_strdup_printf (_("Issuer: %s\n"
"Subject: %s\n"
"Fingerprint: %s\n"
@@ -533,10 +562,19 @@ ssl_bad_cert (void *data, PRFileDesc *sockfd)
CERT_ImportCerts (CERT_GetDefaultCertDB (), certUsageSSLServer, 1, certs,
NULL, TRUE, FALSE, cert->nickname);
#endif
- return SECSuccess;
+
+ if (ccert) {
+ camel_cert_set_trust (certdb, ccert, CAMEL_CERT_TRUST_FULLY);
+ camel_certdb_touch (certdb);
+ }
+ }
+
+ if (certdb) {
+ camel_certdb_cert_unref (certdb, ccert);
+ camel_object_unref (certdb);
}
- return SECFailure;
+ return accept ? SECSuccess : SECFailure;
}
static PRFileDesc *
diff --git a/camel/camel.c b/camel/camel.c
index 25807b5aac..7b250a14b1 100644
--- a/camel/camel.c
+++ b/camel/camel.c
@@ -36,6 +36,7 @@
#endif /* HAVE_NSS */
#include "camel.h"
+#include "camel-certdb.h"
#include "camel-mime-utils.h"
gboolean camel_verbose_debug = FALSE;
@@ -44,15 +45,26 @@ gboolean camel_verbose_debug = FALSE;
static void
camel_shutdown (void)
{
+ CamelCertDB *certdb;
+
NSS_Shutdown ();
PR_Cleanup ();
+
+ certdb = camel_certdb_get_default ();
+ if (certdb) {
+ camel_certdb_save (certdb);
+ camel_object_unref (certdb);
+ }
}
#endif /* HAVE_NSS */
gint
camel_init (const char *configdir, gboolean nss_init)
{
+ CamelCertDB *certdb;
+ char *path;
+
#ifdef ENABLE_THREADS
#ifdef G_THREADS_ENABLED
/*g_thread_init (NULL);*/
@@ -68,7 +80,7 @@ camel_init (const char *configdir, gboolean nss_init)
camel_object_get_type();
camel_mime_utils_init();
-
+
#ifdef HAVE_NSS
if (nss_init) {
PR_Init (PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 10);
@@ -92,5 +104,18 @@ camel_init (const char *configdir, gboolean nss_init)
SSL_OptionSetDefault (SSL_V2_COMPATIBLE_HELLO, PR_TRUE /* maybe? */);
#endif /* HAVE_NSS */
+ path = g_strdup_printf ("%s/camel-cert.db", configdir);
+ certdb = camel_certdb_new ();
+ camel_certdb_set_filename (certdb, path);
+ g_free (path);
+
+ /* if we fail to load, who cares? it'll just be a volatile certdb */
+ camel_certdb_load (certdb);
+
+ /* set this certdb as the default db */
+ camel_certdb_set_default (certdb);
+
+ camel_object_unref (certdb);
+
return 0;
}