tttls1.3 0.3.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/.rubocop.yml +3 -0
  4. data/.ruby-version +1 -1
  5. data/Gemfile +1 -0
  6. data/README.md +2 -2
  7. data/example/README.md +1 -1
  8. data/example/helper.rb +22 -0
  9. data/example/https_client.rb +4 -4
  10. data/example/https_client_using_0rtt.rb +6 -5
  11. data/example/https_client_using_ech.rb +5 -6
  12. data/example/https_client_using_grease_ech.rb +3 -5
  13. data/example/https_client_using_grease_psk.rb +8 -16
  14. data/example/https_client_using_hrr.rb +5 -4
  15. data/example/https_client_using_hrr_and_ech.rb +6 -6
  16. data/example/https_client_using_hrr_and_ticket.rb +5 -4
  17. data/example/https_client_using_status_request.rb +4 -5
  18. data/example/https_client_using_ticket.rb +5 -4
  19. data/example/https_server.rb +14 -1
  20. data/lib/tttls1.3/client.rb +205 -418
  21. data/lib/tttls1.3/connection.rb +21 -362
  22. data/lib/tttls1.3/ech.rb +410 -0
  23. data/lib/tttls1.3/endpoint.rb +276 -0
  24. data/lib/tttls1.3/message/certificate_verify.rb +1 -1
  25. data/lib/tttls1.3/message/extension/ech.rb +12 -10
  26. data/lib/tttls1.3/message/extension/signature_algorithms.rb +2 -2
  27. data/lib/tttls1.3/message/extension/supported_versions.rb +3 -3
  28. data/lib/tttls1.3/message/extension/unknown_extension.rb +2 -2
  29. data/lib/tttls1.3/server.rb +125 -63
  30. data/lib/tttls1.3/utils.rb +37 -0
  31. data/lib/tttls1.3/version.rb +1 -1
  32. data/lib/tttls1.3.rb +2 -1
  33. data/spec/client_spec.rb +21 -60
  34. data/spec/ech_spec.rb +39 -0
  35. data/spec/{connection_spec.rb → endpoint_spec.rb} +41 -49
  36. data/spec/server_spec.rb +12 -12
  37. data/tttls1.3.gemspec +1 -1
  38. metadata +8 -7
  39. data/lib/tttls1.3/hpke.rb +0 -91
