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