2 Copyright (C) 2012-2023 Ben Kibbey <bjk@luxsci.net>
4 This file is part of libpwmd.
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License version 2.1 as published by the Free Software Foundation.
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
28 #include <sys/types.h>
40 #ifdef HAVE_SYS_SOCKET_H
41 # include <sys/socket.h>
48 #define DEFAULT_TLS_PRIORITY "SECURE256:SECURE192:SECURE128:" \
49 "-VERS-SSL3.0:-VERS-TLS1.0"
50 #define WAIT_INTERVAL 50000
51 #define TEST_TIMEOUT(pwm, timeout, ret, start, rc) \
54 if (ret == GNUTLS_E_AGAIN || pwm->cancel) \
56 time_t now = time (NULL); \
57 if (pwm->cancel || (timeout && now - start >= timeout)) \
60 rc = gpg_error (GPG_ERR_CANCELED); \
62 rc = gpg_error (GPG_ERR_ETIMEDOUT); \
63 ret = GNUTLS_E_TIMEDOUT; \
66 struct timeval totv = { 0, WAIT_INTERVAL }; \
67 select (0, NULL, NULL, NULL, &totv); \
69 if (ret != GNUTLS_E_INTERRUPTED) \
77 struct tls_s
*tls
= pwm
->tcp
->tls
;
84 pwmd_free (tls
->cert
);
86 pwmd_free (tls
->priority
);
87 pwmd_free (tls
->server_fp
);
91 time_t start
= time (NULL
);
97 ret
= gnutls_bye (tls
->session
, GNUTLS_SHUT_WR
);
98 TEST_TIMEOUT (pwm
, tls
->timeout
, ret
, start
, rc
);
102 while (ret
== GNUTLS_E_AGAIN
|| ret
== GNUTLS_E_INTERRUPTED
);
104 gnutls_deinit (tls
->session
);
106 else if (pwm
->fd
!= ASSUAN_INVALID_FD
)
108 __assuan_close (pwm
->ctx
, pwm
->fd
);
109 pwm
->fd
= ASSUAN_INVALID_FD
;
113 gnutls_certificate_free_credentials (tls
->x509
);
118 pwm
->tls_error
= ret
;
122 tls_read_hook (pwm_t
*pwm
, assuan_fd_t fd
, void *data
, size_t len
)
124 struct tls_s
*tls
= pwm
->tcp
->tls
;
126 time_t start
= time (NULL
);
128 if (pwm
->fd
== ASSUAN_INVALID_FD
)
135 struct timeval tv
= { 0, WAIT_INTERVAL
};
140 FD_SET (HANDLE2SOCKET (pwm
->fd
), &fds
);
141 n
= select (HANDLE2SOCKET (pwm
->fd
)+1, &fds
, NULL
, NULL
, &tv
);
142 if (n
== 0 && tls
->nl
)
146 memcpy (data
, c
, sizeof(c
));
151 ret
= gnutls_record_recv (tls
->session
, data
, len
);
152 if (ret
== GNUTLS_E_REHANDSHAKE
)
156 ret
= gnutls_handshake (tls
->session
);
157 TEST_TIMEOUT (pwm
, tls
->timeout
, ret
, start
, rc
);
161 while (ret
< 0 && gnutls_error_is_fatal (ret
) == 0);
167 memcpy (data
, c
, sizeof(c
));
174 ret
= GNUTLS_E_AGAIN
;
177 TEST_TIMEOUT (pwm
, tls
->timeout
, ret
, start
, rc
);
181 while (ret
== GNUTLS_E_AGAIN
|| ret
== GNUTLS_E_INTERRUPTED
);
184 pwm
->tls_error
= ret
;
188 else if (ret
== GNUTLS_E_PREMATURE_TERMINATION
)
192 tls
->nl
= ((char *)data
)[ret
-1] == '\n';
194 return ret
< 0 ? -1 : ret
;
198 tls_write_hook (pwm_t
*pwm
, assuan_fd_t fd
, const void *data
, size_t len
)
200 struct tls_s
*tls
= pwm
->tcp
->tls
;
202 /* This is probably the BYE command after a previous call timed
203 * out. If not done, the time(2) call seems to hang.*/
204 if (pwm
->fd
== ASSUAN_INVALID_FD
)
208 time_t start
= time (NULL
);
215 ret
= gnutls_record_send (tls
->session
, data
, len
);
216 TEST_TIMEOUT (pwm
, tls
->timeout
, ret
, start
, rc
);
220 while (ret
== GNUTLS_E_AGAIN
|| ret
== GNUTLS_E_INTERRUPTED
);
223 pwm
->tls_error
= ret
;
227 else if (ret
== GNUTLS_E_PREMATURE_TERMINATION
)
230 return ret
< 0 ? -1 : ret
;
234 tls_fingerprint (pwm_t
*pwm
, char **result
)
236 gnutls_session_t ses
= pwm
->tcp
->tls
->session
;
237 gnutls_x509_crt_t crt
;
238 const gnutls_datum_t
*cert_list
;
240 unsigned char buf
[32];
241 size_t len
= sizeof (buf
);
244 gnutls_x509_crt_init (&crt
);
246 return GPG_ERR_ENOMEM
;
248 cert_list
= gnutls_certificate_get_peers (ses
, &count
);
251 pwm
->tls_error
= gnutls_x509_crt_import (crt
, &cert_list
[0],
252 GNUTLS_X509_FMT_DER
);
254 gnutls_x509_crt_get_fingerprint (crt
, GNUTLS_DIG_SHA256
, buf
, &len
);
257 gnutls_x509_crt_deinit (crt
);
260 pwm
->tls_error
= GNUTLS_CERT_INVALID
;
261 return GPG_ERR_MISSING_CERT
;
265 *result
= bin2hex (buf
, len
);
267 return !pwm
->tls_error
? 0 : GPG_ERR_ENOMEM
;
271 verify_server_certificate (unsigned status
)
273 if (status
& GNUTLS_CERT_REVOKED
)
275 fprintf (stderr
, "server certificate is revoked\n");
276 return GPG_ERR_CERT_REVOKED
;
279 if (status
& GNUTLS_CERT_SIGNER_NOT_FOUND
)
281 fprintf (stderr
, "server certificate has no signer\n");
282 return GPG_ERR_NO_SIGNATURE_SCHEME
;
285 if (status
& GNUTLS_CERT_SIGNATURE_FAILURE
)
287 fprintf (stderr
, "server certificate signature verification failed\n");
288 return GPG_ERR_BAD_SIGNATURE
;
291 if (status
& GNUTLS_CERT_EXPIRED
)
293 fprintf (stderr
, "server certificate expired\n");
294 return GPG_ERR_CERT_EXPIRED
;
297 if (status
& GNUTLS_CERT_SIGNER_NOT_CA
)
299 fprintf (stderr
, "server certificate signer is not from CA\n");
300 return GPG_ERR_BAD_CA_CERT
;
303 if (status
& GNUTLS_CERT_INSECURE_ALGORITHM
)
305 fprintf (stderr
, "server certificate has insecure algorithm\n");
306 return GPG_ERR_UNSUPPORTED_ALGORITHM
;
311 fprintf (stderr
, "server certificate is invalid: %u\n", status
);
312 return GPG_ERR_BAD_CERT
;
319 verify_certificate (pwm_t
*pwm
)
322 const gnutls_datum_t
*cert_list
;
323 unsigned int cert_list_size
;
325 gnutls_x509_crt_t cert
;
328 i
= gnutls_certificate_verify_peers2 (pwm
->tcp
->tls
->session
, &status
);
332 return GPG_ERR_BAD_CERT
;
335 rc
= verify_server_certificate (status
);
339 if (gnutls_certificate_type_get (pwm
->tcp
->tls
->session
) != GNUTLS_CRT_X509
)
340 return GPG_ERR_UNSUPPORTED_CERT
;
342 if (gnutls_x509_crt_init (&cert
) < 0)
343 return gpg_error_from_errno (ENOMEM
);
345 cert_list
= gnutls_certificate_get_peers (pwm
->tcp
->tls
->session
,
349 rc
= GPG_ERR_MISSING_CERT
;
353 for (i
= 0; i
< cert_list_size
; i
++)
355 pwm
->tls_error
= gnutls_x509_crt_import (cert
, &cert_list
[i
],
356 GNUTLS_X509_FMT_DER
);
357 if (pwm
->tls_error
< 0)
359 rc
= GPG_ERR_BAD_CERT_CHAIN
;
363 if (gnutls_x509_crt_get_expiration_time (cert
) < time (0))
365 rc
= GPG_ERR_CERT_EXPIRED
;
369 if (gnutls_x509_crt_get_activation_time (cert
) > time (0))
371 rc
= GPG_ERR_CERT_TOO_YOUNG
;
375 if (pwm
->tcp
->tls
->verify
)
377 if (!gnutls_x509_crt_check_hostname (cert
, pwm
->tcp
->host
))
379 rc
= GPG_ERR_BAD_CERT_CHAIN
;
385 if (pwm
->tcp
->tls
->server_fp
)
389 rc
= tls_fingerprint (pwm
, &result
);
392 if (!result
|| !*result
||
393 strcasecmp (result
, pwm
->tcp
->tls
->server_fp
))
394 rc
= GPG_ERR_BAD_CERT
;
401 gnutls_x509_crt_deinit (cert
);
406 tls_init (pwm_t
* pwm
)
412 ret
= gnutls_certificate_allocate_credentials (&pwm
->tcp
->tls
->x509
);
415 pwm
->tls_error
= ret
;
416 return gpg_error_from_errno (ENOMEM
);
419 /* The client certificate must be signed by the CA of the pwmd server
420 * certificate in order for the client to authenticate successfully. Man in
421 * the middle attacks are still possible if the attacker is running a pwmd
422 * that doesn't require client certificate authentication. So require the
423 * client to verify the server certificate.
425 ret
= gnutls_certificate_set_x509_trust_file (pwm
->tcp
->tls
->x509
,
427 GNUTLS_X509_FMT_PEM
);
430 rc
= GPG_ERR_INV_CERT_OBJ
;
434 ret
= gnutls_certificate_set_x509_key_file (pwm
->tcp
->tls
->x509
,
437 GNUTLS_X509_FMT_PEM
);
438 if (ret
!= GNUTLS_E_SUCCESS
)
440 pwm
->tls_error
= ret
;
441 rc
= GPG_ERR_INV_CERT_OBJ
;
445 ret
= gnutls_init (&pwm
->tcp
->tls
->session
, GNUTLS_CLIENT
);
446 if (ret
!= GNUTLS_E_SUCCESS
)
448 pwm
->tls_error
= ret
;
449 rc
= GPG_ERR_INV_CERT_OBJ
;
453 ret
= gnutls_priority_set_direct (pwm
->tcp
->tls
->session
,
454 pwm
->tcp
->tls
->priority
455 ? pwm
->tcp
->tls
->priority
456 : DEFAULT_TLS_PRIORITY
,
458 if (ret
!= GNUTLS_E_SUCCESS
)
460 pwm
->tls_error
= ret
;
461 rc
= GPG_ERR_INV_CERT_OBJ
;
465 ret
= gnutls_credentials_set (pwm
->tcp
->tls
->session
, GNUTLS_CRD_CERTIFICATE
,
466 pwm
->tcp
->tls
->x509
);
467 if (ret
!= GNUTLS_E_SUCCESS
)
469 pwm
->tls_error
= ret
;
470 rc
= GPG_ERR_INV_CERT_OBJ
;
474 gnutls_transport_set_ptr (pwm
->tcp
->tls
->session
,
475 (gnutls_transport_ptr_t
) HANDLE2SOCKET (pwm
->fd
));
476 rc
= set_non_blocking (pwm
->fd
, 1);
480 time_t start
= time (NULL
);
483 ret
= gnutls_handshake (pwm
->tcp
->tls
->session
);
484 TEST_TIMEOUT (pwm
, pwm
->tcp
->tls
->timeout
, ret
, start
, rc
);
488 while (ret
== GNUTLS_E_AGAIN
|| ret
== GNUTLS_E_INTERRUPTED
);
490 if (ret
!= GNUTLS_E_SUCCESS
)
492 pwm
->tls_error
= ret
;
493 rc
= GPG_ERR_INV_CERT_OBJ
;
497 rc
= verify_certificate (pwm
);
502 /* Keep the original error since it maybe overwritten during TLS
504 ret
= pwm
->tls_error
;
505 /* The session may not have completed a handshake. Will crash during
507 gnutls_deinit (pwm
->tcp
->tls
->session
);
508 pwm
->tcp
->tls
->session
= NULL
;
510 pwm
->tls_error
= ret
;
517 tls_connect (pwm_t
* pwm
, const char *host
, int port
, const char *cert
,
518 const char *key
, const char *cacert
, const char *prio
,
519 const char *server_fp
, int verify
)
524 if (!cert
|| !key
|| !cacert
)
525 return GPG_ERR_INV_ARG
;
527 tcp
= pwmd_calloc (1, sizeof (struct tcp_s
));
529 return GPG_ERR_ENOMEM
;
531 pthread_cond_init (&tcp
->dns_cond
, NULL
);
532 pthread_mutex_init (&tcp
->dns_mutex
, NULL
);
535 tcp
->host
= pwmd_strdup (host
);
542 tcp
->tls
= pwmd_calloc (1, sizeof (struct tls_s
));
549 tcp
->tls
->timeout
= pwm
->socket_timeout
;
550 tcp
->tls
->verify
= verify
;
551 tcp
->tls
->cert
= pwmd_strdup (cert
);
558 tcp
->tls
->key
= pwmd_strdup (key
);
565 tcp
->tls
->ca
= pwmd_strdup (cacert
);
574 tcp
->tls
->priority
= pwmd_strdup (prio
);
575 if (!tcp
->tls
->priority
)
584 tcp
->tls
->server_fp
= pwmd_strdup (server_fp
);
585 if (!tcp
->tls
->server_fp
)
592 rc
= tcp_connect_common (pwm
);
599 rc
= assuan_socket_connect_fd (pwm
->ctx
, pwm
->fh
, 0);
601 rc
= assuan_socket_connect_fd (pwm
->ctx
, pwm
->fd
, 0);
614 * tls[46]://[hostname][:port]
617 tls_parse_url (const char *str
, char **host
, int *port
)
620 return parse_hostname_common (str
, host
, port
);