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/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
  35struct 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
  48void
  49qcrypto_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
  64static ssize_t
  65qcrypto_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
  78static ssize_t
  79qcrypto_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
  92QCryptoTLSSession *
  93qcrypto_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
 213static int
 214qcrypto_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
 348int
 349qcrypto_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
 377void
 378qcrypto_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
 389ssize_t
 390qcrypto_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
 415ssize_t
 416qcrypto_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
 441int
 442qcrypto_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
 463QCryptoTLSSessionHandshakeStatus
 464qcrypto_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
 476int
 477qcrypto_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
 493char *
 494qcrypto_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
 506QCryptoTLSSession *
 507qcrypto_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
 518void
 519qcrypto_tls_session_free(QCryptoTLSSession *sess G_GNUC_UNUSED)
 520{
 521}
 522
 523
 524int
 525qcrypto_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
 533void
 534qcrypto_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
 543ssize_t
 544qcrypto_tls_session_write(QCryptoTLSSession *sess,
 545                          const char *buf,
 546                          size_t len)
 547{
 548    errno = -EIO;
 549    return -1;
 550}
 551
 552
 553ssize_t
 554qcrypto_tls_session_read(QCryptoTLSSession *sess,
 555                         char *buf,
 556                         size_t len)
 557{
 558    errno = -EIO;
 559    return -1;
 560}
 561
 562
 563int
 564qcrypto_tls_session_handshake(QCryptoTLSSession *sess,
 565                              Error **errp)
 566{
 567    error_setg(errp, "TLS requires GNUTLS support");
 568    return -1;
 569}
 570
 571
 572QCryptoTLSSessionHandshakeStatus
 573qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess)
 574{
 575    return QCRYPTO_TLS_HANDSHAKE_COMPLETE;
 576}
 577
 578
 579int
 580qcrypto_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
 588char *
 589qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess)
 590{
 591    return NULL;
 592}
 593
 594#endif
 595