tlssession.c (18391B)
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 37struct 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 50void 51qcrypto_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 66static ssize_t 67qcrypto_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 80static ssize_t 81qcrypto_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 96QCryptoTLSSession * 97qcrypto_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 256static int 257qcrypto_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 } 377 } 378 379 gnutls_x509_crt_deinit(cert); 380 } 381 382 return 0; 383 384 error: 385 gnutls_x509_crt_deinit(cert); 386 return -1; 387} 388 389 390int 391qcrypto_tls_session_check_credentials(QCryptoTLSSession *session, 392 Error **errp) 393{ 394 if (object_dynamic_cast(OBJECT(session->creds), 395 TYPE_QCRYPTO_TLS_CREDS_ANON)) { 396 trace_qcrypto_tls_session_check_creds(session, "nop"); 397 return 0; 398 } else if (object_dynamic_cast(OBJECT(session->creds), 399 TYPE_QCRYPTO_TLS_CREDS_PSK)) { 400 trace_qcrypto_tls_session_check_creds(session, "nop"); 401 return 0; 402 } else if (object_dynamic_cast(OBJECT(session->creds), 403 TYPE_QCRYPTO_TLS_CREDS_X509)) { 404 if (session->creds->verifyPeer) { 405 int ret = qcrypto_tls_session_check_certificate(session, 406 errp); 407 trace_qcrypto_tls_session_check_creds(session, 408 ret == 0 ? "pass" : "fail"); 409 return ret; 410 } else { 411 trace_qcrypto_tls_session_check_creds(session, "skip"); 412 return 0; 413 } 414 } else { 415 trace_qcrypto_tls_session_check_creds(session, "error"); 416 error_setg(errp, "Unexpected credential type %s", 417 object_get_typename(OBJECT(session->creds))); 418 return -1; 419 } 420} 421 422 423void 424qcrypto_tls_session_set_callbacks(QCryptoTLSSession *session, 425 QCryptoTLSSessionWriteFunc writeFunc, 426 QCryptoTLSSessionReadFunc readFunc, 427 void *opaque) 428{ 429 session->writeFunc = writeFunc; 430 session->readFunc = readFunc; 431 session->opaque = opaque; 432} 433 434 435ssize_t 436qcrypto_tls_session_write(QCryptoTLSSession *session, 437 const char *buf, 438 size_t len) 439{ 440 ssize_t ret = gnutls_record_send(session->handle, buf, len); 441 442 if (ret < 0) { 443 switch (ret) { 444 case GNUTLS_E_AGAIN: 445 errno = EAGAIN; 446 break; 447 case GNUTLS_E_INTERRUPTED: 448 errno = EINTR; 449 break; 450 default: 451 errno = EIO; 452 break; 453 } 454 ret = -1; 455 } 456 457 return ret; 458} 459 460 461ssize_t 462qcrypto_tls_session_read(QCryptoTLSSession *session, 463 char *buf, 464 size_t len) 465{ 466 ssize_t ret = gnutls_record_recv(session->handle, buf, len); 467 468 if (ret < 0) { 469 switch (ret) { 470 case GNUTLS_E_AGAIN: 471 errno = EAGAIN; 472 break; 473 case GNUTLS_E_INTERRUPTED: 474 errno = EINTR; 475 break; 476 case GNUTLS_E_PREMATURE_TERMINATION: 477 errno = ECONNABORTED; 478 break; 479 default: 480 errno = EIO; 481 break; 482 } 483 ret = -1; 484 } 485 486 return ret; 487} 488 489 490int 491qcrypto_tls_session_handshake(QCryptoTLSSession *session, 492 Error **errp) 493{ 494 int ret = gnutls_handshake(session->handle); 495 if (ret == 0) { 496 session->handshakeComplete = true; 497 } else { 498 if (ret == GNUTLS_E_INTERRUPTED || 499 ret == GNUTLS_E_AGAIN) { 500 ret = 1; 501 } else { 502 error_setg(errp, "TLS handshake failed: %s", 503 gnutls_strerror(ret)); 504 ret = -1; 505 } 506 } 507 508 return ret; 509} 510 511 512QCryptoTLSSessionHandshakeStatus 513qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *session) 514{ 515 if (session->handshakeComplete) { 516 return QCRYPTO_TLS_HANDSHAKE_COMPLETE; 517 } else if (gnutls_record_get_direction(session->handle) == 0) { 518 return QCRYPTO_TLS_HANDSHAKE_RECVING; 519 } else { 520 return QCRYPTO_TLS_HANDSHAKE_SENDING; 521 } 522} 523 524 525int 526qcrypto_tls_session_get_key_size(QCryptoTLSSession *session, 527 Error **errp) 528{ 529 gnutls_cipher_algorithm_t cipher; 530 int ssf; 531 532 cipher = gnutls_cipher_get(session->handle); 533 ssf = gnutls_cipher_get_key_size(cipher); 534 if (!ssf) { 535 error_setg(errp, "Cannot get TLS cipher key size"); 536 return -1; 537 } 538 return ssf; 539} 540 541 542char * 543qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session) 544{ 545 if (session->peername) { 546 return g_strdup(session->peername); 547 } 548 return NULL; 549} 550 551 552#else /* ! CONFIG_GNUTLS */ 553 554 555QCryptoTLSSession * 556qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED, 557 const char *hostname G_GNUC_UNUSED, 558 const char *authzid G_GNUC_UNUSED, 559 QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED, 560 Error **errp) 561{ 562 error_setg(errp, "TLS requires GNUTLS support"); 563 return NULL; 564} 565 566 567void 568qcrypto_tls_session_free(QCryptoTLSSession *sess G_GNUC_UNUSED) 569{ 570} 571 572 573int 574qcrypto_tls_session_check_credentials(QCryptoTLSSession *sess G_GNUC_UNUSED, 575 Error **errp) 576{ 577 error_setg(errp, "TLS requires GNUTLS support"); 578 return -1; 579} 580 581 582void 583qcrypto_tls_session_set_callbacks( 584 QCryptoTLSSession *sess G_GNUC_UNUSED, 585 QCryptoTLSSessionWriteFunc writeFunc G_GNUC_UNUSED, 586 QCryptoTLSSessionReadFunc readFunc G_GNUC_UNUSED, 587 void *opaque G_GNUC_UNUSED) 588{ 589} 590 591 592ssize_t 593qcrypto_tls_session_write(QCryptoTLSSession *sess, 594 const char *buf, 595 size_t len) 596{ 597 errno = -EIO; 598 return -1; 599} 600 601 602ssize_t 603qcrypto_tls_session_read(QCryptoTLSSession *sess, 604 char *buf, 605 size_t len) 606{ 607 errno = -EIO; 608 return -1; 609} 610 611 612int 613qcrypto_tls_session_handshake(QCryptoTLSSession *sess, 614 Error **errp) 615{ 616 error_setg(errp, "TLS requires GNUTLS support"); 617 return -1; 618} 619 620 621QCryptoTLSSessionHandshakeStatus 622qcrypto_tls_session_get_handshake_status(QCryptoTLSSession *sess) 623{ 624 return QCRYPTO_TLS_HANDSHAKE_COMPLETE; 625} 626 627 628int 629qcrypto_tls_session_get_key_size(QCryptoTLSSession *sess, 630 Error **errp) 631{ 632 error_setg(errp, "TLS requires GNUTLS support"); 633 return -1; 634} 635 636 637char * 638qcrypto_tls_session_get_peer_name(QCryptoTLSSession *sess) 639{ 640 return NULL; 641} 642 643#endif