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