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