Skip to content

Commit 20fc8cd

Browse files
committed
Add TLS protocol parser
Signed-off-by: Dom Del Nano <ddelnano@gmail.com>
1 parent 03184cc commit 20fc8cd

File tree

5 files changed

+897
-0
lines changed

5 files changed

+897
-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: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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+
ParseState ParseFullFrame(BinaryDecoder* decoder, Frame* frame) {
67+
PX_ASSIGN_OR(auto raw_content_type, decoder->ExtractBEInt<uint8_t>(),
68+
return ParseState::kInvalid);
69+
auto content_type = magic_enum::enum_cast<tls::ContentType>(raw_content_type);
70+
if (!content_type.has_value()) {
71+
return ParseState::kInvalid;
72+
}
73+
frame->content_type = content_type.value();
74+
75+
PX_ASSIGN_OR(auto legacy_version, decoder->ExtractBEInt<uint16_t>(), return ParseState::kInvalid);
76+
auto lv = magic_enum::enum_cast<tls::LegacyVersion>(legacy_version);
77+
if (!lv.has_value()) {
78+
return ParseState::kInvalid;
79+
}
80+
frame->legacy_version = lv.value();
81+
82+
PX_ASSIGN_OR(frame->length, decoder->ExtractBEInt<uint16_t>(), return ParseState::kInvalid);
83+
84+
if (frame->content_type == tls::ContentType::kApplicationData ||
85+
frame->content_type == tls::ContentType::kChangeCipherSpec ||
86+
frame->content_type == tls::ContentType::kAlert ||
87+
frame->content_type == tls::ContentType::kHeartbeat) {
88+
if (!decoder->ExtractBufIgnore(frame->length).ok()) {
89+
return ParseState::kInvalid;
90+
}
91+
return ParseState::kSuccess;
92+
}
93+
94+
PX_ASSIGN_OR(auto raw_handshake_type, decoder->ExtractBEInt<uint8_t>(),
95+
return ParseState::kInvalid);
96+
auto handshake_type = magic_enum::enum_cast<tls::HandshakeType>(raw_handshake_type);
97+
if (!handshake_type.has_value()) {
98+
return ParseState::kInvalid;
99+
}
100+
frame->handshake_type = handshake_type.value();
101+
102+
PX_ASSIGN_OR(auto handshake_length, decoder->ExtractBEInt<uint24_t>(),
103+
return ParseState::kInvalid);
104+
frame->handshake_length = handshake_length;
105+
106+
PX_ASSIGN_OR(auto raw_handshake_version, decoder->ExtractBEInt<uint16_t>(),
107+
return ParseState::kInvalid);
108+
auto handshake_version = magic_enum::enum_cast<tls::LegacyVersion>(raw_handshake_version);
109+
if (!handshake_version.has_value()) {
110+
return ParseState::kInvalid;
111+
}
112+
frame->handshake_version = handshake_version.value();
113+
114+
// Skip the random struct.
115+
if (!decoder->ExtractBufIgnore(kRandomStructLength).ok()) {
116+
return ParseState::kInvalid;
117+
}
118+
119+
PX_ASSIGN_OR(auto session_id_len, decoder->ExtractBEInt<uint8_t>(), return ParseState::kInvalid);
120+
if (session_id_len > 32) {
121+
return ParseState::kInvalid;
122+
}
123+
124+
if (session_id_len > 0) {
125+
PX_ASSIGN_OR(frame->session_id, decoder->ExtractString(session_id_len),
126+
return ParseState::kInvalid);
127+
}
128+
129+
PX_ASSIGN_OR(auto cipher_suite_length, decoder->ExtractBEInt<uint16_t>(),
130+
return ParseState::kInvalid);
131+
if (frame->handshake_type == HandshakeType::kClientHello) {
132+
if (!decoder->ExtractBufIgnore(cipher_suite_length).ok()) {
133+
return ParseState::kInvalid;
134+
}
135+
}
136+
137+
PX_ASSIGN_OR(auto compression_methods_length, decoder->ExtractBEInt<uint8_t>(),
138+
return ParseState::kInvalid);
139+
if (frame->handshake_type == HandshakeType::kClientHello) {
140+
if (!decoder->ExtractBufIgnore(compression_methods_length).ok()) {
141+
return ParseState::kInvalid;
142+
}
143+
}
144+
145+
// TODO(ddelnano): Test TLS 1.2 and earlier where extensions are not present
146+
PX_ASSIGN_OR(auto extensions_length, decoder->ExtractBEInt<uint16_t>(),
147+
return ParseState::kInvalid);
148+
if (extensions_length == 0) {
149+
return ParseState::kSuccess;
150+
}
151+
152+
while (extensions_length > 0) {
153+
PX_ASSIGN_OR(auto extension_type, decoder->ExtractBEInt<uint16_t>(),
154+
return ParseState::kInvalid);
155+
PX_ASSIGN_OR(auto extension_length, decoder->ExtractBEInt<uint16_t>(),
156+
return ParseState::kInvalid);
157+
158+
if (extension_length > 0) {
159+
if (extension_type == 0x00) {
160+
if (!ExtractSNIExtension(&frame->extensions, decoder).ok()) {
161+
return ParseState::kInvalid;
162+
}
163+
} else {
164+
if (!decoder->ExtractBufIgnore(extension_length).ok()) {
165+
return ParseState::kInvalid;
166+
}
167+
}
168+
}
169+
170+
extensions_length -= kExtensionMinimumLength + extension_length;
171+
}
172+
173+
return ParseState::kSuccess;
174+
}
175+
176+
} // namespace tls
177+
178+
template <>
179+
ParseState ParseFrame(message_type_t, std::string_view* buf, tls::Frame* frame, NoState*) {
180+
// TLS record header is 5 bytes. The size of the record is in bytes 4 and 5.
181+
if (buf->length() < tls::kTLSRecordHeaderLength) {
182+
return ParseState::kNeedsMoreData;
183+
}
184+
uint16_t length = static_cast<uint8_t>((*buf)[3]) << 8 | static_cast<uint8_t>((*buf)[4]);
185+
if (buf->length() < length + tls::kTLSRecordHeaderLength) {
186+
return ParseState::kNeedsMoreData;
187+
}
188+
189+
BinaryDecoder decoder(*buf);
190+
auto parse_result = tls::ParseFullFrame(&decoder, frame);
191+
if (parse_result == ParseState::kSuccess) {
192+
buf->remove_prefix(length + tls::kTLSRecordHeaderLength);
193+
}
194+
return parse_result;
195+
}
196+
197+
template <>
198+
size_t FindFrameBoundary<tls::Frame>(message_type_t, std::string_view, size_t, NoState*) {
199+
// Not implemented.
200+
return std::string::npos;
201+
}
202+
203+
} // namespace protocols
204+
} // namespace stirling
205+
} // 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)