tttls1.3 0.2.15 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +6 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +5 -1
- data/.ruby-version +1 -0
- data/Gemfile +3 -1
- data/README.md +6 -2
- data/example/https_client.rb +2 -1
- data/example/https_server.rb +3 -2
- data/lib/tttls1.3/client.rb +116 -42
- data/lib/tttls1.3/connection.rb +24 -19
- data/lib/tttls1.3/message/client_hello.rb +1 -0
- data/lib/tttls1.3/message/compressed_certificate.rb +82 -0
- data/lib/tttls1.3/message/end_of_early_data.rb +8 -1
- data/lib/tttls1.3/message/extension/alpn.rb +5 -2
- data/lib/tttls1.3/message/extension/compress_certificate.rb +58 -0
- data/lib/tttls1.3/message/extension/signature_algorithms.rb +2 -2
- data/lib/tttls1.3/message/extension/supported_groups.rb +2 -2
- data/lib/tttls1.3/message/extensions.rb +4 -0
- data/lib/tttls1.3/message/record.rb +28 -16
- data/lib/tttls1.3/message.rb +22 -20
- data/lib/tttls1.3/server.rb +89 -27
- data/lib/tttls1.3/sslkeylogfile.rb +87 -0
- data/lib/tttls1.3/transcript.rb +3 -7
- data/lib/tttls1.3/version.rb +1 -1
- data/lib/tttls1.3.rb +1 -0
- data/spec/client_spec.rb +28 -19
- data/spec/compress_certificate_spec.rb +54 -0
- data/spec/connection_spec.rb +22 -15
- data/spec/end_of_early_data_spec.rb +28 -0
- data/spec/key_schedule_spec.rb +48 -25
- data/spec/record_spec.rb +2 -2
- data/spec/server_spec.rb +23 -11
- data/spec/spec_helper.rb +4 -0
- data/spec/transcript_spec.rb +34 -20
- data/tttls1.3.gemspec +1 -1
- metadata +12 -4
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
using Refinements
|
6
|
+
module Message
|
7
|
+
class CompressedCertificate
|
8
|
+
attr_reader :msg_type
|
9
|
+
attr_reader :certificate_message
|
10
|
+
attr_reader :algorithm
|
11
|
+
|
12
|
+
# @param certificate_message [TTTLS13::Message::Certificate]
|
13
|
+
# @param algorithm [CertificateCompressionAlgorithm]
|
14
|
+
def initialize(certificate_message:, algorithm:)
|
15
|
+
@msg_type = HandshakeType::COMPRESSED_CERTIFICATE
|
16
|
+
@certificate_message = certificate_message
|
17
|
+
@algorithm = algorithm
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [String]
|
21
|
+
def serialize
|
22
|
+
binary = ''
|
23
|
+
binary += @algorithm
|
24
|
+
ct_bin = @certificate_message.serialize[4..]
|
25
|
+
binary += ct_bin.length.to_uint24
|
26
|
+
case @algorithm
|
27
|
+
when Extension::CertificateCompressionAlgorithm::ZLIB
|
28
|
+
binary += Zlib::Deflate.deflate(ct_bin).prefix_uint24_length
|
29
|
+
else # TODO: orig_msgs, ZSTD
|
30
|
+
raise Error::ErrorAlerts, :internal_error
|
31
|
+
end
|
32
|
+
|
33
|
+
@msg_type + binary.prefix_uint24_length
|
34
|
+
end
|
35
|
+
|
36
|
+
alias fragment serialize
|
37
|
+
|
38
|
+
# @param binary [String]
|
39
|
+
#
|
40
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
41
|
+
#
|
42
|
+
# @return [TTTLS13::Message::CompressedCertificate]
|
43
|
+
# rubocop: disable Metrics/AbcSize
|
44
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
45
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
46
|
+
def self.deserialize(binary)
|
47
|
+
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
48
|
+
raise Error::ErrorAlerts, :decode_error if binary.length < 5
|
49
|
+
raise Error::ErrorAlerts, :internal_error \
|
50
|
+
unless binary[0] == HandshakeType::COMPRESSED_CERTIFICATE
|
51
|
+
|
52
|
+
msg_len = Convert.bin2i(binary.slice(1, 3))
|
53
|
+
algorithm = binary.slice(4, 2)
|
54
|
+
uncompressed_length = Convert.bin2i(binary.slice(6, 3))
|
55
|
+
ccm_len = Convert.bin2i(binary.slice(9, 3))
|
56
|
+
ct_bin = ''
|
57
|
+
case algorithm
|
58
|
+
when Extension::CertificateCompressionAlgorithm::ZLIB
|
59
|
+
ct_bin = Zlib::Inflate.inflate(binary.slice(12, ccm_len))
|
60
|
+
else # TODO: BROTLI, ZSTD
|
61
|
+
raise Error::ErrorAlerts, :bad_certificate
|
62
|
+
end
|
63
|
+
|
64
|
+
raise Error::ErrorAlerts, :bad_certificate \
|
65
|
+
unless ct_bin.length == uncompressed_length
|
66
|
+
raise Error::ErrorAlerts, :decode_error \
|
67
|
+
unless ccm_len + 12 == binary.length && msg_len + 4 == binary.length
|
68
|
+
|
69
|
+
certificate_message = Certificate.deserialize(
|
70
|
+
HandshakeType::CERTIFICATE + ct_bin.prefix_uint24_length
|
71
|
+
)
|
72
|
+
CompressedCertificate.new(
|
73
|
+
certificate_message: certificate_message,
|
74
|
+
algorithm: algorithm
|
75
|
+
)
|
76
|
+
end
|
77
|
+
# rubocop: enable Metrics/AbcSize
|
78
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
79
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -2,11 +2,18 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module TTTLS13
|
5
|
+
using Refinements
|
5
6
|
module Message
|
6
7
|
class EndOfEarlyData
|
8
|
+
attr_reader :msg_type
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@msg_type = HandshakeType::END_OF_EARLY_DATA
|
12
|
+
end
|
13
|
+
|
7
14
|
# @return [String]
|
8
15
|
def serialize
|
9
|
-
''
|
16
|
+
@msg_type + ''.prefix_uint24_length
|
10
17
|
end
|
11
18
|
|
12
19
|
# @param binary [String]
|
@@ -27,9 +27,12 @@ module TTTLS13
|
|
27
27
|
|
28
28
|
# @return [String]
|
29
29
|
def serialize
|
30
|
-
binary = @protocol_name_list
|
30
|
+
binary = @protocol_name_list
|
31
|
+
.map(&:prefix_uint8_length)
|
32
|
+
.join
|
33
|
+
.prefix_uint16_length
|
31
34
|
|
32
|
-
@extension_type + binary.prefix_uint16_length
|
35
|
+
@extension_type + binary.prefix_uint16_length
|
33
36
|
end
|
34
37
|
|
35
38
|
# @param binary [String]
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
using Refinements
|
6
|
+
module Message
|
7
|
+
module Extension
|
8
|
+
module CertificateCompressionAlgorithm
|
9
|
+
ZLIB = "\x00\x01"
|
10
|
+
# BROTLI = "\x00\x02" # UNSUPPORTED
|
11
|
+
# ZSTD = "\x00\x03" # UNSUPPORTED
|
12
|
+
end
|
13
|
+
|
14
|
+
# https://tools.ietf.org/html/rfc8879
|
15
|
+
class CompressCertificate
|
16
|
+
attr_reader :extension_type
|
17
|
+
attr_reader :algorithms
|
18
|
+
|
19
|
+
# @param algorithms [Array of CertificateCompressionAlgorithm]
|
20
|
+
#
|
21
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# CompressCertificate([CertificateCompressionAlgorithm::ZLIB])
|
25
|
+
def initialize(algorithms)
|
26
|
+
@extension_type = ExtensionType::COMPRESS_CERTIFICATE
|
27
|
+
@algorithms = algorithms || []
|
28
|
+
raise Error::ErrorAlerts, :internal_error \
|
29
|
+
if @algorithms.join.length < 2 ||
|
30
|
+
@algorithms.join.length > 2**8 - 2
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String]
|
34
|
+
def serialize
|
35
|
+
binary = @algorithms.join.prefix_uint8_length
|
36
|
+
|
37
|
+
@extension_type + binary.prefix_uint16_length
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param binary [String]
|
41
|
+
#
|
42
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
43
|
+
#
|
44
|
+
# @return [TTTLS13::Message::Extension::CompressCertificate, nil]
|
45
|
+
def self.deserialize(binary)
|
46
|
+
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
47
|
+
|
48
|
+
return nil if binary.length < 3
|
49
|
+
|
50
|
+
al_len = Convert.bin2i(binary.slice(0, 1))
|
51
|
+
return nil if binary.length != al_len + 1
|
52
|
+
|
53
|
+
CompressCertificate.new(binary.slice(1, al_len + 1).scan(/.{2}/m))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -35,9 +35,9 @@ module TTTLS13
|
|
35
35
|
|
36
36
|
# @return [String]
|
37
37
|
def serialize
|
38
|
-
binary = @supported_signature_algorithms.join
|
38
|
+
binary = @supported_signature_algorithms.join.prefix_uint16_length
|
39
39
|
|
40
|
-
@extension_type + binary.prefix_uint16_length
|
40
|
+
@extension_type + binary.prefix_uint16_length
|
41
41
|
end
|
42
42
|
|
43
43
|
# @param binary [String]
|
@@ -21,9 +21,9 @@ module TTTLS13
|
|
21
21
|
|
22
22
|
# @return [String]
|
23
23
|
def serialize
|
24
|
-
binary = @named_group_list.join
|
24
|
+
binary = @named_group_list.join.prefix_uint16_length
|
25
25
|
|
26
|
-
@extension_type + binary.prefix_uint16_length
|
26
|
+
@extension_type + binary.prefix_uint16_length
|
27
27
|
end
|
28
28
|
|
29
29
|
# @param binary [String]
|
@@ -121,6 +121,7 @@ module TTTLS13
|
|
121
121
|
# @return [TTTLS13::Message::Extension::$Object, nil]
|
122
122
|
# rubocop: disable Metrics/CyclomaticComplexity
|
123
123
|
# rubocop: disable Metrics/MethodLength
|
124
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
124
125
|
def deserialize_extension(binary, extension_type, msg_type)
|
125
126
|
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
126
127
|
|
@@ -143,6 +144,8 @@ module TTTLS13
|
|
143
144
|
Extension::SignatureAlgorithms.deserialize(binary)
|
144
145
|
when ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION
|
145
146
|
Extension::Alpn.deserialize(binary)
|
147
|
+
when ExtensionType::COMPRESS_CERTIFICATE
|
148
|
+
Extension::CompressCertificate.deserialize(binary)
|
146
149
|
when ExtensionType::RECORD_SIZE_LIMIT
|
147
150
|
Extension::RecordSizeLimit.deserialize(binary)
|
148
151
|
when ExtensionType::PRE_SHARED_KEY
|
@@ -165,6 +168,7 @@ module TTTLS13
|
|
165
168
|
end
|
166
169
|
# rubocop: enable Metrics/CyclomaticComplexity
|
167
170
|
# rubocop: enable Metrics/MethodLength
|
171
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
168
172
|
end
|
169
173
|
end
|
170
174
|
end
|
@@ -11,23 +11,19 @@ module TTTLS13
|
|
11
11
|
attr_reader :type
|
12
12
|
attr_reader :legacy_record_version
|
13
13
|
attr_reader :messages
|
14
|
-
attr_reader :surplus_binary
|
15
14
|
attr_reader :cipher
|
16
15
|
|
17
16
|
# @param type [TTTLS13::Message::ContentType]
|
18
17
|
# @param legacy_record_version [TTTLS13::Message::ProtocolVersion]
|
19
18
|
# @param messages [Array of TTTLS13::Message::$Object]
|
20
|
-
# @param surplus_binary [String]
|
21
19
|
# @param cipher [TTTLS13::Cryptograph::$Object]
|
22
20
|
def initialize(type:,
|
23
21
|
legacy_record_version: ProtocolVersion::TLS_1_2,
|
24
22
|
messages:,
|
25
|
-
surplus_binary: '',
|
26
23
|
cipher:)
|
27
24
|
@type = type
|
28
25
|
@legacy_record_version = legacy_record_version
|
29
26
|
@messages = messages
|
30
|
-
@surplus_binary = surplus_binary
|
31
27
|
@cipher = cipher
|
32
28
|
end
|
33
29
|
|
@@ -40,7 +36,7 @@ module TTTLS13
|
|
40
36
|
#
|
41
37
|
# @return [String]
|
42
38
|
def serialize(record_size_limit = DEFAULT_RECORD_SIZE_LIMIT)
|
43
|
-
tlsplaintext = @messages.map(&:serialize).join
|
39
|
+
tlsplaintext = @messages.map(&:serialize).join
|
44
40
|
if @cipher.is_a?(Cryptograph::Aead)
|
45
41
|
max = @cipher.tlsplaintext_length_limit(record_size_limit)
|
46
42
|
fragments = tlsplaintext.scan(/.{1,#{max}}/m)
|
@@ -66,6 +62,8 @@ module TTTLS13
|
|
66
62
|
# @raise [TTTLS13::Error::ErrorAlerts]
|
67
63
|
#
|
68
64
|
# @return [TTTLS13::Message::Record]
|
65
|
+
# @return [Array of String]
|
66
|
+
# @return [String]
|
69
67
|
# rubocop: disable Metrics/AbcSize
|
70
68
|
# rubocop: disable Metrics/CyclomaticComplexity
|
71
69
|
# rubocop: disable Metrics/PerceivedComplexity
|
@@ -93,13 +91,17 @@ module TTTLS13
|
|
93
91
|
fragment, inner_type = cipher.decrypt(fragment, binary.slice(0, 5))
|
94
92
|
end
|
95
93
|
|
96
|
-
messages, surplus_binary = deserialize_fragment(
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
94
|
+
messages, orig_msgs, surplus_binary = deserialize_fragment(
|
95
|
+
buffered + fragment,
|
96
|
+
inner_type || type
|
97
|
+
)
|
98
|
+
record = Record.new(
|
99
|
+
type: type,
|
100
|
+
legacy_record_version: legacy_record_version,
|
101
|
+
messages: messages,
|
102
|
+
cipher: cipher
|
103
|
+
)
|
104
|
+
[record, orig_msgs, surplus_binary]
|
103
105
|
end
|
104
106
|
# rubocop: enable Metrics/AbcSize
|
105
107
|
# rubocop: enable Metrics/CyclomaticComplexity
|
@@ -116,6 +118,7 @@ module TTTLS13
|
|
116
118
|
Message::ServerHello,
|
117
119
|
Message::EncryptedExtensions,
|
118
120
|
Message::Certificate,
|
121
|
+
Message::CompressedCertificate,
|
119
122
|
Message::CertificateVerify,
|
120
123
|
Message::Finished,
|
121
124
|
Message::EndOfEarlyData,
|
@@ -152,20 +155,24 @@ module TTTLS13
|
|
152
155
|
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
153
156
|
|
154
157
|
surplus_binary = ''
|
158
|
+
orig_msgs = []
|
155
159
|
case type
|
156
160
|
when ContentType::HANDSHAKE
|
157
|
-
messages, surplus_binary = deserialize_handshake(binary)
|
161
|
+
messages, orig_msgs, surplus_binary = deserialize_handshake(binary)
|
158
162
|
when ContentType::CCS
|
159
163
|
messages = [ChangeCipherSpec.deserialize(binary)]
|
164
|
+
orig_msgs = [binary]
|
160
165
|
when ContentType::APPLICATION_DATA
|
161
166
|
messages = [ApplicationData.deserialize(binary)]
|
167
|
+
orig_msgs = [binary]
|
162
168
|
when ContentType::ALERT
|
163
169
|
messages = [Alert.deserialize(binary)]
|
170
|
+
orig_msgs = [binary]
|
164
171
|
else
|
165
172
|
raise Error::ErrorAlerts, :unexpected_message
|
166
173
|
end
|
167
174
|
|
168
|
-
[messages, surplus_binary]
|
175
|
+
[messages, orig_msgs, surplus_binary]
|
169
176
|
end
|
170
177
|
|
171
178
|
# @param binary [String]
|
@@ -173,11 +180,13 @@ module TTTLS13
|
|
173
180
|
# @raise [TTTLS13::Error::ErrorAlerts]
|
174
181
|
#
|
175
182
|
# @return [Array of TTTLS13::Message::$Object]
|
183
|
+
# @return [Array of String]
|
176
184
|
# @return [String]
|
177
185
|
def deserialize_handshake(binary)
|
178
186
|
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
179
187
|
|
180
188
|
handshakes = []
|
189
|
+
orig_msgs = []
|
181
190
|
i = 0
|
182
191
|
while i < binary.length
|
183
192
|
# Handshake.length is kind of uint24 and Record.length is kind of
|
@@ -185,18 +194,19 @@ module TTTLS13
|
|
185
194
|
if binary.length < 4 + i ||
|
186
195
|
binary.length < 4 + i + Convert.bin2i(binary.slice(i + 1, 3))
|
187
196
|
surplus_binary = binary[i..]
|
188
|
-
return [handshakes, surplus_binary]
|
197
|
+
return [handshakes, orig_msgs, surplus_binary]
|
189
198
|
end
|
190
199
|
|
191
200
|
msg_len = Convert.bin2i(binary.slice(i + 1, 3))
|
192
201
|
msg_bin = binary.slice(i, msg_len + 4)
|
202
|
+
orig_msgs << msg_bin
|
193
203
|
message = do_deserialize_handshake(msg_bin)
|
194
204
|
i += msg_len + 4
|
195
205
|
handshakes << message
|
196
206
|
end
|
197
207
|
|
198
208
|
surplus_binary = binary[i..]
|
199
|
-
[handshakes, surplus_binary]
|
209
|
+
[handshakes, orig_msgs, surplus_binary]
|
200
210
|
end
|
201
211
|
|
202
212
|
# @param binary [String]
|
@@ -226,6 +236,8 @@ module TTTLS13
|
|
226
236
|
NewSessionTicket.deserialize(binary)
|
227
237
|
when HandshakeType::END_OF_EARLY_DATA
|
228
238
|
EndOfEarlyData.deserialize(binary)
|
239
|
+
when HandshakeType::COMPRESSED_CERTIFICATE
|
240
|
+
CompressedCertificate.deserialize(binary)
|
229
241
|
else
|
230
242
|
raise Error::ErrorAlerts, :unexpected_message
|
231
243
|
end
|
data/lib/tttls1.3/message.rb
CHANGED
@@ -21,26 +21,27 @@ module TTTLS13
|
|
21
21
|
DEFAULT_VERSIONS = [ProtocolVersion::TLS_1_3].freeze
|
22
22
|
|
23
23
|
module HandshakeType
|
24
|
-
HELLO_REQUEST
|
25
|
-
CLIENT_HELLO
|
26
|
-
SERVER_HELLO
|
27
|
-
HELLO_VERIFY_REQUEST
|
28
|
-
NEW_SESSION_TICKET
|
29
|
-
END_OF_EARLY_DATA
|
30
|
-
HELLO_RETRY_REQUEST
|
31
|
-
ENCRYPTED_EXTENSIONS
|
32
|
-
CERTIFICATE
|
33
|
-
SERVER_KEY_EXCHANGE
|
34
|
-
CERTIFICATE_REQUEST
|
35
|
-
SERVER_HELLO_DONE
|
36
|
-
CERTIFICATE_VERIFY
|
37
|
-
CLIENT_KEY_EXCHANGE
|
38
|
-
FINISHED
|
39
|
-
CERTIFICATE_URL
|
40
|
-
CERTIFICATE_STATUS
|
41
|
-
SUPPLEMENTAL_DATA
|
42
|
-
KEY_UPDATE
|
43
|
-
|
24
|
+
HELLO_REQUEST = "\x00" # RESERVED
|
25
|
+
CLIENT_HELLO = "\x01"
|
26
|
+
SERVER_HELLO = "\x02"
|
27
|
+
HELLO_VERIFY_REQUEST = "\x03" # RESERVED
|
28
|
+
NEW_SESSION_TICKET = "\x04"
|
29
|
+
END_OF_EARLY_DATA = "\x05"
|
30
|
+
HELLO_RETRY_REQUEST = "\x06" # RESERVED
|
31
|
+
ENCRYPTED_EXTENSIONS = "\x08"
|
32
|
+
CERTIFICATE = "\x0b"
|
33
|
+
SERVER_KEY_EXCHANGE = "\x0c" # RESERVED
|
34
|
+
CERTIFICATE_REQUEST = "\x0d"
|
35
|
+
SERVER_HELLO_DONE = "\x0e" # RESERVED
|
36
|
+
CERTIFICATE_VERIFY = "\x0f"
|
37
|
+
CLIENT_KEY_EXCHANGE = "\x10" # RESERVED
|
38
|
+
FINISHED = "\x14"
|
39
|
+
CERTIFICATE_URL = "\x15" # RESERVED
|
40
|
+
CERTIFICATE_STATUS = "\x16" # RESERVED
|
41
|
+
SUPPLEMENTAL_DATA = "\x17" # RESERVED
|
42
|
+
KEY_UPDATE = "\x18"
|
43
|
+
COMPRESSED_CERTIFICATE = "\x19"
|
44
|
+
MESSAGE_HASH = "\xfe"
|
44
45
|
end
|
45
46
|
|
46
47
|
module ExtensionType
|
@@ -56,6 +57,7 @@ module TTTLS13
|
|
56
57
|
CLIENT_CERTIFICATE_TYPE = "\x00\x13"
|
57
58
|
SERVER_CERTIFICATE_TYPE = "\x00\x14"
|
58
59
|
PADDING = "\x00\x15"
|
60
|
+
COMPRESS_CERTIFICATE = "\x00\x1b"
|
59
61
|
RECORD_SIZE_LIMIT = "\x00\x1c"
|
60
62
|
PWD_PROTECT = "\x00\x1d"
|
61
63
|
PWD_CLEAR = "\x00\x1e"
|