xref: /openbmc/qemu/crypto/tlssession.c (revision 36ebc7db)
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.1 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 "tlscredspriv.h"
29 #include "trace.h"
30 
31 #ifdef CONFIG_GNUTLS
32 
33 
34 #include <gnutls/x509.h>
35 
36 
37 struct QCryptoTLSSession {
38     QCryptoTLSCreds *creds;
39     gnutls_session_t handle;
40     char *hostname;
41     char *authzid;
42     bool handshakeComplete;
43     QCryptoTLSSessionWriteFunc writeFunc;
44     QCryptoTLSSessionReadFunc readFunc;
45     void *opaque;
46     char *peername;
47 };
48 
49 
50 void
51 qcrypto_tls_session_free(QCryptoTLSSession *session)
52 {
53     if (!session) {
54         return;
55     }
56 
57     gnutls_deinit(session->handle);
58     g_free(session->hostname);
59     g_free(session->peername);
60     g_free(session->authzid);
61     object_unref(OBJECT(session->creds));
62     g_free(session);
63 }
64 
65 
66 static ssize_t
67 qcrypto_tls_session_push(void *opaque, const void *buf, size_t len)
68 {
69     QCryptoTLSSession *session = opaque;
70 
71     if (!session->writeFunc) {
72         errno = EIO;
73         return -1;
74     };
75 
76     return session->writeFunc(buf, len, session->opaque);
77 }
78 
79 
80 static ssize_t
81 qcrypto_tls_session_pull(void *opaque, void *buf, size_t len)
82 {
83     QCryptoTLSSession *session = opaque;
84 
85     if (!session->readFunc) {
86         errno = EIO;
87         return -1;
88     };
89 
90     return session->readFunc(buf, len, session->opaque);
91 }
92 
93 #define TLS_PRIORITY_ADDITIONAL_ANON "+ANON-DH"
94 #define TLS_PRIORITY_ADDITIONAL_PSK "+ECDHE-PSK:+DHE-PSK:+PSK"
95 
96 QCryptoTLSSession *
97 qcrypto_tls_session_new(QCryptoTLSCreds *creds,
98                         const char *hostname,
99                         const char *authzid,
100                         QCryptoTLSCredsEndpoint endpoint,
101                         Error **errp)
102 {
103     QCryptoTLSSession *session;
104     int ret;
105 
106     session = g_new0(QCryptoTLSSession, 1);
107     trace_qcrypto_tls_session_new(
108         session, creds, hostname ? hostname : "<none>",
109         authzid ? authzid : "<none>", endpoint);
110 
111     if (hostname) {
112         session->hostname = g_strdup(hostname);
113     }
114     if (authzid) {
115         session->authzid = g_strdup(authzid);
116     }
117     session->creds = creds;
118     object_ref(OBJECT(creds));
119 
120     if (creds->endpoint != endpoint) {
121         error_setg(errp, "Credentials endpoint doesn't match session");
122         goto error;
123     }
124 
125     if (endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
126         ret = gnutls_init(&session->handle, GNUTLS_SERVER);
127     } else {
128         ret = gnutls_init(&session->handle, GNUTLS_CLIENT);
129     }
130     if (ret < 0) {
131         error_setg(errp, "Cannot initialize TLS session: %s",
132                    gnutls_strerror(ret));
133         goto error;
134     }
135 
136     if (object_dynamic_cast(OBJECT(creds),
137                             TYPE_QCRYPTO_TLS_CREDS_ANON)) {
138         QCryptoTLSCredsAnon *acreds = QCRYPTO_TLS_CREDS_ANON(creds);
139         char *prio;
140 
141         if (creds->priority != NULL) {
142             prio = g_strdup_printf("%s:%s",
143                                    creds->priority,
144                                    TLS_PRIORITY_ADDITIONAL_ANON);
145         } else {
146             prio = g_strdup(CONFIG_TLS_PRIORITY ":"
147                             TLS_PRIORITY_ADDITIONAL_ANON);
148         }
149 
150         ret = gnutls_priority_set_direct(session->handle, prio, NULL);
151         if (ret < 0) {
152             error_setg(errp, "Unable to set TLS session priority %s: %s",
153                        prio, gnutls_strerror(ret));
154             g_free(prio);
155             goto error;
156         }
157         g_free(prio);
158         if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
159             ret = gnutls_credentials_set(session->handle,
160                                          GNUTLS_CRD_ANON,
161                                          acreds->data.server);
162         } else {
163             ret = gnutls_credentials_set(session->handle,
164                                          GNUTLS_CRD_ANON,
165                                          acreds->data.client);
166         }
167         if (ret < 0) {
168             error_setg(errp, "Cannot set session credentials: %s",
169                        gnutls_strerror(ret));
170             goto error;
171         }
172     } else if (object_dynamic_cast(OBJECT(creds),
173                                    TYPE_QCRYPTO_TLS_CREDS_PSK)) {
174         QCryptoTLSCredsPSK *pcreds = QCRYPTO_TLS_CREDS_PSK(creds);
175         char *prio;
176 
177         if (creds->priority != NULL) {
178             prio = g_strdup_printf("%s:%s",
179                                    creds->priority,
180                                    TLS_PRIORITY_ADDITIONAL_PSK);
181         } else {
182             prio = g_strdup(CONFIG_TLS_PRIORITY ":"
183                             TLS_PRIORITY_ADDITIONAL_PSK);
184         }
185 
186         ret = gnutls_priority_set_direct(session->handle, prio, NULL);
187         if (ret < 0) {
188             error_setg(errp, "Unable to set TLS session priority %s: %s",
189                        prio, gnutls_strerror(ret));
190             g_free(prio);
191             goto error;
192         }
193         g_free(prio);
194         if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
195             ret = gnutls_credentials_set(session->handle,
196                                          GNUTLS_CRD_PSK,
197                                          pcreds->data.server);
198         } else {
199             ret = gnutls_credentials_set(session->handle,
200                                          GNUTLS_CRD_PSK,
201                                          pcreds->data.client);
202         }
203         if (ret < 0) {
204             error_setg(errp, "Cannot set session credentials: %s",
205                        gnutls_strerror(ret));
206             goto error;
207         }
208     } else if (object_dynamic_cast(OBJECT(creds),
209                                    TYPE_QCRYPTO_TLS_CREDS_X509)) {
210         QCryptoTLSCredsX509 *tcreds = QCRYPTO_TLS_CREDS_X509(creds);
211         const char *prio = creds->priority;
212         if (!prio) {
213             prio = CONFIG_TLS_PRIORITY;
214         }
215 
216         ret = gnutls_priority_set_direct(session->handle, prio, NULL);
217         if (ret < 0) {
218             error_setg(errp, "Cannot set default TLS session priority %s: %s",
219                        prio, gnutls_strerror(ret));
220             goto error;
221         }
222         ret = gnutls_credentials_set(session->handle,
223                                      GNUTLS_CRD_CERTIFICATE,
224                                      tcreds->data);
225         if (ret < 0) {
226             error_setg(errp, "Cannot set session credentials: %s",
227                        gnutls_strerror(ret));
228             goto error;
229         }
230 
231         if (creds->endpoint == QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
232             /* This requests, but does not enforce a client cert.
233              * The cert checking code later does enforcement */
234             gnutls_certificate_server_set_request(session->handle,
235                                                   GNUTLS_CERT_REQUEST);
236         }
237     } else {
238         error_setg(errp, "Unsupported TLS credentials type %s",
239                    object_get_typename(OBJECT(creds)));
240         goto error;
241     }
242 
243     gnutls_transport_set_ptr(session->handle, session);
244     gnutls_transport_set_push_function(session->handle,
245                                        qcrypto_tls_session_push);
246     gnutls_transport_set_pull_function(session->handle,
247                                        qcrypto_tls_session_pull);
248 
249     return session;
250 
251  error:
252     qcrypto_tls_session_free(session);
253     return NULL;
254 }
255 
256 static int
257 qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
258                                       Error **errp)
259 {
260     int ret;
261     unsigned int status;
262     const gnutls_datum_t *certs;
263     unsigned int nCerts, i;
264     time_t now;
265     gnutls_x509_crt_t cert = NULL;
266     Error *err = NULL;
267 
268     now = time(NULL);
269     if (now == ((time_t)-1)) {
270         error_setg_errno(errp, errno, "Cannot get current time");
271         return -1;
272     }
273 
274     ret = gnutls_certificate_verify_peers2(session->handle, &status);
275     if (ret < 0) {
276         error_setg(errp, "Verify failed: %s", gnutls_strerror(ret));
277         return -1;
278     }
279 
280     if (status != 0) {
281         const char *reason = "Invalid certificate";
282 
283         if (status & GNUTLS_CERT_INVALID) {
284             reason = "The certificate is not trusted";
285         }
286 
287         if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) {
288             reason = "The certificate hasn't got a known issuer";
289         }
290 
291         if (status & GNUTLS_CERT_REVOKED) {
292             reason = "The certificate has been revoked";
293         }
294 
295         if (status & GNUTLS_CERT_INSECURE_ALGORITHM) {
296             reason = "The certificate uses an insecure algorithm";
297         }
298 
299         error_setg(errp, "%s", reason);
300         return -1;
301     }
302 
303     certs = gnutls_certificate_get_peers(session->handle, &nCerts);
304     if (!certs) {
305         error_setg(errp, "No certificate peers");
306         return -1;
307     }
308 
309     for (i = 0; i < nCerts; i++) {
310         ret = gnutls_x509_crt_init(&cert);
311         if (ret < 0) {
312             error_setg(errp, "Cannot initialize certificate: %s",
313                        gnutls_strerror(ret));
314             return -1;
315         }
316 
317         ret = gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER);
318         if (ret < 0) {
319             error_setg(errp, "Cannot import certificate: %s",
320                        gnutls_strerror(ret));
321             goto error;
322         }
323 
324         if (gnutls_x509_crt_get_expiration_time(cert) < now) {
325             error_setg(errp, "The certificate has expired");
326             goto error;
327         }
328 
329         if (gnutls_x509_crt_get_activation_time(cert) > now) {
330             error_setg(errp, "The certificate is not yet activated");
331             goto error;
332         }
333 
334         if (gnutls_x509_crt_get_activation_time(cert) > now) {
335             error_setg(errp, "The certificate is not yet activated");
336             goto error;
337         }
338 
339         if (i == 0) {
340             size_t dnameSize = 1024;
341             session->peername = g_malloc(dnameSize);
342         requery:
343             ret = gnutls_x509_crt_get_dn(cert, session->peername, &dnameSize);
344             if (ret < 0) {
345                 if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
346                     session->peername = g_realloc(session->peername,
347                                                   dnameSize);
348                     goto requery;
349                 }
350                 error_setg(errp, "Cannot get client distinguished name: %s",
351                            gnutls_strerror(ret));
352                 goto error;
353             }
354             if (session->authzid) {
355                 bool allow;
356 
357                 allow = qauthz_is_allowed_by_id(session->authzid,
358                                                 session->peername, &err);
359                 if (err) {
360                     error_propagate(errp, err);
361                     goto error;
362                 }
363                 if (!allow) {
364                     error_setg(errp, "TLS x509 authz check for %s is denied",
365                                session->peername);
366                     goto error;
367                 }
368             }
369             if (session->hostname) {
370                 if (!gnutls_x509_crt_check_hostname(cert, session->hostname)) {
371                     error_setg(errp,
372                                "Certificate does not match the hostname %s",
373                                session->hostname);
374                     goto error;
375                 }
376             } else {
377                 if (session->creds->endpoint ==
378                     QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
379                     error_setg(errp, "No hostname for certificate validation");
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         case GNUTLS_E_PREMATURE_TERMINATION:
483             errno = ECONNABORTED;
484             break;
485         default:
486             errno = EIO;
487             break;
488         }
489         ret = -1;
490     }
491 
492     return ret;
493 }
494 
495 
496 size_t
497 qcrypto_tls_session_check_pending(QCryptoTLSSession *session)
498 {
499     return gnutls_record_check_pending(session->handle);
500 }
501 
502 
503 int
504 qcrypto_tls_session_handshake(QCryptoTLSSession *session,
505                               Error **errp)
506 {
507     int ret = gnutls_handshake(session->handle);
508     if (ret == 0) {
509         session->handshakeComplete = true;
510     } else {
511         if (ret == GNUTLS_E_INTERRUPTED ||
512             ret == GNUTLS_E_AGAIN) {
513             ret = 1;
514         } else {
515             error_setg(errp, "TLS handshake failed: %s",
516                        gnutls_strerror(ret));
517             ret = -1;
518         }
519     }
520 
521     return ret;
522 }
523 
524 
525 QCryptoTLSSessionHandshakeStatus
526 qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session)
527 {
528     if (session->handshakeComplete) {
529         return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
530     } else if (gnutls_record_get_direction(session->handle) == 0) {
531         return QCRYPTO_TLS_HANDSHAKE_RECVING;
532     } else {
533         return QCRYPTO_TLS_HANDSHAKE_SENDING;
534     }
535 }
536 
537 
538 int
539 qcrypto_tls_session_get_key_size(QCryptoTLSSession *session,
540                                  Error **errp)
541 {
542     gnutls_cipher_algorithm_t cipher;
543     int ssf;
544 
545     cipher = gnutls_cipher_get(session->handle);
546     ssf = gnutls_cipher_get_key_size(cipher);
547     if (!ssf) {
548         error_setg(errp, "Cannot get TLS cipher key size");
549         return -1;
550     }
551     return ssf;
552 }
553 
554 
555 char *
556 qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session)
557 {
558     if (session->peername) {
559         return g_strdup(session->peername);
560     }
561     return NULL;
562 }
563 
564 
565 #else /* ! CONFIG_GNUTLS */
566 
567 
568 QCryptoTLSSession *
569 qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED,
570                         const char *hostname G_GNUC_UNUSED,
571                         const char *authzid G_GNUC_UNUSED,
572                         QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED,
573                         Error **errp)
574 {
575     error_setg(errp, "TLS requires GNUTLS support");
576     return NULL;
577 }
578 
579 
580 void
581 qcrypto_tls_session_free(QCryptoTLSSession *sess G_GNUC_UNUSED)
582 {
583 }
584 
585 
586 int
587 qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess G_GNUC_UNUSED,
588                                       Error **errp)
589 {
590     error_setg(errp, "TLS requires GNUTLS support");
591     return -1;
592 }
593 
594 
595 void
596 qcrypto_tls_session_set_callbacks(
597     QCryptoTLSSession *sess G_GNUC_UNUSED,
598     QCryptoTLSSessionWriteFunc writeFunc G_GNUC_UNUSED,
599     QCryptoTLSSessionReadFunc readFunc G_GNUC_UNUSED,
600     void *opaque G_GNUC_UNUSED)
601 {
602 }
603 
604 
605 ssize_t
606 qcrypto_tls_session_write(QCryptoTLSSession *sess,
607                           const char *buf,
608                           size_t len)
609 {
610     errno = -EIO;
611     return -1;
612 }
613 
614 
615 ssize_t
616 qcrypto_tls_session_read(QCryptoTLSSession *sess,
617                          char *buf,
618                          size_t len)
619 {
620     errno = -EIO;
621     return -1;
622 }
623 
624 
625 size_t
626 qcrypto_tls_session_check_pending(QCryptoTLSSession *session)
627 {
628     return 0;
629 }
630 
631 
632 int
633 qcrypto_tls_session_handshake(QCryptoTLSSession *sess,
634                               Error **errp)
635 {
636     error_setg(errp, "TLS requires GNUTLS support");
637     return -1;
638 }
639 
640 
641 QCryptoTLSSessionHandshakeStatus
642 qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess)
643 {
644     return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
645 }
646 
647 
648 int
649 qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess,
650                                  Error **errp)
651 {
652     error_setg(errp, "TLS requires GNUTLS support");
653     return -1;
654 }
655 
656 
657 char *
658 qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess)
659 {
660     return NULL;
661 }
662 
663 #endif
664