qemu/vnc-tls.c
<<
>>
Prefs
   1/*
   2 * QEMU VNC display driver: TLS helpers
   3 *
   4 * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
   5 * Copyright (C) 2006 Fabrice Bellard
   6 * Copyright (C) 2009 Red Hat, Inc
   7 *
   8 * Permission is hereby granted, free of charge, to any person obtaining a copy
   9 * of this software and associated documentation files (the "Software"), to deal
  10 * in the Software without restriction, including without limitation the rights
  11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12 * copies of the Software, and to permit persons to whom the Software is
  13 * furnished to do so, subject to the following conditions:
  14 *
  15 * The above copyright notice and this permission notice shall be included in
  16 * all copies or substantial portions of the Software.
  17 *
  18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24 * THE SOFTWARE.
  25 */
  26
  27#include "vnc.h"
  28#include "qemu_socket.h"
  29
  30#if defined(_VNC_DEBUG) && _VNC_DEBUG >= 2
  31/* Very verbose, so only enabled for _VNC_DEBUG >= 2 */
  32static void vnc_debug_gnutls_log(int level, const char* str) {
  33    VNC_DEBUG("%d %s", level, str);
  34}
  35#endif /* defined(_VNC_DEBUG) && _VNC_DEBUG >= 2 */
  36
  37
  38#define DH_BITS 1024
  39static gnutls_dh_params_t dh_params;
  40
  41static int vnc_tls_initialize(void)
  42{
  43    static int tlsinitialized = 0;
  44
  45    if (tlsinitialized)
  46        return 1;
  47
  48    if (gnutls_global_init () < 0)
  49        return 0;
  50
  51    /* XXX ought to re-generate diffie-hellmen params periodically */
  52    if (gnutls_dh_params_init (&dh_params) < 0)
  53        return 0;
  54    if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0)
  55        return 0;
  56
  57#if defined(_VNC_DEBUG) && _VNC_DEBUG >= 2
  58    gnutls_global_set_log_level(10);
  59    gnutls_global_set_log_function(vnc_debug_gnutls_log);
  60#endif
  61
  62    tlsinitialized = 1;
  63
  64    return 1;
  65}
  66
  67static ssize_t vnc_tls_push(gnutls_transport_ptr_t transport,
  68                            const void *data,
  69                            size_t len) {
  70    struct VncState *vs = (struct VncState *)transport;
  71    int ret;
  72
  73 retry:
  74    ret = send(vs->csock, data, len, 0);
  75    if (ret < 0) {
  76        if (errno == EINTR)
  77            goto retry;
  78        return -1;
  79    }
  80    return ret;
  81}
  82
  83
  84static ssize_t vnc_tls_pull(gnutls_transport_ptr_t transport,
  85                            void *data,
  86                            size_t len) {
  87    struct VncState *vs = (struct VncState *)transport;
  88    int ret;
  89
  90 retry:
  91    ret = recv(vs->csock, data, len, 0);
  92    if (ret < 0) {
  93        if (errno == EINTR)
  94            goto retry;
  95        return -1;
  96    }
  97    return ret;
  98}
  99
 100
 101static gnutls_anon_server_credentials vnc_tls_initialize_anon_cred(void)
 102{
 103    gnutls_anon_server_credentials anon_cred;
 104    int ret;
 105
 106    if ((ret = gnutls_anon_allocate_server_credentials(&anon_cred)) < 0) {
 107        VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret));
 108        return NULL;
 109    }
 110
 111    gnutls_anon_set_server_dh_params(anon_cred, dh_params);
 112
 113    return anon_cred;
 114}
 115
 116
 117static gnutls_certificate_credentials_t vnc_tls_initialize_x509_cred(VncDisplay *vd)
 118{
 119    gnutls_certificate_credentials_t x509_cred;
 120    int ret;
 121
 122    if (!vd->tls.x509cacert) {
 123        VNC_DEBUG("No CA x509 certificate specified\n");
 124        return NULL;
 125    }
 126    if (!vd->tls.x509cert) {
 127        VNC_DEBUG("No server x509 certificate specified\n");
 128        return NULL;
 129    }
 130    if (!vd->tls.x509key) {
 131        VNC_DEBUG("No server private key specified\n");
 132        return NULL;
 133    }
 134
 135    if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) {
 136        VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret));
 137        return NULL;
 138    }
 139    if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred,
 140                                                      vd->tls.x509cacert,
 141                                                      GNUTLS_X509_FMT_PEM)) < 0) {
 142        VNC_DEBUG("Cannot load CA certificate %s\n", gnutls_strerror(ret));
 143        gnutls_certificate_free_credentials(x509_cred);
 144        return NULL;
 145    }
 146
 147    if ((ret = gnutls_certificate_set_x509_key_file (x509_cred,
 148                                                     vd->tls.x509cert,
 149                                                     vd->tls.x509key,
 150                                                     GNUTLS_X509_FMT_PEM)) < 0) {
 151        VNC_DEBUG("Cannot load certificate & key %s\n", gnutls_strerror(ret));
 152        gnutls_certificate_free_credentials(x509_cred);
 153        return NULL;
 154    }
 155
 156    if (vd->tls.x509cacrl) {
 157        if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred,
 158                                                        vd->tls.x509cacrl,
 159                                                        GNUTLS_X509_FMT_PEM)) < 0) {
 160            VNC_DEBUG("Cannot load CRL %s\n", gnutls_strerror(ret));
 161            gnutls_certificate_free_credentials(x509_cred);
 162            return NULL;
 163        }
 164    }
 165
 166    gnutls_certificate_set_dh_params (x509_cred, dh_params);
 167
 168    return x509_cred;
 169}
 170
 171
 172int vnc_tls_validate_certificate(struct VncState *vs)
 173{
 174    int ret;
 175    unsigned int status;
 176    const gnutls_datum_t *certs;
 177    unsigned int nCerts, i;
 178    time_t now;
 179
 180    VNC_DEBUG("Validating client certificate\n");
 181    if ((ret = gnutls_certificate_verify_peers2 (vs->tls.session, &status)) < 0) {
 182        VNC_DEBUG("Verify failed %s\n", gnutls_strerror(ret));
 183        return -1;
 184    }
 185
 186    if ((now = time(NULL)) == ((time_t)-1)) {
 187        return -1;
 188    }
 189
 190    if (status != 0) {
 191        if (status & GNUTLS_CERT_INVALID)
 192            VNC_DEBUG("The certificate is not trusted.\n");
 193
 194        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
 195            VNC_DEBUG("The certificate hasn't got a known issuer.\n");
 196
 197        if (status & GNUTLS_CERT_REVOKED)
 198            VNC_DEBUG("The certificate has been revoked.\n");
 199
 200        if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
 201            VNC_DEBUG("The certificate uses an insecure algorithm\n");
 202
 203        return -1;
 204    } else {
 205        VNC_DEBUG("Certificate is valid!\n");
 206    }
 207
 208    /* Only support x509 for now */
 209    if (gnutls_certificate_type_get(vs->tls.session) != GNUTLS_CRT_X509)
 210        return -1;
 211
 212    if (!(certs = gnutls_certificate_get_peers(vs->tls.session, &nCerts)))
 213        return -1;
 214
 215    for (i = 0 ; i < nCerts ; i++) {
 216        gnutls_x509_crt_t cert;
 217        VNC_DEBUG ("Checking certificate chain %d\n", i);
 218        if (gnutls_x509_crt_init (&cert) < 0)
 219            return -1;
 220
 221        if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) {
 222            gnutls_x509_crt_deinit (cert);
 223            return -1;
 224        }
 225
 226        if (gnutls_x509_crt_get_expiration_time (cert) < now) {
 227            VNC_DEBUG("The certificate has expired\n");
 228            gnutls_x509_crt_deinit (cert);
 229            return -1;
 230        }
 231
 232        if (gnutls_x509_crt_get_activation_time (cert) > now) {
 233            VNC_DEBUG("The certificate is not yet activated\n");
 234            gnutls_x509_crt_deinit (cert);
 235            return -1;
 236        }
 237
 238        if (gnutls_x509_crt_get_activation_time (cert) > now) {
 239            VNC_DEBUG("The certificate is not yet activated\n");
 240            gnutls_x509_crt_deinit (cert);
 241            return -1;
 242        }
 243
 244        if (i == 0) {
 245            size_t dnameSize = 1024;
 246            vs->tls.dname = qemu_malloc(dnameSize);
 247        requery:
 248            if ((ret = gnutls_x509_crt_get_dn (cert, vs->tls.dname, &dnameSize)) != 0) {
 249                if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
 250                    vs->tls.dname = qemu_realloc(vs->tls.dname, dnameSize);
 251                    goto requery;
 252                }
 253                gnutls_x509_crt_deinit (cert);
 254                VNC_DEBUG("Cannot get client distinguished name: %s",
 255                          gnutls_strerror (ret));
 256                return -1;
 257            }
 258
 259            if (vs->vd->tls.x509verify) {
 260                int allow;
 261                if (!vs->vd->tls.acl) {
 262                    VNC_DEBUG("no ACL activated, allowing access");
 263                    gnutls_x509_crt_deinit (cert);
 264                    continue;
 265                }
 266
 267                allow = qemu_acl_party_is_allowed(vs->vd->tls.acl,
 268                                                  vs->tls.dname);
 269
 270                VNC_DEBUG("TLS x509 ACL check for %s is %s\n",
 271                          vs->tls.dname, allow ? "allowed" : "denied");
 272                if (!allow) {
 273                    gnutls_x509_crt_deinit (cert);
 274                    return -1;
 275                }
 276            }
 277        }
 278
 279        gnutls_x509_crt_deinit (cert);
 280    }
 281
 282    return 0;
 283}
 284
 285
 286int vnc_tls_client_setup(struct VncState *vs,
 287                         int needX509Creds) {
 288    static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 };
 289    static const int protocol_priority[]= { GNUTLS_TLS1_1, GNUTLS_TLS1_0, GNUTLS_SSL3, 0 };
 290    static const int kx_anon[] = {GNUTLS_KX_ANON_DH, 0};
 291    static const int kx_x509[] = {GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0};
 292
 293    VNC_DEBUG("Do TLS setup\n");
 294    if (vnc_tls_initialize() < 0) {
 295        VNC_DEBUG("Failed to init TLS\n");
 296        vnc_client_error(vs);
 297        return -1;
 298    }
 299    if (vs->tls.session == NULL) {
 300        if (gnutls_init(&vs->tls.session, GNUTLS_SERVER) < 0) {
 301            vnc_client_error(vs);
 302            return -1;
 303        }
 304
 305        if (gnutls_set_default_priority(vs->tls.session) < 0) {
 306            gnutls_deinit(vs->tls.session);
 307            vs->tls.session = NULL;
 308            vnc_client_error(vs);
 309            return -1;
 310        }
 311
 312        if (gnutls_kx_set_priority(vs->tls.session, needX509Creds ? kx_x509 : kx_anon) < 0) {
 313            gnutls_deinit(vs->tls.session);
 314            vs->tls.session = NULL;
 315            vnc_client_error(vs);
 316            return -1;
 317        }
 318
 319        if (gnutls_certificate_type_set_priority(vs->tls.session, cert_type_priority) < 0) {
 320            gnutls_deinit(vs->tls.session);
 321            vs->tls.session = NULL;
 322            vnc_client_error(vs);
 323            return -1;
 324        }
 325
 326        if (gnutls_protocol_set_priority(vs->tls.session, protocol_priority) < 0) {
 327            gnutls_deinit(vs->tls.session);
 328            vs->tls.session = NULL;
 329            vnc_client_error(vs);
 330            return -1;
 331        }
 332
 333        if (needX509Creds) {
 334            gnutls_certificate_server_credentials x509_cred = vnc_tls_initialize_x509_cred(vs->vd);
 335            if (!x509_cred) {
 336                gnutls_deinit(vs->tls.session);
 337                vs->tls.session = NULL;
 338                vnc_client_error(vs);
 339                return -1;
 340            }
 341            if (gnutls_credentials_set(vs->tls.session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) {
 342                gnutls_deinit(vs->tls.session);
 343                vs->tls.session = NULL;
 344                gnutls_certificate_free_credentials(x509_cred);
 345                vnc_client_error(vs);
 346                return -1;
 347            }
 348            if (vs->vd->tls.x509verify) {
 349                VNC_DEBUG("Requesting a client certificate\n");
 350                gnutls_certificate_server_set_request (vs->tls.session, GNUTLS_CERT_REQUEST);
 351            }
 352
 353        } else {
 354            gnutls_anon_server_credentials anon_cred = vnc_tls_initialize_anon_cred();
 355            if (!anon_cred) {
 356                gnutls_deinit(vs->tls.session);
 357                vs->tls.session = NULL;
 358                vnc_client_error(vs);
 359                return -1;
 360            }
 361            if (gnutls_credentials_set(vs->tls.session, GNUTLS_CRD_ANON, anon_cred) < 0) {
 362                gnutls_deinit(vs->tls.session);
 363                vs->tls.session = NULL;
 364                gnutls_anon_free_server_credentials(anon_cred);
 365                vnc_client_error(vs);
 366                return -1;
 367            }
 368        }
 369
 370        gnutls_transport_set_ptr(vs->tls.session, (gnutls_transport_ptr_t)vs);
 371        gnutls_transport_set_push_function(vs->tls.session, vnc_tls_push);
 372        gnutls_transport_set_pull_function(vs->tls.session, vnc_tls_pull);
 373    }
 374    return 0;
 375}
 376
 377
 378void vnc_tls_client_cleanup(struct VncState *vs)
 379{
 380    if (vs->tls.session) {
 381        gnutls_deinit(vs->tls.session);
 382        vs->tls.session = NULL;
 383    }
 384    vs->tls.wiremode = VNC_WIREMODE_CLEAR;
 385    free(vs->tls.dname);
 386}
 387
 388
 389
 390static int vnc_set_x509_credential(VncDisplay *vd,
 391                                   const char *certdir,
 392                                   const char *filename,
 393                                   char **cred,
 394                                   int ignoreMissing)
 395{
 396    struct stat sb;
 397
 398    if (*cred) {
 399        qemu_free(*cred);
 400        *cred = NULL;
 401    }
 402
 403    *cred = qemu_malloc(strlen(certdir) + strlen(filename) + 2);
 404
 405    strcpy(*cred, certdir);
 406    strcat(*cred, "/");
 407    strcat(*cred, filename);
 408
 409    VNC_DEBUG("Check %s\n", *cred);
 410    if (stat(*cred, &sb) < 0) {
 411        qemu_free(*cred);
 412        *cred = NULL;
 413        if (ignoreMissing && errno == ENOENT)
 414            return 0;
 415        return -1;
 416    }
 417
 418    return 0;
 419}
 420
 421
 422#define X509_CA_CERT_FILE "ca-cert.pem"
 423#define X509_CA_CRL_FILE "ca-crl.pem"
 424#define X509_SERVER_KEY_FILE "server-key.pem"
 425#define X509_SERVER_CERT_FILE "server-cert.pem"
 426
 427
 428int vnc_tls_set_x509_creds_dir(VncDisplay *vd,
 429                               const char *certdir)
 430{
 431    if (vnc_set_x509_credential(vd, certdir, X509_CA_CERT_FILE, &vd->tls.x509cacert, 0) < 0)
 432        goto cleanup;
 433    if (vnc_set_x509_credential(vd, certdir, X509_CA_CRL_FILE, &vd->tls.x509cacrl, 1) < 0)
 434        goto cleanup;
 435    if (vnc_set_x509_credential(vd, certdir, X509_SERVER_CERT_FILE, &vd->tls.x509cert, 0) < 0)
 436        goto cleanup;
 437    if (vnc_set_x509_credential(vd, certdir, X509_SERVER_KEY_FILE, &vd->tls.x509key, 0) < 0)
 438        goto cleanup;
 439
 440    return 0;
 441
 442 cleanup:
 443    qemu_free(vd->tls.x509cacert);
 444    qemu_free(vd->tls.x509cacrl);
 445    qemu_free(vd->tls.x509cert);
 446    qemu_free(vd->tls.x509key);
 447    vd->tls.x509cacert = vd->tls.x509cacrl = vd->tls.x509cert = vd->tls.x509key = NULL;
 448    return -1;
 449}
 450
 451