libpurple/plugins/ssl/ssl-nss.c
author Tomasz Wasilczyk <tomkiewicz@cpw.pidgin.im>
Tue, 04 Sep 2012 01:49:58 +0200
changeset 52cc04429e2c 52cc04429e2c
parent dc8991868906
child 0d7ead568881
permissions -rw-r--r--
Gadu-Gadu: small bugfixes
     1 /**
     2  * @file ssl-nss.c Mozilla NSS SSL plugin.
     3  *
     4  * purple
     5  *
     6  * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
     7  *
     8  * This program is free software; you can redistribute it and/or modify
     9  * it under the terms of the GNU General Public License as published by
    10  * the Free Software Foundation; either version 2 of the License, or
    11  * (at your option) any later version.
    12  *
    13  * This program is distributed in the hope that it will be useful,
    14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    16  * GNU General Public License for more details.
    17  *
    18  * You should have received a copy of the GNU General Public License
    19  * along with this program; if not, write to the Free Software
    20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
    21  */
    22 #include "internal.h"
    23 #include "debug.h"
    24 #include "certificate.h"
    25 #include "plugin.h"
    26 #include "sslconn.h"
    27 #include "util.h"
    28 #include "version.h"
    29 
    30 #define SSL_NSS_PLUGIN_ID "ssl-nss"
    31 
    32 #undef HAVE_LONG_LONG /* Make Mozilla less angry. If angry, Mozilla SMASH! */
    33 
    34 #include <nspr.h>
    35 #include <nss.h>
    36 #include <nssb64.h>
    37 #include <pk11func.h>
    38 #include <prio.h>
    39 #include <secerr.h>
    40 #include <secmod.h>
    41 #include <ssl.h>
    42 #include <sslerr.h>
    43 #include <sslproto.h>
    44 
    45 /* This is defined in NSPR's <private/pprio.h>, but to avoid including a
    46  * private header we duplicate the prototype here */
    47 NSPR_API(PRFileDesc*)  PR_ImportTCPSocket(PRInt32 osfd);
    48 
    49 typedef struct
    50 {
    51 	PRFileDesc *fd;
    52 	PRFileDesc *in;
    53 	guint handshake_handler;
    54 	guint handshake_timer;
    55 } PurpleSslNssData;
    56 
    57 #define PURPLE_SSL_NSS_DATA(gsc) ((PurpleSslNssData *)gsc->private_data)
    58 
    59 static const PRIOMethods *_nss_methods = NULL;
    60 static PRDescIdentity _identity;
    61 static PurpleCertificateScheme x509_nss;
    62 
    63 /* Thank you, Evolution */
    64 static void
    65 set_errno(int code)
    66 {
    67 	/* FIXME: this should handle more. */
    68 	switch (code) {
    69 	case PR_INVALID_ARGUMENT_ERROR:
    70 		errno = EINVAL;
    71 		break;
    72 	case PR_PENDING_INTERRUPT_ERROR:
    73 		errno = EINTR;
    74 		break;
    75 	case PR_IO_PENDING_ERROR:
    76 		errno = EAGAIN;
    77 		break;
    78 	case PR_WOULD_BLOCK_ERROR:
    79 		errno = EAGAIN;
    80 		/*errno = EWOULDBLOCK; */
    81 		break;
    82 	case PR_IN_PROGRESS_ERROR:
    83 		errno = EINPROGRESS;
    84 		break;
    85 	case PR_ALREADY_INITIATED_ERROR:
    86 		errno = EALREADY;
    87 		break;
    88 	case PR_NETWORK_UNREACHABLE_ERROR:
    89 		errno = EHOSTUNREACH;
    90 		break;
    91 	case PR_CONNECT_REFUSED_ERROR:
    92 		errno = ECONNREFUSED;
    93 		break;
    94 	case PR_CONNECT_TIMEOUT_ERROR:
    95 	case PR_IO_TIMEOUT_ERROR:
    96 		errno = ETIMEDOUT;
    97 		break;
    98 	case PR_NOT_CONNECTED_ERROR:
    99 		errno = ENOTCONN;
   100 		break;
   101 	case PR_CONNECT_RESET_ERROR:
   102 		errno = ECONNRESET;
   103 		break;
   104 	case PR_IO_ERROR:
   105 	default:
   106 		errno = EIO;
   107 		break;
   108 	}
   109 }
   110 
   111 static gchar *get_error_text(void)
   112 {
   113 	PRInt32 len = PR_GetErrorTextLength();
   114 	gchar *ret = NULL;
   115 
   116 	if (len > 0) {
   117 		ret = g_malloc(len + 1);
   118 		len = PR_GetErrorText(ret);
   119 		ret[len] = '\0';
   120 	}
   121 
   122 	return ret;
   123 }
   124 
   125 static void
   126 ssl_nss_init_nss(void)
   127 {
   128 	char *lib;
   129 	PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
   130 	NSS_NoDB_Init(".");
   131 
   132 	/* TODO: Fix this so autoconf does the work trying to find this lib. */
   133 #ifndef _WIN32
   134 	lib = g_strdup(LIBDIR "/libnssckbi.so");
   135 #else
   136 	lib = g_strdup("nssckbi.dll");
   137 #endif
   138 	SECMOD_AddNewModule("Builtins", lib, 0, 0);
   139 	g_free(lib);
   140 	NSS_SetDomesticPolicy();
   141 
   142 	SSL_CipherPrefSetDefault(TLS_DHE_RSA_WITH_AES_256_CBC_SHA, 1);
   143 	SSL_CipherPrefSetDefault(TLS_DHE_DSS_WITH_AES_256_CBC_SHA, 1);
   144 	SSL_CipherPrefSetDefault(TLS_RSA_WITH_AES_256_CBC_SHA, 1);
   145 	SSL_CipherPrefSetDefault(TLS_DHE_DSS_WITH_RC4_128_SHA, 1);
   146 	SSL_CipherPrefSetDefault(TLS_DHE_RSA_WITH_AES_128_CBC_SHA, 1);
   147 	SSL_CipherPrefSetDefault(TLS_DHE_DSS_WITH_AES_128_CBC_SHA, 1);
   148 	SSL_CipherPrefSetDefault(SSL_RSA_WITH_RC4_128_SHA, 1);
   149 	SSL_CipherPrefSetDefault(TLS_RSA_WITH_AES_128_CBC_SHA, 1);
   150 	SSL_CipherPrefSetDefault(SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, 1);
   151 	SSL_CipherPrefSetDefault(SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, 1);
   152 	SSL_CipherPrefSetDefault(SSL_DHE_RSA_WITH_DES_CBC_SHA, 1);
   153 	SSL_CipherPrefSetDefault(SSL_DHE_DSS_WITH_DES_CBC_SHA, 1);
   154 
   155 	_identity = PR_GetUniqueIdentity("Purple");
   156 	_nss_methods = PR_GetDefaultIOMethods();
   157 }
   158 
   159 static SECStatus
   160 ssl_auth_cert(void *arg, PRFileDesc *socket, PRBool checksig,
   161 			  PRBool is_server)
   162 {
   163 	return SECSuccess;
   164 
   165 #if 0
   166 	CERTCertificate *cert;
   167 	void *pinArg;
   168 	SECStatus status;
   169 
   170 	cert = SSL_PeerCertificate(socket);
   171 	pinArg = SSL_RevealPinArg(socket);
   172 
   173 	status = CERT_VerifyCertNow((CERTCertDBHandle *)arg, cert, checksig,
   174 								certUsageSSLClient, pinArg);
   175 
   176 	if (status != SECSuccess) {
   177 		purple_debug_error("nss", "CERT_VerifyCertNow failed\n");
   178 		CERT_DestroyCertificate(cert);
   179 		return status;
   180 	}
   181 
   182 	CERT_DestroyCertificate(cert);
   183 	return SECSuccess;
   184 #endif
   185 }
   186 
   187 #if 0
   188 static SECStatus
   189 ssl_bad_cert(void *arg, PRFileDesc *socket)
   190 {
   191 	SECStatus status = SECFailure;
   192 	PRErrorCode err;
   193 
   194 	if (arg == NULL)
   195 		return status;
   196 
   197 	*(PRErrorCode *)arg = err = PORT_GetError();
   198 
   199 	switch (err)
   200 	{
   201 		case SEC_ERROR_INVALID_AVA:
   202 		case SEC_ERROR_INVALID_TIME:
   203 		case SEC_ERROR_BAD_SIGNATURE:
   204 		case SEC_ERROR_EXPIRED_CERTIFICATE:
   205 		case SEC_ERROR_UNKNOWN_ISSUER:
   206 		case SEC_ERROR_UNTRUSTED_CERT:
   207 		case SEC_ERROR_CERT_VALID:
   208 		case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
   209 		case SEC_ERROR_CRL_EXPIRED:
   210 		case SEC_ERROR_CRL_BAD_SIGNATURE:
   211 		case SEC_ERROR_EXTENSION_VALUE_INVALID:
   212 		case SEC_ERROR_CA_CERT_INVALID:
   213 		case SEC_ERROR_CERT_USAGES_INVALID:
   214 		case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
   215 			status = SECSuccess;
   216 			break;
   217 
   218 		default:
   219 			status = SECFailure;
   220 			break;
   221 	}
   222 
   223 	purple_debug_error("nss", "Bad certificate: %d\n", err);
   224 
   225 	return status;
   226 }
   227 #endif
   228 
   229 static gboolean
   230 ssl_nss_init(void)
   231 {
   232    return TRUE;
   233 }
   234 
   235 static void
   236 ssl_nss_uninit(void)
   237 {
   238 	NSS_Shutdown();
   239 	PR_Cleanup();
   240 
   241 	_nss_methods = NULL;
   242 }
   243 
   244 static void
   245 ssl_nss_verified_cb(PurpleCertificateVerificationStatus st,
   246 		       gpointer userdata)
   247 {
   248 	PurpleSslConnection *gsc = (PurpleSslConnection *) userdata;
   249 
   250 	if (st == PURPLE_CERTIFICATE_VALID) {
   251 		/* Certificate valid? Good! Do the connection! */
   252 		gsc->connect_cb(gsc->connect_cb_data, gsc, PURPLE_INPUT_READ);
   253 	} else {
   254 		/* Otherwise, signal an error */
   255 		if(gsc->error_cb != NULL)
   256 			gsc->error_cb(gsc, PURPLE_SSL_CERTIFICATE_INVALID,
   257 				      gsc->connect_cb_data);
   258 		purple_ssl_close(gsc);
   259 	}
   260 }
   261 
   262 /** Transforms an NSS containing an X.509 certificate into a Certificate instance
   263  *
   264  * @param cert   Certificate to transform
   265  * @return A newly allocated Certificate
   266  */
   267 static PurpleCertificate *
   268 x509_import_from_nss(CERTCertificate* cert)
   269 {
   270 	/* New certificate to return */
   271 	PurpleCertificate * crt;
   272 
   273 	/* Allocate the certificate and load it with data */
   274 	crt = g_new0(PurpleCertificate, 1);
   275 	crt->scheme = &x509_nss;
   276 	crt->data = CERT_DupCertificate(cert);
   277 
   278 	return crt;
   279 }
   280 
   281 static GList *
   282 ssl_nss_get_peer_certificates(PRFileDesc *socket, PurpleSslConnection * gsc)
   283 {
   284 	CERTCertificate *curcert;
   285 	CERTCertificate *issuerCert;
   286 	PurpleCertificate * newcrt;
   287 
   288 	/* List of Certificate instances to return */
   289 	GList * peer_certs = NULL;
   290 	int count;
   291 	int64 now = PR_Now();
   292 
   293 	curcert = SSL_PeerCertificate(socket);
   294 	if (curcert == NULL) {
   295 		purple_debug_error("nss", "could not DupCertificate\n");
   296 		return NULL;
   297 	}
   298 
   299 	for (count = 0 ; count < CERT_MAX_CERT_CHAIN ; count++) {
   300 		purple_debug_info("nss", "subject=%s issuer=%s\n", curcert->subjectName,
   301 						  curcert->issuerName  ? curcert->issuerName : "(null)");
   302 		newcrt = x509_import_from_nss(curcert);
   303 		peer_certs = g_list_append(peer_certs, newcrt);
   304 
   305 		if (curcert->isRoot) {
   306 			break;
   307 		}
   308 		issuerCert = CERT_FindCertIssuer(curcert, now, certUsageSSLServer);
   309 		if (!issuerCert) {
   310 			purple_debug_error("nss", "partial certificate chain\n");
   311 			break;
   312 		}
   313 		CERT_DestroyCertificate(curcert);
   314 		curcert = issuerCert;
   315 	}
   316 	CERT_DestroyCertificate(curcert);
   317 
   318 	return peer_certs;
   319 }
   320 
   321 static void
   322 ssl_nss_handshake_cb(gpointer data, int fd, PurpleInputCondition cond)
   323 {
   324 	PurpleSslConnection *gsc = (PurpleSslConnection *)data;
   325 	PurpleSslNssData *nss_data = gsc->private_data;
   326 
   327 	/* I don't think this the best way to do this...
   328 	 * It seems to work because it'll eventually use the cached value
   329 	 */
   330 	if(SSL_ForceHandshake(nss_data->in) != SECSuccess) {
   331 		gchar *error_txt;
   332 		set_errno(PR_GetError());
   333 		if (errno == EAGAIN || errno == EWOULDBLOCK)
   334 			return;
   335 
   336 		error_txt = get_error_text();
   337 		purple_debug_error("nss", "Handshake failed %s (%d)\n", error_txt ? error_txt : "", PR_GetError());
   338 		g_free(error_txt);
   339 
   340 		if (gsc->error_cb != NULL)
   341 			gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data);
   342 
   343 		purple_ssl_close(gsc);
   344 
   345 		return;
   346 	}
   347 
   348 	purple_input_remove(nss_data->handshake_handler);
   349 	nss_data->handshake_handler = 0;
   350 
   351 	/* If a Verifier was given, hand control over to it */
   352 	if (gsc->verifier) {
   353 		GList *peers;
   354 		/* First, get the peer cert chain */
   355 		peers = ssl_nss_get_peer_certificates(nss_data->in, gsc);
   356 
   357 		/* Now kick off the verification process */
   358 		purple_certificate_verify(gsc->verifier,
   359 				gsc->host,
   360 				peers,
   361 				ssl_nss_verified_cb,
   362 				gsc);
   363 
   364 		purple_certificate_destroy_list(peers);
   365 	} else {
   366 		/* Otherwise, just call the "connection complete"
   367 		   callback */
   368 		gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
   369 	}
   370 }
   371 
   372 static gboolean
   373 start_handshake_cb(gpointer data)
   374 {
   375 	PurpleSslConnection *gsc = data;
   376 	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
   377 
   378 	nss_data->handshake_timer = 0;
   379 
   380 	ssl_nss_handshake_cb(gsc, gsc->fd, PURPLE_INPUT_READ);
   381 	return FALSE;
   382 }
   383 
   384 static void
   385 ssl_nss_connect(PurpleSslConnection *gsc)
   386 {
   387 	PurpleSslNssData *nss_data = g_new0(PurpleSslNssData, 1);
   388 	PRSocketOptionData socket_opt;
   389 
   390 	gsc->private_data = nss_data;
   391 
   392 	nss_data->fd = PR_ImportTCPSocket(gsc->fd);
   393 
   394 	if (nss_data->fd == NULL)
   395 	{
   396 		purple_debug_error("nss", "nss_data->fd == NULL!\n");
   397 
   398 		if (gsc->error_cb != NULL)
   399 			gsc->error_cb(gsc, PURPLE_SSL_CONNECT_FAILED, gsc->connect_cb_data);
   400 
   401 		purple_ssl_close((PurpleSslConnection *)gsc);
   402 
   403 		return;
   404 	}
   405 
   406 	socket_opt.option = PR_SockOpt_Nonblocking;
   407 	socket_opt.value.non_blocking = PR_TRUE;
   408 
   409 	if (PR_SetSocketOption(nss_data->fd, &socket_opt) != PR_SUCCESS) {
   410 		gchar *error_txt = get_error_text();
   411 		purple_debug_warning("nss", "unable to set socket into non-blocking mode: %s (%d)\n", error_txt ? error_txt : "", PR_GetError());
   412 		g_free(error_txt);
   413 	}
   414 
   415 	nss_data->in = SSL_ImportFD(NULL, nss_data->fd);
   416 
   417 	if (nss_data->in == NULL)
   418 	{
   419 		purple_debug_error("nss", "nss_data->in == NUL!\n");
   420 
   421 		if (gsc->error_cb != NULL)
   422 			gsc->error_cb(gsc, PURPLE_SSL_CONNECT_FAILED, gsc->connect_cb_data);
   423 
   424 		purple_ssl_close((PurpleSslConnection *)gsc);
   425 
   426 		return;
   427 	}
   428 
   429 	SSL_OptionSet(nss_data->in, SSL_SECURITY,            PR_TRUE);
   430 	SSL_OptionSet(nss_data->in, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
   431 
   432 	SSL_AuthCertificateHook(nss_data->in,
   433 							(SSLAuthCertificate)ssl_auth_cert,
   434 							(void *)CERT_GetDefaultCertDB());
   435 #if 0
   436 	/* No point in hooking BadCert, since ssl_auth_cert always succeeds */
   437 	SSL_BadCertHook(nss_data->in, (SSLBadCertHandler)ssl_bad_cert, NULL);
   438 #endif
   439 
   440 	if(gsc->host)
   441 		SSL_SetURL(nss_data->in, gsc->host);
   442 
   443 #if 0
   444 	/* This seems like it'd the be the correct way to implement the
   445 	nonblocking stuff, but it doesn't seem to work */
   446 	SSL_HandshakeCallback(nss_data->in,
   447 		(SSLHandshakeCallback) ssl_nss_handshake_cb, gsc);
   448 #endif
   449 	SSL_ResetHandshake(nss_data->in, PR_FALSE);
   450 
   451 	nss_data->handshake_handler = purple_input_add(gsc->fd,
   452 		PURPLE_INPUT_READ, ssl_nss_handshake_cb, gsc);
   453 
   454 	nss_data->handshake_timer = purple_timeout_add(0, start_handshake_cb, gsc);
   455 }
   456 
   457 static void
   458 ssl_nss_close(PurpleSslConnection *gsc)
   459 {
   460 	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
   461 
   462 	if(!nss_data)
   463 		return;
   464 
   465 	if (nss_data->in) {
   466 		PR_Close(nss_data->in);
   467 		gsc->fd = -1;
   468 	} else if (nss_data->fd) {
   469 		PR_Close(nss_data->fd);
   470 		gsc->fd = -1;
   471 	}
   472 
   473 	if (nss_data->handshake_handler)
   474 		purple_input_remove(nss_data->handshake_handler);
   475 
   476 	if (nss_data->handshake_timer)
   477 		purple_timeout_remove(nss_data->handshake_timer);
   478 
   479 	g_free(nss_data);
   480 	gsc->private_data = NULL;
   481 }
   482 
   483 static size_t
   484 ssl_nss_read(PurpleSslConnection *gsc, void *data, size_t len)
   485 {
   486 	ssize_t ret;
   487 	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
   488 
   489 	ret = PR_Read(nss_data->in, data, len);
   490 
   491 	if (ret == -1)
   492 		set_errno(PR_GetError());
   493 
   494 	return ret;
   495 }
   496 
   497 static size_t
   498 ssl_nss_write(PurpleSslConnection *gsc, const void *data, size_t len)
   499 {
   500 	ssize_t ret;
   501 	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
   502 
   503 	if(!nss_data)
   504 		return 0;
   505 
   506 	ret = PR_Write(nss_data->in, data, len);
   507 
   508 	if (ret == -1)
   509 		set_errno(PR_GetError());
   510 
   511 	return ret;
   512 }
   513 
   514 static GList *
   515 ssl_nss_peer_certs(PurpleSslConnection *gsc)
   516 {
   517 #if 0
   518 	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
   519 	CERTCertificate *cert;
   520 /*
   521 	GList *chain = NULL;
   522 	void *pinArg;
   523 	SECStatus status;
   524 */
   525 
   526 	/* TODO: this is a blind guess */
   527 	cert = SSL_PeerCertificate(nss_data->fd);
   528 
   529 	if (cert)
   530 		CERT_DestroyCertificate(cert);
   531 #endif
   532 
   533 
   534 
   535 	return NULL;
   536 }
   537 
   538 /************************************************************************/
   539 /* X.509 functionality                                                  */
   540 /************************************************************************/
   541 static PurpleCertificateScheme x509_nss;
   542 
   543 /** Helpr macro to retrieve the NSS certdata from a PurpleCertificate */
   544 #define X509_NSS_DATA(pcrt) ( (CERTCertificate * ) (pcrt->data) )
   545 
   546 /** Imports a PEM-formatted X.509 certificate from the specified file.
   547  * @param filename Filename to import from. Format is PEM
   548  *
   549  * @return A newly allocated Certificate structure of the x509_nss scheme
   550  */
   551 static PurpleCertificate *
   552 x509_import_from_file(const gchar *filename)
   553 {
   554 	gchar *rawcert;
   555 	gsize len = 0;
   556 	CERTCertificate *crt_dat;
   557 	PurpleCertificate *crt;
   558 
   559 	g_return_val_if_fail(filename != NULL, NULL);
   560 
   561 	purple_debug_info("nss/x509",
   562 			  "Loading certificate from %s\n",
   563 			  filename);
   564 
   565 	/* Load the raw data up */
   566 	if (!g_file_get_contents(filename,
   567 				 &rawcert, &len,
   568 				 NULL)) {
   569 		purple_debug_error("nss/x509", "Unable to read certificate file.\n");
   570 		return NULL;
   571 	}
   572 
   573 	if (len == 0) {
   574 		purple_debug_error("nss/x509",
   575 				"Certificate file has no contents!\n");
   576 		if (rawcert)
   577 			g_free(rawcert);
   578 		return NULL;
   579 	}
   580 
   581 	/* Decode the certificate */
   582 	crt_dat = CERT_DecodeCertFromPackage(rawcert, len);
   583 	g_free(rawcert);
   584 
   585 	g_return_val_if_fail(crt_dat != NULL, NULL);
   586 
   587 	crt = g_new0(PurpleCertificate, 1);
   588 	crt->scheme = &x509_nss;
   589 	crt->data = crt_dat;
   590 
   591 	return crt;
   592 }
   593 
   594 /** Imports a number of PEM-formatted X.509 certificates from the specified file.
   595  * @param filename Filename to import from. Format is PEM
   596  *
   597  * @return A GSList of newly allocated Certificate structures of the x509_nss scheme
   598  */
   599 static GSList *
   600 x509_importcerts_from_file(const gchar *filename)
   601 {
   602 	gchar *rawcert, *begin, *end;
   603 	gsize len = 0;
   604 	GSList *crts = NULL;
   605 	CERTCertificate *crt_dat;
   606 	PurpleCertificate *crt;
   607 
   608 	g_return_val_if_fail(filename != NULL, NULL);
   609 
   610 	purple_debug_info("nss/x509",
   611 			  "Loading certificate from %s\n",
   612 			  filename);
   613 
   614 	/* Load the raw data up */
   615 	if (!g_file_get_contents(filename,
   616 				 &rawcert, &len,
   617 				 NULL)) {
   618 		purple_debug_error("nss/x509", "Unable to read certificate file.\n");
   619 		return NULL;
   620 	}
   621 
   622 	if (len == 0) {
   623 		purple_debug_error("nss/x509",
   624 				"Certificate file has no contents!\n");
   625 		if (rawcert)
   626 			g_free(rawcert);
   627 		return NULL;
   628 	}
   629 
   630 	begin = rawcert;
   631 	while((end = strstr(begin, "-----END CERTIFICATE-----")) != NULL) {
   632 		end += sizeof("-----END CERTIFICATE-----")-1;
   633 		/* Decode the certificate */
   634 		crt_dat = CERT_DecodeCertFromPackage(begin, (end-begin));
   635 
   636 		g_return_val_if_fail(crt_dat != NULL, NULL);
   637 
   638 		crt = g_new0(PurpleCertificate, 1);
   639 		crt->scheme = &x509_nss;
   640 		crt->data = crt_dat;
   641 		crts = g_slist_prepend(crts, crt);
   642 		begin = end;
   643 	}
   644 	g_free(rawcert);
   645 
   646 	return crts;
   647 }
   648 /**
   649  * Exports a PEM-formatted X.509 certificate to the specified file.
   650  * @param filename Filename to export to. Format will be PEM
   651  * @param crt      Certificate to export
   652  *
   653  * @return TRUE if success, otherwise FALSE
   654  */
   655 /* This function should not be so complicated, but NSS doesn't seem to have a
   656    "convert yon certificate to PEM format" function. */
   657 static gboolean
   658 x509_export_certificate(const gchar *filename, PurpleCertificate *crt)
   659 {
   660 	CERTCertificate *crt_dat;
   661 	SECItem *dercrt;
   662 	gchar *b64crt;
   663 	gchar *pemcrt;
   664 	gboolean ret = FALSE;
   665 
   666 	g_return_val_if_fail(filename, FALSE);
   667 	g_return_val_if_fail(crt, FALSE);
   668 	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
   669 
   670 	crt_dat = X509_NSS_DATA(crt);
   671 	g_return_val_if_fail(crt_dat, FALSE);
   672 
   673 	purple_debug_info("nss/x509",
   674 			  "Exporting certificate to %s\n", filename);
   675 
   676 	/* First, use NSS voodoo to create a DER-formatted certificate */
   677 	dercrt = SEC_ASN1EncodeItem(NULL, NULL, crt_dat,
   678 				    SEC_ASN1_GET(SEC_SignedCertificateTemplate));
   679 	g_return_val_if_fail(dercrt != NULL, FALSE);
   680 
   681 	/* Now encode it to b64 */
   682 	b64crt = NSSBase64_EncodeItem(NULL, NULL, 0, dercrt);
   683 	SECITEM_FreeItem(dercrt, PR_TRUE);
   684 	g_return_val_if_fail(b64crt, FALSE);
   685 
   686 	/* Wrap it in nice PEM header things */
   687 	pemcrt = g_strdup_printf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n", b64crt);
   688 	PORT_Free(b64crt); /* Notice that b64crt was allocated by an NSS
   689 			      function; hence, we'll let NSPR free it. */
   690 
   691 	/* Finally, dump the silly thing to a file. */
   692 	ret =  purple_util_write_data_to_file_absolute(filename, pemcrt, -1);
   693 
   694 	g_free(pemcrt);
   695 
   696 	return ret;
   697 }
   698 
   699 static PurpleCertificate *
   700 x509_copy_certificate(PurpleCertificate *crt)
   701 {
   702 	CERTCertificate *crt_dat;
   703 	PurpleCertificate *newcrt;
   704 
   705 	g_return_val_if_fail(crt, NULL);
   706 	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
   707 
   708 	crt_dat = X509_NSS_DATA(crt);
   709 	g_return_val_if_fail(crt_dat, NULL);
   710 
   711 	/* Create the certificate copy */
   712 	newcrt = g_new0(PurpleCertificate, 1);
   713 	newcrt->scheme = &x509_nss;
   714 	/* NSS does refcounting automatically */
   715 	newcrt->data = CERT_DupCertificate(crt_dat);
   716 
   717 	return newcrt;
   718 }
   719 
   720 /** Frees a Certificate
   721  *
   722  *  Destroys a Certificate's internal data structures and frees the pointer
   723  *  given.
   724  *  @param crt  Certificate instance to be destroyed. It WILL NOT be destroyed
   725  *              if it is not of the correct CertificateScheme. Can be NULL
   726  *
   727  */
   728 static void
   729 x509_destroy_certificate(PurpleCertificate * crt)
   730 {
   731 	CERTCertificate *crt_dat;
   732 
   733 	g_return_if_fail(crt);
   734 	g_return_if_fail(crt->scheme == &x509_nss);
   735 
   736 	crt_dat = X509_NSS_DATA(crt);
   737 	g_return_if_fail(crt_dat);
   738 
   739 	/* Finally we have the certificate. So let's kill it */
   740 	/* NSS does refcounting automatically */
   741 	CERT_DestroyCertificate(crt_dat);
   742 
   743 	/* Delete the PurpleCertificate as well */
   744 	g_free(crt);
   745 }
   746 
   747 /** Determines whether one certificate has been issued and signed by another
   748  *
   749  * @param crt       Certificate to check the signature of
   750  * @param issuer    Issuer's certificate
   751  *
   752  * @return TRUE if crt was signed and issued by issuer, otherwise FALSE
   753  * @TODO  Modify this function to return a reason for invalidity?
   754  */
   755 static gboolean
   756 x509_signed_by(PurpleCertificate * crt,
   757 	       PurpleCertificate * issuer)
   758 {
   759 	CERTCertificate *subjectCert;
   760 	CERTCertificate *issuerCert;
   761 	SECStatus st;
   762 
   763 	issuerCert = X509_NSS_DATA(issuer);
   764 	g_return_val_if_fail(issuerCert, FALSE);
   765 
   766 	subjectCert = X509_NSS_DATA(crt);
   767 	g_return_val_if_fail(subjectCert, FALSE);
   768 
   769 	if (subjectCert->issuerName == NULL
   770 			|| PORT_Strcmp(subjectCert->issuerName, issuerCert->subjectName) != 0)
   771 		return FALSE;
   772 	st = CERT_VerifySignedData(&subjectCert->signatureWrap, issuerCert, PR_Now(), NULL);
   773 	return st == SECSuccess;
   774 }
   775 
   776 static GByteArray *
   777 x509_sha1sum(PurpleCertificate *crt)
   778 {
   779 	CERTCertificate *crt_dat;
   780 	size_t hashlen = 20; /* Size of an sha1sum */
   781 	GByteArray *sha1sum;
   782 	SECItem *derCert; /* DER representation of the cert */
   783 	SECStatus st;
   784 
   785 	g_return_val_if_fail(crt, NULL);
   786 	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
   787 
   788 	crt_dat = X509_NSS_DATA(crt);
   789 	g_return_val_if_fail(crt_dat, NULL);
   790 
   791 	/* Get the certificate DER representation */
   792 	derCert = &(crt_dat->derCert);
   793 
   794 	/* Make a hash! */
   795 	sha1sum = g_byte_array_sized_new(hashlen);
   796 	/* glib leaves the size as 0 by default */
   797 	sha1sum->len = hashlen;
   798 
   799 	st = PK11_HashBuf(SEC_OID_SHA1, sha1sum->data,
   800 			  derCert->data, derCert->len);
   801 
   802 	/* Check for errors */
   803 	if (st != SECSuccess) {
   804 		g_byte_array_free(sha1sum, TRUE);
   805 		purple_debug_error("nss/x509",
   806 				   "Error: hashing failed!\n");
   807 		return NULL;
   808 	}
   809 
   810 	return sha1sum;
   811 }
   812 
   813 static gchar *
   814 x509_dn (PurpleCertificate *crt)
   815 {
   816 	CERTCertificate *crt_dat;
   817 
   818 	g_return_val_if_fail(crt, NULL);
   819 	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
   820 
   821 	crt_dat = X509_NSS_DATA(crt);
   822 	g_return_val_if_fail(crt_dat, NULL);
   823 
   824 	return g_strdup(crt_dat->subjectName);
   825 }
   826 
   827 static gchar *
   828 x509_issuer_dn (PurpleCertificate *crt)
   829 {
   830 	CERTCertificate *crt_dat;
   831 
   832 	g_return_val_if_fail(crt, NULL);
   833 	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
   834 
   835 	crt_dat = X509_NSS_DATA(crt);
   836 	g_return_val_if_fail(crt_dat, NULL);
   837 
   838 	return g_strdup(crt_dat->issuerName);
   839 }
   840 
   841 static gchar *
   842 x509_common_name (PurpleCertificate *crt)
   843 {
   844 	CERTCertificate *crt_dat;
   845 	char *nss_cn;
   846 	gchar *ret_cn;
   847 
   848 	g_return_val_if_fail(crt, NULL);
   849 	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
   850 
   851 	crt_dat = X509_NSS_DATA(crt);
   852 	g_return_val_if_fail(crt_dat, NULL);
   853 
   854 	/* Q:
   855 	   Why get a newly allocated string out of NSS, strdup it, and then
   856 	   return the new copy?
   857 
   858 	   A:
   859 	   The NSS LXR docs state that I should use the NSPR free functions on
   860 	   the strings that the NSS cert functions return. Since the libpurple
   861 	   API expects a g_free()-able string, we make our own copy and return
   862 	   that.
   863 
   864 	   NSPR is something of a prima donna. */
   865 
   866 	nss_cn = CERT_GetCommonName( &(crt_dat->subject) );
   867 	ret_cn = g_strdup(nss_cn);
   868 	PORT_Free(nss_cn);
   869 
   870 	return ret_cn;
   871 }
   872 
   873 static gboolean
   874 x509_check_name (PurpleCertificate *crt, const gchar *name)
   875 {
   876 	CERTCertificate *crt_dat;
   877 	SECStatus st;
   878 
   879 	g_return_val_if_fail(crt, FALSE);
   880 	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
   881 
   882 	crt_dat = X509_NSS_DATA(crt);
   883 	g_return_val_if_fail(crt_dat, FALSE);
   884 
   885 	st = CERT_VerifyCertName(crt_dat, name);
   886 
   887 	if (st == SECSuccess) {
   888 		return TRUE;
   889 	}
   890 	else if (st == SECFailure) {
   891 		return FALSE;
   892 	}
   893 
   894 	/* If we get here...bad things! */
   895 	purple_debug_error("nss/x509",
   896 			   "x509_check_name fell through where it shouldn't "
   897 			   "have.\n");
   898 	return FALSE;
   899 }
   900 
   901 static gboolean
   902 x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
   903 {
   904 	CERTCertificate *crt_dat;
   905 	PRTime nss_activ, nss_expir;
   906 
   907 	g_return_val_if_fail(crt, FALSE);
   908 	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
   909 
   910 	crt_dat = X509_NSS_DATA(crt);
   911 	g_return_val_if_fail(crt_dat, FALSE);
   912 
   913 	/* Extract the times into ugly PRTime thingies */
   914 	/* TODO: Maybe this shouldn't throw an error? */
   915 	g_return_val_if_fail(
   916 		SECSuccess == CERT_GetCertTimes(crt_dat,
   917 						&nss_activ, &nss_expir),
   918 		FALSE);
   919 
   920 	/* NSS's native PRTime type *almost* corresponds to time_t; however,
   921 	   it measures *microseconds* since the epoch, not seconds. Hence
   922 	   the funny conversion. */
   923 	if (activation) {
   924 		*activation = nss_activ / 1000000;
   925 	}
   926 	if (expiration) {
   927 		*expiration = nss_expir / 1000000;
   928 	}
   929 
   930 	return TRUE;
   931 }
   932 
   933 static GByteArray *
   934 x509_get_der_data(PurpleCertificate *crt)
   935 {
   936 	CERTCertificate *crt_dat;
   937 	SECItem *dercrt;
   938 	GByteArray *data;
   939 
   940 	crt_dat = X509_NSS_DATA(crt);
   941 	g_return_val_if_fail(crt_dat, NULL);
   942 
   943 	dercrt = SEC_ASN1EncodeItem(NULL, NULL, crt_dat,
   944 	                            SEC_ASN1_GET(SEC_SignedCertificateTemplate));
   945 	g_return_val_if_fail(dercrt != NULL, FALSE);
   946 
   947 	data = g_byte_array_sized_new(dercrt->len);
   948 	memcpy(data->data, dercrt->data, dercrt->len);
   949 	data->len = dercrt->len;
   950 
   951 	SECITEM_FreeItem(dercrt, PR_TRUE);
   952 
   953 	return data;
   954 }
   955 
   956 static gchar *
   957 x509_display_string(PurpleCertificate *crt)
   958 {
   959 	gchar *sha_asc;
   960 	GByteArray *sha_bin;
   961 	gchar *cn;
   962 	time_t activation, expiration;
   963 	gchar *activ_str, *expir_str;
   964 	gchar *text;
   965 
   966 	/* Pull out the SHA1 checksum */
   967 	sha_bin = x509_sha1sum(crt);
   968 	sha_asc = purple_base16_encode_chunked(sha_bin->data, sha_bin->len);
   969 
   970 	/* Get the cert Common Name */
   971 	/* TODO: Will break on CA certs */
   972 	cn = x509_common_name(crt);
   973 
   974 	/* Get the certificate times */
   975 	/* TODO: Check the times against localtime */
   976 	/* TODO: errorcheck? */
   977 	if (!x509_times(crt, &activation, &expiration)) {
   978 		purple_debug_error("certificate",
   979 				   "Failed to get certificate times!\n");
   980 		activation = expiration = 0;
   981 	}
   982 	activ_str = g_strdup(ctime(&activation));
   983 	expir_str = g_strdup(ctime(&expiration));
   984 
   985 	/* Make messages */
   986 	text = g_strdup_printf(_("Common name: %s\n\n"
   987 	                         "Fingerprint (SHA1): %s\n\n"
   988 	                         "Activation date: %s\n"
   989 	                         "Expiration date: %s\n"),
   990 	                       cn ? cn : "(null)",
   991 	                       sha_asc ? sha_asc : "(null)",
   992 	                       activ_str ? activ_str : "(null)",
   993 	                       expir_str ? expir_str : "(null)");
   994 
   995 	/* Cleanup */
   996 	g_free(cn);
   997 	g_free(sha_asc);
   998 	g_free(activ_str);
   999 	g_free(expir_str);
  1000 	g_byte_array_free(sha_bin, TRUE);
  1001 
  1002 	return text;
  1003 }
  1004 
  1005 static PurpleCertificateScheme x509_nss = {
  1006 	"x509",                          /* Scheme name */
  1007 	N_("X.509 Certificates"),        /* User-visible scheme name */
  1008 	x509_import_from_file,           /* Certificate import function */
  1009 	x509_export_certificate,         /* Certificate export function */
  1010 	x509_copy_certificate,           /* Copy */
  1011 	x509_destroy_certificate,        /* Destroy cert */
  1012 	x509_signed_by,                  /* Signed-by */
  1013 	x509_sha1sum,                    /* SHA1 fingerprint */
  1014 	x509_dn,                         /* Unique ID */
  1015 	x509_issuer_dn,                  /* Issuer Unique ID */
  1016 	x509_common_name,                /* Subject name */
  1017 	x509_check_name,                 /* Check subject name */
  1018 	x509_times,                      /* Activation/Expiration time */
  1019 	x509_importcerts_from_file,      /* Multiple certificate import function */
  1020 	x509_get_der_data,               /* Binary DER data */
  1021 	x509_display_string,             /* Display representation */
  1022 
  1023 	NULL
  1024 };
  1025 
  1026 static PurpleSslOps ssl_ops =
  1027 {
  1028 	ssl_nss_init,
  1029 	ssl_nss_uninit,
  1030 	ssl_nss_connect,
  1031 	ssl_nss_close,
  1032 	ssl_nss_read,
  1033 	ssl_nss_write,
  1034 	ssl_nss_peer_certs,
  1035 
  1036 	/* padding */
  1037 	NULL,
  1038 	NULL,
  1039 	NULL
  1040 };
  1041 
  1042 
  1043 static gboolean
  1044 plugin_load(PurplePlugin *plugin)
  1045 {
  1046 	if (!purple_ssl_get_ops()) {
  1047 		purple_ssl_set_ops(&ssl_ops);
  1048 	}
  1049 
  1050 	/* Init NSS now, so others can use it even if sslconn never does */
  1051 	ssl_nss_init_nss();
  1052 
  1053 	/* Register the X.509 functions we provide */
  1054 	purple_certificate_register_scheme(&x509_nss);
  1055 
  1056 	return TRUE;
  1057 }
  1058 
  1059 static gboolean
  1060 plugin_unload(PurplePlugin *plugin)
  1061 {
  1062 	if (purple_ssl_get_ops() == &ssl_ops) {
  1063 		purple_ssl_set_ops(NULL);
  1064 	}
  1065 
  1066 	/* Unregister our X.509 functions */
  1067 	purple_certificate_unregister_scheme(&x509_nss);
  1068 
  1069 	return TRUE;
  1070 }
  1071 
  1072 static PurplePluginInfo info =
  1073 {
  1074 	PURPLE_PLUGIN_MAGIC,
  1075 	PURPLE_MAJOR_VERSION,
  1076 	PURPLE_MINOR_VERSION,
  1077 	PURPLE_PLUGIN_STANDARD,                             /**< type           */
  1078 	NULL,                                             /**< ui_requirement */
  1079 	PURPLE_PLUGIN_FLAG_INVISIBLE,                       /**< flags          */
  1080 	NULL,                                             /**< dependencies   */
  1081 	PURPLE_PRIORITY_DEFAULT,                            /**< priority       */
  1082 
  1083 	SSL_NSS_PLUGIN_ID,                             /**< id             */
  1084 	N_("NSS"),                                        /**< name           */
  1085 	DISPLAY_VERSION,                                  /**< version        */
  1086 	                                                  /**  summary        */
  1087 	N_("Provides SSL support through Mozilla NSS."),
  1088 	                                                  /**  description    */
  1089 	N_("Provides SSL support through Mozilla NSS."),
  1090 	"Christian Hammond <chipx86@gnupdate.org>",
  1091 	PURPLE_WEBSITE,                                     /**< homepage       */
  1092 
  1093 	plugin_load,                                      /**< load           */
  1094 	plugin_unload,                                    /**< unload         */
  1095 	NULL,                                             /**< destroy        */
  1096 
  1097 	NULL,                                             /**< ui_info        */
  1098 	NULL,                                             /**< extra_info     */
  1099 	NULL,                                             /**< prefs_info     */
  1100 	NULL,                                             /**< actions        */
  1101 
  1102 	/* padding */
  1103 	NULL,
  1104 	NULL,
  1105 	NULL,
  1106 	NULL
  1107 };
  1108 
  1109 static void
  1110 init_plugin(PurplePlugin *plugin)
  1111 {
  1112 }
  1113 
  1114 PURPLE_INIT_PLUGIN(ssl_nss, init_plugin, info)