Reimplement GnuTLS support.
[libpwmd.git] / src / tls.c
bloba324d9f77ac233035533be7f7f02deca9e8d40a8
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2012
4 Ben Kibbey <bjk@luxsci.net>
6 This file is part of libpwmd.
8 Libpwmd is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 2 of the License, or
11 (at your option) any later version.
13 Libpwmd is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with Libpwmd. If not, see <http://www.gnu.org/licenses/>.
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <errno.h>
25 #include <ctype.h>
27 #include "types.h"
29 void tls_free(struct tls_s *tls)
31 if (!tls)
32 return;
34 pwmd_free(tls->ca);
35 pwmd_free(tls->cert);
36 pwmd_free(tls->key);
37 pwmd_free(tls->priority);
39 if (tls->session) {
40 gnutls_bye(tls->session, GNUTLS_SHUT_WR);
41 gnutls_deinit(tls->session);
44 if (tls->x509)
45 gnutls_certificate_free_credentials(tls->x509);
47 tls->session = NULL;
48 tls->x509 = NULL;
49 pwmd_free(tls);
52 ssize_t read_hook_tls(struct tls_s *tls, assuan_fd_t fd, void *data,
53 size_t len)
55 ssize_t ret;
57 do {
58 ret = gnutls_record_recv(tls->session, data, len);
59 if (ret == GNUTLS_E_REHANDSHAKE) {
60 ret = gnutls_rehandshake(tls->session);
62 if (ret == GNUTLS_E_WARNING_ALERT_RECEIVED ||
63 ret == GNUTLS_A_NO_RENEGOTIATION) {
64 gnutls_perror(ret);
65 continue;
68 if (ret != GNUTLS_E_SUCCESS) {
69 gnutls_perror(ret);
70 ret = 0;
71 break;
74 ret = gnutls_handshake(tls->session);
75 if (ret != GNUTLS_E_SUCCESS) {
76 gnutls_perror(ret);
77 ret = 0;
78 break;
81 continue;
83 } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
85 return ret <= 0 ? 0 : ret;
88 ssize_t write_hook_tls(struct tls_s *tls, assuan_fd_t fd, const void *data,
89 size_t len)
91 ssize_t ret;
93 do {
94 ret = gnutls_record_send(tls->session, data, len);
95 if (ret == GNUTLS_E_AGAIN)
96 usleep(50000);
97 } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
99 return ret <= 0 ? 0 : ret;
102 static gpg_error_t verify_certificate(pwm_t *pwm)
104 unsigned int status;
105 const gnutls_datum_t *cert_list;
106 unsigned int cert_list_size;
107 int i;
108 gnutls_x509_crt_t cert;
109 gpg_error_t rc;
111 rc = gnutls_certificate_verify_peers2(pwm->tcp->tls->session, &status);
112 if (rc < 0) {
113 gnutls_perror(rc);
114 return GPG_ERR_BAD_CERT;
117 if (status & GNUTLS_CERT_INVALID)
118 return GPG_ERR_BAD_CERT;
120 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
121 return GPG_ERR_BAD_SIGNATURE;
123 if (status & GNUTLS_CERT_REVOKED)
124 return GPG_ERR_CERT_REVOKED;
126 if (gnutls_certificate_type_get(pwm->tcp->tls->session) != GNUTLS_CRT_X509)
127 return GPG_ERR_UNSUPPORTED_CERT;
129 if (gnutls_x509_crt_init(&cert) < 0)
130 return gpg_error_from_errno(ENOMEM);
132 cert_list = gnutls_certificate_get_peers(pwm->tcp->tls->session,
133 &cert_list_size);
134 if (!cert_list) {
135 rc = GPG_ERR_MISSING_CERT;
136 goto done;
139 for (i = 0; i < cert_list_size; i++) {
140 if (gnutls_x509_crt_import(cert, &cert_list[i], GNUTLS_X509_FMT_DER) < 0) {
141 rc = GPG_ERR_BAD_CERT_CHAIN;
142 goto done;
145 if (gnutls_x509_crt_get_expiration_time(cert) < time(0)) {
146 rc = GPG_ERR_CERT_EXPIRED;
147 goto done;
150 if (gnutls_x509_crt_get_activation_time(cert) > time(0)) {
151 rc = GPG_ERR_CERT_TOO_YOUNG;
152 goto done;
155 if (pwm->tcp->tls->verify) {
156 if (!gnutls_x509_crt_check_hostname(cert, pwm->tcp->host)) {
157 rc = GPG_ERR_BAD_CERT_CHAIN;
158 goto done;
163 done:
164 gnutls_x509_crt_deinit(cert);
165 return rc;
168 static gpg_error_t tls_init(pwm_t *pwm)
170 gpg_error_t rc;
171 const char *errstr;
173 rc = gnutls_certificate_allocate_credentials(&pwm->tcp->tls->x509);
174 if (rc)
175 return gpg_error_from_errno(ENOMEM);
177 /* The client certificate must be signed by the CA of the pwmd server
178 * certificate in order for the client to authenticate successfully. Man in
179 * the middle attacks are still possible if the attacker is running a pwmd
180 * that doesn't require client certificate authentication. So require the
181 * client to verify the server certificate.
183 rc = gnutls_certificate_set_x509_trust_file (pwm->tcp->tls->x509,
184 pwm->tcp->tls->ca, GNUTLS_X509_FMT_PEM);
185 if (rc == -1) {
186 gnutls_perror(rc);
187 rc = GPG_ERR_INV_CERT_OBJ;
188 goto fail;
191 rc = gnutls_certificate_set_x509_key_file(pwm->tcp->tls->x509,
192 pwm->tcp->tls->cert, pwm->tcp->tls->key, GNUTLS_X509_FMT_PEM);
193 if (rc != GNUTLS_E_SUCCESS) {
194 gnutls_perror(rc);
195 rc = GPG_ERR_INV_CERT_OBJ;
196 goto fail;
199 rc = gnutls_init(&pwm->tcp->tls->session, GNUTLS_CLIENT);
200 if (rc != GNUTLS_E_SUCCESS) {
201 gnutls_perror(rc);
202 rc = GPG_ERR_INV_CERT_OBJ;
203 goto fail;
206 rc = gnutls_priority_set_direct(pwm->tcp->tls->session,
207 pwm->tcp->tls->priority ? pwm->tcp->tls->priority : "SECURE256",
208 &errstr);
209 if (rc != GNUTLS_E_SUCCESS) {
210 gnutls_perror(rc);
211 rc = GPG_ERR_INV_CERT_OBJ;
212 goto fail;
215 rc = gnutls_credentials_set(pwm->tcp->tls->session, GNUTLS_CRD_CERTIFICATE,
216 pwm->tcp->tls->x509);
217 if (rc != GNUTLS_E_SUCCESS) {
218 gnutls_perror(rc);
219 rc = GPG_ERR_INV_CERT_OBJ;
220 goto fail;
223 gnutls_transport_set_ptr(pwm->tcp->tls->session,
224 (gnutls_transport_ptr_t)pwm->tcp->fd);
225 rc = gnutls_handshake(pwm->tcp->tls->session);
226 if (rc != GNUTLS_E_SUCCESS) {
227 gnutls_perror(rc);
228 rc = GPG_ERR_INV_CERT_OBJ;
229 goto fail;
232 rc = verify_certificate(pwm);
234 fail:
235 if (rc) {
236 free_tcp(pwm->tcp);
237 pwm->tcp = NULL;
240 return rc;
243 gpg_error_t _do_tls_connect(pwm_t *pwm, const char *host, int port,
244 const char *cert, const char *key, const char *cacert,
245 const char *prio, int verify)
247 struct tcp_s *tcp = pwmd_calloc(1, sizeof(struct tcp_s));
248 gpg_error_t rc;
250 if (!tcp)
251 return GPG_ERR_ENOMEM;
253 if (!cert || !key || !cacert) {
254 pwmd_free(tcp);
255 return GPG_ERR_INV_ARG;
258 pwm->tcp = tcp;
259 tcp->port = port;
260 tcp->host = pwmd_strdup(host);
261 if (!tcp->host) {
262 rc = GPG_ERR_ENOMEM;
263 goto fail;
266 tcp->tls = pwmd_calloc(1, sizeof(struct tls_s));
267 if (!tcp->tls) {
268 rc = GPG_ERR_ENOMEM;
269 goto fail;
272 tcp->tls->verify = verify;
273 tcp->tls->cert = pwmd_strdup(cert);
274 if (!tcp->tls->cert) {
275 rc = GPG_ERR_ENOMEM;
276 goto fail;
279 tcp->tls->key = pwmd_strdup(key);
280 if (!tcp->tls->key) {
281 rc = GPG_ERR_ENOMEM;
282 goto fail;
285 tcp->tls->ca = pwmd_strdup(cacert);
286 if (!tcp->tls->ca) {
287 rc = GPG_ERR_ENOMEM;
288 goto fail;
291 if (prio) {
292 tcp->tls->priority = pwmd_strdup(prio);
293 if (!tcp->tls->priority) {
294 rc = GPG_ERR_ENOMEM;
295 goto fail;
299 rc = tcp_connect_common(pwm);
300 if (!rc) {
301 rc = tls_init(pwm);
302 if (!rc)
303 rc = assuan_socket_connect_fd(pwm->ctx, pwm->tcp->fd, 0);
306 fail:
307 if (rc) {
308 free_tcp(pwm->tcp);
309 pwm->tcp = NULL;
312 return rc;
316 * tls[46]://hostname[:port]
318 gpg_error_t _parse_tls_url(const char *str, char **host, int *port)
320 const char *p;
321 char *t;
322 int len;
324 *port = 6466;
325 p = str;
326 t = strchr(p, ':');
327 if (t) {
328 len = strlen(p)-strlen(t)+1;
329 *host = pwmd_malloc(len);
330 if (!*host)
331 return gpg_error_from_errno(ENOMEM);
333 snprintf(*host, len, "%s", p);
334 t++;
335 *port = atoi(t);
336 if (*t == '-')
337 t++;
339 while (*t && isdigit(*t))
340 t++;
342 p = t;
344 else {
345 *host = pwmd_strdup(str);
346 if (!*host)
347 return gpg_error_from_errno(ENOMEM);
350 return 0;