qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

tlssession.c (18647B)


      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 int
    497 qcrypto_tls_session_handshake(QCryptoTLSSession *session,
    498                               Error **errp)
    499 {
    500     int ret = gnutls_handshake(session->handle);
    501     if (ret == 0) {
    502         session->handshakeComplete = true;
    503     } else {
    504         if (ret == GNUTLS_E_INTERRUPTED ||
    505             ret == GNUTLS_E_AGAIN) {
    506             ret = 1;
    507         } else {
    508             error_setg(errp, "TLS handshake failed: %s",
    509                        gnutls_strerror(ret));
    510             ret = -1;
    511         }
    512     }
    513 
    514     return ret;
    515 }
    516 
    517 
    518 QCryptoTLSSessionHandshakeStatus
    519 qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session)
    520 {
    521     if (session->handshakeComplete) {
    522         return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
    523     } else if (gnutls_record_get_direction(session->handle) == 0) {
    524         return QCRYPTO_TLS_HANDSHAKE_RECVING;
    525     } else {
    526         return QCRYPTO_TLS_HANDSHAKE_SENDING;
    527     }
    528 }
    529 
    530 
    531 int
    532 qcrypto_tls_session_get_key_size(QCryptoTLSSession *session,
    533                                  Error **errp)
    534 {
    535     gnutls_cipher_algorithm_t cipher;
    536     int ssf;
    537 
    538     cipher = gnutls_cipher_get(session->handle);
    539     ssf = gnutls_cipher_get_key_size(cipher);
    540     if (!ssf) {
    541         error_setg(errp, "Cannot get TLS cipher key size");
    542         return -1;
    543     }
    544     return ssf;
    545 }
    546 
    547 
    548 char *
    549 qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session)
    550 {
    551     if (session->peername) {
    552         return g_strdup(session->peername);
    553     }
    554     return NULL;
    555 }
    556 
    557 
    558 #else /* ! CONFIG_GNUTLS */
    559 
    560 
    561 QCryptoTLSSession *
    562 qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED,
    563                         const char *hostname G_GNUC_UNUSED,
    564                         const char *authzid G_GNUC_UNUSED,
    565                         QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED,
    566                         Error **errp)
    567 {
    568     error_setg(errp, "TLS requires GNUTLS support");
    569     return NULL;
    570 }
    571 
    572 
    573 void
    574 qcrypto_tls_session_free(QCryptoTLSSession *sess G_GNUC_UNUSED)
    575 {
    576 }
    577 
    578 
    579 int
    580 qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess G_GNUC_UNUSED,
    581                                       Error **errp)
    582 {
    583     error_setg(errp, "TLS requires GNUTLS support");
    584     return -1;
    585 }
    586 
    587 
    588 void
    589 qcrypto_tls_session_set_callbacks(
    590     QCryptoTLSSession *sess G_GNUC_UNUSED,
    591     QCryptoTLSSessionWriteFunc writeFunc G_GNUC_UNUSED,
    592     QCryptoTLSSessionReadFunc readFunc G_GNUC_UNUSED,
    593     void *opaque G_GNUC_UNUSED)
    594 {
    595 }
    596 
    597 
    598 ssize_t
    599 qcrypto_tls_session_write(QCryptoTLSSession *sess,
    600                           const char *buf,
    601                           size_t len)
    602 {
    603     errno = -EIO;
    604     return -1;
    605 }
    606 
    607 
    608 ssize_t
    609 qcrypto_tls_session_read(QCryptoTLSSession *sess,
    610                          char *buf,
    611                          size_t len)
    612 {
    613     errno = -EIO;
    614     return -1;
    615 }
    616 
    617 
    618 int
    619 qcrypto_tls_session_handshake(QCryptoTLSSession *sess,
    620                               Error **errp)
    621 {
    622     error_setg(errp, "TLS requires GNUTLS support");
    623     return -1;
    624 }
    625 
    626 
    627 QCryptoTLSSessionHandshakeStatus
    628 qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess)
    629 {
    630     return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
    631 }
    632 
    633 
    634 int
    635 qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess,
    636                                  Error **errp)
    637 {
    638     error_setg(errp, "TLS requires GNUTLS support");
    639     return -1;
    640 }
    641 
    642 
    643 char *
    644 qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess)
    645 {
    646     return NULL;
    647 }
    648 
    649 #endif