From patchwork Wed Oct 26 13:15:53 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Zaborowski X-Patchwork-Id: 13020598 Received: from mail-wr1-f48.google.com (mail-wr1-f48.google.com [209.85.221.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 324821374 for ; Wed, 26 Oct 2022 13:16:11 +0000 (UTC) Received: by mail-wr1-f48.google.com with SMTP id v1so26253371wrt.11 for ; Wed, 26 Oct 2022 06:16:11 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:message-id:date:subject:to :from:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=2vpkRb4tBAOLWpqjQSO7elJhPPYkoNXrzJ3CPJYah7I=; b=HL1SmlhE96Hex7sTMC3yCJYAGe+NZG8xvB46Xq3SnjkVq6o1ReDCp9yeOkvEggeN1N wDKGlEcNWu19Xs3vuth4eX6iHQpQ8P6A5CaGgOAyqKjCX6DoypMjsheVNa6HDlOPxMc8 lDlNUtCVlDiY8LFUkJvdFZ8GKC/vq+y/iG7Opu3nslZPtPdpsInTDDMkcMO4PRO7hlfl xrvSfxU+eyU0vI/kAP/ETkCeORx0GuDvLzA6RfDsF7X6J0Y2ytdeLY7kUUqZeaXq4IVO 2/WHqlIX4lH0DAObhEfgyIuo71Utsb6Zo+vI4Pm9XKAy5jGvOH39D6fpXNq+z/FogTwB 2mXQ== X-Gm-Message-State: ACrzQf115ZpLrOhTQup4RTIm6ajpOteg7xx6QLiGqcBIvhqF9dqXr05j aU6+Xllk7lQwD28qAnt0Dp/FMhYEiso= X-Google-Smtp-Source: AMsMyM7Db+4BFDAkI4+kjBWGtzBNqKlStmdQh6ghMAzS8DG7XhChubI87zDZmr9H46GPUeR80W0acw== X-Received: by 2002:a05:6000:1052:b0:236:6e8e:8403 with SMTP id c18-20020a056000105200b002366e8e8403mr11966019wrx.178.1666790168582; Wed, 26 Oct 2022 06:16:08 -0700 (PDT) Received: from localhost.localdomain ([82.213.230.158]) by smtp.gmail.com with ESMTPSA id x23-20020a05600c21d700b003a83ca67f73sm1934771wmj.3.2022.10.26.06.16.07 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 26 Oct 2022 06:16:07 -0700 (PDT) From: Andrew Zaborowski To: ell@lists.linux.dev Subject: [PATCH 1/6] time: Add time_realtime_now Date: Wed, 26 Oct 2022 15:15:53 +0200 Message-Id: <20221026131558.2393488-1-andrew.zaborowski@intel.com> X-Mailer: git-send-email 2.34.1 Precedence: bulk X-Mailing-List: ell@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 --- ell/time-private.h | 1 + ell/time.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/ell/time-private.h b/ell/time-private.h index 83c23dd..e107503 100644 --- a/ell/time-private.h +++ b/ell/time-private.h @@ -24,3 +24,4 @@ uint64_t _time_pick_interval_secs(uint32_t min_secs, uint32_t max_secs); uint64_t _time_fuzz_msecs(uint64_t ms); uint64_t _time_fuzz_secs(uint32_t secs, uint32_t max_offset); uint64_t _time_realtime_to_boottime(const struct timeval *ts); +uint64_t time_realtime_now(void); diff --git a/ell/time.c b/ell/time.c index 41e5725..2eac6c4 100644 --- a/ell/time.c +++ b/ell/time.c @@ -58,6 +58,14 @@ LIB_EXPORT uint64_t l_time_now(void) return _time_from_timespec(&now); } +uint64_t time_realtime_now(void) +{ + struct timespec now; + + clock_gettime(CLOCK_REALTIME, &now); + return _time_from_timespec(&now); +} + /** * l_time_after * From patchwork Wed Oct 26 13:15:54 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Zaborowski X-Patchwork-Id: 13020599 Received: from mail-wr1-f50.google.com (mail-wr1-f50.google.com [209.85.221.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E5F90138F for ; Wed, 26 Oct 2022 13:16:12 +0000 (UTC) Received: by mail-wr1-f50.google.com with SMTP id h9so15176656wrt.0 for ; Wed, 26 Oct 2022 06:16:12 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=eyEkCNuSlqIVDW8pW4hy04GzTqYKFDuK7d7Ve8z1OCc=; b=0mwlrqYn4k4WTETvGEM5Hdd2SVwQiOvaOMukr/OW53k6sL6Xia3uTo04y3depjaT/6 JDnAAWjIcBx/ayzMOIhiJGKjKs/dSM/bmerEi9nx2mW9bEOUZS1k7TRCPReOxj51IxGi qNm0rMMb4/LoLTmCKMR/xp3Egx2DFJiZj2au1RGCsRjrjB3TAa/Dr3oOZBQAl6mJakq1 BlsWrMbWWa8tqOwDMWAg6KQENwbihm/4agImQJDGxfl6dJa1A+plrfN+7+6PVBB8aCzO y3DgovVUDRq763Khr6FORBtUu/pUxA4E699bZcHonoXRm8f+EWwfpmpIQleRoW7sqnvR JHRg== X-Gm-Message-State: ACrzQf0tOflkEHDGopSTtv6AEFAOcTFyu7CmnBTq+QSzAN8mXYzVNRED pbZzw310eETfjtVyc7N7MOCI+GZiVwA= X-Google-Smtp-Source: AMsMyM78/l6XmjyUzFsBbLCGWNpuRgs+jsSHxk5WiMNg5CoSepn7YdfXhyLlIKD0hu8SxW0WjAoo0A== X-Received: by 2002:a5d:4f12:0:b0:22e:3920:a09c with SMTP id c18-20020a5d4f12000000b0022e3920a09cmr28124523wru.95.1666790170411; Wed, 26 Oct 2022 06:16:10 -0700 (PDT) Received: from localhost.localdomain ([82.213.230.158]) by smtp.gmail.com with ESMTPSA id x23-20020a05600c21d700b003a83ca67f73sm1934771wmj.3.2022.10.26.06.16.08 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 26 Oct 2022 06:16:09 -0700 (PDT) From: Andrew Zaborowski To: ell@lists.linux.dev Subject: [PATCH 2/6] cert: Add l_cert_get_valid_times Date: Wed, 26 Oct 2022 15:15:54 +0200 Message-Id: <20221026131558.2393488-2-andrew.zaborowski@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221026131558.2393488-1-andrew.zaborowski@intel.com> References: <20221026131558.2393488-1-andrew.zaborowski@intel.com> Precedence: bulk X-Mailing-List: ell@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Add utility to extract the Not Before and Not After timestamps from certificates. --- ell/asn1-private.h | 2 + ell/cert.c | 185 +++++++++++++++++++++++++++++++++++++++++++++ ell/cert.h | 2 + ell/ell.sym | 1 + 4 files changed, 190 insertions(+) diff --git a/ell/asn1-private.h b/ell/asn1-private.h index d794515..36d8de3 100644 --- a/ell/asn1-private.h +++ b/ell/asn1-private.h @@ -34,6 +34,8 @@ #define ASN1_ID_UTF8STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x0c) #define ASN1_ID_PRINTABLESTRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x13) #define ASN1_ID_IA5STRING ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x16) +#define ASN1_ID_UTCTIME ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x17) +#define ASN1_ID_GENERALIZEDTIME ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x18) struct asn1_oid { uint8_t asn1_len; diff --git a/ell/cert.c b/ell/cert.c index a158142..b4f5df7 100644 --- a/ell/cert.c +++ b/ell/cert.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "private.h" #include "useful.h" @@ -33,6 +34,8 @@ #include "asn1-private.h" #include "cipher.h" #include "pem-private.h" +#include "time.h" +#include "utf8.h" #include "cert.h" #include "cert-private.h" #include "tls.h" @@ -186,6 +189,188 @@ LIB_EXPORT const uint8_t *l_cert_get_dn(struct l_cert *cert, size_t *out_len) -1); } +static uint64_t cert_parse_asn1_time(const uint8_t *data, size_t len, + uint8_t tag) +{ + struct tm tm = {}; + int tz_hours; + int tz_mins; + int century; + int msecs = 0; + time_t tt; + unsigned int i; + + for (i = 0; i < len && i < 15; i++) + if (unlikely(!l_ascii_isdigit(data[i]))) + break; + + if (tag == ASN1_ID_UTCTIME) { + if (unlikely(!L_IN_SET(i, 10, 12))) + return L_TIME_INVALID; + + century = 19; + } else if (tag == ASN1_ID_GENERALIZEDTIME) { + if (unlikely(!L_IN_SET(i, 10, 12, 14))) + return L_TIME_INVALID; + + century = (data[0] - '0') * 10 + (data[1] - '0'); + if (century < 19) + return L_TIME_INVALID; + + if (len >= i + 4 && data[i] == '.') { + if (unlikely(!l_ascii_isdigit(data[i + 1]) || + !l_ascii_isdigit(data[i + 2]) || + !l_ascii_isdigit(data[i + 3]))) + return L_TIME_INVALID; + + i++; + msecs += (data[i++] - '0') * 100; + msecs += (data[i++] - '0') * 10; + msecs += (data[i++] - '0'); + } + + data += 2; + len -= 2; + i -= 2; + } else + return L_TIME_INVALID; + + if (unlikely((len != i + 1 || data[i] != 'Z') && + (len != i + 5 || data[i] != '+' || data[i] != '-'))) + return L_TIME_INVALID; + + tm.tm_year = (data[0] - '0') * 10 + (data[1] - '0'); + tm.tm_mon = (data[2] - '0') * 10 + (data[3] - '0'); + tm.tm_mday = (data[4] - '0') * 10 + (data[5] - '0'); + tm.tm_hour = (data[6] - '0') * 10 + (data[7] - '0'); + + if (unlikely(tm.tm_mon < 1 || tm.tm_mon > 12 || + tm.tm_mday < 1 || tm.tm_mday > 31 || + tm.tm_hour > 23)) + return L_TIME_INVALID; + + if (i >= 10) { + tm.tm_min = (data[8] - '0') * 10 + (data[9] - '0'); + if (unlikely(tm.tm_min > 59)) + return L_TIME_INVALID; + } + + if (i >= 12) { + tm.tm_sec = (data[10] - '0') * 10 + (data[11] - '0'); + if (unlikely(tm.tm_sec > 59)) + return L_TIME_INVALID; + } + + /* RFC5280 Section 4.1.2.5.1 */ + if (tag == ASN1_ID_UTCTIME && tm.tm_year < 50) + century = 20; + + tm.tm_year += (century - 19) * 100; + + /* Month number is 1-based in UTCTime and 0-based in struct tm */ + tm.tm_mon -= 1; + + tt = timegm(&tm); + if (unlikely(tt == (time_t) -1)) + return L_TIME_INVALID; + + if (len == i + 5) { + data += i; + + for (i = 1; i < 5; i++) + if (unlikely(!l_ascii_isdigit(data[i]))) + return L_TIME_INVALID; + + tz_hours = (data[1] - '0') * 10 + (data[2] - '0'); + tz_mins = (data[3] - '0') * 10 + (data[4] - '0'); + + if (unlikely(tz_hours > 14 || tz_mins > 59)) + return L_TIME_INVALID; + + /* The sign converts UTC to local so invert it */ + if (data[0] == '+') + tt -= tz_hours * 3600 + tz_mins * 60; + else + tt += tz_hours * 3600 + tz_mins * 60; + } + + return (uint64_t) tt * L_USEC_PER_SEC + msecs * L_USEC_PER_MSEC; +} + +LIB_EXPORT bool l_cert_get_valid_times(struct l_cert *cert, + uint64_t *out_not_before_time, + uint64_t *out_not_after_time) +{ + const uint8_t *validity; + const uint8_t *not_before; + const uint8_t *not_after; + size_t seq_size; + size_t not_before_size; + size_t not_after_size; + uint8_t not_before_tag; + uint8_t not_after_tag; + uint64_t not_before_time = 0; + uint64_t not_after_time = 0; + + if (unlikely(!cert)) + return false; + + validity = asn1_der_find_elem_by_path(cert->asn1, cert->asn1_len, + ASN1_ID_SEQUENCE, &seq_size, + X509_CERTIFICATE_POS, + X509_TBSCERTIFICATE_POS, + X509_TBSCERT_VALIDITY_POS, + -1); + if (unlikely(!validity)) + return false; + + not_before = asn1_der_find_elem(validity, seq_size, 0, ¬_before_tag, + ¬_before_size); + if (!not_before) + return false; + + seq_size -= not_before_size + (not_before - validity); + validity = not_before + not_before_size; + not_after = asn1_der_find_elem(validity, seq_size, 0, ¬_after_tag, + ¬_after_size); + if (!not_after) + return false; + + if (out_not_before_time) { + not_before_time = cert_parse_asn1_time(not_before, + not_before_size, + not_before_tag); + if (not_before_time == L_TIME_INVALID) + return false; + } + + if (out_not_after_time) { + /* + * RFC5280 Section 4.1.2.5: "To indicate that a certificate + * has no well-defined expiration date, the notAfter SHOULD + * be assigned the GeneralizedTime value of 99991231235959Z." + */ + if (not_after_size == 15 && + !memcmp(not_after, "99991231235959Z", 15)) + not_after_time = 0; + else { + not_after_time = cert_parse_asn1_time(not_after, + not_after_size, + not_after_tag); + if (not_after_time == L_TIME_INVALID) + return false; + } + } + + if (out_not_before_time) + *out_not_before_time = not_before_time; + + if (out_not_after_time) + *out_not_after_time = not_after_time; + + return true; +} + const uint8_t *cert_get_extension(struct l_cert *cert, const struct asn1_oid *ext_id, bool *out_critical, size_t *out_len) diff --git a/ell/cert.h b/ell/cert.h index f637588..db116e0 100644 --- a/ell/cert.h +++ b/ell/cert.h @@ -48,6 +48,8 @@ DEFINE_CLEANUP_FUNC(l_cert_free); const uint8_t *l_cert_get_der_data(struct l_cert *cert, size_t *out_len); const uint8_t *l_cert_get_dn(struct l_cert *cert, size_t *out_len); +bool l_cert_get_valid_times(struct l_cert *cert, uint64_t *out_not_before_time, + uint64_t *out_not_after_time); enum l_cert_key_type l_cert_get_pubkey_type(struct l_cert *cert); struct l_key *l_cert_get_pubkey(struct l_cert *cert); diff --git a/ell/ell.sym b/ell/ell.sym index 6df9024..5fe8d6e 100644 --- a/ell/ell.sym +++ b/ell/ell.sym @@ -553,6 +553,7 @@ global: l_cert_free; l_cert_get_der_data; l_cert_get_dn; + l_cert_get_valid_times; l_cert_get_pubkey_type; l_cert_get_pubkey; l_certchain_free; From patchwork Wed Oct 26 13:15:55 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Zaborowski X-Patchwork-Id: 13020600 Received: from mail-wr1-f54.google.com (mail-wr1-f54.google.com [209.85.221.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5E4F71374 for ; Wed, 26 Oct 2022 13:16:14 +0000 (UTC) Received: by mail-wr1-f54.google.com with SMTP id v1so26253625wrt.11 for ; Wed, 26 Oct 2022 06:16:14 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=jqO+uUyC/hSlQWB1J+6q6J4rMTCbxcK8qLrE/OAWUj4=; b=mw58TLEgamQTLO4bDqqim0nH8/5/ZfA7k/M2Q23wGt51SQsrP3QqwWiPLENnvhvY2T ved6kfA2zcKvscemSFJmFVMX8osPI5QxXOnCMzbNLz0V6bASe8lnYl4jRLAX7kWpKhd6 eerzvnytLrmUkYxfZrzTDTJZfxk8jBRYflwnON/kA6xYs3tMGRMDMXcipDPjTAnBZOgb edo5dASlmOKIRvMi9rwSmQ7UTBoESMakodZDToMCTu1q9emrXqPNykc+LwL0temnKj6g cfsHExd29vco18yX3RH4YM/zGiMS/3Ad54y53lulHxDVd11jSCKQF0YcTP4bK0iFMgW0 vXiA== X-Gm-Message-State: ACrzQf0024IpJzMEvW4avlnfzgdQdXRQxz4GLCWuQ2pNbSclWf51GWEd +5dAunr2Pq0flI/TEWpJ9gWeyfLx6M4= X-Google-Smtp-Source: AMsMyM4iDRhNoOHzfsAr42PAQoMrdm8dg86/YHrsgyORsJ200wZD33CgahzFmKJ3+ucOTAwGcDDCbg== X-Received: by 2002:adf:e711:0:b0:236:2f7f:4f08 with SMTP id c17-20020adfe711000000b002362f7f4f08mr22392343wrm.375.1666790172014; Wed, 26 Oct 2022 06:16:12 -0700 (PDT) Received: from localhost.localdomain ([82.213.230.158]) by smtp.gmail.com with ESMTPSA id x23-20020a05600c21d700b003a83ca67f73sm1934771wmj.3.2022.10.26.06.16.10 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 26 Oct 2022 06:16:10 -0700 (PDT) From: Andrew Zaborowski To: ell@lists.linux.dev Subject: [PATCH 3/6] tls: Fix an RFC reference Date: Wed, 26 Oct 2022 15:15:55 +0200 Message-Id: <20221026131558.2393488-3-andrew.zaborowski@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221026131558.2393488-1-andrew.zaborowski@intel.com> References: <20221026131558.2393488-1-andrew.zaborowski@intel.com> Precedence: bulk X-Mailing-List: ell@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 --- ell/tls-extensions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ell/tls-extensions.c b/ell/tls-extensions.c index 6154df0..7796825 100644 --- a/ell/tls-extensions.c +++ b/ell/tls-extensions.c @@ -785,7 +785,7 @@ ssize_t tls_parse_signature_algorithms(struct l_tls *tls, return ptr + len - buf; } -/* RFC 5462, Section 7.4.1.4.1 */ +/* RFC 5246, Section 7.4.1.4.1 */ static ssize_t tls_signature_algorithms_client_write(struct l_tls *tls, uint8_t *buf, size_t len) { From patchwork Wed Oct 26 13:15:56 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Zaborowski X-Patchwork-Id: 13020601 Received: from mail-wr1-f44.google.com (mail-wr1-f44.google.com [209.85.221.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3CB041374 for ; Wed, 26 Oct 2022 13:16:16 +0000 (UTC) Received: by mail-wr1-f44.google.com with SMTP id o4so18052395wrq.6 for ; Wed, 26 Oct 2022 06:16:16 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=u/c0c4puocy1FszDQqGmN35WqiiO/iDH8JLJVap6IN0=; b=rZQb/Se9RMJiE1nXxaFKpZI5ZEwyKnb+4VSAn/fQPeitXSduHpJD2l9sN0qh8s/8sI SqckkaIyQ3UOI6+QwgkYm5jE8tbXb5Tu6i2SJHHztMgT5bmDTTY+0AUMEQGayzMAQjHL BbBKevY5POvmWwCXdaP4Ps8RSLne3Eo7YbGQZKozamAw1vyf6y8oyPr5A5Ohpg8tygsg in8zrJvCubJJphMd78hGaUv1Ubkla2L4C/uR+s2aH10VmJnvFxPPWqjySqN4LlsD9paz XWLOdB1rmhHvoB13q8DuxXxItPwQ2ki/ziAn4rpV5q88mfbBreM0Z8WTXSjtnExxs3tB dslw== X-Gm-Message-State: ACrzQf06Y5Pcv3tFngoqaYH3c7PAof+wCfqYRJzIPEuoFPQhF8to8RcM X+P+Qyw1BVLqaiCaVpNIUYy0iMVgjLE= X-Google-Smtp-Source: AMsMyM6annlkG3ChMqahIPR+XEb0IsitsKRHoe6+fhNAkNL0zLmaWf3iCoS90ieqTV3Zw6/UN1p6yw== X-Received: by 2002:a05:6000:1a85:b0:230:f67b:da03 with SMTP id f5-20020a0560001a8500b00230f67bda03mr30227433wry.296.1666790173708; Wed, 26 Oct 2022 06:16:13 -0700 (PDT) Received: from localhost.localdomain ([82.213.230.158]) by smtp.gmail.com with ESMTPSA id x23-20020a05600c21d700b003a83ca67f73sm1934771wmj.3.2022.10.26.06.16.12 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 26 Oct 2022 06:16:12 -0700 (PDT) From: Andrew Zaborowski To: ell@lists.linux.dev Subject: [PATCH 4/6] tls: Add support for caching client session states Date: Wed, 26 Oct 2022 15:15:56 +0200 Message-Id: <20221026131558.2393488-4-andrew.zaborowski@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221026131558.2393488-1-andrew.zaborowski@intel.com> References: <20221026131558.2393488-1-andrew.zaborowski@intel.com> Precedence: bulk X-Mailing-List: ell@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 If a session cache is configured using l_tls_set_session_cache(), save session states to a set of 7 key/value pairs in a given l_settings object, in a given group. This only implements the client side as the semantics on the server side will be different. --- ell/ell.sym | 1 + ell/tls-private.h | 10 +++ ell/tls.c | 192 +++++++++++++++++++++++++++++++++++++++++++++- ell/tls.h | 7 ++ 4 files changed, 208 insertions(+), 2 deletions(-) diff --git a/ell/ell.sym b/ell/ell.sym index 5fe8d6e..6c836e1 100644 --- a/ell/ell.sym +++ b/ell/ell.sym @@ -518,6 +518,7 @@ global: l_tls_set_auth_data; l_tls_set_version_range; l_tls_set_domain_mask; + l_tls_set_session_cache; l_tls_alert_to_str; l_tls_set_debug; l_tls_set_cert_dump_path; diff --git a/ell/tls-private.h b/ell/tls-private.h index 8941e90..6f09f6a 100644 --- a/ell/tls-private.h +++ b/ell/tls-private.h @@ -218,6 +218,12 @@ struct l_tls { struct tls_cipher_suite **cipher_suite_pref_list; + struct l_settings *session_settings; + char *session_group; + uint64_t session_lifetime; + l_tls_session_update_cb_t session_update_cb; + void *session_update_user_data; + bool in_callback; bool pending_destroy; @@ -251,6 +257,10 @@ struct l_tls { const struct tls_named_group *negotiated_curve; const struct tls_named_group *negotiated_ff_group; + uint8_t session_id[32]; + size_t session_id_size; + bool session_id_new; + /* SecurityParameters current and pending */ struct { diff --git a/ell/tls.c b/ell/tls.c index fa3df5d..ff4fa5b 100644 --- a/ell/tls.c +++ b/ell/tls.c @@ -46,6 +46,9 @@ #include "strv.h" #include "missing.h" #include "string.h" +#include "settings.h" +#include "time.h" +#include "time-private.h" bool tls10_prf(const void *secret, size_t secret_len, const char *label, @@ -204,6 +207,9 @@ static void tls_reset_handshake(struct l_tls *tls) TLS_SET_STATE(TLS_HANDSHAKE_WAIT_START); tls->cert_requested = 0; tls->cert_sent = 0; + + tls->session_id_size = 0; + tls->session_id_new = false; } static void tls_cleanup_handshake(struct l_tls *tls) @@ -820,6 +826,31 @@ parse_error: return false; } +static void tls_forget_cached_client_session(struct l_tls *tls) +{ + /* Note: might want to l_settings_remove_group() instead. */ + l_settings_remove_key(tls->session_settings, tls->session_group, + "TLSSessionID"); + l_settings_remove_key(tls->session_settings, tls->session_group, + "TLSSessionMasterSecret"); + l_settings_remove_key(tls->session_settings, tls->session_group, + "TLSSessionVersion"); + l_settings_remove_key(tls->session_settings, tls->session_group, + "TLSSessionCipherSuite"); + l_settings_remove_key(tls->session_settings, tls->session_group, + "TLSSessionCompressionMethod"); + l_settings_remove_key(tls->session_settings, tls->session_group, + "TLSSessionExpiryTime"); + l_settings_remove_key(tls->session_settings, tls->session_group, + "TLSSessionPeerIdentity"); + + if (tls->session_update_cb) { + tls->in_callback = true; + tls->session_update_cb(tls->session_update_user_data); + tls->in_callback = false; + } +} + #define SWITCH_ENUM_TO_STR(val) \ case (val): \ return L_STRINGIFY(val); @@ -868,6 +899,28 @@ static void tls_send_alert(struct l_tls *tls, bool fatal, void tls_disconnect(struct l_tls *tls, enum l_tls_alert_desc desc, enum l_tls_alert_desc local_desc) { + bool forget_session = false; + + if (!tls->server && (desc || local_desc) && + tls->session_settings && tls->session_id_size && + !tls->session_id_new) + /* + * RFC5246 Section 7.2: "Alert messages with a level of fatal + * result in the immediate termination of the connection. In + * this case, other connections corresponding to the session + * may continue, but the session identifier MUST be + * invalidated, preventing the failed session from being used + * to establish new connections." + * + * and 7.2.1: "Note that as of TLS 1.1, failure to properly + * close a connection no longer requires that a session not + * be resumed." + * + * I.e. we need to remove the session from the cache here but + * not on l_tls_close(). + */ + forget_session = true; + tls_send_alert(tls, true, desc); tls_reset_handshake(tls); @@ -879,6 +932,13 @@ void tls_disconnect(struct l_tls *tls, enum l_tls_alert_desc desc, tls->negotiated_version = 0; tls->ready = false; + if (forget_session) { + tls_forget_cached_client_session(tls); + + if (tls->pending_destroy) + return; + } + tls->disconnected(local_desc ?: desc, local_desc && !desc, tls->user_data); } @@ -1814,6 +1874,15 @@ static void tls_handle_server_hello(struct l_tls *tls, compression_method_id = buf[35 + session_id_size + 2]; len -= session_id_size + 2 + 1; + if (session_id_size > 32) + goto decode_error; + + if (session_id_size && tls->session_settings) { + tls->session_id_new = true; + tls->session_id_size = session_id_size; + memcpy(tls->session_id, buf + 35, session_id_size); + } + extensions_seen = l_queue_new(); result = tls_handle_hello_extensions(tls, buf + 38 + session_id_size, len, extensions_seen); @@ -2350,7 +2419,9 @@ error: static void tls_finished(struct l_tls *tls) { - char *peer_identity = NULL; + _auto_(l_free) char *peer_identity = NULL; + uint64_t peer_cert_expiry; + bool session_update = false; if (tls->peer_authenticated) { peer_identity = tls_get_peer_identity_str(tls->peer_cert); @@ -2359,6 +2430,65 @@ static void tls_finished(struct l_tls *tls) "tls_get_peer_identity_str failed"); return; } + + if (tls->session_id_new && + !l_cert_get_valid_times(tls->peer_cert, NULL, + &peer_cert_expiry)) { + TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, + "l_cert_get_valid_times failed"); + return; + } + } + + if (!tls->server && tls->session_settings && tls->session_id_new) { + _auto_(l_free) char *session_id_str = + l_util_hexstring(tls->session_id, tls->session_id_size); + uint64_t expiry = tls->session_lifetime ? + time_realtime_now() + tls->session_lifetime : 0; + + if (tls->peer_authenticated && + (!expiry || peer_cert_expiry < expiry)) + expiry = peer_cert_expiry; + + l_settings_set_bytes(tls->session_settings, tls->session_group, + "TLSSessionID", tls->session_id, + tls->session_id_size); + l_settings_set_bytes(tls->session_settings, tls->session_group, + "TLSSessionMasterSecret", + tls->pending.master_secret, 48); + l_settings_set_int(tls->session_settings, tls->session_group, + "TLSSessionVersion", + tls->negotiated_version); + l_settings_set_bytes(tls->session_settings, tls->session_group, + "TLSSessionCipherSuite", + tls->pending.cipher_suite->id, 2); + l_settings_set_uint(tls->session_settings, tls->session_group, + "TLSSessionCompressionMethod", + tls->pending.compression_method->id); + + if (expiry) + l_settings_set_uint64(tls->session_settings, + tls->session_group, + "TLSSessionExpiryTime", expiry); + else + /* We may be overwriting an older session's data */ + l_settings_remove_key(tls->session_settings, + tls->session_group, + "TLSSessionExpiryTime"); + + if (tls->peer_authenticated) + l_settings_set_string(tls->session_settings, + tls->session_group, + "TLSSessionPeerIdentity", + peer_identity); + else + /* We may be overwriting an older session's data */ + l_settings_remove_key(tls->session_settings, + tls->session_group, + "TLSSessionPeerIdentity"); + + TLS_DEBUG("Saving new session %s to cache", session_id_str); + session_update = true; } /* Free up the resources used in the handshake */ @@ -2367,10 +2497,18 @@ static void tls_finished(struct l_tls *tls) TLS_SET_STATE(TLS_HANDSHAKE_DONE); tls->ready = true; + if (session_update && tls->session_update_cb) { + tls->in_callback = true; + tls->session_update_cb(tls->session_update_user_data); + tls->in_callback = false; + + if (tls->pending_destroy) + return; + } + tls->in_callback = true; tls->ready_handle(peer_identity, tls->user_data); tls->in_callback = false; - l_free(peer_identity); tls_cleanup_handshake(tls); } @@ -2643,6 +2781,7 @@ LIB_EXPORT struct l_tls *l_tls_new(bool server, tls->cipher_suite_pref_list = tls_cipher_suite_pref; tls->min_version = TLS_MIN_VERSION; tls->max_version = TLS_MAX_VERSION; + tls->session_lifetime = 24 * 3600 * L_USEC_PER_SEC; /* If we're the server wait for the Client Hello already */ if (tls->server) @@ -2669,6 +2808,7 @@ LIB_EXPORT void l_tls_free(struct l_tls *tls) l_tls_set_auth_data(tls, NULL, NULL); l_tls_set_domain_mask(tls, NULL); l_tls_set_cert_dump_path(tls, NULL); + l_tls_set_session_cache(tls, NULL, NULL, 0, NULL, NULL); tls_reset_handshake(tls); tls_cleanup_handshake(tls); @@ -3042,6 +3182,54 @@ LIB_EXPORT void l_tls_set_domain_mask(struct l_tls *tls, char **mask) tls->subject_mask = l_strv_copy(mask); } +/** + * l_tls_set_session_cache: + * @tls: TLS object being configured + * @settings: l_settings object to read and write session data from/to or + * NULL to disable caching session states. The object must remain valid + * until this method is called with a different value. + * @group: group name inside @settings + * @lifetime: a CLOCK_REALTIME-based microsecond resolution lifetime for + * cached sessions. The RFC recommends 24 hours. + * @update_cb: a callback to be invoked whenever the settings in @settings + * have been updated and may need to be written to persistent storage if + * desired, or NULL. + * @user_data: user data pointer to pass to @update_cb. + * + * Enables caching and resuming session states as described in RFC 5246 for + * faster setup. l_tls will maintain the required session state data in + * @settings including removing expired or erroneous sessions. + * + * A client's cache contains at most one session state since the client + * must request one specific Session ID from the server when resuming a + * session. The resumption will only work while the state is cached by + * both the server and the client so clients should keep separate @settings + * objects or use separate groups inside one object for every discrete + * server they may want to connect to. + * + * Multiple l_tls clients connecting to the same server can share one cache + * to allow reusing an established session that is still active (actual + * concurrency is not supported as there's no locking.) + */ +LIB_EXPORT void l_tls_set_session_cache(struct l_tls *tls, + struct l_settings *settings, + const char *group_name, + uint64_t lifetime, + l_tls_session_update_cb_t update_cb, + void *user_data) +{ + if (unlikely(!tls)) + return; + + tls->session_settings = settings; + tls->session_lifetime = lifetime; + tls->session_update_cb = update_cb; + tls->session_update_user_data = user_data; + + l_free(tls->session_group); + tls->session_group = l_strdup(group_name); +} + LIB_EXPORT const char *l_tls_alert_to_str(enum l_tls_alert_desc desc) { switch (desc) { diff --git a/ell/tls.h b/ell/tls.h index a4fd414..92d8b9e 100644 --- a/ell/tls.h +++ b/ell/tls.h @@ -36,6 +36,7 @@ struct l_tls; struct l_key; struct l_certchain; struct l_queue; +struct l_settings; enum l_tls_alert_desc { TLS_ALERT_CLOSE_NOTIFY = 0, @@ -72,6 +73,7 @@ typedef void (*l_tls_disconnect_cb_t)(enum l_tls_alert_desc reason, bool remote, void *user_data); typedef void (*l_tls_debug_cb_t)(const char *str, void *user_data); typedef void (*l_tls_destroy_cb_t)(void *user_data); +typedef void (*l_tls_session_update_cb_t)(void *user_data); /* * app_data_handler gets called with newly received decrypted data. @@ -127,6 +129,11 @@ void l_tls_set_version_range(struct l_tls *tls, void l_tls_set_domain_mask(struct l_tls *tls, char **mask); +void l_tls_set_session_cache(struct l_tls *tls, struct l_settings *settings, + const char *group_name, uint64_t lifetime, + l_tls_session_update_cb_t update_cb, + void *user_data); + const char *l_tls_alert_to_str(enum l_tls_alert_desc desc); enum l_checksum_type; From patchwork Wed Oct 26 13:15:57 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Zaborowski X-Patchwork-Id: 13020603 Received: from mail-wr1-f47.google.com (mail-wr1-f47.google.com [209.85.221.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 911CC1374 for ; Wed, 26 Oct 2022 13:16:18 +0000 (UTC) Received: by mail-wr1-f47.google.com with SMTP id l14so19032093wrw.2 for ; Wed, 26 Oct 2022 06:16:18 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=RzTXWYyobpbjiiAQ23xO4q3lkLXqdizZqO06sm+S0wA=; b=bwEM+gbm9nAch7eacvEasS1SNg+0xLvVz3atQnVzmRirdIJyL4fIBOBsN4J4D93EyJ DFsbBwVYRLh296EXyzXkutGMm4kBWMkPsmnEYxk1S6KoLFlEIH1/tJPl0Vp+ltX3ufkU /MfN54t66NYPnyA5+p8EeKHvh2biGo5meEj0GaNlgEh5IKgtkOPQGfx0AJBnHxoqqJI4 IW7PLCDGprYEyBWB7LMK3R1pE1TPLU6Om1CLXbYQYAxoQjPm83ZsqeeJL2F6WLzqYce0 dc9Wnzt+au+kFYYfAuVqpJTMbP9oVAdqi9d5gCFuTQXdAzxoEMeHm7fVUkA+bpaEqjub LYHg== X-Gm-Message-State: ACrzQf0sOXZsSP5jv/yuNUpeoiOr+jte7G4VGaqtg7vyBakRtTqeYxy1 3Yl1e11LhRhHTYnhjxBN4n51/c2DYDc= X-Google-Smtp-Source: AMsMyM7sqRrt1KYHvLWs+MXqflw3hrymX3f9qh78drSols7wdn+RbQYf2DNGcUYliq9GrgPMjDNN+Q== X-Received: by 2002:a05:6000:1b85:b0:230:3652:335 with SMTP id r5-20020a0560001b8500b0023036520335mr28404114wru.467.1666790175410; Wed, 26 Oct 2022 06:16:15 -0700 (PDT) Received: from localhost.localdomain ([82.213.230.158]) by smtp.gmail.com with ESMTPSA id x23-20020a05600c21d700b003a83ca67f73sm1934771wmj.3.2022.10.26.06.16.13 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 26 Oct 2022 06:16:14 -0700 (PDT) From: Andrew Zaborowski To: ell@lists.linux.dev Subject: [PATCH 5/6] tls: Client session resumption Date: Wed, 26 Oct 2022 15:15:57 +0200 Message-Id: <20221026131558.2393488-5-andrew.zaborowski@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221026131558.2393488-1-andrew.zaborowski@intel.com> References: <20221026131558.2393488-1-andrew.zaborowski@intel.com> Precedence: bulk X-Mailing-List: ell@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 In client mode load a stored session state from the cache in l_tls_start and attempt to resume that session. --- ell/tls-private.h | 3 + ell/tls-suites.c | 4 +- ell/tls.c | 316 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 286 insertions(+), 37 deletions(-) diff --git a/ell/tls-private.h b/ell/tls-private.h index 6f09f6a..7156666 100644 --- a/ell/tls-private.h +++ b/ell/tls-private.h @@ -260,6 +260,9 @@ struct l_tls { uint8_t session_id[32]; size_t session_id_size; bool session_id_new; + uint8_t session_cipher_suite_id[2]; + uint8_t session_compression_method_id; + char *session_peer_identity; /* SecurityParameters current and pending */ diff --git a/ell/tls-suites.c b/ell/tls-suites.c index ee4e7ee..8cbcb63 100644 --- a/ell/tls-suites.c +++ b/ell/tls-suites.c @@ -352,8 +352,8 @@ static bool tls_send_rsa_client_key_xchg(struct l_tls *tls) } /* Must match the version in tls_send_client_hello */ - pre_master_secret[0] = (uint8_t) (tls->max_version >> 8); - pre_master_secret[1] = (uint8_t) (tls->max_version >> 0); + pre_master_secret[0] = (uint8_t) (tls->client_version >> 8); + pre_master_secret[1] = (uint8_t) (tls->client_version >> 0); l_getrandom(pre_master_secret + 2, 46); diff --git a/ell/tls.c b/ell/tls.c index ff4fa5b..88ac3d7 100644 --- a/ell/tls.c +++ b/ell/tls.c @@ -210,6 +210,7 @@ static void tls_reset_handshake(struct l_tls *tls) tls->session_id_size = 0; tls->session_id_new = false; + l_free(l_steal_ptr(tls->session_peer_identity)); } static void tls_cleanup_handshake(struct l_tls *tls) @@ -851,6 +852,155 @@ static void tls_forget_cached_client_session(struct l_tls *tls) } } +static bool tls_load_cached_client_session(struct l_tls *tls) +{ + /* + * The following settings are required: + * TLSSessionID, + * TLSSessionMasterSecret, + * TLSSessionVersion, + * TLSSessionCipherSuite, + * TLSSessionCompressionMethod, + * and these two are optional: + * TLSSessionExpiryTime, + * TLSSessionPeerIdentity. + */ + _auto_(l_free) uint8_t *session_id = NULL; + size_t session_id_size; + _auto_(l_free) char *session_id_str = NULL; + _auto_(l_free) uint8_t *master_secret = NULL; + int version; + _auto_(l_free) uint8_t *cipher_suite_id = NULL; + struct tls_cipher_suite *cipher_suite; + unsigned int compression_method_id; + _auto_(l_free) char *peer_identity = NULL; + size_t size; + const char *error; + + tls->session_id_size = 0; + tls->session_id_new = false; + + if (!tls->session_settings || + !l_settings_has_key(tls->session_settings, + tls->session_group, + "TLSSessionID")) + /* No session cached, no error */ + return false; + + session_id = l_settings_get_bytes(tls->session_settings, + tls->session_group, + "TLSSessionID", + &session_id_size); + if (unlikely(!session_id || + session_id_size < 1 || session_id_size > 32)) + goto warn_corrupt; + + session_id_str = + l_util_hexstring(tls->session_id, tls->session_id_size); + + if (l_settings_has_key(tls->session_settings, tls->session_group, + "TLSSessionExpiryTime")) { + uint64_t expiry_time; + + if (unlikely(!l_settings_get_uint64(tls->session_settings, + tls->session_group, + "TLSSessionExpiryTime", + &expiry_time))) + goto warn_corrupt; + + if (time_realtime_now() > expiry_time) { + TLS_DEBUG("Cached session %s is expired, removing it, " + "will start a new session", + session_id_str); + goto forget; + } + } + + if (unlikely(!l_settings_get_int(tls->session_settings, + tls->session_group, + "TLSSessionVersion", + &version) || + version < TLS_MIN_VERSION || version > TLS_MAX_VERSION)) + goto warn_corrupt; + + master_secret = l_settings_get_bytes(tls->session_settings, + tls->session_group, + "TLSSessionMasterSecret", + &size); + if (unlikely(!master_secret || size != 48)) + goto warn_corrupt; + + cipher_suite_id = l_settings_get_bytes(tls->session_settings, + tls->session_group, + "TLSSessionCipherSuite", + &size); + if (unlikely(!cipher_suite_id || size != 2 || + !(cipher_suite = + tls_find_cipher_suite(cipher_suite_id)))) + goto warn_corrupt; + + /* + * While we could attempt to resume a session even though we're now + * configured with, say, a different certificate type than what we + * had when we cached that session, that is too questionable of a + * scenario to support it. We don't specifically check that all of + * the authentication data is the same, e.g. we don't save the + * certificate serial number or path, but ensure the cached cipher + * suite is compatible with current authentication data. + * + * We filter the cipher suites in our Client Hello to only offer the + * ones compatible with current configuration so if we also include + * a Session ID from a session who's cipher suite is not one of those + * listed in that same Client Hello, the server is likely to notice + * and either start a new session or send a fatal Alert. + * + * It is up to the user to keep multiple cache instances if it needs + * to save multiple sessions. + */ + if (unlikely(!tls_cipher_suite_is_compatible(tls, cipher_suite, + &error))) { + TLS_DEBUG("Cached session %s cipher suite not compatible: %s", + session_id_str, error); + goto forget; + } + + if (unlikely(!l_settings_get_uint(tls->session_settings, + tls->session_group, + "TLSSessionCompressionMethod", + &compression_method_id) || + !tls_find_compression_method(compression_method_id))) + goto warn_corrupt; + + if (l_settings_has_key(tls->session_settings, tls->session_group, + "TLSSessionPeerIdentity")) { + peer_identity = l_settings_get_string(tls->session_settings, + tls->session_group, + "TLSSessionPeerIdentity"); + if (unlikely(!peer_identity || !cipher_suite->signature)) + goto warn_corrupt; + } + + tls->session_id_size = session_id_size; + memcpy(tls->session_id, session_id, session_id_size); + tls->session_id_new = false; + tls->client_version = version; + memcpy(tls->pending.master_secret, master_secret, 48); + memcpy(tls->session_cipher_suite_id, cipher_suite_id, 2); + tls->session_compression_method_id = compression_method_id; + l_free(tls->session_peer_identity); + tls->session_peer_identity = l_steal_ptr(peer_identity); + return true; + +warn_corrupt: + TLS_DEBUG("Cached session %s data is corrupt or has unsupported " + "parameters, removing it, will start a new session", + session_id_str ?: ""); + +forget: + tls_forget_cached_client_session(tls); + return false; +} + #define SWITCH_ENUM_TO_STR(val) \ case (val): \ return L_STRINGIFY(val); @@ -1063,14 +1213,19 @@ static bool tls_send_client_hello(struct l_tls *tls) /* Fill in the Client Hello body */ - *ptr++ = (uint8_t) (tls->max_version >> 8); - *ptr++ = (uint8_t) (tls->max_version >> 0); + *ptr++ = (uint8_t) (tls->client_version >> 8); + *ptr++ = (uint8_t) (tls->client_version >> 0); tls_write_random(tls->pending.client_random); memcpy(ptr, tls->pending.client_random, 32); ptr += 32; - *ptr++ = 0; /* No SessionID */ + if (tls->session_id_size) { + *ptr++ = tls->session_id_size; + memcpy(ptr, tls->session_id, tls->session_id_size); + ptr += tls->session_id_size; + } else + *ptr++ = 0; len_ptr = ptr; ptr += 2; @@ -1317,22 +1472,10 @@ static void tls_send_server_hello_done(struct l_tls *tls) TLS_HANDSHAKE_HEADER_SIZE); } -void tls_generate_master_secret(struct l_tls *tls, - const uint8_t *pre_master_secret, - int pre_master_secret_len) +static void tls_update_key_block(struct l_tls *tls) { uint8_t seed[64]; - int key_block_size; - - memcpy(seed + 0, tls->pending.client_random, 32); - memcpy(seed + 32, tls->pending.server_random, 32); - - tls_prf_get_bytes(tls, pre_master_secret, pre_master_secret_len, - "master secret", seed, 64, - tls->pending.master_secret, 48); - - /* Directly generate the key block while we're at it */ - key_block_size = 0; + int key_block_size = 0; if (tls->pending.cipher_suite->encryption) key_block_size += 2 * @@ -1360,8 +1503,25 @@ void tls_generate_master_secret(struct l_tls *tls, tls_prf_get_bytes(tls, tls->pending.master_secret, 48, "key expansion", seed, 64, tls->pending.key_block, key_block_size); + explicit_bzero(seed, 64); +} +void tls_generate_master_secret(struct l_tls *tls, + const uint8_t *pre_master_secret, + int pre_master_secret_len) +{ + uint8_t seed[64]; + + memcpy(seed + 0, tls->pending.client_random, 32); + memcpy(seed + 32, tls->pending.server_random, 32); + + tls_prf_get_bytes(tls, pre_master_secret, pre_master_secret_len, + "master secret", seed, 64, + tls->pending.master_secret, 48); explicit_bzero(seed, 64); + + /* Directly generate the key block while we're at it */ + tls_update_key_block(tls); } static void tls_get_handshake_hash(struct l_tls *tls, @@ -1856,11 +2016,14 @@ static void tls_handle_server_hello(struct l_tls *tls, int i; struct l_queue *extensions_seen; bool result; + uint16_t version; + bool resuming = false; /* Do we have enough for ProtocolVersion + Random + SessionID len ? */ if (len < 2 + 32 + 1) goto decode_error; + version = l_get_be16(buf); memcpy(tls->pending.server_random, buf + 2, 32); session_id_size = buf[34]; len -= 35; @@ -1877,6 +2040,32 @@ static void tls_handle_server_hello(struct l_tls *tls, if (session_id_size > 32) goto decode_error; + if (tls->session_id_size) { + _auto_(l_free) char *session_id_str = + l_util_hexstring(tls->session_id, tls->session_id_size); + + if (session_id_size == tls->session_id_size && + !memcmp(buf + 35, tls->session_id, + session_id_size)) { + TLS_DEBUG("Negotiated resumption of cached session %s", + session_id_str); + resuming = true; + + /* + * Skip parsing extensions as none of the ones we + * support are used in session resumption. We could + * as well signal an error if the ServerHello has any + * extensions, for now ignore them. + */ + goto check_version; + } + + TLS_DEBUG("Server decided not to resume cached session %s, " + "sent %s session ID", session_id_str, + session_id_size ? "a new" : "no"); + tls->session_id_size = 0; + } + if (session_id_size && tls->session_settings) { tls->session_id_new = true; tls->session_id_size = session_id_size; @@ -1891,18 +2080,17 @@ static void tls_handle_server_hello(struct l_tls *tls, if (!result) return; - tls->negotiated_version = l_get_be16(buf); - - if (tls->negotiated_version < tls->min_version || - tls->negotiated_version > tls->max_version) { - TLS_DISCONNECT(tls->negotiated_version < tls->min_version ? +check_version: + if (version < tls->min_version || version > tls->max_version) { + TLS_DISCONNECT(version < tls->min_version ? TLS_ALERT_PROTOCOL_VERSION : TLS_ALERT_ILLEGAL_PARAM, 0, - "Unsupported version %02x", - tls->negotiated_version); + "Unsupported version %02x", version); return; } + tls->negotiated_version = version; + /* Stop maintaining handshake message hashes other than MD1 and SHA. */ if (tls->negotiated_version < L_TLS_V12) for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++) @@ -1958,7 +2146,30 @@ static void tls_handle_server_hello(struct l_tls *tls, TLS_DEBUG("Negotiated %s", tls->pending.compression_method->name); - if (tls->pending.cipher_suite->signature) + if (resuming) { + /* + * Now that we've validated the Server Hello parameters and + * know that they're supported by this version of ell and + * consistent with the current configuration, ensure that + * they're identical with the ones in the cached session + * being resumed. This serves as a sanity check for + * rare situations like a corrupt session cache file or + * a file written by a newer ell version. + */ + if (tls->negotiated_version != tls->client_version || + memcmp(cipher_suite_id, + tls->session_cipher_suite_id, 2) || + compression_method_id != + tls->session_compression_method_id) { + TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, + "Session parameters don't match"); + return; + } + + tls_update_key_block(tls); + + TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC); + } else if (tls->pending.cipher_suite->signature) TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CERTIFICATE); else TLS_SET_STATE(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE); @@ -2419,18 +2630,22 @@ error: static void tls_finished(struct l_tls *tls) { - _auto_(l_free) char *peer_identity = NULL; + _auto_(l_free) char *peer_cert_identity = NULL; + char *peer_identity = NULL; uint64_t peer_cert_expiry; + bool resuming = tls->session_id_size && !tls->session_id_new; bool session_update = false; - if (tls->peer_authenticated) { - peer_identity = tls_get_peer_identity_str(tls->peer_cert); - if (!peer_identity) { + if (tls->peer_authenticated && !resuming) { + peer_cert_identity = tls_get_peer_identity_str(tls->peer_cert); + if (!peer_cert_identity) { TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, "tls_get_peer_identity_str failed"); return; } + peer_identity = peer_cert_identity; + if (tls->session_id_new && !l_cert_get_valid_times(tls->peer_cert, NULL, &peer_cert_expiry)) { @@ -2438,7 +2653,8 @@ static void tls_finished(struct l_tls *tls) "l_cert_get_valid_times failed"); return; } - } + } else if (tls->peer_authenticated && resuming) + peer_identity = tls->session_peer_identity; if (!tls->server && tls->session_settings && tls->session_id_new) { _auto_(l_free) char *session_id_str = @@ -2516,6 +2732,8 @@ static void tls_finished(struct l_tls *tls) static void tls_handle_handshake(struct l_tls *tls, int type, const uint8_t *buf, size_t len) { + bool resuming; + TLS_DEBUG("Handling a %s of %zi bytes", tls_handshake_type_to_str(type), len); @@ -2703,7 +2921,9 @@ static void tls_handle_handshake(struct l_tls *tls, int type, if (!tls_verify_finished(tls, buf, len)) break; - if (tls->server) { + resuming = tls->session_id_size && !tls->session_id_new; + + if (tls->server || (!tls->server && resuming)) { const char *error; tls_send_change_cipher_spec(tls); @@ -2717,9 +2937,9 @@ static void tls_handle_handshake(struct l_tls *tls, int type, } /* - * On the client, the server's certificate is now verified - * regardless of the key exchange method, based on the - * following logic: + * When starting a new session on the client, the server's + * certificate is now verified regardless of the key exchange + * method, based on the following logic: * * - tls->ca_certs is non-NULL so tls_handle_certificate * (always called on the client) must have veritifed the @@ -2744,9 +2964,14 @@ static void tls_handle_handshake(struct l_tls *tls, int type, * able to sign the client random together with the * ServerKeyExchange parameters using its certified key * pair. + * + * If we're resuming a cached session, we have authenticated + * this server before and the successful decryption of this + * message confirms the server identity hasn't changed. */ if (!tls->server && tls->cipher_suite[0]->signature && - tls->ca_certs) + ((!resuming && tls->ca_certs) || + (resuming && tls->session_peer_identity))) tls->peer_authenticated = true; tls_finished(tls); @@ -3010,6 +3235,27 @@ LIB_EXPORT bool l_tls_start(struct l_tls *tls) if (!tls_init_handshake_hash(tls)) return false; + /* + * If we're going to try resuming a cached session, send the Client + * Hello with the version we think is supported. + * + * RFC5246 Appendix E.1: + * "Whenever a client already knows the highest protocol version known + * to a server (for example, when resuming a session), it SHOULD + * initiate the connection in that native protocol." + * + * Don't directly set tls->{min,max}_version as that would make the + * handshake fail if the server decides to start a new session with + * a new version instead of resuming, which it is allowed to do. + */ + tls->client_version = tls->max_version; + tls_load_cached_client_session(tls); + + if (tls->pending_destroy) { + l_tls_free(tls); + return false; + } + if (!tls_send_client_hello(tls)) return false; From patchwork Wed Oct 26 13:15:58 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Andrew Zaborowski X-Patchwork-Id: 13020602 Received: from mail-wr1-f52.google.com (mail-wr1-f52.google.com [209.85.221.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2A096138F for ; Wed, 26 Oct 2022 13:16:19 +0000 (UTC) Received: by mail-wr1-f52.google.com with SMTP id z14so12763142wrn.7 for ; Wed, 26 Oct 2022 06:16:18 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=RTwfpyEyl2k3fJzkWZFbxgXBb+Ymb7S9/4wV0ao/68c=; b=dEBmaSTPKfi1/ChB9nL0Pmsng0C8oMRMKDHmnLyFPCQGE7HrcUAPMCoTdnHtsVk2Vb 6ORPKoEVcIshT6ROO+nNA8r5TktTKCT6meEV3tmEtztenMx+zVsHtt/Tu3SMYGJFVO7R LTqsf2rscp2JRPcuhRzko2LIba1c8ZBkT3lLBgscARcVJBXNftS5amUcH2Um9xX8ZeTF aIZdwr5TMwXCZwf2aK3Yawv/XYpZ2qnrzQZc0sN60fHEM5WC962LFpiqvH/FzUWrUXwX o4eJALURcmYIZUx7JWVZDDMlcZ8w26Kd2unpq3JMeMo/y3agqp2wC0A6F2gP/NJZVjZI ijaQ== X-Gm-Message-State: ACrzQf2MQa6WSnEALXMRygbgmHKcJ4ko/x62hFOZK+hhqGPvi1/MJ3uy Wrd9sJqMouWTLb4z0UEMF0fhI8nfMBA= X-Google-Smtp-Source: AMsMyM5XzH57ZljALPRtUTptdO6hL9n2cksujrs/M/Nevqp8PyTF6I4AAlUY8r4qAyoeLrbpNBnNkQ== X-Received: by 2002:a5d:58d9:0:b0:236:5b81:2c99 with SMTP id o25-20020a5d58d9000000b002365b812c99mr16542886wrf.494.1666790176537; Wed, 26 Oct 2022 06:16:16 -0700 (PDT) Received: from localhost.localdomain ([82.213.230.158]) by smtp.gmail.com with ESMTPSA id x23-20020a05600c21d700b003a83ca67f73sm1934771wmj.3.2022.10.26.06.16.15 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 26 Oct 2022 06:16:15 -0700 (PDT) From: Andrew Zaborowski To: ell@lists.linux.dev Subject: [PATCH 6/6] examples: Cache sessions in https-client-test Date: Wed, 26 Oct 2022 15:15:58 +0200 Message-Id: <20221026131558.2393488-6-andrew.zaborowski@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221026131558.2393488-1-andrew.zaborowski@intel.com> References: <20221026131558.2393488-1-andrew.zaborowski@intel.com> Precedence: bulk X-Mailing-List: ell@lists.linux.dev List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 If the environment variable TLS_CACHE is set, use l_tls_set_session_cache() to enable session resumption. --- examples/https-client-test.c | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/examples/https-client-test.c b/examples/https-client-test.c index b0c24b4..2c6939a 100644 --- a/examples/https-client-test.c +++ b/examples/https-client-test.c @@ -32,13 +32,18 @@ #include #include #include +#include +#include #include +#include static struct l_io *io; static struct l_tls *tls; static const char *hostname; static bool ready; +static struct l_settings *session_cache; +static char *session_cache_path; static void https_io_disconnect(struct l_io *io, void *user_data) { @@ -127,6 +132,27 @@ static void https_tls_debug_cb(const char *str, void *user_data) printf("%s\n", str); } +static void https_tls_session_cache_update_cb(void *user_data) +{ + size_t len; + char *data = l_settings_to_data(session_cache, &len); + _auto_(close) int fd = L_TFR(creat(session_cache_path, 0600)); + + if (!data) { + fprintf(stderr, "l_settings_to_data() failed\n"); + return; + } + + if (fd < 0) { + fprintf(stderr, "can't open %s: %s\n", + session_cache_path, strerror(errno)); + return; + } + + if (L_TFR(write(fd, data, len)) < (ssize_t) len) + fprintf(stderr, "short write to %s\n", session_cache_path); +} + int main(int argc, char *argv[]) { struct hostent *he; @@ -200,6 +226,23 @@ int main(int argc, char *argv[]) l_free(str); } + if (getenv("TLS_CACHE")) { + const char *homedir = getenv("HOME"); + + if (!homedir) + homedir = "/tmp"; + + session_cache_path = + l_strdup_printf("%s/.ell-https-client-test", homedir); + session_cache = l_settings_new(); + l_settings_load_from_file(session_cache, session_cache_path); + + l_tls_set_session_cache(tls, session_cache, hostname, + 24 * 3600 * L_USEC_PER_SEC, + https_tls_session_cache_update_cb, + NULL); + } + if (argc >= 3) { ca_cert = l_pem_load_certificate_list(argv[2]); if (!ca_cert) { @@ -244,6 +287,11 @@ int main(int argc, char *argv[]) l_io_destroy(io); l_tls_free(tls); + if (session_cache) { + l_settings_free(session_cache); + l_free(session_cache_path); + } + l_main_exit(); return 0;