]> Git Repo - qemu.git/blob - crypto/tlssession.c
crypto: Implement TLS Pre-Shared Keys (PSK).
[qemu.git] / crypto / tlssession.c
1 /*
2  * QEMU crypto TLS session support
3  *
4  * Copyright (c) 2015 Red Hat, Inc.
5  *
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 as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "qemu/osdep.h"
22 #include "crypto/tlssession.h"
23 #include "crypto/tlscredsanon.h"
24 #include "crypto/tlscredspsk.h"
25 #include "crypto/tlscredsx509.h"
26 #include "qapi/error.h"
27 #include "qemu/acl.h"
28 #include "trace.h"
29
30 #ifdef CONFIG_GNUTLS
31
32
33 #include <gnutls/x509.h>
34
35
36 struct QCryptoTLSSession {
37     QCryptoTLSCreds *creds;
38     gnutls_session_t handle;
39     char *hostname;
40     char *aclname;
41     bool handshakeComplete;
42     QCryptoTLSSessionWriteFunc writeFunc;
43     QCryptoTLSSessionReadFunc readFunc;
44     void *opaque;
45     char *peername;
46 };
47
48
49 void
50 qcrypto_tls_session_free(QCryptoTLSSession *session)
51 {
52     if (!session) {
53         return;
54     }
55
56     gnutls_deinit(session->handle);
57     g_free(session->hostname);
58     g_free(session->peername);
59     g_free(session->aclname);
60     object_unref(OBJECT(session->creds));
61     g_free(session);
62 }
63
64
65 static ssize_t
66 qcrypto_tls_session_push(void *opaque, const void *buf, size_t len)
67 {
68     QCryptoTLSSession *session = opaque;
69
70     if (!session->writeFunc) {
71         errno = EIO;
72         return -1;
73     };
74
75     return session->writeFunc(buf, len, session->opaque);
76 }
77
78
79 static ssize_t
80 qcrypto_tls_session_pull(void *opaque, void *buf, size_t len)
81 {
82     QCryptoTLSSession *session = opaque;
83
84     if (!session->readFunc) {
85         errno = EIO;
86         return -1;
87     };
88
89     return session->readFunc(buf, len, session->opaque);
90 }
91
92 #define TLS_PRIORITY_ADDITIONAL_ANON "+ANON-DH"
93
94 #if GNUTLS_VERSION_MAJOR >= 3
95 #define TLS_ECDHE_PSK "+ECDHE-PSK:"
96 #else
97 #define TLS_ECDHE_PSK ""
98 #endif
99 #define TLS_PRIORITY_ADDITIONAL_PSK TLS_ECDHE_PSK "+DHE-PSK:+PSK"
100
101 QCryptoTLSSession *
102 qcrypto_tls_session_new(QCryptoTLSCreds *creds,
103                         const char *hostname,
104                         const char *aclname,
105                         QCryptoTLSCredsEndpoint endpoint,
106                         Error **errp)
107 {
108     QCryptoTLSSession *session;
109     int ret;
110
111     session = g_new0(QCryptoTLSSession, 1);
112     trace_qcrypto_tls_session_new(
113         session, creds, hostname ? hostname : "<none>",
114         aclname ? aclname : "<none>", endpoint);
115
116     if (hostname) {
117         session->hostname = g_strdup(hostname);
118     }
119     if (aclname) {
120         session->aclname = g_strdup(aclname);
121     }
122     session->creds = creds;
123     object_ref(OBJECT(creds));
124
125     if (creds->endpoint != endpoint) {
126         error_setg(errp, "Credentials endpoint doesn't match session");
127         goto error;
128     }
129
130     if (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
131         ret = gnutls_init(&session->handle, GNUTLS_SERVER);
132     } else {
133         ret = gnutls_init(&session->handle, GNUTLS_CLIENT);
134     }
135     if (ret < 0) {
136         error_setg(errp, "Cannot initialize TLS session: %s",
137                    gnutls_strerror(ret));
138         goto error;
139     }
140
141     if (object_dynamic_cast(OBJECT(creds),
142                             TYPE_QCRYPTO_TLS_CREDS_ANON)) {
143         QCryptoTLSCredsAnon *acreds = QCRYPTO_TLS_CREDS_ANON(creds);
144         char *prio;
145
146         if (creds->priority != NULL) {
147             prio = g_strdup_printf("%s:%s",
148                                    creds->priority,
149                                    TLS_PRIORITY_ADDITIONAL_ANON);
150         } else {
151             prio = g_strdup(CONFIG_TLS_PRIORITY ":"
152                             TLS_PRIORITY_ADDITIONAL_ANON);
153         }
154
155         ret = gnutls_priority_set_direct(session->handle, prio, NULL);
156         if (ret < 0) {
157             error_setg(errp, "Unable to set TLS session priority %s: %s",
158                        prio, gnutls_strerror(ret));
159             g_free(prio);
160             goto error;
161         }
162         g_free(prio);
163         if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
164             ret = gnutls_credentials_set(session->handle,
165                                          GNUTLS_CRD_ANON,
166                                          acreds->data.server);
167         } else {
168             ret = gnutls_credentials_set(session->handle,
169                                          GNUTLS_CRD_ANON,
170                                          acreds->data.client);
171         }
172         if (ret < 0) {
173             error_setg(errp, "Cannot set session credentials: %s",
174                        gnutls_strerror(ret));
175             goto error;
176         }
177     } else if (object_dynamic_cast(OBJECT(creds),
178                                    TYPE_QCRYPTO_TLS_CREDS_PSK)) {
179         QCryptoTLSCredsPSK *pcreds = QCRYPTO_TLS_CREDS_PSK(creds);
180         char *prio;
181
182         if (creds->priority != NULL) {
183             prio = g_strdup_printf("%s:%s",
184                                    creds->priority,
185                                    TLS_PRIORITY_ADDITIONAL_PSK);
186         } else {
187             prio = g_strdup(CONFIG_TLS_PRIORITY ":"
188                             TLS_PRIORITY_ADDITIONAL_PSK);
189         }
190
191         ret = gnutls_priority_set_direct(session->handle, prio, NULL);
192         if (ret < 0) {
193             error_setg(errp, "Unable to set TLS session priority %s: %s",
194                        prio, gnutls_strerror(ret));
195             g_free(prio);
196             goto error;
197         }
198         g_free(prio);
199         if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
200             ret = gnutls_credentials_set(session->handle,
201                                          GNUTLS_CRD_PSK,
202                                          pcreds->data.server);
203         } else {
204             ret = gnutls_credentials_set(session->handle,
205                                          GNUTLS_CRD_PSK,
206                                          pcreds->data.client);
207         }
208         if (ret < 0) {
209             error_setg(errp, "Cannot set session credentials: %s",
210                        gnutls_strerror(ret));
211             goto error;
212         }
213     } else if (object_dynamic_cast(OBJECT(creds),
214                                    TYPE_QCRYPTO_TLS_CREDS_X509)) {
215         QCryptoTLSCredsX509 *tcreds = QCRYPTO_TLS_CREDS_X509(creds);
216         const char *prio = creds->priority;
217         if (!prio) {
218             prio = CONFIG_TLS_PRIORITY;
219         }
220
221         ret = gnutls_priority_set_direct(session->handle, prio, NULL);
222         if (ret < 0) {
223             error_setg(errp, "Cannot set default TLS session priority %s: %s",
224                        prio, gnutls_strerror(ret));
225             goto error;
226         }
227         ret = gnutls_credentials_set(session->handle,
228                                      GNUTLS_CRD_CERTIFICATE,
229                                      tcreds->data);
230         if (ret < 0) {
231             error_setg(errp, "Cannot set session credentials: %s",
232                        gnutls_strerror(ret));
233             goto error;
234         }
235
236         if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
237             /* This requests, but does not enforce a client cert.
238              * The cert checking code later does enforcement */
239             gnutls_certificate_server_set_request(session->handle,
240                                                   GNUTLS_CERT_REQUEST);
241         }
242     } else {
243         error_setg(errp, "Unsupported TLS credentials type %s",
244                    object_get_typename(OBJECT(creds)));
245         goto error;
246     }
247
248     gnutls_transport_set_ptr(session->handle, session);
249     gnutls_transport_set_push_function(session->handle,
250                                        qcrypto_tls_session_push);
251     gnutls_transport_set_pull_function(session->handle,
252                                        qcrypto_tls_session_pull);
253
254     return session;
255
256  error:
257     qcrypto_tls_session_free(session);
258     return NULL;
259 }
260
261 static int
262 qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
263                                       Error **errp)
264 {
265     int ret;
266     unsigned int status;
267     const gnutls_datum_t *certs;
268     unsigned int nCerts, i;
269     time_t now;
270     gnutls_x509_crt_t cert = NULL;
271
272     now = time(NULL);
273     if (now == ((time_t)-1)) {
274         error_setg_errno(errp, errno, "Cannot get current time");
275         return -1;
276     }
277
278     ret = gnutls_certificate_verify_peers2(session->handle, &status);
279     if (ret < 0) {
280         error_setg(errp, "Verify failed: %s", gnutls_strerror(ret));
281         return -1;
282     }
283
284     if (status != 0) {
285         const char *reason = "Invalid certificate";
286
287         if (status & GNUTLS_CERT_INVALID) {
288             reason = "The certificate is not trusted";
289         }
290
291         if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
292             reason = "The certificate hasn't got a known issuer";
293         }
294
295         if (status & GNUTLS_CERT_REVOKED) {
296             reason = "The certificate has been revoked";
297         }
298
299         if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
300             reason = "The certificate uses an insecure algorithm";
301         }
302
303         error_setg(errp, "%s", reason);
304         return -1;
305     }
306
307     certs = gnutls_certificate_get_peers(session->handle, &nCerts);
308     if (!certs) {
309         error_setg(errp, "No certificate peers");
310         return -1;
311     }
312
313     for (i = 0; i < nCerts; i++) {
314         ret = gnutls_x509_crt_init(&cert);
315         if (ret < 0) {
316             error_setg(errp, "Cannot initialize certificate: %s",
317                        gnutls_strerror(ret));
318             return -1;
319         }
320
321         ret = gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER);
322         if (ret < 0) {
323             error_setg(errp, "Cannot import certificate: %s",
324                        gnutls_strerror(ret));
325             goto error;
326         }
327
328         if (gnutls_x509_crt_get_expiration_time(cert) < now) {
329             error_setg(errp, "The certificate has expired");
330             goto error;
331         }
332
333         if (gnutls_x509_crt_get_activation_time(cert) > now) {
334             error_setg(errp, "The certificate is not yet activated");
335             goto error;
336         }
337
338         if (gnutls_x509_crt_get_activation_time(cert) > now) {
339             error_setg(errp, "The certificate is not yet activated");
340             goto error;
341         }
342
343         if (i == 0) {
344             size_t dnameSize = 1024;
345             session->peername = g_malloc(dnameSize);
346         requery:
347             ret = gnutls_x509_crt_get_dn(cert, session->peername, &dnameSize);
348             if (ret < 0) {
349                 if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
350                     session->peername = g_realloc(session->peername,
351                                                   dnameSize);
352                     goto requery;
353                 }
354                 error_setg(errp, "Cannot get client distinguished name: %s",
355                            gnutls_strerror(ret));
356                 goto error;
357             }
358             if (session->aclname) {
359                 qemu_acl *acl = qemu_acl_find(session->aclname);
360                 int allow;
361                 if (!acl) {
362                     error_setg(errp, "Cannot find ACL %s",
363                                session->aclname);
364                     goto error;
365                 }
366
367                 allow = qemu_acl_party_is_allowed(acl, session->peername);
368
369                 if (!allow) {
370                     error_setg(errp, "TLS x509 ACL check for %s is denied",
371                                session->peername);
372                     goto error;
373                 }
374             }
375             if (session->hostname) {
376                 if (!gnutls_x509_crt_check_hostname(cert, session->hostname)) {
377                     error_setg(errp,
378                                "Certificate does not match the hostname %s",
379                                session->hostname);
380                     goto error;
381                 }
382             }
383         }
384
385         gnutls_x509_crt_deinit(cert);
386     }
387
388     return 0;
389
390  error:
391     gnutls_x509_crt_deinit(cert);
392     return -1;
393 }
394
395
396 int
397 qcrypto_tls_session_check_credentials(QCryptoTLSSession *session,
398                                       Error **errp)
399 {
400     if (object_dynamic_cast(OBJECT(session->creds),
401                             TYPE_QCRYPTO_TLS_CREDS_ANON)) {
402         trace_qcrypto_tls_session_check_creds(session, "nop");
403         return 0;
404     } else if (object_dynamic_cast(OBJECT(session->creds),
405                             TYPE_QCRYPTO_TLS_CREDS_PSK)) {
406         trace_qcrypto_tls_session_check_creds(session, "nop");
407         return 0;
408     } else if (object_dynamic_cast(OBJECT(session->creds),
409                             TYPE_QCRYPTO_TLS_CREDS_X509)) {
410         if (session->creds->verifyPeer) {
411             int ret = qcrypto_tls_session_check_certificate(session,
412                                                             errp);
413             trace_qcrypto_tls_session_check_creds(session,
414                                                   ret == 0 ? "pass" : "fail");
415             return ret;
416         } else {
417             trace_qcrypto_tls_session_check_creds(session, "skip");
418             return 0;
419         }
420     } else {
421         trace_qcrypto_tls_session_check_creds(session, "error");
422         error_setg(errp, "Unexpected credential type %s",
423                    object_get_typename(OBJECT(session->creds)));
424         return -1;
425     }
426 }
427
428
429 void
430 qcrypto_tls_session_set_callbacks(QCryptoTLSSession *session,
431                                   QCryptoTLSSessionWriteFunc writeFunc,
432                                   QCryptoTLSSessionReadFunc readFunc,
433                                   void *opaque)
434 {
435     session->writeFunc = writeFunc;
436     session->readFunc = readFunc;
437     session->opaque = opaque;
438 }
439
440
441 ssize_t
442 qcrypto_tls_session_write(QCryptoTLSSession *session,
443                           const char *buf,
444                           size_t len)
445 {
446     ssize_t ret = gnutls_record_send(session->handle, buf, len);
447
448     if (ret < 0) {
449         switch (ret) {
450         case GNUTLS_E_AGAIN:
451             errno = EAGAIN;
452             break;
453         case GNUTLS_E_INTERRUPTED:
454             errno = EINTR;
455             break;
456         default:
457             errno = EIO;
458             break;
459         }
460         ret = -1;
461     }
462
463     return ret;
464 }
465
466
467 ssize_t
468 qcrypto_tls_session_read(QCryptoTLSSession *session,
469                          char *buf,
470                          size_t len)
471 {
472     ssize_t ret = gnutls_record_recv(session->handle, buf, len);
473
474     if (ret < 0) {
475         switch (ret) {
476         case GNUTLS_E_AGAIN:
477             errno = EAGAIN;
478             break;
479         case GNUTLS_E_INTERRUPTED:
480             errno = EINTR;
481             break;
482         default:
483             errno = EIO;
484             break;
485         }
486         ret = -1;
487     }
488
489     return ret;
490 }
491
492
493 int
494 qcrypto_tls_session_handshake(QCryptoTLSSession *session,
495                               Error **errp)
496 {
497     int ret = gnutls_handshake(session->handle);
498     if (ret == 0) {
499         session->handshakeComplete = true;
500     } else {
501         if (ret == GNUTLS_E_INTERRUPTED ||
502             ret == GNUTLS_E_AGAIN) {
503             ret = 1;
504         } else {
505             error_setg(errp, "TLS handshake failed: %s",
506                        gnutls_strerror(ret));
507             ret = -1;
508         }
509     }
510
511     return ret;
512 }
513
514
515 QCryptoTLSSessionHandshakeStatus
516 qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session)
517 {
518     if (session->handshakeComplete) {
519         return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
520     } else if (gnutls_record_get_direction(session->handle) == 0) {
521         return QCRYPTO_TLS_HANDSHAKE_RECVING;
522     } else {
523         return QCRYPTO_TLS_HANDSHAKE_SENDING;
524     }
525 }
526
527
528 int
529 qcrypto_tls_session_get_key_size(QCryptoTLSSession *session,
530                                  Error **errp)
531 {
532     gnutls_cipher_algorithm_t cipher;
533     int ssf;
534
535     cipher = gnutls_cipher_get(session->handle);
536     ssf = gnutls_cipher_get_key_size(cipher);
537     if (!ssf) {
538         error_setg(errp, "Cannot get TLS cipher key size");
539         return -1;
540     }
541     return ssf;
542 }
543
544
545 char *
546 qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session)
547 {
548     if (session->peername) {
549         return g_strdup(session->peername);
550     }
551     return NULL;
552 }
553
554
555 #else /* ! CONFIG_GNUTLS */
556
557
558 QCryptoTLSSession *
559 qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED,
560                         const char *hostname G_GNUC_UNUSED,
561                         const char *aclname G_GNUC_UNUSED,
562                         QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED,
563                         Error **errp)
564 {
565     error_setg(errp, "TLS requires GNUTLS support");
566     return NULL;
567 }
568
569
570 void
571 qcrypto_tls_session_free(QCryptoTLSSession *sess G_GNUC_UNUSED)
572 {
573 }
574
575
576 int
577 qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess G_GNUC_UNUSED,
578                                       Error **errp)
579 {
580     error_setg(errp, "TLS requires GNUTLS support");
581     return -1;
582 }
583
584
585 void
586 qcrypto_tls_session_set_callbacks(
587     QCryptoTLSSession *sess G_GNUC_UNUSED,
588     QCryptoTLSSessionWriteFunc writeFunc G_GNUC_UNUSED,
589     QCryptoTLSSessionReadFunc readFunc G_GNUC_UNUSED,
590     void *opaque G_GNUC_UNUSED)
591 {
592 }
593
594
595 ssize_t
596 qcrypto_tls_session_write(QCryptoTLSSession *sess,
597                           const char *buf,
598                           size_t len)
599 {
600     errno = -EIO;
601     return -1;
602 }
603
604
605 ssize_t
606 qcrypto_tls_session_read(QCryptoTLSSession *sess,
607                          char *buf,
608                          size_t len)
609 {
610     errno = -EIO;
611     return -1;
612 }
613
614
615 int
616 qcrypto_tls_session_handshake(QCryptoTLSSession *sess,
617                               Error **errp)
618 {
619     error_setg(errp, "TLS requires GNUTLS support");
620     return -1;
621 }
622
623
624 QCryptoTLSSessionHandshakeStatus
625 qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess)
626 {
627     return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
628 }
629
630
631 int
632 qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess,
633                                  Error **errp)
634 {
635     error_setg(errp, "TLS requires GNUTLS support");
636     return -1;
637 }
638
639
640 char *
641 qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess)
642 {
643     return NULL;
644 }
645
646 #endif
This page took 0.058699 seconds and 4 git commands to generate.