diff options
-rw-r--r-- | camel/ChangeLog | 15 | ||||
-rw-r--r-- | camel/Makefile.am | 4 | ||||
-rw-r--r-- | camel/camel-folder.h | 2 | ||||
-rw-r--r-- | camel/camel-private.h | 20 | ||||
-rw-r--r-- | camel/camel-tcp-stream-openssl.c | 202 | ||||
-rw-r--r-- | camel/camel-tcp-stream-ssl.c | 44 | ||||
-rw-r--r-- | camel/camel.c | 27 |
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; } |