Rocco Rutte:
[apps/madmutt.git] / mutt_ssl_nss.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 2000 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 #if HAVE_CONFIG_H
11 # include "config.h"
12 #endif
13
14 #include <prinit.h>
15 #include <pk11func.h>
16 #include <prtypes.h>
17 #include <prio.h>
18 #include <prnetdb.h>
19 #include "nss.h"
20 #include "ssl.h"
21 #include "sechash.h"
22 #include "cert.h"
23 #include "cdbhdl.h"
24 #include "mutt.h"
25 #include "mutt_socket.h"
26 #include "mutt_curses.h"
27
28 #include "lib/mem.h"
29 #include "lib/intl.h"
30
31 static int MuttNssInitialized = 0;
32
33 /* internal data struct we use with the CONNECTION.  this is where NSS-specific
34  * data gets stuffed so that the main mutt_socket.h doesn't have to be
35  * modified.
36  */
37 typedef struct {
38   PRFileDesc *fd;
39   CERTCertDBHandle *db;
40 } mutt_nss_t;
41
42 /* nss callback to grab the user's password. */
43 static char *mutt_nss_password_func (PK11SlotInfo * slot, PRBool retry,
44                                      void *arg)
45 {
46   return NULL;
47 }
48
49 static int mutt_nss_error (const char *call)
50 {
51   mutt_error (_("%s failed (error %d)"), call, PR_GetError ());
52   return -1;
53 }
54
55 /* initialize the NSS library for use.  must be called prior to any other
56  * functions in this module.
57  */
58 static int mutt_nss_init (void)
59 {
60   if (!MuttNssInitialized) {
61     PK11_SetPasswordFunc (mutt_nss_password_func);
62     if (NSS_Init (SslCertFile) == SECFailure)
63       return mutt_nss_error ("NSS_Init");
64
65     /* always use strong crypto. */
66     if (NSS_SetDomesticPolicy () == SECFailure)
67       return mutt_nss_error ("NSS_SetDomesticPolicy");
68
69     /* intialize the session cache */
70     SSL_ClearSessionCache ();
71
72     MuttNssInitialized = 1;
73   }
74   return 0;
75 }
76
77 /* convert from int64 to a readable string and print on the screen */
78 static void mutt_nss_pretty_time (int64 usecs)
79 {
80   struct tm t;
81   PRExplodedTime ex;
82   char timebuf[128];
83
84   PR_ExplodeTime (usecs, PR_LocalTimeParameters, &ex);
85
86   t.tm_sec = ex.tm_sec;
87   t.tm_min = ex.tm_min;
88   t.tm_hour = ex.tm_hour;
89   t.tm_mday = ex.tm_mday;
90   t.tm_mon = ex.tm_month;
91   t.tm_year = ex.tm_year - 1900;        /* PRExplodedTime uses the absolute year */
92   t.tm_wday = ex.tm_wday;
93   t.tm_yday = ex.tm_yday;
94
95   strfcpy (timebuf, asctime (&t), sizeof (timebuf));
96   timebuf[safe_strlen (timebuf) - 1] = 0;
97
98   addstr (timebuf);
99 }
100
101 /* this function is called by the default hook CERT_AuthCertificate when it
102  * can't verify a cert based upon the contents of the user's certificate
103  * database.  we are given the option to override the decision and accept
104  * it anyway.
105  */
106 static SECStatus mutt_nss_bad_cert (void *arg, PRFileDesc * fd)
107 {
108   PRErrorCode err;
109   CERTCertificate *cert, *issuer;
110   unsigned char hash[16];
111   int i;
112   CERTCertTrust trust;
113   int64 not_before, not_after;
114   event_t ch;
115   char status[256];
116
117   /* first lets see why this certificate failed.  we only want to override
118    * in the case where the cert was not found.
119    */
120   err = PR_GetError ();
121   mutt_error (_("SSL_AuthCertificate failed (error %d)"), err);
122
123   /* fetch the cert in question */
124   cert = SSL_PeerCertificate (fd);
125
126   move (LINES - 8, 0);
127   clrtoeol ();
128   move (LINES - 7, 0);
129   clrtoeol ();
130   addstr ("Issuer:      ");
131   addstr (CERT_NameToAscii (&cert->issuer));
132   move (LINES - 6, 0);
133   clrtoeol ();
134   addstr ("Subject:     ");
135   addstr (CERT_NameToAscii (&cert->subject));
136
137   move (LINES - 5, 0);
138   clrtoeol ();
139   addstr ("Valid:       ");
140   CERT_GetCertTimes (cert, &not_before, &not_after);
141   mutt_nss_pretty_time (not_before);
142   addstr (" to ");
143   mutt_nss_pretty_time (not_after);
144
145   move (LINES - 4, 0);
146   clrtoeol ();
147   addstr ("Fingerprint: ");
148
149   /* calculate the MD5 hash of the raw certificate */
150   HASH_HashBuf (HASH_AlgMD5, hash, cert->derCert.data, cert->derCert.len);
151   for (i = 0; i < 16; i++) {
152     printw ("%0x", hash[i]);
153     if (i != 15)
154       addch (':');
155   }
156
157   mvaddstr (LINES - 3, 0, "Signature:   ");
158   clrtoeol ();
159
160   /* find the cert which signed this cert */
161   issuer = CERT_FindCertByName ((CERTCertDBHandle *) arg, &cert->derIssuer);
162
163   /* verify the sig (only) if we have the issuer cert handy */
164   if (issuer &&
165       CERT_VerifySignedData (&cert->signatureWrap, issuer, PR_Now (), NULL)
166       == SECSuccess)
167     addstr ("GOOD");
168   else
169     addstr ("BAD");
170
171   move (LINES - 2, 0);
172   SETCOLOR (MT_COLOR_STATUS);
173   memset (status, ' ', sizeof (status) - 1);
174   if (COLS < sizeof (status))
175     status[COLS - 1] = 0;
176   else
177     status[sizeof (status) - 1] = 0;
178   memcpy (status, "--- SSL Certificate Check",
179           sizeof ("--- SSL Certificate Check") - 1);
180   addstr (status);
181   clrtoeol ();
182   SETCOLOR (MT_COLOR_NORMAL);
183
184   for (;;) {
185     mvaddstr (LINES - 1, 0, "(r)eject, accept (o)nce, (a)lways accept?");
186     clrtoeol ();
187     ch = mutt_getch ();
188     if (ch.ch == -1) {
189       i = SECFailure;
190       break;
191     }
192     else if (ascii_tolower (ch.ch) == 'r') {
193       i = SECFailure;
194       break;
195     }
196     else if (ascii_tolower (ch.ch) == 'o') {
197       i = SECSuccess;
198       break;
199     }
200     else if (ascii_tolower (ch.ch) == 'a') {
201       /* push this certificate onto the user's certificate store so it
202        * automatically becomes valid next time we see it
203        */
204
205       /* set this certificate as a valid peer for SSL-auth ONLY. */
206       CERT_DecodeTrustString (&trust, "P,,");
207
208       CERT_AddTempCertToPerm (cert, NULL, &trust);
209       i = SECSuccess;
210       break;
211     }
212     BEEP ();
213   }
214
215   /* SSL_PeerCertificate() returns a copy with an updated ref count, so
216    * we have to destroy our copy here.
217    */
218   CERT_DestroyCertificate (cert);
219
220   return i;
221 }
222
223 static int mutt_nss_socket_open (CONNECTION * con)
224 {
225   mutt_nss_t *sockdata;
226   PRNetAddr addr;
227   struct hostent *he;
228
229   memset (&addr, 0, sizeof (addr));
230   addr.inet.family = AF_INET;
231   addr.inet.port = PR_htons (con->account.port);
232   he = gethostbyname (con->account.host);
233   if (!he) {
234     mutt_error (_("Unable to find ip for host %s"), con->account.host);
235     return -1;
236   }
237   addr.inet.ip = *((int *) he->h_addr_list[0]);
238
239   sockdata = safe_calloc (1, sizeof (mutt_nss_t));
240
241   do {
242     sockdata->fd = PR_NewTCPSocket ();
243     if (sockdata->fd == NULL) {
244       mutt_error (_("PR_NewTCPSocket failed."));
245       break;
246     }
247     /* make this a SSL socket */
248     sockdata->fd = SSL_ImportFD (NULL, sockdata->fd);
249
250     /* set SSL version options based upon user's preferences */
251     if (!option (OPTTLSV1))
252       SSL_OptionSet (sockdata->fd, SSL_ENABLE_TLS, PR_FALSE);
253
254     if (!option (OPTSSLV2))
255       SSL_OptionSet (sockdata->fd, SSL_ENABLE_SSL2, PR_FALSE);
256
257     if (!option (OPTSSLV3))
258       SSL_OptionSet (sockdata->fd, SSL_ENABLE_SSL3, PR_FALSE);
259
260     /* set the host we were attempting to connect to in order to verify
261      * the name in the certificate we get back.
262      */
263     if (SSL_SetURL (sockdata->fd, con->account.host)) {
264       mutt_nss_error ("SSL_SetURL");
265       break;
266     }
267
268     /* we don't need no stinking pin.  we don't authenticate ourself
269      * via SSL.
270      */
271     SSL_SetPKCS11PinArg (sockdata->fd, 0);
272
273     sockdata->db = CERT_GetDefaultCertDB ();
274
275     /* use the default supplied hook.  it takes an argument to our
276      * certificate database.  the manual lies, you can't really specify
277      * NULL for the callback to get the default!
278      */
279     SSL_AuthCertificateHook (sockdata->fd, SSL_AuthCertificate, sockdata->db);
280     /* set the callback to be used when SSL_AuthCertificate() fails.  this
281      * allows us to override and insert the cert back into the db
282      */
283     SSL_BadCertHook (sockdata->fd, mutt_nss_bad_cert, sockdata->db);
284
285     if (PR_Connect (sockdata->fd, &addr, PR_INTERVAL_NO_TIMEOUT) ==
286         PR_FAILURE) {
287       mutt_error (_("Unable to connect to host %s"), con->account.host);
288       break;
289     }
290
291     /* store the extra info in the CONNECTION struct for later use. */
292     con->sockdata = sockdata;
293
294     /* HACK.  some of the higher level calls in mutt_socket.c depend on this
295      * being >0 when we are in the connected state.  we just set this to
296      * an arbitrary value to avoid hitting that bug, since we neve have the
297      * real fd.
298      */
299     con->fd = 42;
300
301     /* success */
302     return 0;
303   }
304   while (0);
305
306   /* we get here when we had an oops.  clean up the mess. */
307
308   if (sockdata) {
309     if (sockdata->fd)
310       PR_Close (sockdata->fd);
311     if (sockdata->db)
312       CERT_ClosePermCertDB (sockdata->db);
313     FREE (&sockdata);
314   }
315   return -1;
316 }
317
318 static int mutt_nss_socket_close (CONNECTION * con)
319 {
320   mutt_nss_t *sockdata = (mutt_nss_t *) con->sockdata;
321
322   if (PR_Close (sockdata->fd) == PR_FAILURE)
323     return -1;
324
325   if (sockdata->db)
326     CERT_ClosePermCertDB (sockdata->db);
327   /* free up the memory we used for this connection specific to NSS. */
328   FREE (&con->sockdata);
329   return 0;
330 }
331
332 static int mutt_nss_socket_read (CONNECTION * conn, char *buf, size_t len)
333 {
334   return PR_Read (((mutt_nss_t *) conn->sockdata)->fd, buf, len);
335 }
336
337 static int
338 mutt_nss_socket_write (CONNECTION * con, const char *buf, size_t count)
339 {
340   return PR_Write (((mutt_nss_t *) con->sockdata)->fd, buf, count);
341 }
342
343 /* initialize a new connection for use with NSS */
344 int mutt_nss_socket_setup (CONNECTION * con)
345 {
346   if (mutt_nss_init ())
347     return -1;
348   con->conn_open = mutt_nss_socket_open;
349   con->conn_read = mutt_nss_socket_read;
350   con->conn_write = mutt_nss_socket_write;
351   con->conn_close = mutt_nss_socket_close;
352   return 0;
353 }