From 0c687a87478d0cf3a3c76037f0dac7618c06b4a0 Mon Sep 17 00:00:00 2001 From: Dom Del Nano Date: Tue, 10 Dec 2024 15:20:47 +0000 Subject: [PATCH] Add TLS protocol stitcher Signed-off-by: Dom Del Nano --- .../socket_tracer/protocols/tls/BUILD.bazel | 6 + .../socket_tracer/protocols/tls/stitcher.cc | 145 ++++++++++++++++++ .../socket_tracer/protocols/tls/stitcher.h | 46 ++++++ .../protocols/tls/stitcher_test.cc | 116 ++++++++++++++ 4 files changed, 313 insertions(+) create mode 100644 src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.cc create mode 100644 src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.h create mode 100644 src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher_test.cc diff --git a/src/stirling/source_connectors/socket_tracer/protocols/tls/BUILD.bazel b/src/stirling/source_connectors/socket_tracer/protocols/tls/BUILD.bazel index 45128debb39..96df10ab68a 100644 --- a/src/stirling/source_connectors/socket_tracer/protocols/tls/BUILD.bazel +++ b/src/stirling/source_connectors/socket_tracer/protocols/tls/BUILD.bazel @@ -45,3 +45,9 @@ pl_cc_test( srcs = ["parse_test.cc"], deps = [":cc_library"], ) + +pl_cc_test( + name = "stitcher_test", + srcs = ["stitcher_test.cc"], + deps = [":cc_library"], +) diff --git a/src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.cc b/src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.cc new file mode 100644 index 00000000000..adf312b4732 --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.cc @@ -0,0 +1,145 @@ +/* + * Copyright 2018- The Pixie Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.h" + +#include + +#include "src/stirling/source_connectors/socket_tracer/protocols/common/interface.h" +#include "src/stirling/source_connectors/socket_tracer/protocols/tls/parse.h" +#include "src/stirling/source_connectors/socket_tracer/protocols/tls/types.h" + +namespace px { +namespace stirling { +namespace protocols { +namespace tls { + +/* The TLS protocol is an in-order protocol. The handshake protocol specificially must + * be sent in the expected order or it results in a fatal protocol error. + * + * TLS v1.2 handeshake. Taken from RFC5246 page 35 + * + * Client Server + * + * ClientHello --------> + * ServerHello + * Certificate* + * ServerKeyExchange* + * CertificateRequest* + * <-------- ServerHelloDone + * Certificate* + * ClientKeyExchange + * CertificateVerify* + * [ChangeCipherSpec] + * Finished --------> + * [ChangeCipherSpec] + * <-------- Finished + * Application Data <-------> Application Data + * + * `*` Indicates optional or situation-dependent messages that are not + * always sent. + * + * TLS v1.3 handshake. Taken from RFC8446 page 10 + * + * Client Server + * + * Key ^ ClientHello + * Exch | + key_share* + * | + signature_algorithms* + * | + psk_key_exchange_modes* + * v + pre_shared_key* --------> + * ServerHello ^ Key + * + key_share* | Exch + * + pre_shared_key* v + * {EncryptedExtensions} ^ Server + * {CertificateRequest*} v Params + * {Certificate*} ^ + * {CertificateVerify*} | Auth + * {Finished} v + * <-------- [Application Data*] + * ^ {Certificate*} + * Auth | {CertificateVerify*} + * v {Finished} --------> + * [Application Data] <-------> [Application Data] + * + * + Indicates noteworthy extensions sent in the + * previously noted message. + * + * * Indicates optional or situation-dependent + * messages/extensions that are not always sent. + * + * {} Indicates messages protected using keys + * derived from a [sender]_handshake_traffic_secret. + * + * [] Indicates messages protected using keys + * derived from [sender]_application_traffic_secret_N. + */ +RecordsWithErrorCount StitchFrames(std::deque* reqs, + std::deque* resps) { + std::vector records; + int error_count = 0; + uint64_t latest_resp_ts = 0; + + for (auto& resp : *resps) { + if (resp.timestamp_ns > latest_resp_ts) { + latest_resp_ts = resp.timestamp_ns; + } + + for (auto req_it = reqs->begin(); req_it != reqs->end(); req_it++) { + auto& req = *req_it; + + if (req.consumed) continue; + + // For now only kHandshakes matter since that transmits the interesting data. + // We will probably wnat to support kAlert as well. + if (req.content_type == ContentType::kApplicationData || + req.content_type == ContentType::kChangeCipherSpec || + req.content_type == ContentType::kAlert || req.content_type == ContentType::kHeartbeat) { + req.consumed = true; + continue; + } + + if (req.content_type == ContentType::kHandshake && + req.handshake_type == HandshakeType::kClientHello && + resp.content_type == ContentType::kHandshake && + resp.handshake_type == HandshakeType::kServerHello) { + req.consumed = true; + resp.consumed = true; + records.push_back({std::move(req), std::move(resp)}); + break; + } + } + } + + auto erase_until_iter = reqs->begin(); + while (erase_until_iter != reqs->end() && + (erase_until_iter->consumed || erase_until_iter->timestamp_ns < latest_resp_ts)) { + if (!erase_until_iter->consumed) { + error_count++; + } + erase_until_iter++; + } + reqs->erase(reqs->begin(), erase_until_iter); + resps->clear(); + return {records, error_count}; +} + +} // namespace tls +} // namespace protocols +} // namespace stirling +} // namespace px diff --git a/src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.h b/src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.h new file mode 100644 index 00000000000..a1f1c01b683 --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.h @@ -0,0 +1,46 @@ +/* + * Copyright 2018- The Pixie Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#include "src/common/base/base.h" +#include "src/stirling/source_connectors/socket_tracer/protocols/common/interface.h" +#include "src/stirling/source_connectors/socket_tracer/protocols/tls/types.h" + +namespace px { +namespace stirling { +namespace protocols { +namespace tls { + +RecordsWithErrorCount StitchFrames(std::deque* reqs, + std::deque* resps); +} // namespace tls + +template <> +inline RecordsWithErrorCount StitchFrames(std::deque* reqs, + std::deque* resps, + NoState* /*state*/) { + return tls::StitchFrames(reqs, resps); +} + +} // namespace protocols +} // namespace stirling +} // namespace px diff --git a/src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher_test.cc b/src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher_test.cc new file mode 100644 index 00000000000..ed4b8477610 --- /dev/null +++ b/src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher_test.cc @@ -0,0 +1,116 @@ +/* + * Copyright 2018- The Pixie Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "src/stirling/source_connectors/socket_tracer/protocols/tls/stitcher.h" + +#include + +#include "src/common/testing/testing.h" +#include "src/stirling/source_connectors/socket_tracer/protocols/tls/parse.h" + +namespace px { +namespace stirling { +namespace protocols { + +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Field; +using ::testing::IsEmpty; +using ::testing::SizeIs; +using ::testing::StrEq; + +class StitchFramesTest : public ::testing::Test {}; + +tls::Frame CreateTLSFrame(uint64_t ts_ns, tls::ContentType content_type, + tls::LegacyVersion legacy_version, tls::HandshakeType handshake_type, + std::string session_id) { + tls::Frame frame; + frame.timestamp_ns = ts_ns; + frame.content_type = content_type; + frame.legacy_version = legacy_version; + frame.handshake_type = handshake_type; + frame.session_id = session_id; + return frame; +} + +tls::Frame CreateNonHandshakeFrame(uint64_t ts_ns, tls::ContentType content_type, + tls::LegacyVersion legacy_version) { + tls::Frame frame; + frame.timestamp_ns = ts_ns; + frame.content_type = content_type; + frame.legacy_version = legacy_version; + return frame; +} + +TEST_F(StitchFramesTest, HandlesTLS1_3Handshake) { + std::deque reqs = { + CreateTLSFrame(0, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_0, + tls::HandshakeType::kClientHello, "session_id"), + }; + std::deque resps = { + CreateTLSFrame(1, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_2, + tls::HandshakeType::kServerHello, "session_id"), + }; + RecordsWithErrorCount result = tls::StitchFrames(&reqs, &resps); + EXPECT_EQ(result.error_count, 0); + EXPECT_EQ(result.records.size(), 1); + EXPECT_THAT(reqs, IsEmpty()); + EXPECT_THAT(resps, IsEmpty()); +} + +TEST_F(StitchFramesTest, HandlesApplicationDataAndChangeCipherSpecWithFullHandshake) { + std::deque reqs = { + CreateTLSFrame(0, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_0, + tls::HandshakeType::kClientHello, "session_id"), + CreateNonHandshakeFrame(2, tls::ContentType::kChangeCipherSpec, tls::LegacyVersion::kTLS1_2), + CreateNonHandshakeFrame(3, tls::ContentType::kApplicationData, tls::LegacyVersion::kTLS1_2), + CreateNonHandshakeFrame(4, tls::ContentType::kApplicationData, tls::LegacyVersion::kTLS1_2), + }; + std::deque resps = { + CreateTLSFrame(1, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_2, + tls::HandshakeType::kServerHello, "session_id"), + CreateNonHandshakeFrame(3, tls::ContentType::kChangeCipherSpec, tls::LegacyVersion::kTLS1_2), + CreateNonHandshakeFrame(5, tls::ContentType::kApplicationData, tls::LegacyVersion::kTLS1_2), + }; + RecordsWithErrorCount result = tls::StitchFrames(&reqs, &resps); + EXPECT_EQ(result.error_count, 0); + EXPECT_EQ(result.records.size(), 1); + EXPECT_THAT(reqs, IsEmpty()); + EXPECT_THAT(resps, IsEmpty()); +} + +TEST_F(StitchFramesTest, WaitsToProcessReqFrameUntilRespFrameIsReceived) { + std::deque reqs = { + CreateTLSFrame(0, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_0, + tls::HandshakeType::kClientHello, "session_id"), + CreateNonHandshakeFrame(2, tls::ContentType::kChangeCipherSpec, tls::LegacyVersion::kTLS1_2), + }; + std::deque resps = { + CreateTLSFrame(1, tls::ContentType::kHandshake, tls::LegacyVersion::kTLS1_2, + tls::HandshakeType::kServerHello, "session_id"), + }; + RecordsWithErrorCount result = tls::StitchFrames(&reqs, &resps); + EXPECT_EQ(result.error_count, 0); + EXPECT_EQ(result.records.size(), 1); + EXPECT_THAT(reqs, SizeIs(1)); + EXPECT_THAT(resps, IsEmpty()); +} + +} // namespace protocols +} // namespace stirling +} // namespace px