tttls1.3 0.1.0
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 +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.travis.yml +8 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +133 -0
- data/example/helper.rb +17 -0
- data/example/https_client.rb +32 -0
- data/example/https_client_using_0rtt.rb +64 -0
- data/example/https_client_using_hrr.rb +35 -0
- data/example/https_client_using_ticket.rb +56 -0
- data/lib/tttls1.3/cipher_suites.rb +102 -0
- data/lib/tttls1.3/client.rb +745 -0
- data/lib/tttls1.3/connection.rb +380 -0
- data/lib/tttls1.3/cryptograph/aead.rb +118 -0
- data/lib/tttls1.3/cryptograph/passer.rb +22 -0
- data/lib/tttls1.3/cryptograph.rb +3 -0
- data/lib/tttls1.3/error.rb +22 -0
- data/lib/tttls1.3/key_schedule.rb +242 -0
- data/lib/tttls1.3/message/alert.rb +86 -0
- data/lib/tttls1.3/message/application_data.rb +27 -0
- data/lib/tttls1.3/message/certificate.rb +121 -0
- data/lib/tttls1.3/message/certificate_verify.rb +59 -0
- data/lib/tttls1.3/message/change_cipher_spec.rb +26 -0
- data/lib/tttls1.3/message/client_hello.rb +100 -0
- data/lib/tttls1.3/message/encrypted_extensions.rb +65 -0
- data/lib/tttls1.3/message/end_of_early_data.rb +29 -0
- data/lib/tttls1.3/message/extension/alpn.rb +70 -0
- data/lib/tttls1.3/message/extension/cookie.rb +47 -0
- data/lib/tttls1.3/message/extension/early_data_indication.rb +58 -0
- data/lib/tttls1.3/message/extension/key_share.rb +236 -0
- data/lib/tttls1.3/message/extension/pre_shared_key.rb +205 -0
- data/lib/tttls1.3/message/extension/psk_key_exchange_modes.rb +54 -0
- data/lib/tttls1.3/message/extension/record_size_limit.rb +46 -0
- data/lib/tttls1.3/message/extension/server_name.rb +91 -0
- data/lib/tttls1.3/message/extension/signature_algorithms.rb +69 -0
- data/lib/tttls1.3/message/extension/signature_algorithms_cert.rb +25 -0
- data/lib/tttls1.3/message/extension/status_request.rb +106 -0
- data/lib/tttls1.3/message/extension/supported_groups.rb +145 -0
- data/lib/tttls1.3/message/extension/supported_versions.rb +98 -0
- data/lib/tttls1.3/message/extension/unknown_extension.rb +38 -0
- data/lib/tttls1.3/message/extensions.rb +173 -0
- data/lib/tttls1.3/message/finished.rb +44 -0
- data/lib/tttls1.3/message/new_session_ticket.rb +89 -0
- data/lib/tttls1.3/message/record.rb +232 -0
- data/lib/tttls1.3/message/server_hello.rb +116 -0
- data/lib/tttls1.3/message.rb +48 -0
- data/lib/tttls1.3/sequence_number.rb +31 -0
- data/lib/tttls1.3/signature_scheme.rb +31 -0
- data/lib/tttls1.3/transcript.rb +69 -0
- data/lib/tttls1.3/utils.rb +91 -0
- data/lib/tttls1.3/version.rb +5 -0
- data/lib/tttls1.3.rb +16 -0
- data/spec/aead_spec.rb +95 -0
- data/spec/alert_spec.rb +54 -0
- data/spec/alpn_spec.rb +55 -0
- data/spec/application_data_spec.rb +26 -0
- data/spec/certificate_spec.rb +55 -0
- data/spec/certificate_verify_spec.rb +51 -0
- data/spec/change_cipher_spec_spec.rb +26 -0
- data/spec/cipher_suites_spec.rb +39 -0
- data/spec/client_hello_spec.rb +83 -0
- data/spec/client_spec.rb +319 -0
- data/spec/connection_spec.rb +114 -0
- data/spec/cookie_spec.rb +98 -0
- data/spec/early_data_indication_spec.rb +64 -0
- data/spec/encrypted_extensions_spec.rb +94 -0
- data/spec/error_spec.rb +18 -0
- data/spec/extensions_spec.rb +170 -0
- data/spec/finished_spec.rb +55 -0
- data/spec/key_schedule_spec.rb +198 -0
- data/spec/key_share_spec.rb +199 -0
- data/spec/new_session_ticket_spec.rb +80 -0
- data/spec/pre_shared_key_spec.rb +167 -0
- data/spec/psk_key_exchange_modes_spec.rb +45 -0
- data/spec/record_size_limit_spec.rb +61 -0
- data/spec/record_spec.rb +105 -0
- data/spec/server_hello_spec.rb +101 -0
- data/spec/server_name_spec.rb +110 -0
- data/spec/signature_algorithms_cert_spec.rb +73 -0
- data/spec/signature_algorithms_spec.rb +100 -0
- data/spec/spec_helper.rb +872 -0
- data/spec/status_request_spec.rb +73 -0
- data/spec/supported_groups_spec.rb +79 -0
- data/spec/supported_versions_spec.rb +136 -0
- data/spec/transcript_spec.rb +69 -0
- data/spec/unknown_extension_spec.rb +90 -0
- data/spec/utils_spec.rb +215 -0
- data/tttls1.3.gemspec +25 -0
- metadata +197 -0
@@ -0,0 +1,232 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
using Refinements
|
6
|
+
module Message
|
7
|
+
# https://tools.ietf.org/html/rfc8449#section-4
|
8
|
+
DEFAULT_RECORD_SIZE_LIMIT = 2**14 + 1
|
9
|
+
|
10
|
+
# rubocop: disable Metrics/ClassLength
|
11
|
+
class Record
|
12
|
+
attr_reader :type
|
13
|
+
attr_reader :legacy_record_version
|
14
|
+
attr_reader :messages
|
15
|
+
attr_reader :surplus_binary
|
16
|
+
attr_reader :cipher
|
17
|
+
|
18
|
+
# @param type [TTTLS13::Message::ContentType]
|
19
|
+
# @param legacy_record_version [TTTLS13::Message::ProtocolVersion]
|
20
|
+
# @param messages [Array of TTTLS13::Message::$Object]
|
21
|
+
# @param surplus_binary [String]
|
22
|
+
# @param cipher [TTTLS13::Cryptograph::$Object]
|
23
|
+
def initialize(type:,
|
24
|
+
legacy_record_version: ProtocolVersion::TLS_1_2,
|
25
|
+
messages: [],
|
26
|
+
surplus_binary: '',
|
27
|
+
cipher:)
|
28
|
+
@type = type
|
29
|
+
@legacy_record_version = legacy_record_version
|
30
|
+
@messages = messages
|
31
|
+
@surplus_binary = surplus_binary
|
32
|
+
@cipher = cipher
|
33
|
+
end
|
34
|
+
|
35
|
+
# NOTE:
|
36
|
+
# serialize joins messages.
|
37
|
+
# If serialize is received Server Parameters(EE, CT, CV),
|
38
|
+
# it returns one binary.
|
39
|
+
#
|
40
|
+
# @param record_size_limit [Integer]
|
41
|
+
#
|
42
|
+
# @return [String]
|
43
|
+
def serialize(record_size_limit = DEFAULT_RECORD_SIZE_LIMIT)
|
44
|
+
tlsplaintext = @messages.map(&:serialize).join + @surplus_binary
|
45
|
+
if messages_type == ContentType::APPLICATION_DATA
|
46
|
+
max = cipher.tlsplaintext_length_limit(record_size_limit)
|
47
|
+
fragments = tlsplaintext.scan(/.{1,#{max}}/m)
|
48
|
+
else
|
49
|
+
fragments = [tlsplaintext]
|
50
|
+
end
|
51
|
+
fragments = [''] if fragments.empty?
|
52
|
+
|
53
|
+
binary = ''
|
54
|
+
fragments.each do |s|
|
55
|
+
binary += @type + @legacy_record_version
|
56
|
+
binary += @cipher.encrypt(s, messages_type).prefix_uint16_length
|
57
|
+
end
|
58
|
+
|
59
|
+
binary
|
60
|
+
end
|
61
|
+
|
62
|
+
# NOTE:
|
63
|
+
# If previous Record has surplus_binary,
|
64
|
+
# surplus_binary should is given to Record.deserialize as buffered.
|
65
|
+
#
|
66
|
+
# @param binary [String]
|
67
|
+
# @param cipher [TTTLS13::Cryptograph::$Object]
|
68
|
+
# @param buffered [String] surplus_binary
|
69
|
+
#
|
70
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
71
|
+
#
|
72
|
+
# @return [TTTLS13::Message::Record, String]
|
73
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
74
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
75
|
+
def self.deserialize(binary, cipher, buffered = '')
|
76
|
+
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
77
|
+
raise Error::ErrorAlerts, :decode_error if binary.length < 5
|
78
|
+
|
79
|
+
type = binary[0]
|
80
|
+
legacy_record_version = binary.slice(1, 2)
|
81
|
+
fragment_len = Convert.bin2i(binary.slice(3, 2))
|
82
|
+
raise Error::ErrorAlerts, :record_overflow \
|
83
|
+
if (cipher.is_a?(Cryptograph::Passer) && fragment_len > 2**14) ||
|
84
|
+
(cipher.is_a?(Cryptograph::Aead) && fragment_len > 2**14 + 256)
|
85
|
+
|
86
|
+
fragment = binary.slice(5, fragment_len)
|
87
|
+
raise Error::ErrorAlerts, :decode_error \
|
88
|
+
unless binary.length == 5 + fragment_len
|
89
|
+
|
90
|
+
if type == ContentType::APPLICATION_DATA
|
91
|
+
fragment, inner_type = cipher.decrypt(fragment, binary.slice(0, 5))
|
92
|
+
end
|
93
|
+
messages, surplus_binary = deserialize_fragment(buffered + fragment,
|
94
|
+
inner_type || type)
|
95
|
+
|
96
|
+
Record.new(type: type,
|
97
|
+
legacy_record_version: legacy_record_version,
|
98
|
+
messages: messages,
|
99
|
+
surplus_binary: surplus_binary,
|
100
|
+
cipher: cipher)
|
101
|
+
end
|
102
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
103
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
108
|
+
#
|
109
|
+
# @return [TTTLS13::Message::ContentType]
|
110
|
+
def messages_type
|
111
|
+
types = @messages.map(&:class).uniq
|
112
|
+
raise Error::ErrorAlerts, :internal_error unless types.length == 1
|
113
|
+
|
114
|
+
type = types.first
|
115
|
+
if [Message::ClientHello,
|
116
|
+
Message::ServerHello,
|
117
|
+
Message::EncryptedExtensions,
|
118
|
+
Message::Certificate,
|
119
|
+
Message::CertificateVerify,
|
120
|
+
Message::Finished,
|
121
|
+
Message::EndOfEarlyData,
|
122
|
+
Message::NewSessionTicket].include?(type)
|
123
|
+
ContentType::HANDSHAKE
|
124
|
+
elsif type == ChangeCipherSpec
|
125
|
+
ContentType::CCS
|
126
|
+
elsif type == Message::ApplicationData
|
127
|
+
ContentType::APPLICATION_DATA
|
128
|
+
elsif type == Message::Alert
|
129
|
+
ContentType::ALERT
|
130
|
+
else
|
131
|
+
raise Error::ErrorAlerts, :internal_error
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class << self
|
136
|
+
private
|
137
|
+
|
138
|
+
# @param binary [String]
|
139
|
+
# @param type [TTTLS13::Message::ContentType]
|
140
|
+
#
|
141
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
142
|
+
#
|
143
|
+
# @return [Array of TTTLS13::Message::$Object, String]
|
144
|
+
# @return [String]
|
145
|
+
def deserialize_fragment(binary, type)
|
146
|
+
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
147
|
+
|
148
|
+
surplus_binary = ''
|
149
|
+
case type
|
150
|
+
when ContentType::HANDSHAKE
|
151
|
+
messages, surplus_binary = deserialize_handshake(binary)
|
152
|
+
when ContentType::CCS
|
153
|
+
messages = [ChangeCipherSpec.deserialize(binary)]
|
154
|
+
when ContentType::APPLICATION_DATA
|
155
|
+
messages = [ApplicationData.deserialize(binary)]
|
156
|
+
when ContentType::ALERT
|
157
|
+
messages = [Alert.deserialize(binary)]
|
158
|
+
else
|
159
|
+
raise Error::ErrorAlerts, :unexpected_message
|
160
|
+
end
|
161
|
+
|
162
|
+
[messages, surplus_binary]
|
163
|
+
end
|
164
|
+
|
165
|
+
# @param binary [String]
|
166
|
+
#
|
167
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
168
|
+
#
|
169
|
+
# @return [Array of TTTLS13::Message::$Object]
|
170
|
+
# @return [String]
|
171
|
+
def deserialize_handshake(binary)
|
172
|
+
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
173
|
+
|
174
|
+
handshakes = []
|
175
|
+
i = 0
|
176
|
+
while i < binary.length
|
177
|
+
# Handshake.length is kind of uint24 and Record.length is kind of
|
178
|
+
# uint16, so Handshake can be longer than Record capacity.
|
179
|
+
if binary.length < 4 + i ||
|
180
|
+
binary.length < 4 + i + Convert.bin2i(binary.slice(i + 1, 3))
|
181
|
+
surplus_binary = binary[i..]
|
182
|
+
return [handshakes, surplus_binary]
|
183
|
+
end
|
184
|
+
|
185
|
+
msg_len = Convert.bin2i(binary.slice(i + 1, 3))
|
186
|
+
msg_bin = binary.slice(i, msg_len + 4)
|
187
|
+
message = do_deserialize_handshake(msg_bin)
|
188
|
+
i += msg_len + 4
|
189
|
+
handshakes << message
|
190
|
+
end
|
191
|
+
|
192
|
+
surplus_binary = binary[i..]
|
193
|
+
[handshakes, surplus_binary]
|
194
|
+
end
|
195
|
+
|
196
|
+
# @param binary [String]
|
197
|
+
#
|
198
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
199
|
+
#
|
200
|
+
# @return [Array of TTTLS13::Message::$Object]
|
201
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
202
|
+
def do_deserialize_handshake(binary)
|
203
|
+
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
204
|
+
raise Error::ErrorAlerts, :decode_error if binary.empty?
|
205
|
+
|
206
|
+
case binary[0]
|
207
|
+
when HandshakeType::CLIENT_HELLO
|
208
|
+
ClientHello.deserialize(binary)
|
209
|
+
when HandshakeType::SERVER_HELLO
|
210
|
+
ServerHello.deserialize(binary)
|
211
|
+
when HandshakeType::ENCRYPTED_EXTENSIONS
|
212
|
+
EncryptedExtensions.deserialize(binary)
|
213
|
+
when HandshakeType::CERTIFICATE
|
214
|
+
Certificate.deserialize(binary)
|
215
|
+
when HandshakeType::CERTIFICATE_VERIFY
|
216
|
+
CertificateVerify.deserialize(binary)
|
217
|
+
when HandshakeType::FINISHED
|
218
|
+
Finished.deserialize(binary)
|
219
|
+
when HandshakeType::NEW_SESSION_TICKET
|
220
|
+
NewSessionTicket.deserialize(binary)
|
221
|
+
when HandshakeType::END_OF_EARLY_DATA
|
222
|
+
EndOfEarlyData.deserialize(binary)
|
223
|
+
else
|
224
|
+
raise Error::ErrorAlerts, :unexpected_message
|
225
|
+
end
|
226
|
+
end
|
227
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
228
|
+
end
|
229
|
+
end
|
230
|
+
# rubocop: enable Metrics/ClassLength
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
using Refinements
|
6
|
+
module Message
|
7
|
+
class ServerHello
|
8
|
+
# special value of the SHA-256 of "HelloRetryRequest"
|
9
|
+
HRR_RANDOM \
|
10
|
+
= "\xcf\x21\xad\x74\xe5\x9a\x61\x11\xbe\x1d\x8c\x02\x1e\x65\xb8\x91" \
|
11
|
+
"\xc2\xa2\x11\x16\x7a\xbb\x8c\x5e\x07\x9e\x09\xe2\xc8\xa8\x33\x9c"
|
12
|
+
|
13
|
+
attr_reader :msg_type
|
14
|
+
attr_reader :legacy_version
|
15
|
+
attr_reader :random
|
16
|
+
attr_reader :legacy_session_id_echo
|
17
|
+
attr_reader :cipher_suite
|
18
|
+
attr_reader :legacy_compression_method
|
19
|
+
attr_reader :extensions
|
20
|
+
|
21
|
+
# @param legacy_version [String]
|
22
|
+
# @param random [String]
|
23
|
+
# @param legacy_session_id_echo [String]
|
24
|
+
# @param cipher_suite [TTTLS13::CipherSuite]
|
25
|
+
# @param legacy_compression_method [String]
|
26
|
+
# @param extensions [TTTLS13::Message::Extensions]
|
27
|
+
# rubocop: disable Metrics/ParameterLists
|
28
|
+
def initialize(legacy_version: ProtocolVersion::TLS_1_2,
|
29
|
+
random: OpenSSL::Random.random_bytes(32),
|
30
|
+
legacy_session_id_echo:,
|
31
|
+
cipher_suite:,
|
32
|
+
legacy_compression_method: "\x00",
|
33
|
+
extensions: Extensions.new)
|
34
|
+
@msg_type = HandshakeType::SERVER_HELLO
|
35
|
+
@legacy_version = legacy_version
|
36
|
+
@random = random
|
37
|
+
@legacy_session_id_echo = legacy_session_id_echo
|
38
|
+
@cipher_suite = cipher_suite
|
39
|
+
@legacy_compression_method = legacy_compression_method
|
40
|
+
@extensions = extensions
|
41
|
+
@hrr = (random == HRR_RANDOM)
|
42
|
+
end
|
43
|
+
# rubocop: enable Metrics/ParameterLists
|
44
|
+
|
45
|
+
# @return [String]
|
46
|
+
def serialize
|
47
|
+
binary = ''
|
48
|
+
binary += @legacy_version
|
49
|
+
binary += @random
|
50
|
+
binary += @legacy_session_id_echo.prefix_uint8_length
|
51
|
+
binary += @cipher_suite
|
52
|
+
binary += @legacy_compression_method
|
53
|
+
binary += @extensions.serialize
|
54
|
+
|
55
|
+
@msg_type + binary.prefix_uint24_length
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param binary [String]
|
59
|
+
#
|
60
|
+
# @raise [TTTLS13::Error::ErrorAlerts]
|
61
|
+
#
|
62
|
+
# @return [TTTLS13::Message::ServerHello]
|
63
|
+
# rubocop: disable Metrics/AbcSize
|
64
|
+
# rubocop: disable Metrics/CyclomaticComplexity
|
65
|
+
# rubocop: disable Metrics/MethodLength
|
66
|
+
# rubocop: disable Metrics/PerceivedComplexity
|
67
|
+
def self.deserialize(binary)
|
68
|
+
raise Error::ErrorAlerts, :internal_error if binary.nil?
|
69
|
+
raise Error::ErrorAlerts, :decode_error if binary.length < 39
|
70
|
+
raise Error::ErrorAlerts, :internal_error \
|
71
|
+
unless binary[0] == HandshakeType::SERVER_HELLO
|
72
|
+
|
73
|
+
msg_len = Convert.bin2i(binary.slice(1, 3))
|
74
|
+
legacy_version = binary.slice(4, 2)
|
75
|
+
random = binary.slice(6, 32)
|
76
|
+
lsid_len = Convert.bin2i(binary[38])
|
77
|
+
legacy_session_id_echo = binary.slice(39, lsid_len)
|
78
|
+
i = 39 + lsid_len
|
79
|
+
cipher_suite = binary.slice(i, 2)
|
80
|
+
i += 2
|
81
|
+
legacy_compression_method = binary[i]
|
82
|
+
i += 1
|
83
|
+
exs_len = Convert.bin2i(binary.slice(i, 2))
|
84
|
+
i += 2
|
85
|
+
exs_bin = binary.slice(i, exs_len)
|
86
|
+
if random == HRR_RANDOM
|
87
|
+
msg_type = HandshakeType::HELLO_RETRY_REQUEST
|
88
|
+
@hrr = true
|
89
|
+
else
|
90
|
+
msg_type = HandshakeType::SERVER_HELLO
|
91
|
+
@hrr = false
|
92
|
+
end
|
93
|
+
extensions = Extensions.deserialize(exs_bin, msg_type)
|
94
|
+
i += exs_len
|
95
|
+
raise Error::ErrorAlerts, :decode_error unless i == msg_len + 4 &&
|
96
|
+
i == binary.length
|
97
|
+
|
98
|
+
ServerHello.new(legacy_version: legacy_version,
|
99
|
+
random: random,
|
100
|
+
legacy_session_id_echo: legacy_session_id_echo,
|
101
|
+
cipher_suite: cipher_suite,
|
102
|
+
legacy_compression_method: legacy_compression_method,
|
103
|
+
extensions: extensions)
|
104
|
+
end
|
105
|
+
# rubocop: enable Metrics/AbcSize
|
106
|
+
# rubocop: enable Metrics/CyclomaticComplexity
|
107
|
+
# rubocop: enable Metrics/MethodLength
|
108
|
+
# rubocop: enable Metrics/PerceivedComplexity
|
109
|
+
|
110
|
+
# @return [Boolean]
|
111
|
+
def hrr?
|
112
|
+
@hrr
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
Dir[File.dirname(__FILE__) + '/message/*.rb'].each { |f| require f }
|
5
|
+
|
6
|
+
module TTTLS13
|
7
|
+
module Message
|
8
|
+
module ContentType
|
9
|
+
INVALID = "\x00"
|
10
|
+
CCS = "\x14"
|
11
|
+
ALERT = "\x15"
|
12
|
+
HANDSHAKE = "\x16"
|
13
|
+
APPLICATION_DATA = "\x17"
|
14
|
+
end
|
15
|
+
|
16
|
+
module ProtocolVersion
|
17
|
+
TLS_1_0 = "\x03\x01"
|
18
|
+
TLS_1_1 = "\x03\x02"
|
19
|
+
TLS_1_2 = "\x03\x03"
|
20
|
+
TLS_1_3 = "\x03\x04"
|
21
|
+
end
|
22
|
+
|
23
|
+
DEFAULT_VERSIONS = [ProtocolVersion::TLS_1_3].freeze
|
24
|
+
|
25
|
+
module HandshakeType
|
26
|
+
HELLO_REQUEST = "\x00" # RESERVED
|
27
|
+
CLIENT_HELLO = "\x01"
|
28
|
+
SERVER_HELLO = "\x02"
|
29
|
+
HELLO_VERIFY_REQUEST = "\x03" # RESERVED
|
30
|
+
NEW_SESSION_TICKET = "\x04"
|
31
|
+
END_OF_EARLY_DATA = "\x05"
|
32
|
+
HELLO_RETRY_REQUEST = "\x06" # RESERVED
|
33
|
+
ENCRYPTED_EXTENSIONS = "\x08"
|
34
|
+
CERTIFICATE = "\x0b"
|
35
|
+
SERVER_KEY_EXCHANGE = "\x0c" # RESERVED
|
36
|
+
CERTIFICATE_REQUEST = "\x0d"
|
37
|
+
SERVER_HELLO_DONE = "\x0e" # RESERVED
|
38
|
+
CERTIFICATE_VERIFY = "\x0f"
|
39
|
+
CLIENT_KEY_EXCHANGE = "\x10" # RESERVED
|
40
|
+
FINISHED = "\x14"
|
41
|
+
CERTIFICATE_URL = "\x15" # RESERVED
|
42
|
+
CERTIFICATE_STATUS = "\x16" # RESERVED
|
43
|
+
SUPPLEMENTAL_DATA = "\x17" # RESERVED
|
44
|
+
KEY_UPDATE = "\x18"
|
45
|
+
MESSAGE_HASH = "\xfe"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
using Refinements
|
6
|
+
class SequenceNumber
|
7
|
+
def initialize
|
8
|
+
@seq_num = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param str [String]
|
12
|
+
# @param iv_len [Integer]
|
13
|
+
#
|
14
|
+
# @return [String]
|
15
|
+
def xor(str, iv_len)
|
16
|
+
l = @seq_num.to_uint64.unpack('C*')
|
17
|
+
l.unshift(0) while l.length < iv_len
|
18
|
+
r = str.unpack('C*')
|
19
|
+
l.zip(r).map { |x, y| (x ^ y).chr }.join
|
20
|
+
end
|
21
|
+
|
22
|
+
def succ
|
23
|
+
@seq_num += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Boolean]
|
27
|
+
def next?
|
28
|
+
@seq_num < 2**64 - 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
module SignatureScheme
|
6
|
+
# RSASSA-PKCS1-v1_5 algorithms
|
7
|
+
RSA_PKCS1_SHA256 = "\x04\x01"
|
8
|
+
RSA_PKCS1_SHA384 = "\x05\x01"
|
9
|
+
RSA_PKCS1_SHA512 = "\x06\x01"
|
10
|
+
# ECDSA algorithms
|
11
|
+
ECDSA_SECP256R1_SHA256 = "\x04\x03"
|
12
|
+
ECDSA_SECP384R1_SHA384 = "\x05\x03"
|
13
|
+
ECDSA_SECP521R1_SHA512 = "\x06\x03"
|
14
|
+
# RSASSA-PSS algorithms with public key OID rsaEncryption
|
15
|
+
RSA_PSS_RSAE_SHA256 = "\x08\x04"
|
16
|
+
RSA_PSS_RSAE_SHA384 = "\x08\x05"
|
17
|
+
RSA_PSS_RSAE_SHA512 = "\x08\x06"
|
18
|
+
# EdDSA algorithms
|
19
|
+
# ED25519 = "\x08\x07" # UNSUPPORTED
|
20
|
+
# ED448 = "\x08\x08" # UNSUPPORTED
|
21
|
+
# RSASSA-PSS algorithms with public key OID RSASSA-PSS
|
22
|
+
RSA_PSS_PSS_SHA256 = "\x08\x09"
|
23
|
+
RSA_PSS_PSS_SHA384 = "\x08\x0a"
|
24
|
+
RSA_PSS_PSS_SHA512 = "\x08\x0b"
|
25
|
+
# Legacy algorithms
|
26
|
+
# RSA_PKCS1_SHA1 = "\x02\x01" # UNSUPPORTED
|
27
|
+
# ECDSA_SHA1 = "\x02\x03" # UNSUPPORTED
|
28
|
+
# Reserved Code Points
|
29
|
+
# private_use "\xfe\x00" ~ "\xff\xff"
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
using Refinements
|
6
|
+
|
7
|
+
CH1 = 0
|
8
|
+
HRR = 1
|
9
|
+
CH = 2
|
10
|
+
SH = 3
|
11
|
+
EE = 4
|
12
|
+
CR = 5
|
13
|
+
CT = 6
|
14
|
+
CV = 7
|
15
|
+
SF = 8
|
16
|
+
EOED = 9
|
17
|
+
CCT = 10
|
18
|
+
CCV = 11
|
19
|
+
CF = 12
|
20
|
+
|
21
|
+
class Transcript < Hash
|
22
|
+
def initialize
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param digest [String] name of digest algorithm
|
27
|
+
# @param end_index [Integer]
|
28
|
+
#
|
29
|
+
# @return [String]
|
30
|
+
def hash(digest, end_index)
|
31
|
+
s = concat_messages(digest, end_index)
|
32
|
+
OpenSSL::Digest.digest(digest, s)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param digest [String] name of digest algorithm
|
36
|
+
# @param end_index [Integer]
|
37
|
+
# @param truncate_bytes [Integer]
|
38
|
+
#
|
39
|
+
# @return [String]
|
40
|
+
def truncate_hash(digest, end_index, truncate_bytes)
|
41
|
+
s = concat_messages(digest, end_index)
|
42
|
+
truncated = s[0...-truncate_bytes]
|
43
|
+
OpenSSL::Digest.digest(digest, truncated)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# @param digest [String] name of digest algorithm
|
49
|
+
# @param end_index [Integer]
|
50
|
+
#
|
51
|
+
# @return [String]
|
52
|
+
def concat_messages(digest, end_index)
|
53
|
+
exc_prefix = ''
|
54
|
+
if include?(HRR)
|
55
|
+
# as an exception to the general rule
|
56
|
+
exc_prefix = Message::HandshakeType::MESSAGE_HASH \
|
57
|
+
+ "\x00\x00" \
|
58
|
+
+ OpenSSL::Digest.new(digest).digest_length.to_uint8 \
|
59
|
+
+ OpenSSL::Digest.digest(digest, self[CH1].serialize) \
|
60
|
+
+ self[HRR].serialize
|
61
|
+
end
|
62
|
+
|
63
|
+
messages = (CH..end_index).to_a.map do |m|
|
64
|
+
include?(m) ? self[m].serialize : ''
|
65
|
+
end
|
66
|
+
exc_prefix + messages.join
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module TTTLS13
|
5
|
+
module Refinements
|
6
|
+
refine Integer do
|
7
|
+
def to_uint8
|
8
|
+
raise Error::ErrorAlerts, :internal_error \
|
9
|
+
if negative? || self >= (1 << 8)
|
10
|
+
|
11
|
+
chr
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_uint16
|
15
|
+
raise Error::ErrorAlerts, :internal_error \
|
16
|
+
if negative? || self >= (1 << 16)
|
17
|
+
|
18
|
+
[
|
19
|
+
self / (1 << 8),
|
20
|
+
self % (1 << 8)
|
21
|
+
].map(&:chr).join
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_uint24
|
25
|
+
raise Error::ErrorAlerts, :internal_error \
|
26
|
+
if negative? || self >= (1 << 24)
|
27
|
+
|
28
|
+
[
|
29
|
+
self / (1 << 16),
|
30
|
+
self % (1 << 16) / (1 << 8),
|
31
|
+
self % (1 << 8)
|
32
|
+
].map(&:chr).join
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_uint32
|
36
|
+
raise Error::ErrorAlerts, :internal_error \
|
37
|
+
if negative? || self >= (1 << 32)
|
38
|
+
|
39
|
+
[
|
40
|
+
self / (1 << 24),
|
41
|
+
self % (1 << 24) / (1 << 16),
|
42
|
+
self % (1 << 16) / (1 << 8),
|
43
|
+
self % (1 << 8)
|
44
|
+
].map(&:chr).join
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_uint64
|
48
|
+
raise Error::ErrorAlerts, :internal_error \
|
49
|
+
if negative? || self >= (1 << 64)
|
50
|
+
|
51
|
+
[
|
52
|
+
self / (1 << 32),
|
53
|
+
self % (1 << 32) / (1 << 24),
|
54
|
+
self % (1 << 24) / (1 << 16),
|
55
|
+
self % (1 << 16) / (1 << 8),
|
56
|
+
self % (1 << 8)
|
57
|
+
].map(&:chr).join
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
refine String do
|
62
|
+
def prefix_uint8_length
|
63
|
+
length.to_uint8 + self
|
64
|
+
end
|
65
|
+
|
66
|
+
def prefix_uint16_length
|
67
|
+
length.to_uint16 + self
|
68
|
+
end
|
69
|
+
|
70
|
+
def prefix_uint24_length
|
71
|
+
length.to_uint24 + self
|
72
|
+
end
|
73
|
+
|
74
|
+
def prefix_uint32_length
|
75
|
+
length.to_uint32 + self
|
76
|
+
end
|
77
|
+
|
78
|
+
def prefix_uint64_length
|
79
|
+
length.to_uint64 + self
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
module Convert
|
85
|
+
class << self
|
86
|
+
def bin2i(binary)
|
87
|
+
binary.unpack('C*').reverse.map.with_index { |x, i| x << 8 * i }.sum
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/tttls1.3.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
require 'tttls1.3/version'
|
6
|
+
require 'tttls1.3/utils'
|
7
|
+
require 'tttls1.3/error'
|
8
|
+
require 'tttls1.3/cipher_suites'
|
9
|
+
require 'tttls1.3/signature_scheme'
|
10
|
+
require 'tttls1.3/cryptograph'
|
11
|
+
require 'tttls1.3/transcript'
|
12
|
+
require 'tttls1.3/key_schedule'
|
13
|
+
require 'tttls1.3/message'
|
14
|
+
require 'tttls1.3/sequence_number'
|
15
|
+
require 'tttls1.3/connection'
|
16
|
+
require 'tttls1.3/client'
|