Mercurial > pidgin
view libpurple/plugins/ssl/ssl-nss.c @ 19083:5b8035030053
- Comment on NSS's refcounting prowess
author | William Ehlhardt <williamehlhardt@gmail.com> |
---|---|
date | Fri, 10 Aug 2007 03:51:49 +0000 |
parents | 3004bfa0e846 |
children | 7fa5d10969f4 |
line wrap: on
line source
/** * @file ssl-nss.c Mozilla NSS SSL plugin. * * purple * * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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 */ #include "internal.h" #include "debug.h" #include "certificate.h" #include "plugin.h" #include "sslconn.h" #include "version.h" #define SSL_NSS_PLUGIN_ID "ssl-nss" #ifdef HAVE_NSS #undef HAVE_LONG_LONG /* Make Mozilla less angry. If angry, Mozilla SMASH! */ #include <nspr.h> #include <nss.h> #include <pk11func.h> #include <prio.h> #include <secerr.h> #include <secmod.h> #include <ssl.h> #include <sslerr.h> #include <sslproto.h> /* This is defined in NSPR's <private/pprio.h>, but to avoid including a * private header we duplicate the prototype here */ NSPR_API(PRFileDesc*) PR_ImportTCPSocket(PRInt32 osfd); typedef struct { PRFileDesc *fd; PRFileDesc *in; guint handshake_handler; } PurpleSslNssData; #define PURPLE_SSL_NSS_DATA(gsc) ((PurpleSslNssData *)gsc->private_data) static const PRIOMethods *_nss_methods = NULL; static PRDescIdentity _identity; /* Thank you, Evolution */ static void set_errno(int code) { /* FIXME: this should handle more. */ switch (code) { case PR_INVALID_ARGUMENT_ERROR: errno = EINVAL; break; case PR_PENDING_INTERRUPT_ERROR: errno = EINTR; break; case PR_IO_PENDING_ERROR: errno = EAGAIN; break; case PR_WOULD_BLOCK_ERROR: errno = EAGAIN; /*errno = EWOULDBLOCK; */ break; case PR_IN_PROGRESS_ERROR: errno = EINPROGRESS; break; case PR_ALREADY_INITIATED_ERROR: errno = EALREADY; break; case PR_NETWORK_UNREACHABLE_ERROR: errno = EHOSTUNREACH; break; case PR_CONNECT_REFUSED_ERROR: errno = ECONNREFUSED; break; case PR_CONNECT_TIMEOUT_ERROR: case PR_IO_TIMEOUT_ERROR: errno = ETIMEDOUT; break; case PR_NOT_CONNECTED_ERROR: errno = ENOTCONN; break; case PR_CONNECT_RESET_ERROR: errno = ECONNRESET; break; case PR_IO_ERROR: default: errno = EIO; break; } } static void ssl_nss_init_nss(void) { char *lib; PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); NSS_NoDB_Init("."); /* TODO: Fix this so autoconf does the work trying to find this lib. */ #ifndef _WIN32 lib = g_strdup(LIBDIR "/libnssckbi.so"); #else lib = g_strdup("nssckbi.dll"); #endif SECMOD_AddNewModule("Builtins", lib, 0, 0); g_free(lib); NSS_SetDomesticPolicy(); _identity = PR_GetUniqueIdentity("Purple"); _nss_methods = PR_GetDefaultIOMethods(); } static SECStatus ssl_auth_cert(void *arg, PRFileDesc *socket, PRBool checksig, PRBool is_server) { return SECSuccess; #if 0 CERTCertificate *cert; void *pinArg; SECStatus status; cert = SSL_PeerCertificate(socket); pinArg = SSL_RevealPinArg(socket); status = CERT_VerifyCertNow((CERTCertDBHandle *)arg, cert, checksig, certUsageSSLClient, pinArg); if (status != SECSuccess) { purple_debug_error("nss", "CERT_VerifyCertNow failed\n"); CERT_DestroyCertificate(cert); return status; } CERT_DestroyCertificate(cert); return SECSuccess; #endif } static SECStatus ssl_bad_cert(void *arg, PRFileDesc *socket) { SECStatus status = SECFailure; PRErrorCode err; if (arg == NULL) return status; *(PRErrorCode *)arg = err = PORT_GetError(); switch (err) { case SEC_ERROR_INVALID_AVA: case SEC_ERROR_INVALID_TIME: case SEC_ERROR_BAD_SIGNATURE: case SEC_ERROR_EXPIRED_CERTIFICATE: case SEC_ERROR_UNKNOWN_ISSUER: case SEC_ERROR_UNTRUSTED_CERT: case SEC_ERROR_CERT_VALID: case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: case SEC_ERROR_CRL_EXPIRED: case SEC_ERROR_CRL_BAD_SIGNATURE: case SEC_ERROR_EXTENSION_VALUE_INVALID: case SEC_ERROR_CA_CERT_INVALID: case SEC_ERROR_CERT_USAGES_INVALID: case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: status = SECSuccess; break; default: status = SECFailure; break; } purple_debug_error("nss", "Bad certificate: %d\n", err); return status; } static gboolean ssl_nss_init(void) { return TRUE; } static void ssl_nss_uninit(void) { PR_Cleanup(); _nss_methods = NULL; } static void ssl_nss_handshake_cb(gpointer data, int fd, PurpleInputCondition cond) { PurpleSslConnection *gsc = (PurpleSslConnection *)data; PurpleSslNssData *nss_data = gsc->private_data; /* I don't think this the best way to do this... * It seems to work because it'll eventually use the cached value */ if(SSL_ForceHandshake(nss_data->in) != SECSuccess) { set_errno(PR_GetError()); if (errno == EAGAIN || errno == EWOULDBLOCK) return; purple_debug_error("nss", "Handshake failed %d\n", PR_GetError()); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data); purple_ssl_close(gsc); return; } purple_input_remove(nss_data->handshake_handler); nss_data->handshake_handler = 0; gsc->connect_cb(gsc->connect_cb_data, gsc, cond); } static void ssl_nss_connect(PurpleSslConnection *gsc) { PurpleSslNssData *nss_data = g_new0(PurpleSslNssData, 1); PRSocketOptionData socket_opt; gsc->private_data = nss_data; nss_data->fd = PR_ImportTCPSocket(gsc->fd); if (nss_data->fd == NULL) { purple_debug_error("nss", "nss_data->fd == NULL!\n"); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_CONNECT_FAILED, gsc->connect_cb_data); purple_ssl_close((PurpleSslConnection *)gsc); return; } socket_opt.option = PR_SockOpt_Nonblocking; socket_opt.value.non_blocking = PR_TRUE; if (PR_SetSocketOption(nss_data->fd, &socket_opt) != PR_SUCCESS) purple_debug_warning("nss", "unable to set socket into non-blocking mode: %d\n", PR_GetError()); nss_data->in = SSL_ImportFD(NULL, nss_data->fd); if (nss_data->in == NULL) { purple_debug_error("nss", "nss_data->in == NUL!\n"); if (gsc->error_cb != NULL) gsc->error_cb(gsc, PURPLE_SSL_CONNECT_FAILED, gsc->connect_cb_data); purple_ssl_close((PurpleSslConnection *)gsc); return; } SSL_OptionSet(nss_data->in, SSL_SECURITY, PR_TRUE); SSL_OptionSet(nss_data->in, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE); SSL_AuthCertificateHook(nss_data->in, (SSLAuthCertificate)ssl_auth_cert, (void *)CERT_GetDefaultCertDB()); SSL_BadCertHook(nss_data->in, (SSLBadCertHandler)ssl_bad_cert, NULL); if(gsc->host) SSL_SetURL(nss_data->in, gsc->host); #if 0 /* This seems like it'd the be the correct way to implement the nonblocking stuff, but it doesn't seem to work */ SSL_HandshakeCallback(nss_data->in, (SSLHandshakeCallback) ssl_nss_handshake_cb, gsc); #endif SSL_ResetHandshake(nss_data->in, PR_FALSE); nss_data->handshake_handler = purple_input_add(gsc->fd, PURPLE_INPUT_READ, ssl_nss_handshake_cb, gsc); ssl_nss_handshake_cb(gsc, gsc->fd, PURPLE_INPUT_READ); } static void ssl_nss_close(PurpleSslConnection *gsc) { PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc); if(!nss_data) return; if (nss_data->in) { PR_Close(nss_data->in); gsc->fd = -1; } else if (nss_data->fd) { PR_Close(nss_data->fd); gsc->fd = -1; } if (nss_data->handshake_handler) purple_input_remove(nss_data->handshake_handler); g_free(nss_data); gsc->private_data = NULL; } static size_t ssl_nss_read(PurpleSslConnection *gsc, void *data, size_t len) { ssize_t ret; PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc); ret = PR_Read(nss_data->in, data, len); if (ret == -1) set_errno(PR_GetError()); return ret; } static size_t ssl_nss_write(PurpleSslConnection *gsc, const void *data, size_t len) { ssize_t ret; PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc); if(!nss_data) return 0; ret = PR_Write(nss_data->in, data, len); if (ret == -1) set_errno(PR_GetError()); return ret; } static GList * ssl_nss_peer_certs(PurpleSslConnection *gsc) { PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc); GList *chain = NULL; CERTCertificate *cert; void *pinArg; SECStatus status; /* TODO: this is a blind guess */ cert = SSL_PeerCertificate(nss_data->fd); return NULL; } /************************************************************************/ /* X.509 functionality */ /************************************************************************/ static PurpleCertificateScheme x509_nss; /** Helpr macro to retrieve the NSS certdata from a PurpleCertificate */ #define X509_NSS_DATA(pcrt) ( (CERTCertificate * ) (pcrt->data) ) /** Imports a PEM-formatted X.509 certificate from the specified file. * @param filename Filename to import from. Format is PEM * * @return A newly allocated Certificate structure of the x509_gnutls scheme */ static PurpleCertificate * x509_import_from_file(const gchar *filename) { /* TODO: Write me! */ return NULL; } /** * Exports a PEM-formatted X.509 certificate to the specified file. * @param filename Filename to export to. Format will be PEM * @param crt Certificate to export * * @return TRUE if success, otherwise FALSE */ static gboolean x509_export_certificate(const gchar *filename, PurpleCertificate *crt) { /* TODO: WRITEME */ return FALSE; } static PurpleCertificate * x509_copy_certificate(PurpleCertificate *crt) { CERTCertificate *crt_dat; PurpleCertificate *newcrt; g_return_val_if_fail(crt, NULL); g_return_val_if_fail(crt->scheme == &x509_nss, NULL); crt_dat = X509_NSS_DATA(crt); g_return_val_if_fail(crt_dat, NULL); /* Create the certificate copy */ newcrt = g_new0(PurpleCertificate, 1); newcrt->scheme = &x509_nss; /* NSS does refcounting automatically */ newcrt->data = CERT_DupCertificate(crt_dat); return newcrt; } /** Frees a Certificate * * Destroys a Certificate's internal data structures and frees the pointer * given. * @param crt Certificate instance to be destroyed. It WILL NOT be destroyed * if it is not of the correct CertificateScheme. Can be NULL * */ static void x509_destroy_certificate(PurpleCertificate * crt) { CERTCertificate *crt_dat; g_return_if_fail(crt); g_return_if_fail(crt->scheme == &x509_nss); crt_dat = X509_NSS_DATA(crt); g_return_if_fail(crt_dat); /* Finally we have the certificate. So let's kill it */ /* NSS does refcounting automatically */ CERT_DestroyCertificate(crt_dat); } /** Determines whether one certificate has been issued and signed by another * * @param crt Certificate to check the signature of * @param issuer Issuer's certificate * * @return TRUE if crt was signed and issued by issuer, otherwise FALSE * @TODO Modify this function to return a reason for invalidity? */ static gboolean x509_certificate_signed_by(PurpleCertificate * crt, PurpleCertificate * issuer) { return FALSE; } static GByteArray * x509_sha1sum(PurpleCertificate *crt) { CERTCertificate *crt_dat; size_t hashlen = 20; /* Size of an sha1sum */ GByteArray *sha1sum; SECItem *derCert; /* DER representation of the cert */ SECStatus st; g_return_val_if_fail(crt, NULL); g_return_val_if_fail(crt->scheme == &x509_nss, NULL); crt_dat = X509_NSS_DATA(crt); g_return_val_if_fail(crt_dat, NULL); /* Get the certificate DER representation */ derCert = &(crt_dat->derCert); /* Make a hash! */ sha1sum = g_byte_array_sized_new(hashlen); st = PK11_HashBuf(SEC_OID_SHA1, sha1sum->data, derCert->data, derCert->len); /* Check for errors */ if (st != SECSuccess) { g_byte_array_free(sha1sum, TRUE); purple_debug_error("nss/x509", "Error: hashing failed!\n"); return NULL; } return sha1sum; } static gchar * x509_common_name (PurpleCertificate *crt) { CERTCertificate *crt_dat; char *nss_cn; gchar *ret_cn; g_return_val_if_fail(crt, NULL); g_return_val_if_fail(crt->scheme == &x509_nss, NULL); crt_dat = X509_NSS_DATA(crt); g_return_val_if_fail(crt_dat, NULL); /* Q: Why get a newly allocated string out of NSS, strdup it, and then return the new copy? A: The NSS LXR docs state that I should use the NSPR free functions on the strings that the NSS cert functions return. Since the libpurple API expects a g_free()-able string, we make our own copy and return that. NSPR is something of a prima donna. */ nss_cn = CERT_GetCommonName( &(crt_dat->subject) ); ret_cn = g_strdup(nss_cn); PORT_Free(nss_cn); return ret_cn; } static gboolean x509_check_name (PurpleCertificate *crt, const gchar *name) { CERTCertificate *crt_dat; SECStatus st; g_return_val_if_fail(crt, FALSE); g_return_val_if_fail(crt->scheme == &x509_nss, FALSE); crt_dat = X509_NSS_DATA(crt); g_return_val_if_fail(crt_dat, FALSE); st = CERT_VerifyCertName(crt_dat, name); if (st == SECSuccess) { return TRUE; } else if (st == SECFailure) { return FALSE; } /* If we get here...bad things! */ g_assert(FALSE); return FALSE; } static gboolean x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration) { CERTCertificate *crt_dat; PRTime nss_activ, nss_expir; g_return_val_if_fail(crt, FALSE); g_return_val_if_fail(crt->scheme == &x509_nss, FALSE); crt_dat = X509_NSS_DATA(crt); g_return_val_if_fail(crt_dat, FALSE); /* Extract the times into ugly PRTime thingies */ /* TODO: Maybe this shouldn't throw an error? */ g_return_val_if_fail( SECSuccess == CERT_GetCertTimes(crt_dat, &nss_activ, &nss_expir), FALSE); if (activation) { *activation = nss_activ; } if (expiration) { *expiration = nss_expir; } return TRUE; } static PurpleCertificateScheme x509_nss = { "x509", /* Scheme name */ N_("X.509 Certificates"), /* User-visible scheme name */ x509_import_from_file, /* Certificate import function */ x509_export_certificate, /* Certificate export function */ x509_copy_certificate, /* Copy */ x509_destroy_certificate, /* Destroy cert */ x509_sha1sum, /* SHA1 fingerprint */ NULL, /* Unique ID */ NULL, /* Issuer Unique ID */ x509_common_name, /* Subject name */ x509_check_name, /* Check subject name */ x509_times /* Activation/Expiration time */ }; static PurpleSslOps ssl_ops = { ssl_nss_init, ssl_nss_uninit, ssl_nss_connect, ssl_nss_close, ssl_nss_read, ssl_nss_write, ssl_nss_peer_certs, /* padding */ NULL, NULL, NULL }; #endif /* HAVE_NSS */ static gboolean plugin_load(PurplePlugin *plugin) { #ifdef HAVE_NSS if (!purple_ssl_get_ops()) { purple_ssl_set_ops(&ssl_ops); } /* Init NSS now, so others can use it even if sslconn never does */ ssl_nss_init_nss(); /* Register the X.509 functions we provide */ purple_certificate_register_scheme(&x509_nss); return TRUE; #else return FALSE; #endif } static gboolean plugin_unload(PurplePlugin *plugin) { #ifdef HAVE_NSS if (purple_ssl_get_ops() == &ssl_ops) { purple_ssl_set_ops(NULL); } /* Unregister our X.509 functions */ purple_certificate_unregister_scheme(&x509_nss); #endif return TRUE; } static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_STANDARD, /**< type */ NULL, /**< ui_requirement */ PURPLE_PLUGIN_FLAG_INVISIBLE, /**< flags */ NULL, /**< dependencies */ PURPLE_PRIORITY_DEFAULT, /**< priority */ SSL_NSS_PLUGIN_ID, /**< id */ N_("NSS"), /**< name */ VERSION, /**< version */ /** summary */ N_("Provides SSL support through Mozilla NSS."), /** description */ N_("Provides SSL support through Mozilla NSS."), "Christian Hammond <chipx86@gnupdate.org>", PURPLE_WEBSITE, /**< homepage */ plugin_load, /**< load */ plugin_unload, /**< unload */ NULL, /**< destroy */ NULL, /**< ui_info */ NULL, /**< extra_info */ NULL, /**< prefs_info */ NULL, /**< actions */ /* padding */ NULL, NULL, NULL, NULL }; static void init_plugin(PurplePlugin *plugin) { } PURPLE_INIT_PLUGIN(ssl_nss, init_plugin, info)