1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122
|
/*
* Copyright (C) 2016 American Civil Liberties Union (ACLU)
* Copyright (C) CZ.NIC, z.s.p.o
*
* Initial Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
* Ondřej Surý <ondrej@sury.org>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <gnutls/abstract.h>
#include <gnutls/crypto.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <uv.h>
#include <errno.h>
#include <stdlib.h>
#include "contrib/ucw/lib.h"
#include "contrib/base64.h"
#include "daemon/tls.h"
#include "daemon/worker.h"
#include "daemon/session.h"
#define EPHEMERAL_CERT_EXPIRATION_SECONDS_RENEW_BEFORE ((time_t)60*60*24*7)
#define GNUTLS_PIN_MIN_VERSION 0x030400
#define VERBOSE_MSG(cl_side, ...)\
if (cl_side) \
kr_log_debug(TLSCLIENT, __VA_ARGS__); \
else \
kr_log_debug(TLS, __VA_ARGS__);
/** @internal Debugging facility. */
#ifdef DEBUG
#define DEBUG_MSG(...) kr_log_debug(TLS, __VA_ARGS__)
#else
#define DEBUG_MSG(...)
#endif
struct async_write_ctx {
uv_write_t write_req;
struct tls_common_ctx *t;
char buf[];
};
static int client_verify_certificate(gnutls_session_t tls_session);
/**
* Set mandatory security settings from
* https://tools.ietf.org/html/draft-ietf-dprive-dtls-and-tls-profiles-11#section-9
* Performance optimizations are not implemented at the moment.
*/
static int kres_gnutls_set_priority(gnutls_session_t session) {
static const char * const priorities =
"NORMAL:" /* GnuTLS defaults */
"-VERS-TLS1.0:-VERS-TLS1.1:" /* TLS 1.2 and higher */
/* Some distros by default allow features that are considered
* too insecure nowadays, so let's disable them explicitly. */
"-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL:+COMP-NULL";
const char *errpos = NULL;
int err = gnutls_priority_set_direct(session, priorities, &errpos);
if (err != GNUTLS_E_SUCCESS) {
kr_log_error(TLS, "setting priority '%s' failed at character %zd (...'%s') with %s (%d)\n",
priorities, errpos - priorities, errpos, gnutls_strerror_name(err), err);
}
return err;
}
static ssize_t kres_gnutls_pull(gnutls_transport_ptr_t h, void *buf, size_t len)
{
struct tls_common_ctx *t = (struct tls_common_ctx *)h;
if (kr_fails_assert(t)) {
errno = EFAULT;
return -1;
}
ssize_t avail = t->nread - t->consumed;
DEBUG_MSG("[%s] pull wanted: %zu available: %zu\n",
t->client_side ? "tls_client" : "tls", len, avail);
if (t->nread <= t->consumed) {
errno = EAGAIN;
return -1;
}
ssize_t transfer = MIN(avail, len);
memcpy(buf, t->buf + t->consumed, transfer);
t->consumed += transfer;
return transfer;
}
static ssize_t kres_gnutls_vec_push(gnutls_transport_ptr_t h, const giovec_t * iov, int iovcnt)
{
struct tls_common_ctx *t = (struct tls_common_ctx *)h;
if (kr_fails_assert(t)) {
errno = EFAULT;
return -1;
}
if (iovcnt == 0) {
return 0;
}
if (kr_fails_assert(t->session)) {
errno = EFAULT;
return -1;
}
uv_stream_t *handle = (uv_stream_t *)session_get_handle(t->session);
if (kr_fails_assert(handle && handle->type == UV_TCP)) {
errno = EFAULT;
return -1;
}
size_t total_len = 0;
uv_buf_t uv_buf[iovcnt];
for (int i = 0; i < iovcnt; ++i) {
uv_buf[i].base = iov[i].iov_base;
uv_buf[i].len = iov[i].iov_len;
total_len += iov[i].iov_len;
}
int ret;
{ // indentation kept to reduce diff:
ret = uv_try_write(handle, uv_buf, iovcnt);
DEBUG_MSG("[%s] push %zu <%p> = %d\n",
t->client_side ? "tls_client" : "tls", total_len, h, ret);
/* from libuv documentation -
uv_try_write will return either:
> 0: number of bytes written (can be less than the supplied buffer size).
< 0: negative error code (UV_EAGAIN is returned if no data can be sent immediately).
*/
if (ret == total_len) {
/* All the data were buffered by libuv.
* Return. */
return ret;
}
if (ret < 0 && ret != UV_EAGAIN) {
/* uv_try_write() has returned error code other then UV_EAGAIN.
* Return. */
VERBOSE_MSG(t->client_side, "uv_try_write error: %s\n",
uv_strerror(ret));
ret = -1;
errno = EIO;
return ret;
}
return kr_error(ENOBUFS);
}
}
/** Perform TLS handshake and handle error codes according to the documentation.
* See See https://gnutls.org/manual/html_node/TLS-handshake.html#TLS-handshake
* The function returns kr_ok() or success or non fatal error, kr_error(EAGAIN) on blocking, or kr_error(EIO) on fatal error.
*/
static int tls_handshake(struct tls_common_ctx *ctx, tls_handshake_cb handshake_cb) {
struct session *session = ctx->session;
int err = gnutls_handshake(ctx->tls_session);
if (err == GNUTLS_E_SUCCESS) {
/* Handshake finished, return success */
ctx->handshake_state = TLS_HS_DONE;
struct sockaddr *peer = session_get_peer(session);
VERBOSE_MSG(ctx->client_side, "TLS handshake with %s has completed\n",
kr_straddr(peer));
if (handshake_cb) {
if (handshake_cb(session, 0) != kr_ok()) {
return kr_error(EIO);
}
}
} else if (err == GNUTLS_E_AGAIN) {
return kr_error(EAGAIN);
} else if (gnutls_error_is_fatal(err)) {
/* Fatal errors, return error as it's not recoverable */
VERBOSE_MSG(ctx->client_side, "gnutls_handshake failed: %s (%d)\n",
gnutls_strerror_name(err), err);
/* Notify the peer about handshake failure via an alert. */
gnutls_alert_send_appropriate(ctx->tls_session, err);
if (handshake_cb) {
handshake_cb(session, -1);
}
return kr_error(EIO);
} else if (err == GNUTLS_E_WARNING_ALERT_RECEIVED) {
/* Handle warning when in verbose mode */
const char *alert_name = gnutls_alert_get_name(gnutls_alert_get(ctx->tls_session));
if (alert_name != NULL) {
struct sockaddr *peer = session_get_peer(session);
VERBOSE_MSG(ctx->client_side, "TLS alert from %s received: %s\n",
kr_straddr(peer), alert_name);
}
}
return kr_ok();
}
struct tls_ctx *tls_new(struct worker_ctx *worker)
{
if (kr_fails_assert(worker && worker->engine))
return NULL;
struct network *net = &worker->engine->net;
if (!net->tls_credentials) {
net->tls_credentials = tls_get_ephemeral_credentials(worker->engine);
if (!net->tls_credentials) {
kr_log_error(TLS, "X.509 credentials are missing, and ephemeral credentials failed; no TLS\n");
return NULL;
}
kr_log_info(TLS, "Using ephemeral TLS credentials\n");
tls_credentials_log_pins(net->tls_credentials);
}
time_t now = time(NULL);
if (net->tls_credentials->valid_until != GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION) {
if (net->tls_credentials->ephemeral_servicename) {
/* ephemeral cert: refresh if due to expire within a week */
if (now >= net->tls_credentials->valid_until - EPHEMERAL_CERT_EXPIRATION_SECONDS_RENEW_BEFORE) {
struct tls_credentials *newcreds = tls_get_ephemeral_credentials(worker->engine);
if (newcreds) {
tls_credentials_release(net->tls_credentials);
net->tls_credentials = newcreds;
kr_log_info(TLS, "Renewed expiring ephemeral X.509 cert\n");
} else {
kr_log_error(TLS, "Failed to renew expiring ephemeral X.509 cert, using existing one\n");
}
}
} else {
/* non-ephemeral cert: warn once when certificate expires */
if (now >= net->tls_credentials->valid_until) {
kr_log_error(TLS, "X.509 certificate has expired!\n");
net->tls_credentials->valid_until = GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION;
}
}
}
struct tls_ctx *tls = calloc(1, sizeof(struct tls_ctx));
if (tls == NULL) {
kr_log_error(TLS, "failed to allocate TLS context\n");
return NULL;
}
int flags = GNUTLS_SERVER | GNUTLS_NONBLOCK;
#if GNUTLS_VERSION_NUMBER >= 0x030705
if (gnutls_check_version("3.7.5"))
flags |= GNUTLS_NO_TICKETS_TLS12;
#endif
int err = gnutls_init(&tls->c.tls_session, flags);
if (err != GNUTLS_E_SUCCESS) {
kr_log_error(TLS, "gnutls_init(): %s (%d)\n", gnutls_strerror_name(err), err);
tls_free(tls);
return NULL;
}
tls->credentials = tls_credentials_reserve(net->tls_credentials);
err = gnutls_credentials_set(tls->c.tls_session, GNUTLS_CRD_CERTIFICATE,
tls->credentials->credentials);
if (err != GNUTLS_E_SUCCESS) {
kr_log_error(TLS, "gnutls_credentials_set(): %s (%d)\n", gnutls_strerror_name(err), err);
tls_free(tls);
return NULL;
}
if (kres_gnutls_set_priority(tls->c.tls_session) != GNUTLS_E_SUCCESS) {
tls_free(tls);
return NULL;
}
tls->c.worker = worker;
tls->c.client_side = false;
gnutls_transport_set_pull_function(tls->c.tls_session, kres_gnutls_pull);
gnutls_transport_set_vec_push_function(tls->c.tls_session, kres_gnutls_vec_push);
gnutls_transport_set_ptr(tls->c.tls_session, tls);
if (net->tls_session_ticket_ctx) {
tls_session_ticket_enable(net->tls_session_ticket_ctx,
tls->c.tls_session);
}
return tls;
}
void tls_close(struct tls_common_ctx *ctx)
{
if (ctx == NULL || ctx->tls_session == NULL || kr_fails_assert(ctx->session))
return;
if (ctx->handshake_state == TLS_HS_DONE) {
const struct sockaddr *peer = session_get_peer(ctx->session);
VERBOSE_MSG(ctx->client_side, "closing tls connection to `%s`\n",
kr_straddr(peer));
ctx->handshake_state = TLS_HS_CLOSING;
gnutls_bye(ctx->tls_session, GNUTLS_SHUT_RDWR);
}
}
void tls_client_close(struct tls_client_ctx *ctx)
{
/* Store the current session data for potential resumption of this session */
if (ctx->params) {
gnutls_free(ctx->params->session_data.data);
ctx->params->session_data.data = NULL;
ctx->params->session_data.size = 0;
gnutls_session_get_data2(ctx->c.tls_session, &ctx->params->session_data);
}
tls_close(&ctx->c);
}
void tls_free(struct tls_ctx *tls)
{
if (!tls) {
return;
}
if (tls->c.tls_session) {
/* Don't terminate TLS connection, just tear it down */
gnutls_deinit(tls->c.tls_session);
tls->c.tls_session = NULL;
}
tls_credentials_release(tls->credentials);
free(tls);
}
int tls_write(uv_handle_t *handle, knot_pkt_t *pkt)
{
if (!pkt || !handle || !handle->data) {
return kr_error(EINVAL);
}
struct session *s = handle->data;
struct tls_common_ctx *tls_ctx = session_tls_get_common_ctx(s);
if (kr_fails_assert(tls_ctx && session_flags(s)->outgoing == tls_ctx->client_side))
return kr_error(EINVAL);
const uint16_t pkt_size = htons(pkt->size);
gnutls_session_t tls_session = tls_ctx->tls_session;
gnutls_record_cork(tls_session);
ssize_t count = 0;
if ((count = gnutls_record_send(tls_session, &pkt_size, sizeof(pkt_size)) < 0) ||
(count = gnutls_record_send(tls_session, pkt->wire, pkt->size) < 0)) {
VERBOSE_MSG(tls_ctx->client_side, "gnutls_record_send failed: %s (%zd)\n",
gnutls_strerror_name(count), count);
return kr_error(EIO);
}
const ssize_t submitted = sizeof(pkt_size) + pkt->size;
int ret = gnutls_record_uncork(tls_session, GNUTLS_RECORD_WAIT);
if (ret < 0) {
if (!gnutls_error_is_fatal(ret)) {
return kr_error(EAGAIN);
} else {
VERBOSE_MSG(tls_ctx->client_side, "gnutls_record_uncork failed: %s (%d)\n",
gnutls_strerror_name(ret), ret);
return kr_error(EIO);
}
}
if (ret != submitted) {
kr_log_error(TLS, "gnutls_record_uncork didn't send all data (%d of %zd)\n", ret, submitted);
return kr_error(EIO);
}
/* The data is now accepted in gnutls internal buffers, the message can be treated as sent */
return kr_ok();
}
ssize_t tls_process_input_data(struct session *s, const uint8_t *buf, ssize_t nread)
{
struct tls_common_ctx *tls_p = session_tls_get_common_ctx(s);
if (!tls_p) {
return kr_error(ENOSYS);
}
if (kr_fails_assert(tls_p->session == s))
return kr_error(EINVAL);
const bool ok = tls_p->recv_buf == buf && nread <= sizeof(tls_p->recv_buf);
if (kr_fails_assert(ok)) /* don't risk overflowing the buffer if we have a mistake somewhere */
return kr_error(EINVAL);
tls_p->buf = buf;
tls_p->nread = nread >= 0 ? nread : 0;
tls_p->consumed = 0;
/* Ensure TLS handshake is performed before receiving data.
* See https://www.gnutls.org/manual/html_node/TLS-handshake.html */
while (tls_p->handshake_state <= TLS_HS_IN_PROGRESS) {
int err = tls_handshake(tls_p, tls_p->handshake_cb);
if (err == kr_error(EAGAIN)) {
return 0; /* Wait for more data */
} else if (err != kr_ok()) {
return err;
}
}
/* See https://gnutls.org/manual/html_node/Data-transfer-and-termination.html#Data-transfer-and-termination */
ssize_t submitted = 0;
uint8_t *wire_buf = session_wirebuf_get_free_start(s);
size_t wire_buf_size = session_wirebuf_get_free_size(s);
while (true) {
ssize_t count = gnutls_record_recv(tls_p->tls_session, wire_buf, wire_buf_size);
if (count == GNUTLS_E_AGAIN) {
if (tls_p->consumed == tls_p->nread) {
/* See https://www.gnutls.org/manual/html_node/Asynchronous-operation.html */
break; /* No more data available in this libuv buffer */
}
continue;
} else if (count == GNUTLS_E_INTERRUPTED) {
continue;
} else if (count == GNUTLS_E_REHANDSHAKE) {
/* See https://www.gnutls.org/manual/html_node/Re_002dauthentication.html */
struct sockaddr *peer = session_get_peer(s);
VERBOSE_MSG(tls_p->client_side, "TLS rehandshake with %s has started\n",
kr_straddr(peer));
tls_set_hs_state(tls_p, TLS_HS_IN_PROGRESS);
int err = kr_ok();
while (tls_p->handshake_state <= TLS_HS_IN_PROGRESS) {
err = tls_handshake(tls_p, tls_p->handshake_cb);
if (err == kr_error(EAGAIN)) {
break;
} else if (err != kr_ok()) {
return err;
}
}
if (err == kr_error(EAGAIN)) {
/* pull function is out of data */
break;
}
/* There are can be data available, check it. */
continue;
} else if (count < 0) {
VERBOSE_MSG(tls_p->client_side, "gnutls_record_recv failed: %s (%zd)\n",
gnutls_strerror_name(count), count);
return kr_error(EIO);
} else if (count == 0) {
break;
}
DEBUG_MSG("[%s] received %zd data\n", tls_p->client_side ? "tls_client" : "tls", count);
wire_buf += count;
wire_buf_size -= count;
submitted += count;
if (wire_buf_size == 0 && tls_p->consumed != tls_p->nread) {
/* session buffer is full
* whereas not all the data were consumed */
return kr_error(ENOSPC);
}
}
/* Here all data must be consumed. */
if (tls_p->consumed != tls_p->nread) {
/* Something went wrong, better return error.
* This is most probably due to gnutls_record_recv() did not
* consume all available network data by calling kres_gnutls_pull().
* TODO assess the need for buffering of data amount.
*/
return kr_error(ENOSPC);
}
return submitted;
}
#if TLS_CAN_USE_PINS
/*
DNS-over-TLS Out of band key-pinned authentication profile uses the
same form of pins as HPKP:
e.g. pin-sha256="FHkyLhvI0n70E47cJlRTamTrnYVcsYdjUGbr79CfAVI="
DNS-over-TLS OOB key-pins: https://tools.ietf.org/html/rfc7858#appendix-A
HPKP pin reference: https://tools.ietf.org/html/rfc7469#appendix-A
*/
#define PINLEN ((((32) * 8 + 4)/6) + 3 + 1)
/* Compute pin_sha256 for the certificate.
* It may be in raw format - just TLS_SHA256_RAW_LEN bytes without termination,
* or it may be a base64 0-terminated string requiring up to
* TLS_SHA256_BASE64_BUFLEN bytes.
* \return error code */
static int get_oob_key_pin(gnutls_x509_crt_t crt, char *outchar, ssize_t outchar_len, bool raw)
{
/* TODO: simplify this function by using gnutls_x509_crt_get_key_id() */
if (kr_fails_assert(!raw || outchar_len >= TLS_SHA256_RAW_LEN)) {
return kr_error(ENOSPC);
/* With !raw we have check inside kr_base64_encode. */
}
gnutls_pubkey_t key;
int err = gnutls_pubkey_init(&key);
if (err != GNUTLS_E_SUCCESS) return err;
gnutls_datum_t datum = { .data = NULL, .size = 0 };
err = gnutls_pubkey_import_x509(key, crt, 0);
if (err != GNUTLS_E_SUCCESS) goto leave;
err = gnutls_pubkey_export2(key, GNUTLS_X509_FMT_DER, &datum);
if (err != GNUTLS_E_SUCCESS) goto leave;
char raw_pin[TLS_SHA256_RAW_LEN]; /* TMP buffer if raw == false */
err = gnutls_hash_fast(GNUTLS_DIG_SHA256, datum.data, datum.size,
(raw ? outchar : raw_pin));
if (err != GNUTLS_E_SUCCESS || raw/*success*/)
goto leave;
/* Convert to non-raw. */
err = kr_base64_encode((uint8_t *)raw_pin, sizeof(raw_pin),
(uint8_t *)outchar, outchar_len);
if (err >= 0 && err < outchar_len) {
err = GNUTLS_E_SUCCESS;
outchar[err] = '\0'; /* kr_base64_encode() doesn't do it */
} else if (kr_fails_assert(err < 0)) {
err = kr_error(ENOSPC); /* base64 fits but '\0' doesn't */
outchar[outchar_len - 1] = '\0';
}
leave:
gnutls_free(datum.data);
gnutls_pubkey_deinit(key);
return err;
}
void tls_credentials_log_pins(struct tls_credentials *tls_credentials)
{
for (int index = 0;; index++) {
gnutls_x509_crt_t *certs = NULL;
unsigned int cert_count = 0;
int err = gnutls_certificate_get_x509_crt(tls_credentials->credentials,
index, &certs, &cert_count);
if (err != GNUTLS_E_SUCCESS) {
if (err != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
kr_log_error(TLS, "could not get X.509 certificates (%d) %s\n",
err, gnutls_strerror_name(err));
}
return;
}
for (int i = 0; i < cert_count; i++) {
char pin[TLS_SHA256_BASE64_BUFLEN] = { 0 };
err = get_oob_key_pin(certs[i], pin, sizeof(pin), false);
if (err != GNUTLS_E_SUCCESS) {
kr_log_error(TLS, "could not calculate RFC 7858 OOB key-pin from cert %d (%d) %s\n",
i, err, gnutls_strerror_name(err));
} else {
kr_log_info(TLS, "RFC 7858 OOB key-pin (%d): pin-sha256=\"%s\"\n",
i, pin);
}
gnutls_x509_crt_deinit(certs[i]);
}
gnutls_free(certs);
}
}
#else
void tls_credentials_log_pins(struct tls_credentials *tls_credentials)
{
kr_log_debug(TLS, "could not calculate RFC 7858 OOB key-pin; GnuTLS 3.4.0+ required\n");
}
#endif
static int str_replace(char **where_ptr, const char *with)
{
char *copy = with ? strdup(with) : NULL;
if (with && !copy) {
return kr_error(ENOMEM);
}
free(*where_ptr);
*where_ptr = copy;
return kr_ok();
}
static time_t get_end_entity_expiration(gnutls_certificate_credentials_t creds)
{
gnutls_datum_t data;
gnutls_x509_crt_t cert = NULL;
int err;
time_t ret = GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION;
if ((err = gnutls_certificate_get_crt_raw(creds, 0, 0, &data)) != GNUTLS_E_SUCCESS) {
kr_log_error(TLS, "failed to get cert to check expiration: (%d) %s\n",
err, gnutls_strerror_name(err));
goto done;
}
if ((err = gnutls_x509_crt_init(&cert)) != GNUTLS_E_SUCCESS) {
kr_log_error(TLS, "failed to initialize cert: (%d) %s\n",
err, gnutls_strerror_name(err));
goto done;
}
if ((err = gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_DER)) != GNUTLS_E_SUCCESS) {
kr_log_error(TLS, "failed to construct cert while checking expiration: (%d) %s\n",
err, gnutls_strerror_name(err));
goto done;
}
ret = gnutls_x509_crt_get_expiration_time (cert);
done:
/* do not free data; g_c_get_crt_raw() says to treat it as
* constant. */
gnutls_x509_crt_deinit(cert);
return ret;
}
int tls_certificate_set(struct network *net, const char *tls_cert, const char *tls_key)
{
if (!net) {
return kr_error(EINVAL);
}
struct tls_credentials *tls_credentials = calloc(1, sizeof(*tls_credentials));
if (tls_credentials == NULL) {
return kr_error(ENOMEM);
}
int err = 0;
if ((err = gnutls_certificate_allocate_credentials(&tls_credentials->credentials)) != GNUTLS_E_SUCCESS) {
kr_log_error(TLS, "gnutls_certificate_allocate_credentials() failed: (%d) %s\n",
err, gnutls_strerror_name(err));
tls_credentials_free(tls_credentials);
return kr_error(ENOMEM);
}
if ((err = gnutls_certificate_set_x509_system_trust(tls_credentials->credentials)) < 0) {
if (err != GNUTLS_E_UNIMPLEMENTED_FEATURE) {
kr_log_warning(TLS, "warning: gnutls_certificate_set_x509_system_trust() failed: (%d) %s\n",
err, gnutls_strerror_name(err));
tls_credentials_free(tls_credentials);
return err;
}
}
if ((str_replace(&tls_credentials->tls_cert, tls_cert) != 0) ||
(str_replace(&tls_credentials->tls_key, tls_key) != 0)) {
tls_credentials_free(tls_credentials);
return kr_error(ENOMEM);
}
if ((err = gnutls_certificate_set_x509_key_file(tls_credentials->credentials,
tls_cert, tls_key, GNUTLS_X509_FMT_PEM)) != GNUTLS_E_SUCCESS) {
tls_credentials_free(tls_credentials);
kr_log_error(TLS, "gnutls_certificate_set_x509_key_file(%s,%s) failed: %d (%s)\n",
tls_cert, tls_key, err, gnutls_strerror_name(err));
return kr_error(EINVAL);
}
/* record the expiration date: */
tls_credentials->valid_until = get_end_entity_expiration(tls_credentials->credentials);
/* Exchange the x509 credentials */
struct tls_credentials *old_credentials = net->tls_credentials;
/* Start using the new x509_credentials */
net->tls_credentials = tls_credentials;
tls_credentials_log_pins(net->tls_credentials);
if (old_credentials) {
err = tls_credentials_release(old_credentials);
if (err != kr_error(EBUSY)) {
return err;
}
}
return kr_ok();
}
struct tls_credentials *tls_credentials_reserve(struct tls_credentials *tls_credentials) {
if (!tls_credentials) {
return NULL;
}
tls_credentials->count++;
return tls_credentials;
}
int tls_credentials_release(struct tls_credentials *tls_credentials) {
if (!tls_credentials) {
return kr_error(EINVAL);
}
if (--tls_credentials->count < 0) {
tls_credentials_free(tls_credentials);
} else {
return kr_error(EBUSY);
}
return kr_ok();
}
void tls_credentials_free(struct tls_credentials *tls_credentials) {
if (!tls_credentials) {
return;
}
if (tls_credentials->credentials) {
gnutls_certificate_free_credentials(tls_credentials->credentials);
}
if (tls_credentials->tls_cert) {
free(tls_credentials->tls_cert);
}
if (tls_credentials->tls_key) {
free(tls_credentials->tls_key);
}
if (tls_credentials->ephemeral_servicename) {
free(tls_credentials->ephemeral_servicename);
}
free(tls_credentials);
}
void tls_client_param_unref(tls_client_param_t *entry)
{
if (!entry || kr_fails_assert(entry->refs)) return;
--(entry->refs);
if (entry->refs) return;
DEBUG_MSG("freeing TLS parameters %p\n", (void *)entry);
for (int i = 0; i < entry->ca_files.len; ++i) {
free_const(entry->ca_files.at[i]);
}
array_clear(entry->ca_files);
free_const(entry->hostname);
for (int i = 0; i < entry->pins.len; ++i) {
free_const(entry->pins.at[i]);
}
array_clear(entry->pins);
if (entry->credentials) {
gnutls_certificate_free_credentials(entry->credentials);
}
if (entry->session_data.data) {
gnutls_free(entry->session_data.data);
}
free(entry);
}
static int param_free(void **param, void *null)
{
if (kr_fails_assert(param && *param))
return -1;
tls_client_param_unref(*param);
return 0;
}
void tls_client_params_free(tls_client_params_t *params)
{
if (!params) return;
trie_apply(params, param_free, NULL);
trie_free(params);
}
tls_client_param_t * tls_client_param_new(void)
{
tls_client_param_t *e = calloc(1, sizeof(*e));
if (kr_fails_assert(e))
return NULL;
/* Note: those array_t don't need further initialization. */
e->refs = 1;
int ret = gnutls_certificate_allocate_credentials(&e->credentials);
if (ret != GNUTLS_E_SUCCESS) {
kr_log_error(TLSCLIENT, "error: gnutls_certificate_allocate_credentials() fails (%s)\n",
gnutls_strerror_name(ret));
free(e);
return NULL;
}
gnutls_certificate_set_verify_function(e->credentials, client_verify_certificate);
return e;
}
/**
* Convert an IP address and port number to binary key.
*
* \precond buffer \param key must have sufficient size
* \param addr[in]
* \param len[out] output length
* \param key[out] output buffer
*/
static bool construct_key(const union kr_sockaddr *addr, uint32_t *len, char *key)
{
switch (addr->ip.sa_family) {
case AF_INET:
memcpy(key, &addr->ip4.sin_port, sizeof(addr->ip4.sin_port));
memcpy(key + sizeof(addr->ip4.sin_port), &addr->ip4.sin_addr,
sizeof(addr->ip4.sin_addr));
*len = sizeof(addr->ip4.sin_port) + sizeof(addr->ip4.sin_addr);
return true;
case AF_INET6:
memcpy(key, &addr->ip6.sin6_port, sizeof(addr->ip6.sin6_port));
memcpy(key + sizeof(addr->ip6.sin6_port), &addr->ip6.sin6_addr,
sizeof(addr->ip6.sin6_addr));
*len = sizeof(addr->ip6.sin6_port) + sizeof(addr->ip6.sin6_addr);
return true;
default:
kr_assert(!EINVAL);
return false;
}
}
tls_client_param_t ** tls_client_param_getptr(tls_client_params_t **params,
const struct sockaddr *addr, bool do_insert)
{
if (kr_fails_assert(params && addr))
return NULL;
/* We accept NULL for empty map; ensure the map exists if needed. */
if (!*params) {
if (!do_insert) return NULL;
*params = trie_create(NULL);
if (kr_fails_assert(*params))
return NULL;
}
/* Construct the key. */
const union kr_sockaddr *ia = (const union kr_sockaddr *)addr;
char key[sizeof(ia->ip6.sin6_port) + sizeof(ia->ip6.sin6_addr)];
uint32_t len;
if (!construct_key(ia, &len, key))
return NULL;
/* Get the entry. */
return (tls_client_param_t **)
(do_insert ? trie_get_ins : trie_get_try)(*params, key, len);
}
int tls_client_param_remove(tls_client_params_t *params, const struct sockaddr *addr)
{
const union kr_sockaddr *ia = (const union kr_sockaddr *)addr;
char key[sizeof(ia->ip6.sin6_port) + sizeof(ia->ip6.sin6_addr)];
uint32_t len;
if (!construct_key(ia, &len, key))
return kr_error(EINVAL);
trie_val_t param_ptr;
int ret = trie_del(params, key, len, ¶m_ptr);
if (ret != KNOT_EOK)
return kr_error(ret);
tls_client_param_unref(param_ptr);
return kr_ok();
}
/**
* Verify that at least one certificate in the certificate chain matches
* at least one certificate pin in the non-empty params->pins array.
* \returns GNUTLS_E_SUCCESS if pin matches, any other value is an error
*/
static int client_verify_pin(const unsigned int cert_list_size,
const gnutls_datum_t *cert_list,
tls_client_param_t *params)
{
if (kr_fails_assert(params->pins.len > 0))
return GNUTLS_E_CERTIFICATE_ERROR;
#if TLS_CAN_USE_PINS
for (int i = 0; i < cert_list_size; i++) {
gnutls_x509_crt_t cert;
int ret = gnutls_x509_crt_init(&cert);
if (ret != GNUTLS_E_SUCCESS) {
return ret;
}
ret = gnutls_x509_crt_import(cert, &cert_list[i], GNUTLS_X509_FMT_DER);
if (ret != GNUTLS_E_SUCCESS) {
gnutls_x509_crt_deinit(cert);
return ret;
}
#ifdef DEBUG
if (kr_log_is_debug(TLS, NULL)) {
char pin_base64[TLS_SHA256_BASE64_BUFLEN];
/* DEBUG: additionally compute and print the base64 pin.
* Not very efficient, but that's OK for DEBUG. */
ret = get_oob_key_pin(cert, pin_base64, sizeof(pin_base64), false);
if (ret == GNUTLS_E_SUCCESS) {
DEBUG_MSG("[tls_client] received pin: %s\n", pin_base64);
} else {
DEBUG_MSG("[tls_client] failed to convert received pin\n");
/* Now we hope that `ret` below can't differ. */
}
}
#endif
char cert_pin[TLS_SHA256_RAW_LEN];
/* Get raw pin and compare. */
ret = get_oob_key_pin(cert, cert_pin, sizeof(cert_pin), true);
gnutls_x509_crt_deinit(cert);
if (ret != GNUTLS_E_SUCCESS) {
return ret;
}
for (size_t j = 0; j < params->pins.len; ++j) {
const uint8_t *pin = params->pins.at[j];
if (memcmp(cert_pin, pin, TLS_SHA256_RAW_LEN) != 0)
continue; /* mismatch */
DEBUG_MSG("[tls_client] matched a configured pin no. %zd\n", j);
return GNUTLS_E_SUCCESS;
}
DEBUG_MSG("[tls_client] none of %zd configured pin(s) matched\n",
params->pins.len);
}
kr_log_error(TLSCLIENT, "no pin matched: %zu pins * %d certificates\n",
params->pins.len, cert_list_size);
return GNUTLS_E_CERTIFICATE_ERROR;
#else /* TLS_CAN_USE_PINS */
kr_log_error(TLSCLIENT, "internal inconsistency: TLS_CAN_USE_PINS\n");
kr_assert(false);
return GNUTLS_E_CERTIFICATE_ERROR;
#endif
}
/**
* Verify that \param tls_session contains a valid X.509 certificate chain
* with given hostname.
*
* \returns GNUTLS_E_SUCCESS if certificate chain is valid, any other value is an error
*/
static int client_verify_certchain(struct tls_common_ctx *ctx, const char *hostname)
{
if (kr_fails_assert(hostname)) {
kr_log_error(TLSCLIENT, "internal config inconsistency: no hostname set\n");
return GNUTLS_E_CERTIFICATE_ERROR;
}
unsigned int status;
int ret = gnutls_certificate_verify_peers3(ctx->tls_session, hostname, &status);
if ((ret == GNUTLS_E_SUCCESS) && (status == 0)) {
return GNUTLS_E_SUCCESS;
}
const char *addr_str = kr_straddr(session_get_peer(ctx->session));
if (ret == GNUTLS_E_SUCCESS) {
gnutls_datum_t msg;
ret = gnutls_certificate_verification_status_print(
status, gnutls_certificate_type_get(ctx->tls_session), &msg, 0);
if (ret == GNUTLS_E_SUCCESS) {
kr_log_error(TLSCLIENT, "failed to verify peer certificate of %s: "
"%s\n", addr_str, msg.data);
gnutls_free(msg.data);
} else {
kr_log_error(TLSCLIENT, "failed to verify peer certificate of %s: "
"unable to print reason: %s (%s)\n",
addr_str,
gnutls_strerror(ret), gnutls_strerror_name(ret));
} /* gnutls_certificate_verification_status_print end */
} else {
kr_log_error(TLSCLIENT, "failed to verify peer certificate of %s: "
"gnutls_certificate_verify_peers3 error: %s (%s)\n",
addr_str,
gnutls_strerror(ret), gnutls_strerror_name(ret));
} /* gnutls_certificate_verify_peers3 end */
return GNUTLS_E_CERTIFICATE_ERROR;
}
/**
* Verify that actual TLS security parameters of \param tls_session
* match requirements provided by user in tls_session->params.
* \returns GNUTLS_E_SUCCESS if requirements were met, any other value is an error
*/
static int client_verify_certificate(gnutls_session_t tls_session)
{
struct tls_client_ctx *ctx = gnutls_session_get_ptr(tls_session);
if (kr_fails_assert(ctx->params))
return GNUTLS_E_CERTIFICATE_ERROR;
if (ctx->params->insecure) {
return GNUTLS_E_SUCCESS;
}
gnutls_certificate_type_t cert_type = gnutls_certificate_type_get(tls_session);
if (cert_type != GNUTLS_CRT_X509) {
kr_log_error(TLSCLIENT, "invalid certificate type %i has been received\n",
cert_type);
return GNUTLS_E_CERTIFICATE_ERROR;
}
unsigned int cert_list_size = 0;
const gnutls_datum_t *cert_list =
gnutls_certificate_get_peers(tls_session, &cert_list_size);
if (cert_list == NULL || cert_list_size == 0) {
kr_log_error(TLSCLIENT, "empty certificate list\n");
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (ctx->params->pins.len > 0)
/* check hash of the certificate but ignore everything else */
return client_verify_pin(cert_list_size, cert_list, ctx->params);
else
return client_verify_certchain(&ctx->c, ctx->params->hostname);
}
struct tls_client_ctx *tls_client_ctx_new(tls_client_param_t *entry,
struct worker_ctx *worker)
{
struct tls_client_ctx *ctx = calloc(1, sizeof (struct tls_client_ctx));
if (!ctx) {
return NULL;
}
unsigned int flags = GNUTLS_CLIENT | GNUTLS_NONBLOCK
#ifdef GNUTLS_ENABLE_FALSE_START
| GNUTLS_ENABLE_FALSE_START
#endif
;
#if GNUTLS_VERSION_NUMBER >= 0x030705
if (gnutls_check_version("3.7.5"))
flags |= GNUTLS_NO_TICKETS_TLS12;
#endif
int ret = gnutls_init(&ctx->c.tls_session, flags);
if (ret != GNUTLS_E_SUCCESS) {
tls_client_ctx_free(ctx);
return NULL;
}
ret = kres_gnutls_set_priority(ctx->c.tls_session);
if (ret != GNUTLS_E_SUCCESS) {
tls_client_ctx_free(ctx);
return NULL;
}
/* Must take a reference on parameters as the credentials are owned by it
* and must not be freed while the session is active. */
++(entry->refs);
ctx->params = entry;
ret = gnutls_credentials_set(ctx->c.tls_session, GNUTLS_CRD_CERTIFICATE,
entry->credentials);
if (ret == GNUTLS_E_SUCCESS && entry->hostname) {
ret = gnutls_server_name_set(ctx->c.tls_session, GNUTLS_NAME_DNS,
entry->hostname, strlen(entry->hostname));
kr_log_debug(TLSCLIENT, "set hostname, ret = %d\n", ret);
} else if (!entry->hostname) {
kr_log_debug(TLSCLIENT, "no hostname\n");
}
if (ret != GNUTLS_E_SUCCESS) {
tls_client_ctx_free(ctx);
return NULL;
}
ctx->c.worker = worker;
ctx->c.client_side = true;
gnutls_transport_set_pull_function(ctx->c.tls_session, kres_gnutls_pull);
gnutls_transport_set_vec_push_function(ctx->c.tls_session, kres_gnutls_vec_push);
gnutls_transport_set_ptr(ctx->c.tls_session, ctx);
return ctx;
}
void tls_client_ctx_free(struct tls_client_ctx *ctx)
{
if (ctx == NULL) {
return;
}
if (ctx->c.tls_session != NULL) {
gnutls_deinit(ctx->c.tls_session);
ctx->c.tls_session = NULL;
}
/* Must decrease the refcount for referenced parameters */
tls_client_param_unref(ctx->params);
free (ctx);
}
int tls_pull_timeout_func(gnutls_transport_ptr_t h, unsigned int ms)
{
struct tls_common_ctx *t = (struct tls_common_ctx *)h;
if (kr_fails_assert(t)) {
errno = EFAULT;
return -1;
}
ssize_t avail = t->nread - t->consumed;
DEBUG_MSG("[%s] timeout check: available: %zu\n",
t->client_side ? "tls_client" : "tls", avail);
if (avail <= 0) {
errno = EAGAIN;
return -1;
}
return avail;
}
int tls_client_connect_start(struct tls_client_ctx *client_ctx,
struct session *session,
tls_handshake_cb handshake_cb)
{
if (session == NULL || client_ctx == NULL)
return kr_error(EINVAL);
if (kr_fails_assert(session_flags(session)->outgoing && session_get_handle(session)->type == UV_TCP))
return kr_error(EINVAL);
struct tls_common_ctx *ctx = &client_ctx->c;
gnutls_session_set_ptr(ctx->tls_session, client_ctx);
gnutls_handshake_set_timeout(ctx->tls_session, ctx->worker->engine->net.tcp.tls_handshake_timeout);
gnutls_transport_set_pull_timeout_function(ctx->tls_session, tls_pull_timeout_func);
session_tls_set_client_ctx(session, client_ctx);
ctx->handshake_cb = handshake_cb;
ctx->handshake_state = TLS_HS_IN_PROGRESS;
ctx->session = session;
tls_client_param_t *tls_params = client_ctx->params;
if (tls_params->session_data.data != NULL) {
gnutls_session_set_data(ctx->tls_session, tls_params->session_data.data,
tls_params->session_data.size);
}
/* See https://www.gnutls.org/manual/html_node/Asynchronous-operation.html */
while (ctx->handshake_state <= TLS_HS_IN_PROGRESS) {
int ret = tls_handshake(ctx, handshake_cb);
if (ret != kr_ok()) {
return ret;
}
}
return kr_ok();
}
tls_hs_state_t tls_get_hs_state(const struct tls_common_ctx *ctx)
{
return ctx->handshake_state;
}
int tls_set_hs_state(struct tls_common_ctx *ctx, tls_hs_state_t state)
{
if (state >= TLS_HS_LAST) {
return kr_error(EINVAL);
}
ctx->handshake_state = state;
return kr_ok();
}
int tls_client_ctx_set_session(struct tls_client_ctx *ctx, struct session *session)
{
if (!ctx) {
return kr_error(EINVAL);
}
ctx->c.session = session;
return kr_ok();
}
#undef DEBUG_MSG
#undef VERBOSE_MSG
|