xref: /openbmc/qemu/crypto/tlssession.c (revision fa59483d)
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 "authz/base.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 *authzid;
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->authzid);
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 #define TLS_PRIORITY_ADDITIONAL_PSK "+ECDHE-PSK:+DHE-PSK:+PSK"
94 
95 QCryptoTLSSession *
96 qcrypto_tls_session_new(QCryptoTLSCreds *creds,
97                         const char *hostname,
98                         const char *authzid,
99                         QCryptoTLSCredsEndpoint endpoint,
100                         Error **errp)
101 {
102     QCryptoTLSSession *session;
103     int ret;
104 
105     session = g_new0(QCryptoTLSSession, 1);
106     trace_qcrypto_tls_session_new(
107         session, creds, hostname ? hostname : "<none>",
108         authzid ? authzid : "<none>", endpoint);
109 
110     if (hostname) {
111         session->hostname = g_strdup(hostname);
112     }
113     if (authzid) {
114         session->authzid = g_strdup(authzid);
115     }
116     session->creds = creds;
117     object_ref(OBJECT(creds));
118 
119     if (creds->endpoint != endpoint) {
120         error_setg(errp, "Credentials endpoint doesn't match session");
121         goto error;
122     }
123 
124     if (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
125         ret = gnutls_init(&session->handle, GNUTLS_SERVER);
126     } else {
127         ret = gnutls_init(&session->handle, GNUTLS_CLIENT);
128     }
129     if (ret < 0) {
130         error_setg(errp, "Cannot initialize TLS session: %s",
131                    gnutls_strerror(ret));
132         goto error;
133     }
134 
135     if (object_dynamic_cast(OBJECT(creds),
136                             TYPE_QCRYPTO_TLS_CREDS_ANON)) {
137         QCryptoTLSCredsAnon *acreds = QCRYPTO_TLS_CREDS_ANON(creds);
138         char *prio;
139 
140         if (creds->priority != NULL) {
141             prio = g_strdup_printf("%s:%s",
142                                    creds->priority,
143                                    TLS_PRIORITY_ADDITIONAL_ANON);
144         } else {
145             prio = g_strdup(CONFIG_TLS_PRIORITY ":"
146                             TLS_PRIORITY_ADDITIONAL_ANON);
147         }
148 
149         ret = gnutls_priority_set_direct(session->handle, prio, NULL);
150         if (ret < 0) {
151             error_setg(errp, "Unable to set TLS session priority %s: %s",
152                        prio, gnutls_strerror(ret));
153             g_free(prio);
154             goto error;
155         }
156         g_free(prio);
157         if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
158             ret = gnutls_credentials_set(session->handle,
159                                          GNUTLS_CRD_ANON,
160                                          acreds->data.server);
161         } else {
162             ret = gnutls_credentials_set(session->handle,
163                                          GNUTLS_CRD_ANON,
164                                          acreds->data.client);
165         }
166         if (ret < 0) {
167             error_setg(errp, "Cannot set session credentials: %s",
168                        gnutls_strerror(ret));
169             goto error;
170         }
171     } else if (object_dynamic_cast(OBJECT(creds),
172                                    TYPE_QCRYPTO_TLS_CREDS_PSK)) {
173         QCryptoTLSCredsPSK *pcreds = QCRYPTO_TLS_CREDS_PSK(creds);
174         char *prio;
175 
176         if (creds->priority != NULL) {
177             prio = g_strdup_printf("%s:%s",
178                                    creds->priority,
179                                    TLS_PRIORITY_ADDITIONAL_PSK);
180         } else {
181             prio = g_strdup(CONFIG_TLS_PRIORITY ":"
182                             TLS_PRIORITY_ADDITIONAL_PSK);
183         }
184 
185         ret = gnutls_priority_set_direct(session->handle, prio, NULL);
186         if (ret < 0) {
187             error_setg(errp, "Unable to set TLS session priority %s: %s",
188                        prio, gnutls_strerror(ret));
189             g_free(prio);
190             goto error;
191         }
192         g_free(prio);
193         if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
194             ret = gnutls_credentials_set(session->handle,
195                                          GNUTLS_CRD_PSK,
196                                          pcreds->data.server);
197         } else {
198             ret = gnutls_credentials_set(session->handle,
199                                          GNUTLS_CRD_PSK,
200                                          pcreds->data.client);
201         }
202         if (ret < 0) {
203             error_setg(errp, "Cannot set session credentials: %s",
204                        gnutls_strerror(ret));
205             goto error;
206         }
207     } else if (object_dynamic_cast(OBJECT(creds),
208                                    TYPE_QCRYPTO_TLS_CREDS_X509)) {
209         QCryptoTLSCredsX509 *tcreds = QCRYPTO_TLS_CREDS_X509(creds);
210         const char *prio = creds->priority;
211         if (!prio) {
212             prio = CONFIG_TLS_PRIORITY;
213         }
214 
215         ret = gnutls_priority_set_direct(session->handle, prio, NULL);
216         if (ret < 0) {
217             error_setg(errp, "Cannot set default TLS session priority %s: %s",
218                        prio, gnutls_strerror(ret));
219             goto error;
220         }
221         ret = gnutls_credentials_set(session->handle,
222                                      GNUTLS_CRD_CERTIFICATE,
223                                      tcreds->data);
224         if (ret < 0) {
225             error_setg(errp, "Cannot set session credentials: %s",
226                        gnutls_strerror(ret));
227             goto error;
228         }
229 
230         if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
231             /* This requests, but does not enforce a client cert.
232              * The cert checking code later does enforcement */
233             gnutls_certificate_server_set_request(session->handle,
234                                                   GNUTLS_CERT_REQUEST);
235         }
236     } else {
237         error_setg(errp, "Unsupported TLS credentials type %s",
238                    object_get_typename(OBJECT(creds)));
239         goto error;
240     }
241 
242     gnutls_transport_set_ptr(session->handle, session);
243     gnutls_transport_set_push_function(session->handle,
244                                        qcrypto_tls_session_push);
245     gnutls_transport_set_pull_function(session->handle,
246                                        qcrypto_tls_session_pull);
247 
248     return session;
249 
250  error:
251     qcrypto_tls_session_free(session);
252     return NULL;
253 }
254 
255 static int
256 qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
257                                       Error **errp)
258 {
259     int ret;
260     unsigned int status;
261     const gnutls_datum_t *certs;
262     unsigned int nCerts, i;
263     time_t now;
264     gnutls_x509_crt_t cert = NULL;
265     Error *err = NULL;
266 
267     now = time(NULL);
268     if (now == ((time_t)-1)) {
269         error_setg_errno(errp, errno, "Cannot get current time");
270         return -1;
271     }
272 
273     ret = gnutls_certificate_verify_peers2(session->handle, &status);
274     if (ret < 0) {
275         error_setg(errp, "Verify failed: %s", gnutls_strerror(ret));
276         return -1;
277     }
278 
279     if (status != 0) {
280         const char *reason = "Invalid certificate";
281 
282         if (status & GNUTLS_CERT_INVALID) {
283             reason = "The certificate is not trusted";
284         }
285 
286         if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
287             reason = "The certificate hasn't got a known issuer";
288         }
289 
290         if (status & GNUTLS_CERT_REVOKED) {
291             reason = "The certificate has been revoked";
292         }
293 
294         if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
295             reason = "The certificate uses an insecure algorithm";
296         }
297 
298         error_setg(errp, "%s", reason);
299         return -1;
300     }
301 
302     certs = gnutls_certificate_get_peers(session->handle, &nCerts);
303     if (!certs) {
304         error_setg(errp, "No certificate peers");
305         return -1;
306     }
307 
308     for (i = 0; i < nCerts; i++) {
309         ret = gnutls_x509_crt_init(&cert);
310         if (ret < 0) {
311             error_setg(errp, "Cannot initialize certificate: %s",
312                        gnutls_strerror(ret));
313             return -1;
314         }
315 
316         ret = gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER);
317         if (ret < 0) {
318             error_setg(errp, "Cannot import certificate: %s",
319                        gnutls_strerror(ret));
320             goto error;
321         }
322 
323         if (gnutls_x509_crt_get_expiration_time(cert) < now) {
324             error_setg(errp, "The certificate has expired");
325             goto error;
326         }
327 
328         if (gnutls_x509_crt_get_activation_time(cert) > now) {
329             error_setg(errp, "The certificate is not yet activated");
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 (i == 0) {
339             size_t dnameSize = 1024;
340             session->peername = g_malloc(dnameSize);
341         requery:
342             ret = gnutls_x509_crt_get_dn(cert, session->peername, &dnameSize);
343             if (ret < 0) {
344                 if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
345                     session->peername = g_realloc(session->peername,
346                                                   dnameSize);
347                     goto requery;
348                 }
349                 error_setg(errp, "Cannot get client distinguished name: %s",
350                            gnutls_strerror(ret));
351                 goto error;
352             }
353             if (session->authzid) {
354                 bool allow;
355 
356                 allow = qauthz_is_allowed_by_id(session->authzid,
357                                                 session->peername, &err);
358                 if (err) {
359                     error_propagate(errp, err);
360                     goto error;
361                 }
362                 if (!allow) {
363                     error_setg(errp, "TLS x509 authz check for %s is denied",
364                                session->peername);
365                     goto error;
366                 }
367             }
368             if (session->hostname) {
369                 if (!gnutls_x509_crt_check_hostname(cert, session->hostname)) {
370                     error_setg(errp,
371                                "Certificate does not match the hostname %s",
372                                session->hostname);
373                     goto error;
374                 }
375             }
376         }
377 
378         gnutls_x509_crt_deinit(cert);
379     }
380 
381     return 0;
382 
383  error:
384     gnutls_x509_crt_deinit(cert);
385     return -1;
386 }
387 
388 
389 int
390 qcrypto_tls_session_check_credentials(QCryptoTLSSession *session,
391                                       Error **errp)
392 {
393     if (object_dynamic_cast(OBJECT(session->creds),
394                             TYPE_QCRYPTO_TLS_CREDS_ANON)) {
395         trace_qcrypto_tls_session_check_creds(session, "nop");
396         return 0;
397     } else if (object_dynamic_cast(OBJECT(session->creds),
398                             TYPE_QCRYPTO_TLS_CREDS_PSK)) {
399         trace_qcrypto_tls_session_check_creds(session, "nop");
400         return 0;
401     } else if (object_dynamic_cast(OBJECT(session->creds),
402                             TYPE_QCRYPTO_TLS_CREDS_X509)) {
403         if (session->creds->verifyPeer) {
404             int ret = qcrypto_tls_session_check_certificate(session,
405                                                             errp);
406             trace_qcrypto_tls_session_check_creds(session,
407                                                   ret == 0 ? "pass" : "fail");
408             return ret;
409         } else {
410             trace_qcrypto_tls_session_check_creds(session, "skip");
411             return 0;
412         }
413     } else {
414         trace_qcrypto_tls_session_check_creds(session, "error");
415         error_setg(errp, "Unexpected credential type %s",
416                    object_get_typename(OBJECT(session->creds)));
417         return -1;
418     }
419 }
420 
421 
422 void
423 qcrypto_tls_session_set_callbacks(QCryptoTLSSession *session,
424                                   QCryptoTLSSessionWriteFunc writeFunc,
425                                   QCryptoTLSSessionReadFunc readFunc,
426                                   void *opaque)
427 {
428     session->writeFunc = writeFunc;
429     session->readFunc = readFunc;
430     session->opaque = opaque;
431 }
432 
433 
434 ssize_t
435 qcrypto_tls_session_write(QCryptoTLSSession *session,
436                           const char *buf,
437                           size_t len)
438 {
439     ssize_t ret = gnutls_record_send(session->handle, buf, len);
440 
441     if (ret < 0) {
442         switch (ret) {
443         case GNUTLS_E_AGAIN:
444             errno = EAGAIN;
445             break;
446         case GNUTLS_E_INTERRUPTED:
447             errno = EINTR;
448             break;
449         default:
450             errno = EIO;
451             break;
452         }
453         ret = -1;
454     }
455 
456     return ret;
457 }
458 
459 
460 ssize_t
461 qcrypto_tls_session_read(QCryptoTLSSession *session,
462                          char *buf,
463                          size_t len)
464 {
465     ssize_t ret = gnutls_record_recv(session->handle, buf, len);
466 
467     if (ret < 0) {
468         switch (ret) {
469         case GNUTLS_E_AGAIN:
470             errno = EAGAIN;
471             break;
472         case GNUTLS_E_INTERRUPTED:
473             errno = EINTR;
474             break;
475         case GNUTLS_E_PREMATURE_TERMINATION:
476             errno = ECONNABORTED;
477             break;
478         default:
479             errno = EIO;
480             break;
481         }
482         ret = -1;
483     }
484 
485     return ret;
486 }
487 
488 
489 int
490 qcrypto_tls_session_handshake(QCryptoTLSSession *session,
491                               Error **errp)
492 {
493     int ret = gnutls_handshake(session->handle);
494     if (ret == 0) {
495         session->handshakeComplete = true;
496     } else {
497         if (ret == GNUTLS_E_INTERRUPTED ||
498             ret == GNUTLS_E_AGAIN) {
499             ret = 1;
500         } else {
501             error_setg(errp, "TLS handshake failed: %s",
502                        gnutls_strerror(ret));
503             ret = -1;
504         }
505     }
506 
507     return ret;
508 }
509 
510 
511 QCryptoTLSSessionHandshakeStatus
512 qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session)
513 {
514     if (session->handshakeComplete) {
515         return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
516     } else if (gnutls_record_get_direction(session->handle) == 0) {
517         return QCRYPTO_TLS_HANDSHAKE_RECVING;
518     } else {
519         return QCRYPTO_TLS_HANDSHAKE_SENDING;
520     }
521 }
522 
523 
524 int
525 qcrypto_tls_session_get_key_size(QCryptoTLSSession *session,
526                                  Error **errp)
527 {
528     gnutls_cipher_algorithm_t cipher;
529     int ssf;
530 
531     cipher = gnutls_cipher_get(session->handle);
532     ssf = gnutls_cipher_get_key_size(cipher);
533     if (!ssf) {
534         error_setg(errp, "Cannot get TLS cipher key size");
535         return -1;
536     }
537     return ssf;
538 }
539 
540 
541 char *
542 qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session)
543 {
544     if (session->peername) {
545         return g_strdup(session->peername);
546     }
547     return NULL;
548 }
549 
550 
551 #else /* ! CONFIG_GNUTLS */
552 
553 
554 QCryptoTLSSession *
555 qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED,
556                         const char *hostname G_GNUC_UNUSED,
557                         const char *authzid G_GNUC_UNUSED,
558                         QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED,
559                         Error **errp)
560 {
561     error_setg(errp, "TLS requires GNUTLS support");
562     return NULL;
563 }
564 
565 
566 void
567 qcrypto_tls_session_free(QCryptoTLSSession *sess G_GNUC_UNUSED)
568 {
569 }
570 
571 
572 int
573 qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess G_GNUC_UNUSED,
574                                       Error **errp)
575 {
576     error_setg(errp, "TLS requires GNUTLS support");
577     return -1;
578 }
579 
580 
581 void
582 qcrypto_tls_session_set_callbacks(
583     QCryptoTLSSession *sess G_GNUC_UNUSED,
584     QCryptoTLSSessionWriteFunc writeFunc G_GNUC_UNUSED,
585     QCryptoTLSSessionReadFunc readFunc G_GNUC_UNUSED,
586     void *opaque G_GNUC_UNUSED)
587 {
588 }
589 
590 
591 ssize_t
592 qcrypto_tls_session_write(QCryptoTLSSession *sess,
593                           const char *buf,
594                           size_t len)
595 {
596     errno = -EIO;
597     return -1;
598 }
599 
600 
601 ssize_t
602 qcrypto_tls_session_read(QCryptoTLSSession *sess,
603                          char *buf,
604                          size_t len)
605 {
606     errno = -EIO;
607     return -1;
608 }
609 
610 
611 int
612 qcrypto_tls_session_handshake(QCryptoTLSSession *sess,
613                               Error **errp)
614 {
615     error_setg(errp, "TLS requires GNUTLS support");
616     return -1;
617 }
618 
619 
620 QCryptoTLSSessionHandshakeStatus
621 qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess)
622 {
623     return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
624 }
625 
626 
627 int
628 qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess,
629                                  Error **errp)
630 {
631     error_setg(errp, "TLS requires GNUTLS support");
632     return -1;
633 }
634 
635 
636 char *
637 qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess)
638 {
639     return NULL;
640 }
641 
642 #endif
643