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