]> sjero.net Git - wget/blob - src/gnutls.c
Fix some problems with GNU TLS.
[wget] / src / gnutls.c
1 /* SSL support via GnuTLS library.
2    Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
3    Foundation, Inc.
4
5 This file is part of GNU Wget.
6
7 GNU Wget is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 GNU Wget is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Wget.  If not, see <http://www.gnu.org/licenses/>.
19
20 Additional permission under GNU GPL version 3 section 7
21
22 If you modify this program, or any covered work, by linking or
23 combining it with the OpenSSL project's OpenSSL library (or a
24 modified version of that library), containing parts covered by the
25 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
26 grants you additional permission to convey the resulting work.
27 Corresponding Source for a non-source form of such a combination
28 shall include the source code for the parts of OpenSSL used as well
29 as that of the covered work.  */
30
31 #include "wget.h"
32
33 #include <assert.h>
34 #include <errno.h>
35 #ifdef HAVE_UNISTD_H
36 # include <unistd.h>
37 #endif
38 #include <string.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41
42 #include <gnutls/gnutls.h>
43 #include <gnutls/x509.h>
44
45 #include "utils.h"
46 #include "connect.h"
47 #include "url.h"
48 #include "ssl.h"
49
50 /* Note: some of the functions private to this file have names that
51    begin with "wgnutls_" (e.g. wgnutls_read) so that they wouldn't be
52    confused with actual gnutls functions -- such as the gnutls_read
53    preprocessor macro.  */
54
55 static gnutls_certificate_credentials credentials;
56
57 bool
58 ssl_init ()
59 {
60   gnutls_global_init ();
61   gnutls_certificate_allocate_credentials (&credentials);
62   if (opt.ca_cert)
63     gnutls_certificate_set_x509_trust_file (credentials, opt.ca_cert,
64                                             GNUTLS_X509_FMT_PEM);
65   return true;
66 }
67
68 struct wgnutls_transport_context {
69   gnutls_session session;       /* GnuTLS session handle */
70   int last_error;               /* last error returned by read/write/... */
71
72   /* Since GnuTLS doesn't support the equivalent to recv(...,
73      MSG_PEEK) or SSL_peek(), we have to do it ourselves.  Peeked data
74      is stored to PEEKBUF, and wgnutls_read checks that buffer before
75      actually reading.  */
76   char peekbuf[512];
77   int peeklen;
78 };
79
80 #ifndef MIN
81 # define MIN(i, j) ((i) <= (j) ? (i) : (j))
82 #endif
83
84 static int
85 wgnutls_read (int fd, char *buf, int bufsize, void *arg)
86 {
87   int ret = 0;
88   struct wgnutls_transport_context *ctx = arg;
89
90   if (ctx->peeklen)
91     {
92       /* If we have any peek data, simply return that. */
93       int copysize = MIN (bufsize, ctx->peeklen);
94       memcpy (buf, ctx->peekbuf, copysize);
95       ctx->peeklen -= copysize;
96       if (ctx->peeklen != 0)
97         memmove (ctx->peekbuf, ctx->peekbuf + copysize, ctx->peeklen);
98
99       return copysize;
100     }
101
102   do
103     ret = gnutls_record_recv (ctx->session, buf, bufsize);
104   while (ret == GNUTLS_E_INTERRUPTED);
105
106   if (ret < 0)
107     ctx->last_error = ret;
108   return ret;
109 }
110
111 static int
112 wgnutls_write (int fd, char *buf, int bufsize, void *arg)
113 {
114   int ret;
115   struct wgnutls_transport_context *ctx = arg;
116   do
117     ret = gnutls_record_send (ctx->session, buf, bufsize);
118   while (ret == GNUTLS_E_INTERRUPTED);
119   if (ret < 0)
120     ctx->last_error = ret;
121   return ret;
122 }
123
124 static int
125 wgnutls_poll (int fd, double timeout, int wait_for, void *arg)
126 {
127   struct wgnutls_transport_context *ctx = arg;
128   return ctx->peeklen || gnutls_record_check_pending (ctx->session)
129     || select_fd (fd, timeout, wait_for);
130 }
131
132 static int
133 wgnutls_peek (int fd, char *buf, int bufsize, void *arg)
134 {
135   int ret = 0;
136   struct wgnutls_transport_context *ctx = arg;
137   int offset = ctx->peeklen;
138
139   if (bufsize > sizeof ctx->peekbuf)
140     bufsize = sizeof ctx->peekbuf;
141
142   if (offset)
143     memcpy (buf, ctx->peekbuf, offset);
144
145   do
146     {
147       if (gnutls_record_check_pending (ctx->session)
148           || select_fd (fd, 0, WAIT_FOR_READ))
149         ret = gnutls_record_recv (ctx->session, buf + offset, bufsize - offset);
150     }
151   while (ret == GNUTLS_E_INTERRUPTED);
152
153   if (ret > 0)
154     {
155       memcpy (ctx->peekbuf + offset, buf + offset, ret);
156       ctx->peeklen += ret;
157     }
158   return ctx->peeklen;
159 }
160
161 static const char *
162 wgnutls_errstr (int fd, void *arg)
163 {
164   struct wgnutls_transport_context *ctx = arg;
165   return gnutls_strerror (ctx->last_error);
166 }
167
168 static void
169 wgnutls_close (int fd, void *arg)
170 {
171   struct wgnutls_transport_context *ctx = arg;
172   /*gnutls_bye (ctx->session, GNUTLS_SHUT_RDWR);*/
173   gnutls_deinit (ctx->session);
174   xfree (ctx);
175   close (fd);
176 }
177
178 /* gnutls_transport is the singleton that describes the SSL transport
179    methods provided by this file.  */
180
181 static struct transport_implementation wgnutls_transport = {
182   wgnutls_read, wgnutls_write, wgnutls_poll,
183   wgnutls_peek, wgnutls_errstr, wgnutls_close
184 };
185
186 bool
187 ssl_connect_wget (int fd)
188 {
189   static const int cert_type_priority[] = {
190     GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0
191   };
192   struct wgnutls_transport_context *ctx;
193   gnutls_session session;
194   int err;
195   gnutls_init (&session, GNUTLS_CLIENT);
196   gnutls_set_default_priority (session);
197   gnutls_certificate_type_set_priority (session, cert_type_priority);
198   gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, credentials);
199   gnutls_transport_set_ptr (session, (gnutls_transport_ptr) fd);
200   err = gnutls_handshake (session);
201   if (err < 0)
202     {
203       logprintf (LOG_NOTQUIET, "GnuTLS: %s\n", gnutls_strerror (err));
204       gnutls_deinit (session);
205       return false;
206     }
207   ctx = xnew0 (struct wgnutls_transport_context);
208   ctx->session = session;
209   fd_register_transport (fd, &wgnutls_transport, ctx);
210   return true;
211 }
212
213 bool
214 ssl_check_certificate (int fd, const char *host)
215 {
216   struct wgnutls_transport_context *ctx = fd_transport_context (fd);
217
218   unsigned int status;
219   int err;
220
221   /* If the user has specified --no-check-cert, we still want to warn
222      him about problems with the server's certificate.  */
223   const char *severity = opt.check_cert ? _("ERROR") : _("WARNING");
224   bool success = true;
225
226   err = gnutls_certificate_verify_peers2 (ctx->session, &status);
227   if (err < 0)
228     {
229       logprintf (LOG_NOTQUIET, _("%s: No certificate presented by %s.\n"),
230                  severity, quotearg_style (escape_quoting_style, host));
231       success = false;
232       goto out;
233     }
234
235   if (status & GNUTLS_CERT_INVALID)
236     {
237       logprintf (LOG_NOTQUIET, _("%s: The certificate of %s is not trusted.\n"),
238                  severity, quote (host));
239       success = false;
240     }
241   if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
242     {
243       logprintf (LOG_NOTQUIET, _("%s: The certificate of %s hasn't got a known issuer.\n"),
244                  severity, quote (host));
245       success = false;
246     }
247   if (status & GNUTLS_CERT_REVOKED)
248     {
249       logprintf (LOG_NOTQUIET, _("%s: The certificate of %s has been revoked.\n"),
250                  severity, quote (host));
251       success = false;
252     }
253
254   if (gnutls_certificate_type_get (ctx->session) == GNUTLS_CRT_X509)
255     {
256       time_t now = time (NULL);
257       gnutls_x509_crt cert;
258       const gnutls_datum *cert_list;
259       unsigned int cert_list_size;
260
261       if ((err = gnutls_x509_crt_init (&cert)) < 0)
262         {
263           logprintf (LOG_NOTQUIET, _("Error initializing X509 certificate: %s\n"),
264                      gnutls_strerror (err));
265           success = false;
266           goto out;
267         }
268
269       cert_list = gnutls_certificate_get_peers (ctx->session, &cert_list_size);
270       if (!cert_list)
271         {
272           logprintf (LOG_NOTQUIET, _("No certificate found\n"));
273           success = false;
274           goto out;
275         }
276       err = gnutls_x509_crt_import (cert, cert_list, GNUTLS_X509_FMT_DER);
277       if (err < 0)
278         {
279           logprintf (LOG_NOTQUIET, _("Error parsing certificate: %s\n"),
280                      gnutls_strerror (err));
281           success = false;
282           goto out;
283         }
284       if (now < gnutls_x509_crt_get_activation_time (cert))
285         {
286           logprintf (LOG_NOTQUIET, _("The certificate has not yet been activated\n"));
287           success = false;
288         }
289       if (now >= gnutls_x509_crt_get_expiration_time (cert))
290         {
291           logprintf (LOG_NOTQUIET, _("The certificate has expired\n"));
292           success = false;
293         }
294       if (!gnutls_x509_crt_check_hostname (cert, host))
295         {
296           logprintf (LOG_NOTQUIET,
297                      _("The certificate's owner does not match hostname %s\n"),
298                      quote (host));
299           success = false;
300         }
301       gnutls_x509_crt_deinit (cert);
302    }
303
304  out:
305   return opt.check_cert ? success : true;
306 }