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