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 #ifdef _WIN32
33 # ifndef HAVE_LONG_LONG
34 #define HAVE_LONG_LONG
35 /* WINDDK_BUILD is defined because the checks around usage of
36  * intrisic functions are wrong in nspr */
37 #define WINDDK_BUILD
38 # endif
39 #else
40 /* TODO: Why is this done?
41  * This is probably being overridden by <nspr.h> (prcpucfg.h) on *nix OSes */
42 #undef HAVE_LONG_LONG /* Make Mozilla less angry. If angry, Mozilla SMASH! */
43 #endif
44 
45 #include <nspr.h>
46 #include <nss.h>
47 #include <nssb64.h>
48 #include <ocsp.h>
49 #include <pk11func.h>
50 #include <prio.h>
51 #include <secerr.h>
52 #include <secmod.h>
53 #include <ssl.h>
54 #include <sslerr.h>
55 #include <sslproto.h>
56 
57 /* There's a bug in some versions of this header that requires that some of
58    the headers above be included first. This is true for at least libnss
59    3.15.4. */
60 #include <certdb.h>
61 
62 /* This is defined in NSPR's <private/pprio.h>, but to avoid including a
63  * private header we duplicate the prototype here */
64 NSPR_API(PRFileDesc*)  PR_ImportTCPSocket(PRInt32 osfd);
65 
66 typedef struct
67 {
68 	PRFileDesc *fd;
69 	PRFileDesc *in;
70 	guint handshake_handler;
71 	guint handshake_timer;
72 } PurpleSslNssData;
73 
74 #define PURPLE_SSL_NSS_DATA(gsc) ((PurpleSslNssData *)gsc->private_data)
75 
76 static const PRIOMethods *_nss_methods = NULL;
77 static PRDescIdentity _identity;
78 static PurpleCertificateScheme x509_nss;
79 
80 /* Thank you, Evolution */
81 static void
set_errno(int code)82 set_errno(int code)
83 {
84 	/* FIXME: this should handle more. */
85 	switch (code) {
86 	case PR_INVALID_ARGUMENT_ERROR:
87 		errno = EINVAL;
88 		break;
89 	case PR_PENDING_INTERRUPT_ERROR:
90 		errno = EINTR;
91 		break;
92 	case PR_IO_PENDING_ERROR:
93 		errno = EAGAIN;
94 		break;
95 	case PR_WOULD_BLOCK_ERROR:
96 		errno = EAGAIN;
97 		/*errno = EWOULDBLOCK; */
98 		break;
99 	case PR_IN_PROGRESS_ERROR:
100 		errno = EINPROGRESS;
101 		break;
102 	case PR_ALREADY_INITIATED_ERROR:
103 		errno = EALREADY;
104 		break;
105 	case PR_NETWORK_UNREACHABLE_ERROR:
106 		errno = EHOSTUNREACH;
107 		break;
108 	case PR_CONNECT_REFUSED_ERROR:
109 		errno = ECONNREFUSED;
110 		break;
111 	case PR_CONNECT_TIMEOUT_ERROR:
112 	case PR_IO_TIMEOUT_ERROR:
113 		errno = ETIMEDOUT;
114 		break;
115 	case PR_NOT_CONNECTED_ERROR:
116 		errno = ENOTCONN;
117 		break;
118 	case PR_CONNECT_RESET_ERROR:
119 		errno = ECONNRESET;
120 		break;
121 	case PR_IO_ERROR:
122 	default:
123 		errno = EIO;
124 		break;
125 	}
126 }
127 
get_error_text(void)128 static gchar *get_error_text(void)
129 {
130 	PRInt32 len = PR_GetErrorTextLength();
131 	gchar *ret = NULL;
132 
133 	if (len > 0) {
134 		ret = g_malloc(len + 1);
135 		len = PR_GetErrorText(ret);
136 		ret[len] = '\0';
137 	}
138 
139 	return ret;
140 }
141 
ssl_nss_init_ciphers(void)142 static void ssl_nss_init_ciphers(void) {
143 	const PRUint16 *cipher;
144 
145 	/* Log the available and enabled Ciphers */
146 	for (cipher = SSL_GetImplementedCiphers(); *cipher != 0; ++cipher) {
147 		const PRUint16 suite = *cipher;
148 		SECStatus rv;
149 		PRBool enabled;
150 		SSLCipherSuiteInfo info;
151 
152 		rv = SSL_CipherPrefGetDefault(suite, &enabled);
153 		if (rv != SECSuccess) {
154 			gchar *error_txt = get_error_text();
155 			purple_debug_warning("nss",
156 					"SSL_CipherPrefGetDefault didn't like value 0x%04x: %s\n",
157 					suite, error_txt);
158 			g_free(error_txt);
159 			continue;
160 		}
161 		rv = SSL_GetCipherSuiteInfo(suite, &info, (int)(sizeof info));
162 		if (rv != SECSuccess) {
163 			gchar *error_txt = get_error_text();
164 			purple_debug_warning("nss",
165 					"SSL_GetCipherSuiteInfo didn't like value 0x%04x: %s\n",
166 					suite, error_txt);
167 			g_free(error_txt);
168 			continue;
169 		}
170 		purple_debug_info("nss", "Cipher - %s: %s\n",
171 				info.cipherSuiteName,
172 				enabled ? "Enabled" : "Disabled");
173 	}
174 }
175 
176 static void
ssl_nss_init_nss(void)177 ssl_nss_init_nss(void)
178 {
179 #if NSS_VMAJOR > 3 || ( NSS_VMAJOR == 3 && NSS_VMINOR >= 14 )
180 	SSLVersionRange supported, enabled;
181 #endif /* NSS >= 3.14 */
182 
183 	PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
184 	NSS_NoDB_Init(".");
185 #if (NSS_VMAJOR == 3 && (NSS_VMINOR < 15 || (NSS_VMINOR == 15 && NSS_VPATCH < 2)))
186 	NSS_SetDomesticPolicy();
187 #endif /* NSS < 3.15.2 */
188 
189 	ssl_nss_init_ciphers();
190 
191 #if NSS_VMAJOR > 3 || ( NSS_VMAJOR == 3 && NSS_VMINOR >= 14 )
192 	/* Get the ranges of supported and enabled SSL versions */
193 	if ((SSL_VersionRangeGetSupported(ssl_variant_stream, &supported) == SECSuccess) &&
194 			(SSL_VersionRangeGetDefault(ssl_variant_stream, &enabled) == SECSuccess)) {
195 		purple_debug_info("nss", "TLS supported versions: "
196 				"0x%04hx through 0x%04hx\n", supported.min, supported.max);
197 		purple_debug_info("nss", "TLS versions allowed by default: "
198 				"0x%04hx through 0x%04hx\n", enabled.min, enabled.max);
199 	}
200 #endif /* NSS >= 3.14 */
201 
202 	/** Disable OCSP Checking until we can make that use our HTTP & Proxy stuff */
203 	CERT_EnableOCSPChecking(PR_FALSE);
204 
205 	_identity = PR_GetUniqueIdentity("Purple");
206 	_nss_methods = PR_GetDefaultIOMethods();
207 
208 }
209 
210 static SECStatus
ssl_auth_cert(void * arg,PRFileDesc * socket,PRBool checksig,PRBool is_server)211 ssl_auth_cert(void *arg, PRFileDesc *socket, PRBool checksig, PRBool is_server)
212 {
213 	/* We just skip cert verification here, and will verify the whole chain
214 	 * in ssl_nss_handshake_cb, after the handshake is complete.
215 	 *
216 	 * The problem is, purple_certificate_verify is asynchronous and
217 	 * ssl_auth_cert should return the result synchronously (it may ask the
218 	 * user, if an unknown certificate should be trusted or not).
219 	 *
220 	 * Ideally, SSL_AuthCertificateHook/ssl_auth_cert should decide
221 	 * immediately, if the certificate chain is already trusted and possibly
222 	 * SSL_BadCertHook to deal with unknown certificates.
223 	 *
224 	 * Current implementation may not be ideal, but is no less secure in
225 	 * terms of MITM attack.
226 	 */
227 	return SECSuccess;
228 }
229 
230 static gboolean
ssl_nss_init(void)231 ssl_nss_init(void)
232 {
233    return TRUE;
234 }
235 
236 static void
ssl_nss_uninit(void)237 ssl_nss_uninit(void)
238 {
239 	NSS_Shutdown();
240 	PR_Cleanup();
241 
242 	_nss_methods = NULL;
243 }
244 
245 static void
ssl_nss_verified_cb(PurpleCertificateVerificationStatus st,gpointer userdata)246 ssl_nss_verified_cb(PurpleCertificateVerificationStatus st,
247 		       gpointer userdata)
248 {
249 	PurpleSslConnection *gsc = (PurpleSslConnection *) userdata;
250 
251 	if (st == PURPLE_CERTIFICATE_VALID) {
252 		/* Certificate valid? Good! Do the connection! */
253 		gsc->connect_cb(gsc->connect_cb_data, gsc, PURPLE_INPUT_READ);
254 	} else {
255 		/* Otherwise, signal an error */
256 		if(gsc->error_cb != NULL)
257 			gsc->error_cb(gsc, PURPLE_SSL_CERTIFICATE_INVALID,
258 				      gsc->connect_cb_data);
259 		purple_ssl_close(gsc);
260 	}
261 }
262 
263 /** Transforms an NSS containing an X.509 certificate into a Certificate instance
264  *
265  * @param cert   Certificate to transform
266  * @return A newly allocated Certificate
267  */
268 static PurpleCertificate *
x509_import_from_nss(CERTCertificate * cert)269 x509_import_from_nss(CERTCertificate* cert)
270 {
271 	/* New certificate to return */
272 	PurpleCertificate * crt;
273 
274 	/* Allocate the certificate and load it with data */
275 	crt = g_new0(PurpleCertificate, 1);
276 	crt->scheme = &x509_nss;
277 	crt->data = CERT_DupCertificate(cert);
278 
279 	return crt;
280 }
281 
282 static GList *
ssl_nss_get_peer_certificates(PRFileDesc * socket,PurpleSslConnection * gsc)283 ssl_nss_get_peer_certificates(PRFileDesc *socket, PurpleSslConnection * gsc)
284 {
285 	CERTCertificate *curcert;
286 	CERTCertificate *issuerCert;
287 	PurpleCertificate * newcrt;
288 
289 	/* List of Certificate instances to return */
290 	GList * peer_certs = NULL;
291 	int count;
292 	int64 now = PR_Now();
293 
294 	curcert = SSL_PeerCertificate(socket);
295 	if (curcert == NULL) {
296 		purple_debug_error("nss", "could not DupCertificate\n");
297 		return NULL;
298 	}
299 
300 	for (count = 0 ; count < CERT_MAX_CERT_CHAIN ; count++) {
301 		purple_debug_info("nss", "subject=%s issuer=%s\n", curcert->subjectName,
302 						  curcert->issuerName  ? curcert->issuerName : "(null)");
303 		newcrt = x509_import_from_nss(curcert);
304 		peer_certs = g_list_append(peer_certs, newcrt);
305 
306 		if (curcert->isRoot) {
307 			break;
308 		}
309 		issuerCert = CERT_FindCertIssuer(curcert, now, certUsageSSLServer);
310 		if (!issuerCert) {
311 			purple_debug_error("nss", "partial certificate chain\n");
312 			break;
313 		}
314 		CERT_DestroyCertificate(curcert);
315 		curcert = issuerCert;
316 	}
317 	CERT_DestroyCertificate(curcert);
318 
319 	return peer_certs;
320 }
321 
322 /*
323  * Ideally this information would be exposed to the UI somehow, but for now we
324  * just print it to the debug log
325  */
326 static void
print_security_info(PRFileDesc * fd)327 print_security_info(PRFileDesc *fd)
328 {
329 	SECStatus result;
330 	SSLChannelInfo channel;
331 	SSLCipherSuiteInfo suite;
332 
333 	result = SSL_GetChannelInfo(fd, &channel, sizeof channel);
334 	if (result == SECSuccess && channel.length == sizeof channel
335 			&& channel.cipherSuite) {
336 		result = SSL_GetCipherSuiteInfo(channel.cipherSuite,
337 				&suite, sizeof suite);
338 
339 		if (result == SECSuccess) {
340 			purple_debug_info("nss", "SSL version %d.%d using "
341 					"%d-bit %s with %d-bit %s MAC\n"
342 					"Server Auth: %d-bit %s, "
343 					"Key Exchange: %d-bit %s, "
344 					"Compression: %s\n"
345 					"Cipher Suite Name: %s\n",
346 					channel.protocolVersion >> 8,
347 					channel.protocolVersion & 0xff,
348 					suite.effectiveKeyBits,
349 					suite.symCipherName,
350 					suite.macBits,
351 					suite.macAlgorithmName,
352 					channel.authKeyBits,
353 					suite.authAlgorithmName,
354 					channel.keaKeyBits, suite.keaTypeName,
355 					channel.compressionMethodName,
356 					suite.cipherSuiteName);
357 		}
358 	}
359 }
360 
361 
362 static void
ssl_nss_handshake_cb(gpointer data,int fd,PurpleInputCondition cond)363 ssl_nss_handshake_cb(gpointer data, int fd, PurpleInputCondition cond)
364 {
365 	PurpleSslConnection *gsc = (PurpleSslConnection *)data;
366 	PurpleSslNssData *nss_data = gsc->private_data;
367 
368 	/* I don't think this the best way to do this...
369 	 * It seems to work because it'll eventually use the cached value
370 	 */
371 	if(SSL_ForceHandshake(nss_data->in) != SECSuccess) {
372 		gchar *error_txt;
373 		set_errno(PR_GetError());
374 		if (errno == EAGAIN || errno == EWOULDBLOCK)
375 			return;
376 
377 		error_txt = get_error_text();
378 		purple_debug_error("nss", "Handshake failed %s (%d)\n", error_txt ? error_txt : "", PR_GetError());
379 		g_free(error_txt);
380 
381 		if (gsc->error_cb != NULL)
382 			gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data);
383 
384 		purple_ssl_close(gsc);
385 
386 		return;
387 	}
388 
389 	print_security_info(nss_data->in);
390 
391 	purple_input_remove(nss_data->handshake_handler);
392 	nss_data->handshake_handler = 0;
393 
394 	/* If a Verifier was given, hand control over to it */
395 	if (gsc->verifier) {
396 		GList *peers;
397 		/* First, get the peer cert chain */
398 		peers = ssl_nss_get_peer_certificates(nss_data->in, gsc);
399 
400 		/* Now kick off the verification process */
401 		purple_certificate_verify(gsc->verifier,
402 				gsc->host,
403 				peers,
404 				ssl_nss_verified_cb,
405 				gsc);
406 
407 		purple_certificate_destroy_list(peers);
408 	} else {
409 		/* Otherwise, just call the "connection complete"
410 		 * callback. The verification was already done with
411 		 * SSL_AuthCertificate, the default verifier
412 		 * (SSL_AuthCertificateHook was not called in ssl_nss_connect).
413 		 */
414 		gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
415 	}
416 }
417 
418 static gboolean
start_handshake_cb(gpointer data)419 start_handshake_cb(gpointer data)
420 {
421 	PurpleSslConnection *gsc = data;
422 	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
423 
424 	nss_data->handshake_timer = 0;
425 
426 	ssl_nss_handshake_cb(gsc, gsc->fd, PURPLE_INPUT_READ);
427 	return FALSE;
428 }
429 
430 static void
ssl_nss_connect(PurpleSslConnection * gsc)431 ssl_nss_connect(PurpleSslConnection *gsc)
432 {
433 	PurpleSslNssData *nss_data = g_new0(PurpleSslNssData, 1);
434 	PRSocketOptionData socket_opt;
435 
436 	gsc->private_data = nss_data;
437 
438 	nss_data->fd = PR_ImportTCPSocket(gsc->fd);
439 
440 	if (nss_data->fd == NULL)
441 	{
442 		purple_debug_error("nss", "nss_data->fd == NULL!\n");
443 
444 		if (gsc->error_cb != NULL)
445 			gsc->error_cb(gsc, PURPLE_SSL_CONNECT_FAILED, gsc->connect_cb_data);
446 
447 		purple_ssl_close((PurpleSslConnection *)gsc);
448 
449 		return;
450 	}
451 
452 	socket_opt.option = PR_SockOpt_Nonblocking;
453 	socket_opt.value.non_blocking = PR_TRUE;
454 
455 	if (PR_SetSocketOption(nss_data->fd, &socket_opt) != PR_SUCCESS) {
456 		gchar *error_txt = get_error_text();
457 		purple_debug_warning("nss", "unable to set socket into non-blocking mode: %s (%d)\n", error_txt ? error_txt : "", PR_GetError());
458 		g_free(error_txt);
459 	}
460 
461 	nss_data->in = SSL_ImportFD(NULL, nss_data->fd);
462 
463 	if (nss_data->in == NULL)
464 	{
465 		purple_debug_error("nss", "nss_data->in == NUL!\n");
466 
467 		if (gsc->error_cb != NULL)
468 			gsc->error_cb(gsc, PURPLE_SSL_CONNECT_FAILED, gsc->connect_cb_data);
469 
470 		purple_ssl_close((PurpleSslConnection *)gsc);
471 
472 		return;
473 	}
474 
475 	SSL_OptionSet(nss_data->in, SSL_SECURITY,            PR_TRUE);
476 	SSL_OptionSet(nss_data->in, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
477 
478 	/* If we have our internal verifier set up, use it. Otherwise,
479 	 * use default. */
480 	if (gsc->verifier != NULL)
481 		SSL_AuthCertificateHook(nss_data->in, ssl_auth_cert, NULL);
482 
483 	if(gsc->host)
484 		SSL_SetURL(nss_data->in, gsc->host);
485 
486 #if 0
487 	/* This seems like it'd the be the correct way to implement the
488 	nonblocking stuff, but it doesn't seem to work */
489 	SSL_HandshakeCallback(nss_data->in,
490 		(SSLHandshakeCallback) ssl_nss_handshake_cb, gsc);
491 #endif
492 	SSL_ResetHandshake(nss_data->in, PR_FALSE);
493 
494 	nss_data->handshake_handler = purple_input_add(gsc->fd,
495 		PURPLE_INPUT_READ, ssl_nss_handshake_cb, gsc);
496 
497 	nss_data->handshake_timer = purple_timeout_add(0, start_handshake_cb, gsc);
498 }
499 
500 static void
ssl_nss_close(PurpleSslConnection * gsc)501 ssl_nss_close(PurpleSslConnection *gsc)
502 {
503 	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
504 
505 	if(!nss_data)
506 		return;
507 
508 	if (nss_data->in) {
509 		PR_Close(nss_data->in);
510 		gsc->fd = -1;
511 	} else if (nss_data->fd) {
512 		PR_Close(nss_data->fd);
513 		gsc->fd = -1;
514 	}
515 
516 	if (nss_data->handshake_handler)
517 		purple_input_remove(nss_data->handshake_handler);
518 
519 	if (nss_data->handshake_timer)
520 		purple_timeout_remove(nss_data->handshake_timer);
521 
522 	g_free(nss_data);
523 	gsc->private_data = NULL;
524 }
525 
526 static size_t
ssl_nss_read(PurpleSslConnection * gsc,void * data,size_t len)527 ssl_nss_read(PurpleSslConnection *gsc, void *data, size_t len)
528 {
529 	PRInt32 ret;
530 	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
531 
532 	if (!nss_data)
533 		return 0;
534 
535 	ret = PR_Read(nss_data->in, data, len);
536 
537 	if (ret == -1)
538 		set_errno(PR_GetError());
539 
540 	return ret;
541 }
542 
543 static size_t
ssl_nss_write(PurpleSslConnection * gsc,const void * data,size_t len)544 ssl_nss_write(PurpleSslConnection *gsc, const void *data, size_t len)
545 {
546 	PRInt32 ret;
547 	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
548 
549 	if(!nss_data)
550 		return 0;
551 
552 	ret = PR_Write(nss_data->in, data, len);
553 
554 	if (ret == -1)
555 		set_errno(PR_GetError());
556 
557 	return ret;
558 }
559 
560 static GList *
ssl_nss_peer_certs(PurpleSslConnection * gsc)561 ssl_nss_peer_certs(PurpleSslConnection *gsc)
562 {
563 #if 0
564 	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
565 	CERTCertificate *cert;
566 /*
567 	GList *chain = NULL;
568 	void *pinArg;
569 	SECStatus status;
570 */
571 
572 	/* TODO: this is a blind guess */
573 	cert = SSL_PeerCertificate(nss_data->fd);
574 
575 	if (cert)
576 		CERT_DestroyCertificate(cert);
577 #endif
578 
579 
580 
581 	return NULL;
582 }
583 
584 /************************************************************************/
585 /* X.509 functionality                                                  */
586 /************************************************************************/
587 static PurpleCertificateScheme x509_nss;
588 
589 /** Helpr macro to retrieve the NSS certdata from a PurpleCertificate */
590 #define X509_NSS_DATA(pcrt) ( (CERTCertificate * ) (pcrt->data) )
591 
592 /** Imports a PEM-formatted X.509 certificate from the specified file.
593  * @param filename Filename to import from. Format is PEM
594  *
595  * @return A newly allocated Certificate structure of the x509_nss scheme
596  */
597 static PurpleCertificate *
x509_import_from_file(const gchar * filename)598 x509_import_from_file(const gchar *filename)
599 {
600 	gchar *rawcert;
601 	gsize len = 0;
602 	CERTCertificate *crt_dat;
603 	PurpleCertificate *crt;
604 
605 	g_return_val_if_fail(filename != NULL, NULL);
606 
607 	purple_debug_info("nss/x509",
608 			  "Loading certificate from %s\n",
609 			  filename);
610 
611 	/* Load the raw data up */
612 	if (!g_file_get_contents(filename,
613 				 &rawcert, &len,
614 				 NULL)) {
615 		purple_debug_error("nss/x509", "Unable to read certificate file.\n");
616 		return NULL;
617 	}
618 
619 	if (len == 0) {
620 		purple_debug_error("nss/x509",
621 				"Certificate file has no contents!\n");
622 		if (rawcert)
623 			g_free(rawcert);
624 		return NULL;
625 	}
626 
627 	/* Decode the certificate */
628 	crt_dat = CERT_DecodeCertFromPackage(rawcert, len);
629 	g_free(rawcert);
630 
631 	g_return_val_if_fail(crt_dat != NULL, NULL);
632 
633 	crt = g_new0(PurpleCertificate, 1);
634 	crt->scheme = &x509_nss;
635 	crt->data = crt_dat;
636 
637 	return crt;
638 }
639 
640 /** Imports a number of PEM-formatted X.509 certificates from the specified file.
641  * @param filename Filename to import from. Format is PEM
642  *
643  * @return A GSList of newly allocated Certificate structures of the x509_nss scheme
644  */
645 static GSList *
x509_importcerts_from_file(const gchar * filename)646 x509_importcerts_from_file(const gchar *filename)
647 {
648 	gchar *rawcert, *begin, *end;
649 	gsize len = 0;
650 	GSList *crts = NULL;
651 	CERTCertificate *crt_dat;
652 	PurpleCertificate *crt;
653 
654 	g_return_val_if_fail(filename != NULL, NULL);
655 
656 	purple_debug_info("nss/x509",
657 			  "Loading certificate from %s\n",
658 			  filename);
659 
660 	/* Load the raw data up */
661 	if (!g_file_get_contents(filename,
662 				 &rawcert, &len,
663 				 NULL)) {
664 		purple_debug_error("nss/x509", "Unable to read certificate file.\n");
665 		return NULL;
666 	}
667 
668 	if (len == 0) {
669 		purple_debug_error("nss/x509",
670 				"Certificate file has no contents!\n");
671 		if (rawcert)
672 			g_free(rawcert);
673 		return NULL;
674 	}
675 
676 	begin = rawcert;
677 	while((end = strstr(begin, "-----END CERTIFICATE-----")) != NULL) {
678 		end += sizeof("-----END CERTIFICATE-----")-1;
679 		/* Decode the certificate */
680 		crt_dat = CERT_DecodeCertFromPackage(begin, (end-begin));
681 
682 		g_return_val_if_fail(crt_dat != NULL, NULL);
683 
684 		crt = g_new0(PurpleCertificate, 1);
685 		crt->scheme = &x509_nss;
686 		crt->data = crt_dat;
687 		crts = g_slist_prepend(crts, crt);
688 		begin = end;
689 	}
690 	g_free(rawcert);
691 
692 	return crts;
693 }
694 /**
695  * Exports a PEM-formatted X.509 certificate to the specified file.
696  * @param filename Filename to export to. Format will be PEM
697  * @param crt      Certificate to export
698  *
699  * @return TRUE if success, otherwise FALSE
700  */
701 /* This function should not be so complicated, but NSS doesn't seem to have a
702    "convert yon certificate to PEM format" function. */
703 static gboolean
x509_export_certificate(const gchar * filename,PurpleCertificate * crt)704 x509_export_certificate(const gchar *filename, PurpleCertificate *crt)
705 {
706 	CERTCertificate *crt_dat;
707 	SECItem *dercrt;
708 	gchar *b64crt;
709 	gchar *pemcrt;
710 	gboolean ret = FALSE;
711 
712 	g_return_val_if_fail(filename, FALSE);
713 	g_return_val_if_fail(crt, FALSE);
714 	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
715 
716 	crt_dat = X509_NSS_DATA(crt);
717 	g_return_val_if_fail(crt_dat, FALSE);
718 
719 	purple_debug_info("nss/x509",
720 			  "Exporting certificate to %s\n", filename);
721 
722 	/* First, use NSS voodoo to create a DER-formatted certificate */
723 	dercrt = SEC_ASN1EncodeItem(NULL, NULL, crt_dat,
724 				    SEC_ASN1_GET(SEC_SignedCertificateTemplate));
725 	g_return_val_if_fail(dercrt != NULL, FALSE);
726 
727 	/* Now encode it to b64 */
728 	b64crt = NSSBase64_EncodeItem(NULL, NULL, 0, dercrt);
729 	SECITEM_FreeItem(dercrt, PR_TRUE);
730 	g_return_val_if_fail(b64crt, FALSE);
731 
732 	/* Wrap it in nice PEM header things */
733 	pemcrt = g_strdup_printf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n", b64crt);
734 	PORT_Free(b64crt); /* Notice that b64crt was allocated by an NSS
735 			      function; hence, we'll let NSPR free it. */
736 
737 	/* Finally, dump the silly thing to a file. */
738 	ret =  purple_util_write_data_to_file_absolute(filename, pemcrt, -1);
739 
740 	g_free(pemcrt);
741 
742 	return ret;
743 }
744 
745 static PurpleCertificate *
x509_copy_certificate(PurpleCertificate * crt)746 x509_copy_certificate(PurpleCertificate *crt)
747 {
748 	CERTCertificate *crt_dat;
749 	PurpleCertificate *newcrt;
750 
751 	g_return_val_if_fail(crt, NULL);
752 	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
753 
754 	crt_dat = X509_NSS_DATA(crt);
755 	g_return_val_if_fail(crt_dat, NULL);
756 
757 	/* Create the certificate copy */
758 	newcrt = g_new0(PurpleCertificate, 1);
759 	newcrt->scheme = &x509_nss;
760 	/* NSS does refcounting automatically */
761 	newcrt->data = CERT_DupCertificate(crt_dat);
762 
763 	return newcrt;
764 }
765 
766 /** Frees a Certificate
767  *
768  *  Destroys a Certificate's internal data structures and frees the pointer
769  *  given.
770  *  @param crt  Certificate instance to be destroyed. It WILL NOT be destroyed
771  *              if it is not of the correct CertificateScheme. Can be NULL
772  *
773  */
774 static void
x509_destroy_certificate(PurpleCertificate * crt)775 x509_destroy_certificate(PurpleCertificate * crt)
776 {
777 	CERTCertificate *crt_dat;
778 
779 	g_return_if_fail(crt);
780 	g_return_if_fail(crt->scheme == &x509_nss);
781 
782 	crt_dat = X509_NSS_DATA(crt);
783 	g_return_if_fail(crt_dat);
784 
785 	/* Finally we have the certificate. So let's kill it */
786 	/* NSS does refcounting automatically */
787 	CERT_DestroyCertificate(crt_dat);
788 
789 	/* Delete the PurpleCertificate as well */
790 	g_free(crt);
791 }
792 
793 /** Determines whether one certificate has been issued and signed by another
794  *
795  * @param crt       Certificate to check the signature of
796  * @param issuer    Issuer's certificate
797  *
798  * @return TRUE if crt was signed and issued by issuer, otherwise FALSE
799  * @TODO  Modify this function to return a reason for invalidity?
800  */
801 static gboolean
x509_signed_by(PurpleCertificate * crt,PurpleCertificate * issuer)802 x509_signed_by(PurpleCertificate * crt,
803 	       PurpleCertificate * issuer)
804 {
805 	CERTCertificate *subjectCert;
806 	CERTCertificate *issuerCert;
807 	SECStatus st;
808 
809 	issuerCert = X509_NSS_DATA(issuer);
810 	g_return_val_if_fail(issuerCert, FALSE);
811 
812 	subjectCert = X509_NSS_DATA(crt);
813 	g_return_val_if_fail(subjectCert, FALSE);
814 
815 	if (subjectCert->issuerName == NULL || issuerCert->subjectName == NULL
816 			|| PORT_Strcmp(subjectCert->issuerName, issuerCert->subjectName) != 0)
817 		return FALSE;
818 	st = CERT_VerifySignedData(&subjectCert->signatureWrap, issuerCert, PR_Now(), NULL);
819 	return st == SECSuccess;
820 }
821 
822 static GByteArray *
x509_shasum(PurpleCertificate * crt,SECOidTag algo)823 x509_shasum(PurpleCertificate *crt, SECOidTag algo)
824 {
825 	CERTCertificate *crt_dat;
826 	size_t hashlen = (algo == SEC_OID_SHA1) ? 20 : 32;
827 	GByteArray *hash;
828 	SECItem *derCert; /* DER representation of the cert */
829 	SECStatus st;
830 
831 	g_return_val_if_fail(crt, NULL);
832 	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
833 
834 	crt_dat = X509_NSS_DATA(crt);
835 	g_return_val_if_fail(crt_dat, NULL);
836 
837 	/* Get the certificate DER representation */
838 	derCert = &(crt_dat->derCert);
839 
840 	/* Make a hash! */
841 	hash = g_byte_array_sized_new(hashlen);
842 	/* glib leaves the size as 0 by default */
843 	hash->len = hashlen;
844 
845 	st = PK11_HashBuf(algo, hash->data,
846 			  derCert->data, derCert->len);
847 
848 	/* Check for errors */
849 	if (st != SECSuccess) {
850 		g_byte_array_free(hash, TRUE);
851 		purple_debug_error("nss/x509",
852 				   "Error: hashing failed!\n");
853 		return NULL;
854 	}
855 
856 	return hash;
857 }
858 
859 static GByteArray *
x509_sha1sum(PurpleCertificate * crt)860 x509_sha1sum(PurpleCertificate *crt)
861 {
862 	return x509_shasum(crt, SEC_OID_SHA1);
863 }
864 
865 static GByteArray *
x509_sha256sum(PurpleCertificate * crt)866 x509_sha256sum(PurpleCertificate *crt)
867 {
868 	return x509_shasum(crt, SEC_OID_SHA256);
869 }
870 
871 static gchar *
x509_dn(PurpleCertificate * crt)872 x509_dn (PurpleCertificate *crt)
873 {
874 	CERTCertificate *crt_dat;
875 
876 	g_return_val_if_fail(crt, NULL);
877 	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
878 
879 	crt_dat = X509_NSS_DATA(crt);
880 	g_return_val_if_fail(crt_dat, NULL);
881 
882 	return g_strdup(crt_dat->subjectName);
883 }
884 
885 static gchar *
x509_issuer_dn(PurpleCertificate * crt)886 x509_issuer_dn (PurpleCertificate *crt)
887 {
888 	CERTCertificate *crt_dat;
889 
890 	g_return_val_if_fail(crt, NULL);
891 	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
892 
893 	crt_dat = X509_NSS_DATA(crt);
894 	g_return_val_if_fail(crt_dat, NULL);
895 
896 	return g_strdup(crt_dat->issuerName);
897 }
898 
899 static gchar *
x509_common_name(PurpleCertificate * crt)900 x509_common_name (PurpleCertificate *crt)
901 {
902 	CERTCertificate *crt_dat;
903 	char *nss_cn;
904 	gchar *ret_cn;
905 
906 	g_return_val_if_fail(crt, NULL);
907 	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
908 
909 	crt_dat = X509_NSS_DATA(crt);
910 	g_return_val_if_fail(crt_dat, NULL);
911 
912 	/* Q:
913 	   Why get a newly allocated string out of NSS, strdup it, and then
914 	   return the new copy?
915 
916 	   A:
917 	   The NSS LXR docs state that I should use the NSPR free functions on
918 	   the strings that the NSS cert functions return. Since the libpurple
919 	   API expects a g_free()-able string, we make our own copy and return
920 	   that.
921 
922 	   NSPR is something of a prima donna. */
923 
924 	nss_cn = CERT_GetCommonName( &(crt_dat->subject) );
925 	ret_cn = g_strdup(nss_cn);
926 	PORT_Free(nss_cn);
927 
928 	return ret_cn;
929 }
930 
931 static gboolean
x509_check_name(PurpleCertificate * crt,const gchar * name)932 x509_check_name (PurpleCertificate *crt, const gchar *name)
933 {
934 	CERTCertificate *crt_dat;
935 	SECStatus st;
936 
937 	g_return_val_if_fail(crt, FALSE);
938 	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
939 
940 	crt_dat = X509_NSS_DATA(crt);
941 	g_return_val_if_fail(crt_dat, FALSE);
942 
943 	st = CERT_VerifyCertName(crt_dat, name);
944 
945 	if (st == SECSuccess) {
946 		return TRUE;
947 	}
948 	else if (st == SECFailure) {
949 		return FALSE;
950 	}
951 
952 	/* If we get here...bad things! */
953 	purple_debug_error("nss/x509",
954 			   "x509_check_name fell through where it shouldn't "
955 			   "have.\n");
956 	return FALSE;
957 }
958 
959 static gboolean
x509_times(PurpleCertificate * crt,time_t * activation,time_t * expiration)960 x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
961 {
962 	CERTCertificate *crt_dat;
963 	PRTime nss_activ, nss_expir;
964 	SECStatus cert_times_success;
965 
966 	g_return_val_if_fail(crt, FALSE);
967 	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
968 
969 	crt_dat = X509_NSS_DATA(crt);
970 	g_return_val_if_fail(crt_dat, FALSE);
971 
972 	/* Extract the times into ugly PRTime thingies */
973 	/* TODO: Maybe this shouldn't throw an error? */
974 	cert_times_success = CERT_GetCertTimes(crt_dat,
975 						&nss_activ, &nss_expir);
976 	g_return_val_if_fail(cert_times_success == SECSuccess, FALSE);
977 
978 	/* NSS's native PRTime type *almost* corresponds to time_t; however,
979 	   it measures *microseconds* since the epoch, not seconds. Hence
980 	   the funny conversion. */
981 	nss_activ = nss_activ / 1000000;
982 	nss_expir = nss_expir / 1000000;
983 
984 	if (activation) {
985 		*activation = nss_activ;
986 #if SIZEOF_TIME_T == 4
987 		/** Hack to deal with dates past the 32-bit barrier.
988 		    Handling is different for signed vs unsigned 32-bit types.
989 		 */
990 		if (*activation != nss_activ) {
991 			if (nss_activ < 0) {
992 				purple_debug_warning("nss",
993 					"Setting Activation Date to epoch to handle pre-epoch value\n");
994 				*activation = 0;
995 			} else {
996 				purple_debug_error("nss",
997 					"Activation date past 32-bit barrier, forcing invalidity\n");
998 				return FALSE;
999 			}
1000 		}
1001 #endif
1002 	}
1003 	if (expiration) {
1004 		*expiration = nss_expir;
1005 #if SIZEOF_TIME_T == 4
1006 		if (*expiration != nss_expir) {
1007 			if (*expiration < nss_expir) {
1008 				if (*expiration < 0) {
1009 					purple_debug_warning("nss",
1010 						"Setting Expiration Date to 32-bit signed max\n");
1011 					*expiration = PR_INT32_MAX;
1012 				} else {
1013 					purple_debug_warning("nss",
1014 						"Setting Expiration Date to 32-bit unsigned max\n");
1015 					*expiration = PR_UINT32_MAX;
1016 				}
1017 			} else {
1018 				purple_debug_error("nss",
1019 					"Expiration date prior to unix epoch, forcing invalidity\n");
1020 				return FALSE;
1021 			}
1022 		}
1023 #endif
1024 	}
1025 
1026 	return TRUE;
1027 }
1028 
1029 static gboolean
x509_register_trusted_tls_cert(PurpleCertificate * crt,gboolean ca)1030 x509_register_trusted_tls_cert(PurpleCertificate *crt, gboolean ca)
1031 {
1032 	CERTCertDBHandle *certdb = CERT_GetDefaultCertDB();
1033 	CERTCertificate *crt_dat;
1034 	CERTCertTrust trust;
1035 
1036 	g_return_val_if_fail(crt, FALSE);
1037 	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
1038 
1039 	crt_dat = X509_NSS_DATA(crt);
1040 	g_return_val_if_fail(crt_dat, FALSE);
1041 
1042 	purple_debug_info("nss", "Trusting %s\n", crt_dat->subjectName);
1043 
1044 	if (ca && !CERT_IsCACert(crt_dat, NULL)) {
1045 		purple_debug_error("nss",
1046 			"Refusing to set non-CA cert as trusted CA\n");
1047 		return FALSE;
1048 	}
1049 
1050 	if (crt_dat->isperm) {
1051 		purple_debug_info("nss",
1052 			"Skipping setting trust for cert in permanent DB\n");
1053 		return TRUE;
1054 	}
1055 
1056 	if (ca) {
1057 		trust.sslFlags = CERTDB_TRUSTED_CA | CERTDB_TRUSTED_CLIENT_CA;
1058 	} else {
1059 		trust.sslFlags = CERTDB_TRUSTED;
1060 	}
1061 	trust.emailFlags = 0;
1062 	trust.objectSigningFlags = 0;
1063 
1064 	CERT_ChangeCertTrust(certdb, crt_dat, &trust);
1065 
1066 	return TRUE;
1067 }
1068 
x509_verify_cert(PurpleCertificateVerificationRequest * vrq,PurpleCertificateInvalidityFlags * flags)1069 static void x509_verify_cert(PurpleCertificateVerificationRequest *vrq, PurpleCertificateInvalidityFlags *flags)
1070 {
1071 	CERTCertDBHandle *certdb = CERT_GetDefaultCertDB();
1072 	CERTCertificate *crt_dat;
1073 	PRTime now = PR_Now();
1074 	SECStatus rv;
1075 	PurpleCertificate *first_cert = vrq->cert_chain->data;
1076 	CERTVerifyLog log;
1077 	gboolean self_signed = FALSE;
1078 
1079 	crt_dat = X509_NSS_DATA(first_cert);
1080 
1081 	log.arena = PORT_NewArena(512);
1082 	log.head = log.tail = NULL;
1083 	log.count = 0;
1084 	rv = CERT_VerifyCert(certdb, crt_dat, PR_TRUE, certUsageSSLServer, now, NULL, &log);
1085 
1086 	if (rv != SECSuccess || log.count > 0) {
1087 		CERTVerifyLogNode *node   = NULL;
1088 		unsigned int depth = (unsigned int)-1;
1089 
1090 		if (crt_dat->isRoot) {
1091 			self_signed = TRUE;
1092 			*flags |= PURPLE_CERTIFICATE_SELF_SIGNED;
1093 		}
1094 
1095 		/* Handling of untrusted, etc. modeled after
1096 		 * source/security/manager/ssl/src/TransportSecurityInfo.cpp in Firefox
1097 		 */
1098 		for (node = log.head; node; node = node->next) {
1099 			if (depth != node->depth) {
1100 				depth = node->depth;
1101 				purple_debug_error("nss", "CERT %d. %s %s:\n", depth,
1102 					node->cert->subjectName,
1103 					depth ? "[Certificate Authority]": "");
1104 			}
1105 			purple_debug_error("nss", "  ERROR %ld: %s\n", node->error,
1106 				PR_ErrorToName(node->error));
1107 			switch (node->error) {
1108 				case SEC_ERROR_EXPIRED_CERTIFICATE:
1109 					*flags |= PURPLE_CERTIFICATE_EXPIRED;
1110 					break;
1111 				case SEC_ERROR_REVOKED_CERTIFICATE:
1112 					*flags |= PURPLE_CERTIFICATE_REVOKED;
1113 					break;
1114 				case SEC_ERROR_UNKNOWN_ISSUER:
1115 				case SEC_ERROR_UNTRUSTED_ISSUER:
1116 					if (!self_signed) {
1117 						*flags |= PURPLE_CERTIFICATE_CA_UNKNOWN;
1118 					}
1119 					break;
1120 				case SEC_ERROR_CA_CERT_INVALID:
1121 				case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
1122 				case SEC_ERROR_UNTRUSTED_CERT:
1123 #ifdef SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
1124 				case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
1125 #endif
1126 					if (!self_signed) {
1127 						*flags |= PURPLE_CERTIFICATE_INVALID_CHAIN;
1128 					}
1129 					break;
1130 				case SEC_ERROR_BAD_SIGNATURE:
1131 				default:
1132 					*flags |= PURPLE_CERTIFICATE_INVALID_CHAIN;
1133 			}
1134 			if (node->cert)
1135 				CERT_DestroyCertificate(node->cert);
1136 		}
1137 	}
1138 
1139 	rv = CERT_VerifyCertName(crt_dat, vrq->subject_name);
1140 	if (rv != SECSuccess) {
1141 		purple_debug_error("nss", "subject name not verified\n");
1142 		*flags |= PURPLE_CERTIFICATE_NAME_MISMATCH;
1143 	}
1144 
1145 	PORT_FreeArena(log.arena, PR_FALSE);
1146 }
1147 
1148 static PurpleCertificateScheme x509_nss = {
1149 	"x509",                          /* Scheme name */
1150 	N_("X.509 Certificates"),        /* User-visible scheme name */
1151 	x509_import_from_file,           /* Certificate import function */
1152 	x509_export_certificate,         /* Certificate export function */
1153 	x509_copy_certificate,           /* Copy */
1154 	x509_destroy_certificate,        /* Destroy cert */
1155 	x509_signed_by,                  /* Signed-by */
1156 	x509_sha1sum,                    /* SHA1 fingerprint */
1157 	x509_dn,                         /* Unique ID */
1158 	x509_issuer_dn,                  /* Issuer Unique ID */
1159 	x509_common_name,                /* Subject name */
1160 	x509_check_name,                 /* Check subject name */
1161 	x509_times,                      /* Activation/Expiration time */
1162 	x509_importcerts_from_file,      /* Multiple certificate import function */
1163 	x509_register_trusted_tls_cert,  /* Register a certificate as trusted for TLS */
1164 	x509_verify_cert,                /* Verify that the specified cert chain is trusted */
1165 	sizeof(PurpleCertificateScheme), /* struct_size */
1166 	x509_sha256sum,                  /* SHA256 fingerprint */
1167 	NULL,
1168 };
1169 
1170 static PurpleSslOps ssl_ops =
1171 {
1172 	ssl_nss_init,
1173 	ssl_nss_uninit,
1174 	ssl_nss_connect,
1175 	ssl_nss_close,
1176 	ssl_nss_read,
1177 	ssl_nss_write,
1178 	ssl_nss_peer_certs,
1179 
1180 	/* padding */
1181 	NULL,
1182 	NULL,
1183 	NULL
1184 };
1185 
1186 
1187 static gboolean
plugin_load(PurplePlugin * plugin)1188 plugin_load(PurplePlugin *plugin)
1189 {
1190 	if (!purple_ssl_get_ops()) {
1191 		purple_ssl_set_ops(&ssl_ops);
1192 	}
1193 
1194 	/* Init NSS now, so others can use it even if sslconn never does */
1195 	ssl_nss_init_nss();
1196 
1197 	/* Register the X.509 functions we provide */
1198 	purple_certificate_register_scheme(&x509_nss);
1199 
1200 	return TRUE;
1201 }
1202 
1203 static gboolean
plugin_unload(PurplePlugin * plugin)1204 plugin_unload(PurplePlugin *plugin)
1205 {
1206 	if (purple_ssl_get_ops() == &ssl_ops) {
1207 		purple_ssl_set_ops(NULL);
1208 	}
1209 
1210 	/* Unregister our X.509 functions */
1211 	purple_certificate_unregister_scheme(&x509_nss);
1212 
1213 	return TRUE;
1214 }
1215 
1216 static PurplePluginInfo info =
1217 {
1218 	PURPLE_PLUGIN_MAGIC,
1219 	PURPLE_MAJOR_VERSION,
1220 	PURPLE_MINOR_VERSION,
1221 	PURPLE_PLUGIN_STANDARD,                             /**< type           */
1222 	NULL,                                             /**< ui_requirement */
1223 	PURPLE_PLUGIN_FLAG_INVISIBLE,                       /**< flags          */
1224 	NULL,                                             /**< dependencies   */
1225 	PURPLE_PRIORITY_DEFAULT,                            /**< priority       */
1226 
1227 	SSL_NSS_PLUGIN_ID,                             /**< id             */
1228 	N_("NSS"),                                        /**< name           */
1229 	DISPLAY_VERSION,                                  /**< version        */
1230 	                                                  /**  summary        */
1231 	N_("Provides SSL support through Mozilla NSS."),
1232 	                                                  /**  description    */
1233 	N_("Provides SSL support through Mozilla NSS."),
1234 	"Christian Hammond <chipx86@gnupdate.org>",
1235 	PURPLE_WEBSITE,                                     /**< homepage       */
1236 
1237 	plugin_load,                                      /**< load           */
1238 	plugin_unload,                                    /**< unload         */
1239 	NULL,                                             /**< destroy        */
1240 
1241 	NULL,                                             /**< ui_info        */
1242 	NULL,                                             /**< extra_info     */
1243 	NULL,                                             /**< prefs_info     */
1244 	NULL,                                             /**< actions        */
1245 
1246 	/* padding */
1247 	NULL,
1248 	NULL,
1249 	NULL,
1250 	NULL
1251 };
1252 
1253 static void
init_plugin(PurplePlugin * plugin)1254 init_plugin(PurplePlugin *plugin)
1255 {
1256 }
1257 
1258 PURPLE_INIT_PLUGIN(ssl_nss, init_plugin, info)
1259