Add ACL tests.
[pwmd.git] / src / tls.c
blob57f048e513f3fdaba57c486047228a5f04091522
1 /*
2 Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
3 Ben Kibbey <bjk@luxsci.net>
5 This file is part of pwmd.
7 Pwmd 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 2 of the License, or
10 (at your option) any later version.
12 Pwmd 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.
17 You should have received a copy of the GNU General Public License
18 along with Pwmd. If not, see <http://www.gnu.org/licenses/>.
20 #include <gnutls/x509.h>
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
26 #include <errno.h>
28 #include "pwmd-error.h"
29 #include "mem.h"
30 #include "util-misc.h"
31 #include "util-string.h"
32 #include "common.h"
33 #include "tls.h"
34 #include "mutex.h"
36 #define WAIT_INTERVAL 50000
37 #define TEST_TIMEOUT(timeout, ret, start, rc) \
38 do { \
39 if (ret == GNUTLS_E_AGAIN) \
40 { \
41 time_t now = time (NULL); \
42 if (timeout && now - start >= timeout) \
43 { \
44 rc = gpg_error (GPG_ERR_ETIMEDOUT); \
45 break; \
46 } \
47 } \
48 if (ret != GNUTLS_E_INTERRUPTED) \
49 break; \
50 struct timeval tv = { 0, WAIT_INTERVAL }; \
51 select (0, NULL, NULL, NULL, &tv); \
52 } \
53 while (0)
55 static char *
56 tls_fingerprint (gnutls_session_t ses)
58 gnutls_x509_crt_t crt;
59 const gnutls_datum_t *cert_list;
60 unsigned count;
61 unsigned char buf[20];
62 size_t len = sizeof (buf);
64 gnutls_x509_crt_init (&crt);
65 if (!crt)
67 log_write ("%s(%i): %s(): %s", __FILE__, __LINE__, __FUNCTION__,
68 gnutls_strerror (GNUTLS_E_MEMORY_ERROR));
69 return NULL;
72 cert_list = gnutls_certificate_get_peers (ses, &count);
73 if (count)
75 gnutls_x509_crt_import (crt, &cert_list[0], GNUTLS_X509_FMT_DER);
76 gnutls_x509_crt_get_fingerprint (crt, GNUTLS_DIG_SHA1, buf, &len);
79 gnutls_x509_crt_deinit (crt);
80 return bin2hex (buf, len);
83 static int
84 verify_client_certificate (unsigned status)
86 if (!status)
87 return 0;
89 if (status & GNUTLS_CERT_INVALID)
90 log_write (_("client certificate is invalid"));
92 if (status & GNUTLS_CERT_REVOKED)
93 log_write (_("client certificate is revoked"));
95 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
96 log_write (_("client certificate has no signer"));
98 if (status & GNUTLS_CERT_SIGNER_NOT_CA)
99 log_write (_("client certificate signer is not from CA"));
101 if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
102 log_write (_("client certificate has insecure algorithm"));
104 return GNUTLS_E_CERTIFICATE_ERROR;
107 struct tls_s *
108 tls_init (int fd, int timeout, const char *prio)
110 struct tls_s *tls = xcalloc (1, sizeof (struct tls_s));
111 int ret;
112 unsigned status;
113 const char *prio_error;
114 gnutls_kx_algorithm_t kx;
116 if (!tls)
118 log_write ("%s(%i): %s: %s", __FILE__, __LINE__, __FUNCTION__,
119 strerror (ENOMEM));
120 return NULL;
123 ret = gnutls_init (&tls->ses, GNUTLS_SERVER);
124 if (ret != GNUTLS_E_SUCCESS)
125 goto fail;
127 ret = gnutls_priority_set_direct (tls->ses, prio, &prio_error);
128 if (ret != GNUTLS_E_SUCCESS)
129 goto fail;
131 ret = gnutls_credentials_set (tls->ses, GNUTLS_CRD_CERTIFICATE, x509_cred);
132 if (ret != GNUTLS_E_SUCCESS)
133 goto fail;
135 gnutls_certificate_server_set_request (tls->ses, GNUTLS_CERT_REQUIRE);
136 gnutls_transport_set_ptr (tls->ses, (gnutls_transport_ptr_t) fd);
137 time_t start = time (NULL);
140 gpg_error_t rc = 0;
142 ret = gnutls_handshake (tls->ses);
143 TEST_TIMEOUT (timeout, ret, start, rc);
144 if (rc)
145 goto fail;
147 while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
149 if (ret != GNUTLS_E_SUCCESS)
150 goto fail;
152 ret = gnutls_certificate_verify_peers2 (tls->ses, &status);
153 if (ret)
154 goto fail;
156 kx = gnutls_kx_get (tls->ses);
157 tls->fp = tls_fingerprint (tls->ses);
158 log_write ("PROTO=%s CIPHER=%s MAC=%s KX=%s(%d) FP=%s",
159 gnutls_protocol_get_name (gnutls_protocol_get_version
160 (tls->ses)),
161 gnutls_cipher_get_name (gnutls_cipher_get (tls->ses)),
162 gnutls_mac_get_name (gnutls_mac_get (tls->ses)),
163 gnutls_kx_get_name (kx), gnutls_dh_get_prime_bits (tls->ses),
164 tls->fp ? tls->fp : "N/A");
165 ret = verify_client_certificate (status);
166 if (ret)
167 goto fail;
169 return tls;
171 fail:
172 log_write ("%s", gnutls_strerror (ret));
173 gnutls_deinit (tls->ses);
174 xfree (tls->fp);
175 xfree (tls);
176 return NULL;
179 /* From the documentation. */
181 tls_get_params (gnutls_session_t ses, gnutls_params_type_t type,
182 gnutls_params_st * st)
184 if (type == GNUTLS_PARAMS_RSA_EXPORT)
185 st->params.rsa_export = rsa_params;
186 else if (type == GNUTLS_PARAMS_DH)
187 st->params.dh = dh_params;
188 else
189 return -1;
191 st->type = type;
192 st->deinit = 0;
193 return 0;
196 void
197 tls_log (int level, const char *msg)
199 log_write ("TLS: %i: %s", level, msg);
202 ssize_t
203 tls_read_hook (assuan_context_t ctx, assuan_fd_t fd, void *data, size_t len)
205 struct client_s *client = assuan_get_pointer (ctx);
206 time_t start = time (NULL);
207 ssize_t ret;
209 rehandshake:
212 gpg_error_t rc = 0;
214 ret = gnutls_record_recv (client->thd->tls->ses, data, len);
215 if (ret == GNUTLS_E_REHANDSHAKE)
217 ret = gnutls_rehandshake (client->thd->tls->ses);
218 if (ret == GNUTLS_E_GOT_APPLICATION_DATA)
219 goto rehandshake;
220 else if (ret != GNUTLS_E_SUCCESS)
222 log_write ("%s", gnutls_strerror (ret));
223 ret = 0;
224 break;
229 ret = gnutls_handshake (client->thd->tls->ses);
230 rc = 0;
231 TEST_TIMEOUT (client->thd->timeout, ret, start, rc);
232 if (rc)
234 log_write ("%s", gnutls_strerror (ret));
235 ret = 0;
236 close (client->thd->fd);
237 client->thd->fd = -1;
238 break;
241 while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
243 if (rc)
244 break;
246 continue;
249 TEST_TIMEOUT (client->thd->timeout, ret, start, rc);
250 if (rc)
252 errno = ETIMEDOUT;
253 close (client->thd->fd);
254 client->thd->fd = -1;
255 return -1;
258 while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
260 if (ret < 0)
262 log_write ("%s", gnutls_strerror (ret));
263 ret = 0;
266 return ret;
269 ssize_t
270 tls_write_hook (assuan_context_t ctx, assuan_fd_t fd, const void *data,
271 size_t len)
273 struct client_s *client = assuan_get_pointer (ctx);
274 ssize_t ret;
275 time_t start = time (NULL);
279 gpg_error_t rc = 0;
281 ret = gnutls_record_send (client->thd->tls->ses, data, len);
282 TEST_TIMEOUT (client->thd->timeout, ret, start, rc);
283 if (rc)
285 errno = ETIMEDOUT;
286 close (client->thd->fd);
287 client->thd->fd = -1;
288 return -1;
291 while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
293 return ret;
296 gpg_error_t
297 tls_init_params ()
299 int n;
300 char *tmp, *tmp2;
301 gpg_error_t rc = GPG_ERR_UNKNOWN_ERRNO;
303 n = gnutls_certificate_allocate_credentials (&x509_cred);
304 if (n != GNUTLS_E_SUCCESS)
306 log_write ("%s", gnutls_strerror (n));
307 x509_cred = NULL;
308 goto fail;
311 tmp = str_asprintf ("%s/ca-cert.pem", homedir);
312 if (!tmp)
314 rc = GPG_ERR_ENOMEM;
315 goto fail;
318 n = gnutls_certificate_set_x509_trust_file (x509_cred, tmp,
319 GNUTLS_X509_FMT_PEM);
320 if (n < 0)
322 log_write ("%s: %s", tmp, gnutls_strerror (n));
323 xfree (tmp);
324 goto fail;
327 xfree (tmp);
328 tmp = str_asprintf ("%s/server-cert.pem", homedir);
329 if (!tmp)
331 rc = GPG_ERR_ENOMEM;
332 goto fail;
335 tmp2 = str_asprintf ("%s/server-key.pem", homedir);
336 if (!tmp2)
338 xfree (tmp);
339 log_write ("%s(%i): %s", __FILE__, __LINE__, strerror (ENOMEM));
340 goto fail;
343 n = gnutls_certificate_set_x509_key_file (x509_cred, tmp, tmp2,
344 GNUTLS_X509_FMT_PEM);
345 xfree (tmp);
346 xfree (tmp2);
347 if (n != GNUTLS_E_SUCCESS)
349 log_write ("%s", gnutls_strerror (n));
350 goto fail;
353 log_write ("%s", _("Generating key exchange parameters..."));
354 n = gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
355 if (n)
357 log_write ("%s", pwmd_strerror (n));
358 goto fail;
361 n = gnutls_dh_params_init (&dh_params);
362 if (n != GNUTLS_E_SUCCESS)
364 log_write ("%s", gnutls_strerror (n));
365 goto fail;
368 n = gnutls_dh_params_generate2 (dh_params, 1024);
369 if (n != GNUTLS_E_SUCCESS)
371 log_write ("%s", gnutls_strerror (n));
372 goto fail;
375 gnutls_certificate_set_dh_params (x509_cred, dh_params);
376 n = gnutls_rsa_params_init (&rsa_params);
377 if (n != GNUTLS_E_SUCCESS)
379 log_write ("%s", gnutls_strerror (n));
380 goto fail;
383 n = gnutls_rsa_params_generate2 (rsa_params, 512);
384 if (n != GNUTLS_E_SUCCESS)
386 log_write ("%s", gnutls_strerror (n));
387 goto fail;
390 gnutls_certificate_set_rsa_export_params (x509_cred, rsa_params);
391 gnutls_certificate_set_params_function (x509_cred, tls_get_params);
392 return 0;
394 fail:
395 return rc;
398 void
399 tls_deinit_params ()
401 if (dh_params)
403 gnutls_dh_params_deinit (dh_params);
404 dh_params = NULL;
407 if (rsa_params)
409 gnutls_rsa_params_deinit (rsa_params);
410 rsa_params = NULL;
413 if (x509_cred)
415 gnutls_certificate_free_credentials (x509_cred);
416 x509_cred = NULL;
420 static gpg_error_t
421 do_tls_validate_access (struct client_s *client, const char *section)
423 char **access = config_get_list (section, "tls_access");
424 char **p;
425 int allowed = 0;
427 if (!access || !*access)
428 return GPG_ERR_INV_USER_ID;
430 for (p = access; p && *p; p++)
432 int not = 0;
433 char *fp = *p;
435 if (*fp && *fp == '+' && *(fp + 1) == 0) // allow all connections
437 allowed = 1;
438 continue;
441 if (*fp == '!' || *fp == '-')
443 not = 1;
444 fp++;
445 if (!*fp)
446 break;
449 if (*fp == '#') // avoid confusion with ACL's: allow prefixing the hash
450 fp++;
452 if (!strcasecmp (client->thd->tls->fp, fp))
453 allowed = not ? 0 : 1;
456 strv_free (access);
457 return allowed ? 0 : GPG_ERR_INV_USER_ID;
460 gpg_error_t
461 tls_validate_access (struct client_s *client, const char *filename)
463 gpg_error_t rc = do_tls_validate_access (client, "global");
465 if (!rc && filename)
466 rc = do_tls_validate_access (client, filename);
468 return rc;