Skip to content

Commit 672e351

Browse files
authored
Add TLS protocol parser (#2050)
Summary: Add TLS protocol parser This is the first piece of supporting TLS protocol tracing. Relevant Issues: N/A Type of change: /kind feature Test Plan: Parser test coverage verifies parser works for TLS v1.2 and later --------- Signed-off-by: Dom Del Nano <ddelnano@gmail.com>
1 parent 041c100 commit 672e351

File tree

5 files changed

+902
-0
lines changed

5 files changed

+902
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2018- The Pixie Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
17+
load("//bazel:pl_build_system.bzl", "pl_cc_library", "pl_cc_test")
18+
19+
package(default_visibility = ["//src/stirling:__subpackages__"])
20+
21+
pl_cc_library(
22+
name = "cc_library",
23+
srcs = glob(
24+
[
25+
"*.cc",
26+
],
27+
exclude = [
28+
"**/*_test.cc",
29+
],
30+
),
31+
hdrs = glob(
32+
[
33+
"*.h",
34+
],
35+
),
36+
deps = [
37+
"//src/common/json:cc_library",
38+
"//src/stirling/source_connectors/socket_tracer/protocols/common:cc_library",
39+
"//src/stirling/utils:cc_library",
40+
],
41+
)
42+
43+
pl_cc_test(
44+
name = "parse_test",
45+
srcs = ["parse_test.cc"],
46+
deps = [":cc_library"],
47+
)
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/*
2+
* Copyright 2018- The Pixie Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
#include "src/stirling/source_connectors/socket_tracer/protocols/tls/parse.h"
19+
20+
#include <map>
21+
#include <string>
22+
#include <utility>
23+
#include <vector>
24+
25+
#include <magic_enum.hpp>
26+
27+
#include "src/stirling/utils/binary_decoder.h"
28+
29+
namespace px {
30+
namespace stirling {
31+
namespace protocols {
32+
namespace tls {
33+
34+
constexpr size_t kTLSRecordHeaderLength = 5;
35+
constexpr size_t kExtensionMinimumLength = 4;
36+
constexpr size_t kSNIExtensionMinimumLength = 3;
37+
38+
// In TLS 1.3, Random is 32 bytes.
39+
// In TLS 1.2 and earlier, gmt_unix_time is 4 bytes and Random is 28 bytes.
40+
constexpr size_t kRandomStructLength = 32;
41+
42+
StatusOr<ParseState> ExtractSNIExtension(std::map<std::string, std::string>* exts,
43+
BinaryDecoder* decoder) {
44+
PX_ASSIGN_OR(auto server_name_list_length, decoder->ExtractBEInt<uint16_t>(),
45+
return ParseState::kInvalid);
46+
std::vector<std::string> server_names;
47+
while (server_name_list_length > 0) {
48+
PX_ASSIGN_OR(auto server_name_type, decoder->ExtractBEInt<uint8_t>(),
49+
return error::Internal("Failed to extract server name type"));
50+
51+
// This is the only valid value for server_name_type and corresponds to host_name.
52+
DCHECK_EQ(server_name_type, 0);
53+
54+
PX_ASSIGN_OR(auto server_name_length, decoder->ExtractBEInt<uint16_t>(),
55+
return error::Internal("Failed to extract server name length"));
56+
PX_ASSIGN_OR(auto server_name, decoder->ExtractString(server_name_length),
57+
return error::Internal("Failed to extract server name"));
58+
59+
server_names.push_back(std::string(server_name));
60+
server_name_list_length -= kSNIExtensionMinimumLength + server_name_length;
61+
}
62+
exts->insert({"server_name", ToJSONString(server_names)});
63+
return ParseState::kSuccess;
64+
}
65+
66+
/*
67+
* The TLS wire protocol is best described in each of the RFCs for the protocol
68+
* SSL v3.0: https://tools.ietf.org/html/rfc6101
69+
* TLS v1.0: https://tools.ietf.org/html/rfc2246
70+
* TLS v1.1: https://tools.ietf.org/html/rfc4346
71+
* TLS v1.2: https://tools.ietf.org/html/rfc5246
72+
* TLS v1.3: https://tools.ietf.org/html/rfc8446
73+
*
74+
* These specs have c struct style definitions of the wire protocol. The wikipedia
75+
* page is also a good resource to see it explained in a more typical ascii binary format
76+
* diagram: https://en.wikipedia.org/wiki/Transport_Layer_Security#TLS_record
77+
*/
78+
79+
ParseState ParseFullFrame(BinaryDecoder* decoder, Frame* frame) {
80+
PX_ASSIGN_OR(auto raw_content_type, decoder->ExtractBEInt<uint8_t>(),
81+
return ParseState::kInvalid);
82+
auto content_type = magic_enum::enum_cast<tls::ContentType>(raw_content_type);
83+
if (!content_type.has_value()) {
84+
return ParseState::kInvalid;
85+
}
86+
frame->content_type = content_type.value();
87+
88+
PX_ASSIGN_OR(auto legacy_version, decoder->ExtractBEInt<uint16_t>(), return ParseState::kInvalid);
89+
auto lv = magic_enum::enum_cast<tls::LegacyVersion>(legacy_version);
90+
if (!lv.has_value()) {
91+
return ParseState::kInvalid;
92+
}
93+
frame->legacy_version = lv.value();
94+
95+
PX_ASSIGN_OR(frame->length, decoder->ExtractBEInt<uint16_t>(), return ParseState::kInvalid);
96+
97+
if (frame->content_type == tls::ContentType::kApplicationData ||
98+
frame->content_type == tls::ContentType::kChangeCipherSpec ||
99+
frame->content_type == tls::ContentType::kAlert ||
100+
frame->content_type == tls::ContentType::kHeartbeat) {
101+
if (!decoder->ExtractBufIgnore(frame->length).ok()) {
102+
return ParseState::kInvalid;
103+
}
104+
return ParseState::kSuccess;
105+
}
106+
107+
PX_ASSIGN_OR(auto raw_handshake_type, decoder->ExtractBEInt<uint8_t>(),
108+
return ParseState::kInvalid);
109+
auto handshake_type = magic_enum::enum_cast<tls::HandshakeType>(raw_handshake_type);
110+
if (!handshake_type.has_value()) {
111+
return ParseState::kInvalid;
112+
}
113+
frame->handshake_type = handshake_type.value();
114+
115+
PX_ASSIGN_OR(auto handshake_length, decoder->ExtractBEInt<uint24_t>(),
116+
return ParseState::kInvalid);
117+
frame->handshake_length = handshake_length;
118+
119+
PX_ASSIGN_OR(auto raw_handshake_version, decoder->ExtractBEInt<uint16_t>(),
120+
return ParseState::kInvalid);
121+
auto handshake_version = magic_enum::enum_cast<tls::LegacyVersion>(raw_handshake_version);
122+
if (!handshake_version.has_value()) {
123+
return ParseState::kInvalid;
124+
}
125+
frame->handshake_version = handshake_version.value();
126+
127+
// Skip the random struct.
128+
if (!decoder->ExtractBufIgnore(kRandomStructLength).ok()) {
129+
return ParseState::kInvalid;
130+
}
131+
132+
PX_ASSIGN_OR(auto session_id_len, decoder->ExtractBEInt<uint8_t>(), return ParseState::kInvalid);
133+
if (session_id_len > 32) {
134+
return ParseState::kInvalid;
135+
}
136+
137+
if (session_id_len > 0) {
138+
PX_ASSIGN_OR(frame->session_id, decoder->ExtractString(session_id_len),
139+
return ParseState::kInvalid);
140+
}
141+
142+
PX_ASSIGN_OR(auto cipher_suite_length, decoder->ExtractBEInt<uint16_t>(),
143+
return ParseState::kInvalid);
144+
if (frame->handshake_type == HandshakeType::kClientHello) {
145+
if (!decoder->ExtractBufIgnore(cipher_suite_length).ok()) {
146+
return ParseState::kInvalid;
147+
}
148+
}
149+
150+
PX_ASSIGN_OR(auto compression_methods_length, decoder->ExtractBEInt<uint8_t>(),
151+
return ParseState::kInvalid);
152+
if (frame->handshake_type == HandshakeType::kClientHello) {
153+
if (!decoder->ExtractBufIgnore(compression_methods_length).ok()) {
154+
return ParseState::kInvalid;
155+
}
156+
}
157+
158+
// TODO(ddelnano): Test TLS 1.2 and earlier where extensions are not present
159+
PX_ASSIGN_OR(auto extensions_length, decoder->ExtractBEInt<uint16_t>(),
160+
return ParseState::kInvalid);
161+
if (extensions_length == 0) {
162+
return ParseState::kSuccess;
163+
}
164+
165+
while (extensions_length > 0) {
166+
PX_ASSIGN_OR(auto extension_type, decoder->ExtractBEInt<uint16_t>(),
167+
return ParseState::kInvalid);
168+
PX_ASSIGN_OR(auto extension_length, decoder->ExtractBEInt<uint16_t>(),
169+
return ParseState::kInvalid);
170+
171+
if (extension_length > 0) {
172+
if (extension_type == 0x00) {
173+
if (!ExtractSNIExtension(&frame->extensions, decoder).ok()) {
174+
return ParseState::kInvalid;
175+
}
176+
} else {
177+
if (!decoder->ExtractBufIgnore(extension_length).ok()) {
178+
return ParseState::kInvalid;
179+
}
180+
}
181+
}
182+
183+
extensions_length -= kExtensionMinimumLength + extension_length;
184+
}
185+
186+
return ParseState::kSuccess;
187+
}
188+
189+
} // namespace tls
190+
191+
template <>
192+
ParseState ParseFrame(message_type_t, std::string_view* buf, tls::Frame* frame, NoState*) {
193+
// TLS record header is 5 bytes. The size of the record is in bytes 4 and 5.
194+
if (buf->length() < tls::kTLSRecordHeaderLength) {
195+
return ParseState::kNeedsMoreData;
196+
}
197+
uint16_t length = static_cast<uint8_t>((*buf)[3]) << 8 | static_cast<uint8_t>((*buf)[4]);
198+
if (buf->length() < length + tls::kTLSRecordHeaderLength) {
199+
return ParseState::kNeedsMoreData;
200+
}
201+
202+
BinaryDecoder decoder(*buf);
203+
auto parse_result = tls::ParseFullFrame(&decoder, frame);
204+
if (parse_result == ParseState::kSuccess) {
205+
buf->remove_prefix(length + tls::kTLSRecordHeaderLength);
206+
}
207+
return parse_result;
208+
}
209+
210+
template <>
211+
size_t FindFrameBoundary<tls::Frame>(message_type_t, std::string_view, size_t, NoState*) {
212+
// Not implemented.
213+
return std::string::npos;
214+
}
215+
216+
} // namespace protocols
217+
} // namespace stirling
218+
} // namespace px
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2018- The Pixie Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
19+
#pragma once
20+
21+
#include "src/common/base/base.h"
22+
#include "src/stirling/source_connectors/socket_tracer/protocols/common/interface.h"
23+
#include "src/stirling/source_connectors/socket_tracer/protocols/tls/types.h"
24+
#include "src/stirling/utils/binary_decoder.h"
25+
26+
namespace px {
27+
namespace stirling {
28+
namespace protocols {
29+
namespace tls {
30+
31+
ParseState ParseFullFrame(BinaryDecoder* decoder, Frame* frame);
32+
33+
}
34+
35+
template <>
36+
ParseState ParseFrame(message_type_t type, std::string_view* buf, tls::Frame* frame, NoState*);
37+
38+
template <>
39+
size_t FindFrameBoundary<tls::Frame>(message_type_t type, std::string_view buf, size_t start_pos,
40+
NoState*);
41+
42+
} // namespace protocols
43+
} // namespace stirling
44+
} // namespace px

0 commit comments

Comments
 (0)