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