Annotation of qemu/vnc-tls.c, revision 1.1

1.1     ! root        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 */
        !            32: static 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
        !            39: static gnutls_dh_params_t dh_params;
        !            40: 
        !            41: static 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: 
        !            67: static 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: 
        !            84: static 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: 
        !           101: static 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: 
        !           117: static 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: 
        !           172: int 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: 
        !           286: int 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: 
        !           378: void 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: 
        !           390: static 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: 
        !           428: int 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: 

unix.superglobalmegacorp.com