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