@@ -0,0 +1,410 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module TTTLS13
5
+ using Refinements
6
+
7
+ SUPPORTED_ECHCONFIG_VERSIONS = ["\xfe\x0d"].freeze
8
+ private_constant :SUPPORTED_ECHCONFIG_VERSIONS
9
+
10
+ # rubocop: disable Metrics/ModuleLength
11
+ module Ech
12
+ # @param inner [TTTLS13::Message::ClientHello]
13
+ # @param ech_config [ECHConfig]
14
+ # @param hpke_cipher_suite_selector [Method]
15
+ #
16
+ # @return [TTTLS13::Message::ClientHello]
17
+ # @return [TTTLS13::Message::ClientHello]
18
+ # @return [TTTLS13::EchState]
19
+ # rubocop: disable Metrics/AbcSize
20
+ def self.offer_ech(inner, ech_config, hpke_cipher_suite_selector)
21
+ return [new_greased_ch(inner, new_grease_ech), nil, nil] \
22
+ if ech_config.nil? ||
23
+ !SUPPORTED_ECHCONFIG_VERSIONS.include?(ech_config.version)
24
+
25
+ # Encrypted ClientHello Configuration
26
+ ech_state, enc = encrypted_ech_config(
27
+ ech_config,
28
+ hpke_cipher_suite_selector
29
+ )
30
+ return [new_greased_ch(inner, new_grease_ech), nil, nil] \
31
+ if ech_state.nil? || enc.nil?
32
+
33
+ encoded = encode_ch_inner(inner, ech_state.maximum_name_length)
34
+ overhead_len = aead_id2overhead_len(
35
+ ech_state.cipher_suite.aead_id.uint16
36
+ )
37
+
38
+ # Encoding the ClientHelloInner
39
+ aad = new_ch_outer_aad(
40
+ inner,
41
+ ech_state.cipher_suite,
42
+ ech_state.config_id,
43
+ enc,
44
+ encoded.length + overhead_len,
45
+ ech_state.public_name
46
+ )
47
+ # Authenticating the ClientHelloOuter
48
+ # which does not include the Handshake structure's four byte header.
49
+ outer = new_ch_outer(
50
+ aad,
51
+ ech_state.cipher_suite,
52
+ ech_state.config_id,
53
+ enc,
54
+ ech_state.ctx.seal(aad.serialize[4..], encoded)
55
+ )
56
+
57
+ [outer, inner, ech_state]
58
+ end
59
+ # rubocop: enable Metrics/AbcSize
60
+
61
+ # @param ech_config [ECHConfig]
62
+ # @param hpke_cipher_suite_selector [Method]
63
+ #
64
+ # @return [TTTLS13::EchState or nil]
65
+ # @return [String or nil]
66
+ # rubocop: disable Metrics/AbcSize
67
+ def self.encrypted_ech_config(ech_config, hpke_cipher_suite_selector)
68
+ public_name = ech_config.echconfig_contents.public_name
69
+ key_config = ech_config.echconfig_contents.key_config
70
+ public_key = key_config.public_key.opaque
71
+ kem_id = key_config&.kem_id&.uint16
72
+ config_id = key_config.config_id
73
+ cipher_suite = hpke_cipher_suite_selector.call(key_config)
74
+ aead_cipher = aead_id2aead_cipher(cipher_suite&.aead_id&.uint16)
75
+ kdf_hash = kdf_id2kdf_hash(cipher_suite&.kdf_id&.uint16)
76
+ return [nil, nil] \
77
+ if [kem_id, aead_cipher, kdf_hash].any?(&:nil?)
78
+
79
+ kem_curve_name, kem_hash = kem_id2dhkem(kem_id)
80
+ dhkem = kem_curve_name2dhkem(kem_curve_name)
81
+ pkr = dhkem&.new(kem_hash)&.deserialize_public_key(public_key)
82
+ return [nil, nil] if pkr.nil?
83
+
84
+ hpke = HPKE.new(kem_curve_name, kem_hash, kdf_hash, aead_cipher)
85
+ base_s = hpke.setup_base_s(pkr, "tls ech\x00" + ech_config.encode)
86
+ enc = base_s[:enc]
87
+ ctx = base_s[:context_s]
88
+ mnl = ech_config.echconfig_contents.maximum_name_length
89
+ ech_state = EchState.new(
90
+ mnl,
91
+ config_id,
92
+ cipher_suite,
93
+ public_name,
94
+ ctx
95
+ )
96
+
97
+ [ech_state, enc]
98
+ end
99
+ # rubocop: enable Metrics/AbcSize
100
+
101
+ # @param inner [TTTLS13::Message::ClientHello]
102
+ # @param ech_state [TTTLS13::EchState]
103
+ #
104
+ # @return [TTTLS13::Message::ClientHello]
105
+ # @return [TTTLS13::Message::ClientHello]
106
+ def self.offer_new_ech(inner, ech_state)
107
+ encoded = encode_ch_inner(inner, ech_state.maximum_name_length)
108
+ overhead_len \
109
+ = aead_id2overhead_len(ech_state.cipher_suite.aead_id.uint16)
110
+
111
+ # It encrypts EncodedClientHelloInner as described in Section 6.1.1, using
112
+ # the second partial ClientHelloOuterAAD, to obtain a second
113
+ # ClientHelloOuter. It reuses the original HPKE encryption context
114
+ # computed in Section 6.1 and uses the empty string for enc.
115
+ #
116
+ # https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-17#section-6.1.5-4.4.1
117
+ aad = new_ch_outer_aad(
118
+ inner,
119
+ ech_state.cipher_suite,
120
+ ech_state.config_id,
121
+ '',
122
+ encoded.length + overhead_len,
123
+ ech_state.public_name
124
+ )
125
+ # Authenticating the ClientHelloOuter
126
+ # which does not include the Handshake structure's four byte header.
127
+ outer = new_ch_outer(
128
+ aad,
129
+ ech_state.cipher_suite,
130
+ ech_state.config_id,
131
+ '',
132
+ ech_state.ctx.seal(aad.serialize[4..], encoded)
133
+ )
134
+
135
+ [outer, inner]
136
+ end
137
+
138
+ # @param inner [TTTLS13::Message::ClientHello]
139
+ # @param maximum_name_length [Integer]
140
+ #
141
+ # @return [String] EncodedClientHelloInner
142
+ def self.encode_ch_inner(inner, maximum_name_length)
143
+ # TODO: ech_outer_extensions
144
+ encoded = Message::ClientHello.new(
145
+ legacy_version: inner.legacy_version,
146
+ random: inner.random,
147
+ legacy_session_id: '',
148
+ cipher_suites: inner.cipher_suites,
149
+ legacy_compression_methods: inner.legacy_compression_methods,
150
+ extensions: inner.extensions
151
+ )
152
+ server_name_length = \
153
+ inner.extensions[Message::ExtensionType::SERVER_NAME].server_name.length
154
+
155
+ # which does not include the Handshake structure's four byte header.
156
+ padding_encoded_ch_inner(
157
+ encoded.serialize[4..],
158
+ server_name_length,
159
+ maximum_name_length
160
+ )
161
+ end
162
+
163
+ # @param s [String]
164
+ # @param server_name_length [Integer]
165
+ # @param maximum_name_length [Integer]
166
+ #
167
+ # @return [String]
168
+ def self.padding_encoded_ch_inner(s,
169
+ server_name_length,
170
+ maximum_name_length)
171
+ padding_len =
172
+ if server_name_length.positive?
173
+ [maximum_name_length - server_name_length, 0].max
174
+ else
175
+ 9 + maximum_name_length
176
+ end
177
+
178
+ padding_len = 31 - ((s.length + padding_len - 1) % 32)
179
+ s + padding_len.zeros
180
+ end
181
+
182
+ # @param inner [TTTLS13::Message::ClientHello]
183
+ # @param cipher_suite [HpkeSymmetricCipherSuite]
184
+ # @param config_id [Integer]
185
+ # @param enc [String]
186
+ # @param payload_len [Integer]
187
+ # @param server_name [String]
188
+ #
189
+ # @return [TTTLS13::Message::ClientHello]
190
+ # rubocop: disable Metrics/ParameterLists
191
+ def self.new_ch_outer_aad(inner,
192
+ cipher_suite,
193
+ config_id,
194
+ enc,
195
+ payload_len,
196
+ server_name)
197
+ aad_ech = Message::Extension::ECHClientHello.new_outer(
198
+ cipher_suite: cipher_suite,
199
+ config_id: config_id,
200
+ enc: enc,
201
+ payload: payload_len.zeros
202
+ )
203
+ Message::ClientHello.new(
204
+ legacy_version: inner.legacy_version,
205
+ legacy_session_id: inner.legacy_session_id,
206
+ cipher_suites: inner.cipher_suites,
207
+ legacy_compression_methods: inner.legacy_compression_methods,
208
+ extensions: inner.extensions.merge(
209
+ Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => aad_ech,
210
+ Message::ExtensionType::SERVER_NAME => \
211
+ Message::Extension::ServerName.new(server_name)
212
+ )
213
+ )
214
+ end
215
+ # rubocop: enable Metrics/ParameterLists
216
+
217
+ # @param aad [TTTLS13::Message::ClientHello]
218
+ # @param cipher_suite [HpkeSymmetricCipherSuite]
219
+ # @param config_id [Integer]
220
+ # @param enc [String]
221
+ # @param payload [String]
222
+ #
223
+ # @return [TTTLS13::Message::ClientHello]
224
+ def self.new_ch_outer(aad, cipher_suite, config_id, enc, payload)
225
+ outer_ech = Message::Extension::ECHClientHello.new_outer(
226
+ cipher_suite: cipher_suite,
227
+ config_id: config_id,
228
+ enc: enc,
229
+ payload: payload
230
+ )
231
+ Message::ClientHello.new(
232
+ legacy_version: aad.legacy_version,
233
+ random: aad.random,
234
+ legacy_session_id: aad.legacy_session_id,
235
+ cipher_suites: aad.cipher_suites,
236
+ legacy_compression_methods: aad.legacy_compression_methods,
237
+ extensions: aad.extensions.merge(
238
+ Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => outer_ech
239
+ )
240
+ )
241
+ end
242
+
243
+ # @return [Message::Extension::ECHClientHello]
244
+ def self.new_grease_ech
245
+ # https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-17#name-compliance-requirements
246
+ cipher_suite = HpkeSymmetricCipherSuite.new(
247
+ HpkeSymmetricCipherSuite::HpkeKdfId.new(
248
+ KdfId::HKDF_SHA256
249
+ ),
250
+ HpkeSymmetricCipherSuite::HpkeAeadId.new(
251
+ AeadId::AES_128_GCM
252
+ )
253
+ )
254
+ # Set the enc field to a randomly-generated valid encapsulated public key
255
+ # output by the HPKE KEM.
256
+ #
257
+ # https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-17#section-6.2-2.3.1
258
+ public_key = OpenSSL::PKey.read(
259
+ OpenSSL::PKey.generate_key('X25519').public_to_pem
260
+ )
261
+ hpke = HPKE.new(:x25519, :sha256, :sha256, :aes_128_gcm)
262
+ enc = hpke.setup_base_s(public_key, '')[:enc]
263
+ # Set the payload field to a randomly-generated string of L+C bytes, where
264
+ # C is the ciphertext expansion of the selected AEAD scheme and L is the
265
+ # size of the EncodedClientHelloInner the client would compute when
266
+ # offering ECH, padded according to Section 6.1.3.
267
+ #
268
+ # https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-17#section-6.2-2.4.1
269
+ payload_len = placeholder_encoded_ch_inner_len \
270
+ + aead_id2overhead_len(AeadId::AES_128_GCM)
271
+
272
+ Message::Extension::ECHClientHello.new_outer(
273
+ cipher_suite: cipher_suite,
274
+ config_id: Convert.bin2i(OpenSSL::Random.random_bytes(1)),
275
+ enc: enc,
276
+ payload: OpenSSL::Random.random_bytes(payload_len)
277
+ )
278
+ end
279
+
280
+ # @return [Integer]
281
+ def self.placeholder_encoded_ch_inner_len
282
+ 448
283
+ end
284
+
285
+ # @param inner [TTTLS13::Message::ClientHello]
286
+ # @param ech [Message::Extension::ECHClientHello]
287
+ def self.new_greased_ch(inner, ech)
288
+ Message::ClientHello.new(
289
+ legacy_version: inner.legacy_version,
290
+ random: inner.random,
291
+ legacy_session_id: inner.legacy_session_id,
292
+ cipher_suites: inner.cipher_suites,
293
+ legacy_compression_methods: inner.legacy_compression_methods,
294
+ extensions: inner.extensions.merge(
295
+ Message::ExtensionType::ENCRYPTED_CLIENT_HELLO => ech
296
+ )
297
+ )
298
+ end
299
+
300
+ module KemId
301
+ # https://www.iana.org/assignments/hpke/hpke.xhtml#hpke-kem-ids
302
+ P_256_SHA256 = 0x0010
303
+ P_384_SHA384 = 0x0011
304
+ P_521_SHA512 = 0x0012
305
+ X25519_SHA256 = 0x0020
306
+ X448_SHA512 = 0x0021
307
+ end
308
+
309
+ def self.kem_id2dhkem(kem_id)
310
+ case kem_id
311
+ when KemId::P_256_SHA256
312
+ %i[p_256 sha256]
313
+ when KemId::P_384_SHA384
314
+ %i[p_384 sha384]
315
+ when KemId::P_521_SHA512
316
+ %i[p_521 sha512]
317
+ when KemId::X25519_SHA256
318
+ %i[x25519 sha256]
319
+ when KemId::X448_SHA512
320
+ %i[x448 sha512]
321
+ end
322
+ end
323
+
324
+ def self.kem_curve_name2dhkem(kem_curve_name)
325
+ case kem_curve_name
326
+ when :p_256
327
+ HPKE::DHKEM::EC::P_256
328
+ when :p_384
329
+ HPKE::DHKEM::EC::P_384
330
+ when :p_521
331
+ HPKE::DHKEM::EC::P_521
332
+ when :x25519
333
+ HPKE::DHKEM::X25519
334
+ when :x448
335
+ HPKE::DHKEM::X448
336
+ end
337
+ end
338
+
339
+ module KdfId
340
+ # https://www.iana.org/assignments/hpke/hpke.xhtml#hpke-kdf-ids
341
+ HKDF_SHA256 = 0x0001
342
+ HKDF_SHA384 = 0x0002
343
+ HKDF_SHA512 = 0x0003
344
+ end
345
+
346
+ def self.kdf_id2kdf_hash(kdf_id)
347
+ case kdf_id
348
+ when KdfId::HKDF_SHA256
349
+ :sha256
350
+ when KdfId::HKDF_SHA384
351
+ :sha384
352
+ when KdfId::HKDF_SHA512
353
+ :sha512
354
+ end
355
+ end
356
+
357
+ module AeadId
358
+ # https://www.iana.org/assignments/hpke/hpke.xhtml#hpke-aead-ids
359
+ AES_128_GCM = 0x0001
360
+ AES_256_GCM = 0x0002
361
+ CHACHA20_POLY1305 = 0x0003
362
+ end
363
+
364
+ def self.aead_id2overhead_len(aead_id)
365
+ case aead_id
366
+ when AeadId::AES_128_GCM, AeadId::CHACHA20_POLY1305
367
+ 16
368
+ when AeadId::AES_256_GCM
369
+ 32
370
+ end
371
+ end
372
+
373
+ def self.aead_id2aead_cipher(aead_id)
374
+ case aead_id
375
+ when AeadId::AES_128_GCM
376
+ :aes_128_gcm
377
+ when AeadId::AES_256_GCM
378
+ :aes_256_gcm
379
+ when AeadId::CHACHA20_POLY1305
380
+ :chacha20_poly1305
381
+ end
382
+ end
383
+ end
384
+
385
+ class EchState
386
+ attr_reader :maximum_name_length
387
+ attr_reader :config_id
388
+ attr_reader :cipher_suite
389
+ attr_reader :public_name
390
+ attr_reader :ctx
391
+
392
+ # @param maximum_name_length [Integer]
393
+ # @param config_id [Integer]
394
+ # @param cipher_suite [HpkeSymmetricCipherSuite]
395
+ # @param public_name [String]
396
+ # @param ctx [[HPKE::ContextS]
397
+ def initialize(maximum_name_length,
398
+ config_id,
399
+ cipher_suite,
400
+ public_name,
401
+ ctx)
402
+ @maximum_name_length = maximum_name_length
403
+ @config_id = config_id
404
+ @cipher_suite = cipher_suite
405
+ @public_name = public_name
406
+ @ctx = ctx
407
+ end
408
+ end
409
+ # rubocop: enable Metrics/ModuleLength
410
+ end
@@ -0,0 +1,276 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ module TTTLS13
5
+ # rubocop: disable Metrics/ClassLength
6
+ class Endpoint
7
+ # @param label [String]
8
+ # @param context [String]
9
+ # @param key_length [Integer]
10
+ # @param exporter_secret [String]
11
+ # @param cipher_suite [TTTLS13::CipherSuite]
12
+ #
13
+ # @return [String, nil]
14
+ def self.exporter(label, context, key_length, exporter_secret, cipher_suite)
15
+ return nil if exporter_secret.nil? || cipher_suite.nil?
16
+
17
+ digest = CipherSuite.digest(cipher_suite)
18
+ do_exporter(exporter_secret, digest, label, context, key_length)
19
+ end
20
+
21
+ # @param cipher_suite [TTTLS13::CipherSuite]
22
+ # @param write_key [String]
23
+ # @param write_iv [String]
24
+ #
25
+ # @return [TTTLS13::Cryptograph::Aead]
26
+ def self.gen_cipher(cipher_suite, write_key, write_iv)
27
+ seq_num = SequenceNumber.new
28
+ Cryptograph::Aead.new(
29
+ cipher_suite: cipher_suite,
30
+ write_key: write_key,
31
+ write_iv: write_iv,
32
+ sequence_number: seq_num
33
+ )
34
+ end
35
+
36
+ # @param ch1 [TTTLS13::Message::ClientHello]
37
+ # @param hrr [TTTLS13::Message::ServerHello]
38
+ # @param ch [TTTLS13::Message::ClientHello]
39
+ # @param binder_key [String]
40
+ # @param digest [String] name of digest algorithm
41
+ #
42
+ # @return [String]
43
+ def self.sign_psk_binder(ch1:, hrr:, ch:, binder_key:, digest:)
44
+ # TODO: ext binder
45
+ hash_len = OpenSSL::Digest.new(digest).digest_length
46
+ tt = Transcript.new
47
+ tt[CH1] = [ch1, ch1.serialize] unless ch1.nil?
48
+ tt[HRR] = [hrr, hrr.serialize] unless hrr.nil?
49
+ tt[CH] = [ch, ch.serialize]
50
+ # transcript-hash (CH1 + HRR +) truncated-CH
51
+ hash = tt.truncate_hash(digest, CH, hash_len + 3)
52
+ OpenSSL::HMAC.digest(digest, binder_key, hash)
53
+ end
54
+
55
+ # @param key [OpenSSL::PKey::PKey]
56
+ # @param signature_scheme [TTTLS13::SignatureScheme]
57
+ # @param context [String]
58
+ # @param hash [String]
59
+ #
60
+ # @raise [TTTLS13::Error::ErrorAlerts]
61
+ #
62
+ # @return [String]
63
+ # rubocop: disable Metrics/CyclomaticComplexity
64
+ def self.sign_certificate_verify(key:, signature_scheme:, context:, hash:)
65
+ content = "\x20" * 64 + context + "\x00" + hash
66
+
67
+ # RSA signatures MUST use an RSASSA-PSS algorithm, regardless of whether
68
+ # RSASSA-PKCS1-v1_5 algorithms appear in "signature_algorithms".
69
+ case signature_scheme
70
+ when SignatureScheme::RSA_PKCS1_SHA256,
71
+ SignatureScheme::RSA_PSS_RSAE_SHA256,
72
+ SignatureScheme::RSA_PSS_PSS_SHA256
73
+ key.sign_pss('SHA256', content, salt_length: :digest,
74
+ mgf1_hash: 'SHA256')
75
+ when SignatureScheme::RSA_PKCS1_SHA384,
76
+ SignatureScheme::RSA_PSS_RSAE_SHA384,
77
+ SignatureScheme::RSA_PSS_PSS_SHA384
78
+ key.sign_pss('SHA384', content, salt_length: :digest,
79
+ mgf1_hash: 'SHA384')
80
+ when SignatureScheme::RSA_PKCS1_SHA512,
81
+ SignatureScheme::RSA_PSS_RSAE_SHA512,
82
+ SignatureScheme::RSA_PSS_PSS_SHA512
83
+ key.sign_pss('SHA512', content, salt_length: :digest,
84
+ mgf1_hash: 'SHA512')
85
+ when SignatureScheme::ECDSA_SECP256R1_SHA256
86
+ key.sign('SHA256', content)
87
+ when SignatureScheme::ECDSA_SECP384R1_SHA384
88
+ key.sign('SHA384', content)
89
+ when SignatureScheme::ECDSA_SECP521R1_SHA512
90
+ key.sign('SHA512', content)
91
+ else # TODO: ED25519, ED448
92
+ terminate(:internal_error)
93
+ end
94
+ end
95
+ # rubocop: enable Metrics/CyclomaticComplexity
96
+
97
+ # @param public_key [OpenSSL::PKey::PKey]
98
+ # @param signature_scheme [TTTLS13::SignatureScheme]
99
+ # @param signature [String]
100
+ # @param context [String]
101
+ # @param hash [String]
102
+ #
103
+ # @raise [TTTLS13::Error::ErrorAlerts]
104
+ #
105
+ # @return [Boolean]
106
+ # rubocop: disable Metrics/CyclomaticComplexity
107
+ def self.verified_certificate_verify?(public_key:, signature_scheme:,
108
+ signature:, context:, hash:)
109
+ content = "\x20" * 64 + context + "\x00" + hash
110
+
111
+ # RSA signatures MUST use an RSASSA-PSS algorithm, regardless of whether
112
+ # RSASSA-PKCS1-v1_5 algorithms appear in "signature_algorithms".
113
+ case signature_scheme
114
+ when SignatureScheme::RSA_PKCS1_SHA256,
115
+ SignatureScheme::RSA_PSS_RSAE_SHA256,
116
+ SignatureScheme::RSA_PSS_PSS_SHA256
117
+ public_key.verify_pss('SHA256', signature, content, salt_length: :auto,
118
+ mgf1_hash: 'SHA256')
119
+ when SignatureScheme::RSA_PKCS1_SHA384,
120
+ SignatureScheme::RSA_PSS_RSAE_SHA384,
121
+ SignatureScheme::RSA_PSS_PSS_SHA384
122
+ public_key.verify_pss('SHA384', signature, content, salt_length: :auto,
123
+ mgf1_hash: 'SHA384')
124
+ when SignatureScheme::RSA_PKCS1_SHA512,
125
+ SignatureScheme::RSA_PSS_RSAE_SHA512,
126
+ SignatureScheme::RSA_PSS_PSS_SHA512
127
+ public_key.verify_pss('SHA512', signature, content, salt_length: :auto,
128
+ mgf1_hash: 'SHA512')
129
+ when SignatureScheme::ECDSA_SECP256R1_SHA256
130
+ public_key.verify('SHA256', signature, content)
131
+ when SignatureScheme::ECDSA_SECP384R1_SHA384
132
+ public_key.verify('SHA384', signature, content)
133
+ when SignatureScheme::ECDSA_SECP521R1_SHA512
134
+ public_key.verify('SHA512', signature, content)
135
+ else # TODO: ED25519, ED448
136
+ terminate(:internal_error)
137
+ end
138
+ end
139
+ # rubocop: enable Metrics/CyclomaticComplexity
140
+
141
+ # @param digest [String] name of digest algorithm
142
+ # @param finished_key [String]
143
+ # @param hash [String]
144
+ #
145
+ # @return [String]
146
+ def self.sign_finished(digest:, finished_key:, hash:)
147
+ OpenSSL::HMAC.digest(digest, finished_key, hash)
148
+ end
149
+
150
+ # @param finished [TTTLS13::Message::Finished]
151
+ # @param digest [String] name of digest algorithm
152
+ # @param finished_key [String]
153
+ # @param hash [String]
154
+ #
155
+ # @return [Boolean]
156
+ def self.verified_finished?(finished:, digest:, finished_key:, hash:)
157
+ sign_finished(digest: digest, finished_key: finished_key, hash: hash) \
158
+ == finished.verify_data
159
+ end
160
+
161
+ # @param key_exchange [String]
162
+ # @param priv_key [OpenSSL::PKey::$Object]
163
+ # @param group [TTTLS13::NamedGroup]
164
+ #
165
+ # @return [String]
166
+ def self.gen_shared_secret(key_exchange, priv_key, group)
167
+ curve = NamedGroup.curve_name(group)
168
+ terminate(:internal_error) if curve.nil?
169
+
170
+ pub_key = OpenSSL::PKey::EC::Point.new(
171
+ OpenSSL::PKey::EC::Group.new(curve),
172
+ OpenSSL::BN.new(key_exchange, 2)
173
+ )
174
+
175
+ priv_key.dh_compute_key(pub_key)
176
+ end
177
+
178
+ # @param certificate_list [Array of CertificateEntry]
179
+ # @param ca_file [String] path to ca.crt
180
+ # @param hostname [String]
181
+ #
182
+ # @return [Boolean]
183
+ def self.trusted_certificate?(certificate_list,
184
+ ca_file = nil,
185
+ hostname = nil)
186
+ chain = certificate_list.map(&:cert_data).map do |c|
187
+ OpenSSL::X509::Certificate.new(c)
188
+ end
189
+ cert = chain.shift
190
+
191
+ # not support CN matching, only support SAN matching
192
+ return false if !hostname.nil? && !matching_san?(cert, hostname)
193
+
194
+ store = OpenSSL::X509::Store.new
195
+ store.set_default_paths
196
+ store.add_file(ca_file) unless ca_file.nil?
197
+ # TODO: parse authorityInfoAccess::CA Issuers
198
+ ctx = OpenSSL::X509::StoreContext.new(store, cert, chain)
199
+ now = Time.now
200
+ ctx.verify && cert.not_before < now && now < cert.not_after
201
+ end
202
+
203
+ # @param signature_algorithms [Array of SignatureAlgorithms]
204
+ # @param crt [OpenSSL::X509::Certificate]
205
+ #
206
+ # @return [Array of TTTLS13::Message::Extension::SignatureAlgorithms]
207
+ def self.select_signature_algorithms(signature_algorithms, crt)
208
+ pka = OpenSSL::ASN1.decode(crt.public_key.to_der)
209
+ .value.first.value.first.value
210
+ signature_algorithms.select do |sa|
211
+ case sa
212
+ when SignatureScheme::ECDSA_SECP256R1_SHA256,
213
+ SignatureScheme::ECDSA_SECP384R1_SHA384,
214
+ SignatureScheme::ECDSA_SECP521R1_SHA512
215
+ pka == 'id-ecPublicKey'
216
+ when SignatureScheme::RSA_PSS_PSS_SHA256,
217
+ SignatureScheme::RSA_PSS_PSS_SHA384,
218
+ SignatureScheme::RSA_PSS_PSS_SHA512
219
+ pka == 'rsassaPss'
220
+ when SignatureScheme::RSA_PSS_RSAE_SHA256,
221
+ SignatureScheme::RSA_PSS_RSAE_SHA384,
222
+ SignatureScheme::RSA_PSS_RSAE_SHA512
223
+ pka == 'rsaEncryption'
224
+ else
225
+ # RSASSA-PKCS1-v1_5 algorithms refer solely to signatures which appear
226
+ # in certificates and are not defined for use in signed TLS handshake
227
+ # messages
228
+ false
229
+ end
230
+ end
231
+ end
232
+
233
+ # @param cert [OpenSSL::X509::Certificate]
234
+ # @param name [String]
235
+ #
236
+ # @return [Boolean]
237
+ def self.matching_san?(cert, name)
238
+ san = cert.extensions.find { |ex| ex.oid == 'subjectAltName' }
239
+ return false if san.nil?
240
+
241
+ ostr = OpenSSL::ASN1.decode(san.to_der).value.last
242
+ OpenSSL::ASN1.decode(ostr.value)
243
+ .map(&:value)
244
+ .map { |s| s.gsub('.', '\.').gsub('*', '.*') }
245
+ .any? { |s| name.match(/#{s}/) }
246
+ end
247
+
248
+ class << self
249
+ # @param secret [String] (early_)exporter_secret
250
+ # @param digest [String] name of digest algorithm
251
+ # @param label [String]
252
+ # @param context [String]
253
+ # @param key_length [Integer]
254
+ #
255
+ # @return [String]
256
+ def do_exporter(secret, digest, label, context, key_length)
257
+ derived_secret = KeySchedule.hkdf_expand_label(
258
+ secret,
259
+ label,
260
+ OpenSSL::Digest.digest(digest, ''),
261
+ OpenSSL::Digest.new(digest).digest_length,
262
+ digest
263
+ )
264
+
265
+ KeySchedule.hkdf_expand_label(
266
+ derived_secret,
267
+ 'exporter',
268
+ OpenSSL::Digest.digest(digest, context),
269
+ key_length,
270
+ digest
271
+ )
272
+ end
273
+ end
274
+ end
275
+ # rubocop: enable Metrics/ClassLength
276
+ end
@@ -47,7 +47,7 @@ module TTTLS13
47
47
  signature_scheme = binary.slice(4, 2)
48
48
  signature_len = Convert.bin2i(binary.slice(6, 2))
49
49
  signature = binary.slice(8, signature_len)
50
- raise Error::ErrorAlerts, :internal_error \
50
+ raise Error::ErrorAlerts, :decode_error \
51
51
  unless signature_len + 4 == msg_len &&
52
52
  signature_len + 8 == binary.length
53
53