Add pwmd_deinit().
[libpwmd.git] / src / tls.c
blob674cc8192549aa27d41e896abeb2800219e30ba6
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"
28 #include "misc.h"
30 void tls_free(struct tls_s *tls)
32 if (!tls)
33 return;
35 pwmd_free(tls->ca);
36 pwmd_free(tls->cert);
37 pwmd_free(tls->key);
38 pwmd_free(tls->priority);
40 if (tls->session) {
41 gnutls_bye(tls->session, GNUTLS_SHUT_WR);
42 gnutls_deinit(tls->session);
45 if (tls->x509)
46 gnutls_certificate_free_credentials(tls->x509);
48 tls->session = NULL;
49 tls->x509 = NULL;
50 pwmd_free(tls);
53 ssize_t read_hook_tls(struct tls_s *tls, assuan_fd_t fd, void *data,
54 size_t len)
56 ssize_t ret;
58 do {
59 ret = gnutls_record_recv(tls->session, data, len);
60 if (ret == GNUTLS_E_REHANDSHAKE) {
61 ret = gnutls_rehandshake(tls->session);
63 if (ret == GNUTLS_E_WARNING_ALERT_RECEIVED ||
64 ret == GNUTLS_A_NO_RENEGOTIATION) {
65 gnutls_perror(ret);
66 continue;
69 if (ret != GNUTLS_E_SUCCESS) {
70 gnutls_perror(ret);
71 ret = 0;
72 break;
75 ret = gnutls_handshake(tls->session);
76 if (ret != GNUTLS_E_SUCCESS) {
77 gnutls_perror(ret);
78 ret = 0;
79 break;
82 continue;
84 } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
86 return ret <= 0 ? 0 : ret;
89 ssize_t write_hook_tls(struct tls_s *tls, assuan_fd_t fd, const void *data,
90 size_t len)
92 ssize_t ret;
94 do {
95 ret = gnutls_record_send(tls->session, data, len);
96 if (ret == GNUTLS_E_AGAIN)
97 usleep(50000);
98 } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
100 return ret <= 0 ? 0 : ret;
103 static gpg_error_t verify_certificate(pwm_t *pwm)
105 unsigned int status;
106 const gnutls_datum_t *cert_list;
107 unsigned int cert_list_size;
108 int i;
109 gnutls_x509_crt_t cert;
110 gpg_error_t rc;
112 rc = gnutls_certificate_verify_peers2(pwm->tcp->tls->session, &status);
113 if (rc < 0) {
114 gnutls_perror(rc);
115 return GPG_ERR_BAD_CERT;
118 if (status & GNUTLS_CERT_INVALID)
119 return GPG_ERR_BAD_CERT;
121 if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
122 return GPG_ERR_BAD_SIGNATURE;
124 if (status & GNUTLS_CERT_REVOKED)
125 return GPG_ERR_CERT_REVOKED;
127 if (gnutls_certificate_type_get(pwm->tcp->tls->session) != GNUTLS_CRT_X509)
128 return GPG_ERR_UNSUPPORTED_CERT;
130 if (gnutls_x509_crt_init(&cert) < 0)
131 return gpg_error_from_errno(ENOMEM);
133 cert_list = gnutls_certificate_get_peers(pwm->tcp->tls->session,
134 &cert_list_size);
135 if (!cert_list) {
136 rc = GPG_ERR_MISSING_CERT;
137 goto done;
140 for (i = 0; i < cert_list_size; i++) {
141 if (gnutls_x509_crt_import(cert, &cert_list[i], GNUTLS_X509_FMT_DER) < 0) {
142 rc = GPG_ERR_BAD_CERT_CHAIN;
143 goto done;
146 if (gnutls_x509_crt_get_expiration_time(cert) < time(0)) {
147 rc = GPG_ERR_CERT_EXPIRED;
148 goto done;
151 if (gnutls_x509_crt_get_activation_time(cert) > time(0)) {
152 rc = GPG_ERR_CERT_TOO_YOUNG;
153 goto done;
156 if (pwm->tcp->tls->verify) {
157 if (!gnutls_x509_crt_check_hostname(cert, pwm->tcp->host)) {
158 rc = GPG_ERR_BAD_CERT_CHAIN;
159 goto done;
164 done:
165 gnutls_x509_crt_deinit(cert);
166 return rc;
169 static gpg_error_t tls_init(pwm_t *pwm)
171 gpg_error_t rc;
172 const char *errstr;
174 rc = gnutls_certificate_allocate_credentials(&pwm->tcp->tls->x509);
175 if (rc)
176 return gpg_error_from_errno(ENOMEM);
178 /* The client certificate must be signed by the CA of the pwmd server
179 * certificate in order for the client to authenticate successfully. Man in
180 * the middle attacks are still possible if the attacker is running a pwmd
181 * that doesn't require client certificate authentication. So require the
182 * client to verify the server certificate.
184 rc = gnutls_certificate_set_x509_trust_file (pwm->tcp->tls->x509,
185 pwm->tcp->tls->ca, GNUTLS_X509_FMT_PEM);
186 if (rc == -1) {
187 gnutls_perror(rc);
188 rc = GPG_ERR_INV_CERT_OBJ;
189 goto fail;
192 rc = gnutls_certificate_set_x509_key_file(pwm->tcp->tls->x509,
193 pwm->tcp->tls->cert, pwm->tcp->tls->key, GNUTLS_X509_FMT_PEM);
194 if (rc != GNUTLS_E_SUCCESS) {
195 gnutls_perror(rc);
196 rc = GPG_ERR_INV_CERT_OBJ;
197 goto fail;
200 rc = gnutls_init(&pwm->tcp->tls->session, GNUTLS_CLIENT);
201 if (rc != GNUTLS_E_SUCCESS) {
202 gnutls_perror(rc);
203 rc = GPG_ERR_INV_CERT_OBJ;
204 goto fail;
207 rc = gnutls_priority_set_direct(pwm->tcp->tls->session,
208 pwm->tcp->tls->priority ? pwm->tcp->tls->priority : "SECURE256",
209 &errstr);
210 if (rc != GNUTLS_E_SUCCESS) {
211 gnutls_perror(rc);
212 rc = GPG_ERR_INV_CERT_OBJ;
213 goto fail;
216 rc = gnutls_credentials_set(pwm->tcp->tls->session, GNUTLS_CRD_CERTIFICATE,
217 pwm->tcp->tls->x509);
218 if (rc != GNUTLS_E_SUCCESS) {
219 gnutls_perror(rc);
220 rc = GPG_ERR_INV_CERT_OBJ;
221 goto fail;
224 gnutls_transport_set_ptr(pwm->tcp->tls->session,
225 (gnutls_transport_ptr_t)pwm->tcp->fd);
226 rc = gnutls_handshake(pwm->tcp->tls->session);
227 if (rc != GNUTLS_E_SUCCESS) {
228 gnutls_perror(rc);
229 rc = GPG_ERR_INV_CERT_OBJ;
230 goto fail;
233 rc = verify_certificate(pwm);
235 fail:
236 if (rc) {
237 free_tcp(pwm->tcp);
238 pwm->tcp = NULL;
241 return rc;
244 gpg_error_t _do_tls_connect(pwm_t *pwm, const char *host, int port,
245 const char *cert, const char *key, const char *cacert,
246 const char *prio, int verify)
248 struct tcp_s *tcp = pwmd_calloc(1, sizeof(struct tcp_s));
249 gpg_error_t rc;
251 if (!tcp)
252 return GPG_ERR_ENOMEM;
254 if (!cert || !key || !cacert) {
255 pwmd_free(tcp);
256 return GPG_ERR_INV_ARG;
259 pwm->tcp = tcp;
260 tcp->port = port;
261 tcp->host = pwmd_strdup(host);
262 if (!tcp->host) {
263 rc = GPG_ERR_ENOMEM;
264 goto fail;
267 tcp->tls = pwmd_calloc(1, sizeof(struct tls_s));
268 if (!tcp->tls) {
269 rc = GPG_ERR_ENOMEM;
270 goto fail;
273 tcp->tls->verify = verify;
274 tcp->tls->cert = pwmd_strdup(cert);
275 if (!tcp->tls->cert) {
276 rc = GPG_ERR_ENOMEM;
277 goto fail;
280 tcp->tls->key = pwmd_strdup(key);
281 if (!tcp->tls->key) {
282 rc = GPG_ERR_ENOMEM;
283 goto fail;
286 tcp->tls->ca = pwmd_strdup(cacert);
287 if (!tcp->tls->ca) {
288 rc = GPG_ERR_ENOMEM;
289 goto fail;
292 if (prio) {
293 tcp->tls->priority = pwmd_strdup(prio);
294 if (!tcp->tls->priority) {
295 rc = GPG_ERR_ENOMEM;
296 goto fail;
300 rc = tcp_connect_common(pwm);
301 if (!rc) {
302 rc = tls_init(pwm);
303 if (!rc)
304 rc = assuan_socket_connect_fd(pwm->ctx, pwm->tcp->fd, 0);
307 fail:
308 if (rc) {
309 free_tcp(pwm->tcp);
310 pwm->tcp = NULL;
313 return rc;
317 * tls[46]://[hostname][:port]
319 gpg_error_t _parse_tls_url(const char *str, char **host, int *port)
321 *port = 6466;
322 return parse_hostname_common(str, host, port);