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