tttls1.3 0.2.15 → 0.2.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba824030b1a295566777d4d12c35e259d379bc9a830b0cb95792356cc547a436
4
- data.tar.gz: c3f2e8fd07567133cce7e8f24fcaf02499b8cceff6088ca403ca2e9be9e5ab5f
3
+ metadata.gz: 7006bce3031f6232ae949b44eb31111562d581e359769952f48a537848d50418
4
+ data.tar.gz: d332e823eb8c677ff534e46a87e96c039aad2d5538c0f823ac7eb365f372ca88
5
5
  SHA512:
6
- metadata.gz: 00e939bf927db1923274985cdad6526d16b6d99216f62ff3978b3bae9cdcedf675482b96f49a224f9f982f1d8c6f7ca0b49f282086422e73e93b4b74d13f0722
7
- data.tar.gz: 1e523ad0d29d6f29dbd94388f9f895abf69324ceb4336406b13e21793395abdf795a0f043ee89899e888b851d85f2190064e93b70efec092964e34c94b15ac76
6
+ metadata.gz: ca37fd2570b905759da152932eb2e6f29c3e37f59135353b9e3cfbce0c683f7493fdd360f9a56c50f2ab252f0460728c09585d018d04b8668de4000b1567d249
7
+ data.tar.gz: 6e37405f3034de1fe648f0882bbc9d9b65baeac8d1ef142fc776010b8480cd987a87060edf6ae85fd20327afc5327aacb23318e460f510259c99056aa76fb389
@@ -13,7 +13,7 @@ jobs:
13
13
  runs-on: ubuntu-latest
14
14
  strategy:
15
15
  matrix:
16
- ruby-version: ['2.6.x', '2.7.x']
16
+ ruby-version: ['2.6.x', '2.7.x', '3.0.x']
17
17
  steps:
18
18
  - uses: docker://thekuwayama/openssl:latest
19
19
  - name: Set up Ruby
data/README.md CHANGED
@@ -102,6 +102,7 @@ tttls1.3 client is configurable using keyword arguments.
102
102
  | `:record_size_limit` | Integer | nil | The record\_size\_limit offerd in ClientHello extensions. If not needed to be present, set nil. |
103
103
  | `:check_certificate_status` | Boolean | false | If needed to check certificate status, set true. |
104
104
  | `:process_certificate_status` | Proc | `TTTLS13::Client.method(:softfail_check_certificate_status)` | Proc(or Method) that checks received OCSPResponse. Its 3 arguments are OpenSSL::OCSP::Response, end-entity certificate(OpenSSL::X509::Certificate) and certificates chain(Array of Certificate) used for verification and it returns Boolean. |
105
+ | `:compress_certificate_algorithms` | Array of TTTLS13::Message::Extension::CertificateCompressionAlgorithm constant | `ZLIB` | The compression algorithms are supported for compressing the Certificate message. |
105
106
  | `:compatibility_mode` | Boolean | true | If needed to send ChangeCipherSpec, set true. |
106
107
  | `:loglevel` | Logger constant | Logger::WARN | If needed to print verbose, set Logger::DEBUG. |
107
108
 
@@ -120,6 +121,7 @@ tttls1.3 server is configurable using keyword arguments.
120
121
  | `:supported_groups` | Array of TTTLS13::NamedGroup constant | `SECP256R1`, `SECP384R1`, `SECP521R1` | List of supported named groups. |
121
122
  | `:alpn` | Array of String | nil | List of supported application protocols. If not needed to check this extension, set nil. |
122
123
  | `:process_ocsp_response` | Proc | nil | Proc that gets OpenSSL::OCSP::Response. If not needed to staple OCSP::Response, set nil. |
124
+ | `:compress_certificate_algorithms` | Array of TTTLS13::Message::Extension::CertificateCompressionAlgorithm constant | `ZLIB` | The compression algorithms are supported for compressing the Certificate message. |
123
125
  | `:compatibility_mode` | Boolean | true | If needed to send ChangeCipherSpec, set true. |
124
126
  | `:loglevel` | Logger constant | Logger::WARN | If needed to print verbose, set Logger::DEBUG. |
125
127
 
@@ -43,6 +43,11 @@ module TTTLS13
43
43
  ].freeze
44
44
  private_constant :DEFAULT_CH_NAMED_GROUP_LIST
45
45
 
46
+ DEFALUT_CH_COMPRESS_CERTIFICATE_ALGORITHMS = [
47
+ Message::Extension::CertificateCompressionAlgorithm::ZLIB
48
+ ].freeze
49
+ private_constant :DEFALUT_CH_COMPRESS_CERTIFICATE_ALGORITHMS
50
+
46
51
  DEFAULT_CLIENT_SETTINGS = {
47
52
  ca_file: nil,
48
53
  cipher_suites: DEFAULT_CH_CIPHER_SUITES,
@@ -61,6 +66,7 @@ module TTTLS13
61
66
  record_size_limit: nil,
62
67
  check_certificate_status: false,
63
68
  process_certificate_status: nil,
69
+ compress_certificate_algorithms: DEFALUT_CH_COMPRESS_CERTIFICATE_ALGORITHMS,
64
70
  compatibility_mode: true,
65
71
  loglevel: Logger::WARN
66
72
  }.freeze
@@ -154,8 +160,8 @@ module TTTLS13
154
160
 
155
161
  extensions, priv_keys = gen_ch_extensions
156
162
  binder_key = (use_psk? ? key_schedule.binder_key_res : nil)
157
- transcript[CH] = send_client_hello(extensions, binder_key)
158
-
163
+ ch = send_client_hello(extensions, binder_key)
164
+ transcript[CH] = [ch, ch.serialize]
159
165
  send_ccs if @settings[:compatibility_mode]
160
166
  if use_early_data?
161
167
  e_wcipher = gen_cipher(
@@ -170,7 +176,7 @@ module TTTLS13
170
176
  when ClientState::WAIT_SH
171
177
  logger.debug('ClientState::WAIT_SH')
172
178
 
173
- sh = transcript[SH] = recv_server_hello
179
+ sh, = transcript[SH] = recv_server_hello
174
180
 
175
181
  # downgrade protection
176
182
  if !sh.negotiated_tls_1_3? && sh.downgraded?
@@ -187,7 +193,7 @@ module TTTLS13
187
193
  unless sh.legacy_compression_method == "\x00"
188
194
 
189
195
  # validate sh using ch
190
- ch = transcript[CH]
196
+ ch, = transcript[CH]
191
197
  terminate(:illegal_parameter) \
192
198
  unless sh.legacy_version == ch.legacy_version
193
199
  terminate(:illegal_parameter) \
@@ -199,7 +205,7 @@ module TTTLS13
199
205
 
200
206
  # validate sh using hrr
201
207
  if transcript.include?(HRR)
202
- hrr = transcript[HRR]
208
+ hrr, = transcript[HRR]
203
209
  terminate(:illegal_parameter) \
204
210
  unless sh.cipher_suite == hrr.cipher_suite
205
211
 
@@ -212,8 +218,9 @@ module TTTLS13
212
218
  # handling HRR
213
219
  if sh.hrr?
214
220
  terminate(:unexpected_message) if transcript.include?(HRR)
215
- ch1 = transcript[CH1] = transcript.delete(CH)
216
- hrr = transcript[HRR] = transcript.delete(SH)
221
+
222
+ ch1, = transcript[CH1] = transcript.delete(CH)
223
+ hrr, = transcript[HRR] = transcript.delete(SH)
217
224
 
218
225
  # validate cookie
219
226
  diff_sets = sh.extensions.keys - ch1.extensions.keys
@@ -235,8 +242,9 @@ module TTTLS13
235
242
  extensions, pk = gen_newch_extensions(ch1, hrr)
236
243
  priv_keys = pk.merge(priv_keys)
237
244
  binder_key = (use_psk? ? key_schedule.binder_key_res : nil)
238
- transcript[CH] = send_new_client_hello(ch1, hrr, extensions,
239
- binder_key)
245
+ ch = send_new_client_hello(ch1, hrr, extensions, binder_key)
246
+ transcript[CH] = [ch, ch.serialize]
247
+
240
248
  @state = ClientState::WAIT_SH
241
249
  next
242
250
  end
@@ -277,37 +285,46 @@ module TTTLS13
277
285
  when ClientState::WAIT_EE
278
286
  logger.debug('ClientState::WAIT_EE')
279
287
 
280
- ee = transcript[EE] = recv_encrypted_extensions(hs_rcipher)
288
+ ee, = transcript[EE] = recv_encrypted_extensions(hs_rcipher)
281
289
  terminate(:illegal_parameter) unless ee.appearable_extensions?
282
290
 
283
- ch = transcript[CH]
291
+ ch, = transcript[CH]
284
292
  terminate(:unsupported_extension) \
285
293
  unless (ee.extensions.keys - ch.extensions.keys).empty?
286
294
 
287
295
  rsl = ee.extensions[Message::ExtensionType::RECORD_SIZE_LIMIT]
288
296
  @recv_record_size = rsl.record_size_limit unless rsl.nil?
289
-
290
297
  @succeed_early_data = true \
291
298
  if ee.extensions.include?(Message::ExtensionType::EARLY_DATA)
292
-
293
299
  @alpn = ee.extensions[
294
300
  Message::ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION
295
301
  ]&.protocol_name_list&.first
296
-
297
302
  @state = ClientState::WAIT_CERT_CR
298
303
  @state = ClientState::WAIT_FINISHED unless psk.nil?
299
304
  when ClientState::WAIT_CERT_CR
300
305
  logger.debug('ClientState::WAIT_CERT_CR')
301
306
 
302
- message = recv_message(receivable_ccs: true, cipher: hs_rcipher)
303
- if message.msg_type == Message::HandshakeType::CERTIFICATE
304
- ct = transcript[CT] = message
305
- alert = check_invalid_certificate(ct, transcript[CH])
307
+ message, orig_msg = recv_message(
308
+ receivable_ccs: true,
309
+ cipher: hs_rcipher
310
+ )
311
+ case message.msg_type
312
+ when Message::HandshakeType::CERTIFICATE,
313
+ Message::HandshakeType::COMPRESSED_CERTIFICATE
314
+ ct, = transcript[CT] = [message, orig_msg]
315
+ terminate(:bad_certificate) \
316
+ if ct.is_a?(Message::CompressedCertificate) &&
317
+ !@settings[:compress_certificate_algorithms]
318
+ .include?(ct.algorithm)
319
+
320
+ ct = ct.certificate_message \
321
+ if ct.is_a?(Message::CompressedCertificate)
322
+ alert = check_invalid_certificate(ct, transcript[CH].first)
306
323
  terminate(alert) unless alert.nil?
307
324
 
308
325
  @state = ClientState::WAIT_CV
309
- elsif message.msg_type == Message::HandshakeType::CERTIFICATE_REQUEST
310
- transcript[CR] = message
326
+ when Message::HandshakeType::CERTIFICATE_REQUEST
327
+ transcript[CR] = [message, orig_msg]
311
328
  # TODO: client authentication
312
329
  @state = ClientState::WAIT_CERT
313
330
  else
@@ -316,46 +333,56 @@ module TTTLS13
316
333
  when ClientState::WAIT_CERT
317
334
  logger.debug('ClientState::WAIT_CERT')
318
335
 
319
- ct = transcript[CT] = recv_certificate(hs_rcipher)
320
- alert = check_invalid_certificate(ct, transcript[CH])
336
+ ct, = transcript[CT] = recv_certificate(hs_rcipher)
337
+ if ct.is_a?(Message::CompressedCertificate) &&
338
+ !@settings[:compress_certificate_algorithms].include?(ct.algorithm)
339
+ terminate(:bad_certificate)
340
+ elsif ct.is_a?(Message::CompressedCertificate)
341
+ ct = ct.certificate_message
342
+ end
343
+
344
+ alert = check_invalid_certificate(ct, transcript[CH].first)
321
345
  terminate(alert) unless alert.nil?
322
346
 
323
347
  @state = ClientState::WAIT_CV
324
348
  when ClientState::WAIT_CV
325
349
  logger.debug('ClientState::WAIT_CV')
326
350
 
327
- cv = transcript[CV] = recv_certificate_verify(hs_rcipher)
351
+ cv, = transcript[CV] = recv_certificate_verify(hs_rcipher)
328
352
  digest = CipherSuite.digest(@cipher_suite)
329
353
  hash = transcript.hash(digest, CT)
354
+ ct, = transcript[CT]
355
+ ct = ct.certificate_message \
356
+ if ct.is_a?(Message::CompressedCertificate)
330
357
  terminate(:decrypt_error) \
331
- unless verified_certificate_verify?(transcript[CT], cv, hash)
358
+ unless verified_certificate_verify?(ct, cv, hash)
332
359
 
333
360
  @signature_scheme = cv.signature_scheme
334
-
335
361
  @state = ClientState::WAIT_FINISHED
336
362
  when ClientState::WAIT_FINISHED
337
363
  logger.debug('ClientState::WAIT_FINISHED')
338
364
 
339
- sf = transcript[SF] = recv_finished(hs_rcipher)
365
+ sf, = transcript[SF] = recv_finished(hs_rcipher)
340
366
  digest = CipherSuite.digest(@cipher_suite)
341
- verified = verified_finished?(
367
+ terminate(:decrypt_error) unless verified_finished?(
342
368
  finished: sf,
343
369
  digest: digest,
344
370
  finished_key: key_schedule.server_finished_key,
345
371
  hash: transcript.hash(digest, CV)
346
372
  )
347
- terminate(:decrypt_error) unless verified
348
-
349
- transcript[EOED] = send_eoed(e_wcipher) \
350
- if use_early_data? && succeed_early_data?
351
373
 
374
+ if use_early_data? && succeed_early_data?
375
+ eoed = send_eoed(e_wcipher)
376
+ transcript[EOED] = [eoed, eoed.serialize]
377
+ end
352
378
  # TODO: Send Certificate [+ CertificateVerify]
353
379
  signature = sign_finished(
354
380
  digest: digest,
355
381
  finished_key: key_schedule.client_finished_key,
356
382
  hash: transcript.hash(digest, EOED)
357
383
  )
358
- transcript[CF] = send_finished(signature, hs_wcipher)
384
+ cf = send_finished(signature, hs_wcipher)
385
+ transcript[CF] = [cf, cf.serialize]
359
386
  @alert_wcipher = @ap_wcipher = gen_cipher(
360
387
  @cipher_suite,
361
388
  key_schedule.client_application_write_key,
@@ -578,6 +605,14 @@ module TTTLS13
578
605
  exs << Message::Extension::OCSPStatusRequest.new \
579
606
  if @settings[:check_certificate_status]
580
607
 
608
+ # compress_certificate
609
+ if !@settings[:compress_certificate_algorithms].nil? &&
610
+ !@settings[:compress_certificate_algorithms].empty?
611
+ exs << Message::Extension::CompressCertificate.new(
612
+ @settings[:compress_certificate_algorithms]
613
+ )
614
+ end
615
+
581
616
  [exs, priv_keys]
582
617
  end
583
618
  # rubocop: enable Metrics/AbcSize
@@ -735,11 +770,15 @@ module TTTLS13
735
770
  # @raise [TTTLS13::Error::ErrorAlerts]
736
771
  #
737
772
  # @return [TTTLS13::Message::ServerHello]
773
+ # @return [String]
738
774
  def recv_server_hello
739
- sh = recv_message(receivable_ccs: true, cipher: Cryptograph::Passer.new)
775
+ sh, orig_msg = recv_message(
776
+ receivable_ccs: true,
777
+ cipher: Cryptograph::Passer.new
778
+ )
740
779
  terminate(:unexpected_message) unless sh.is_a?(Message::ServerHello)
741
780
 
742
- sh
781
+ [sh, orig_msg]
743
782
  end
744
783
 
745
784
  # @param cipher [TTTLS13::Cryptograph::Aead]
@@ -747,12 +786,13 @@ module TTTLS13
747
786
  # @raise [TTTLS13::Error::ErrorAlerts]
748
787
  #
749
788
  # @return [TTTLS13::Message::EncryptedExtensions]
789
+ # @return [String]
750
790
  def recv_encrypted_extensions(cipher)
751
- ee = recv_message(receivable_ccs: true, cipher: cipher)
791
+ ee, orig_msg = recv_message(receivable_ccs: true, cipher: cipher)
752
792
  terminate(:unexpected_message) \
753
793
  unless ee.is_a?(Message::EncryptedExtensions)
754
794
 
755
- ee
795
+ [ee, orig_msg]
756
796
  end
757
797
 
758
798
  # @param cipher [TTTLS13::Cryptograph::Aead]
@@ -760,11 +800,12 @@ module TTTLS13
760
800
  # @raise [TTTLS13::Error::ErrorAlerts]
761
801
  #
762
802
  # @return [TTTLS13::Message::Certificate]
803
+ # @return [String]
763
804
  def recv_certificate(cipher)
764
- ct = recv_message(receivable_ccs: true, cipher: cipher)
805
+ ct, orig_msg = recv_message(receivable_ccs: true, cipher: cipher)
765
806
  terminate(:unexpected_message) unless ct.is_a?(Message::Certificate)
766
807
 
767
- ct
808
+ [ct, orig_msg]
768
809
  end
769
810
 
770
811
  # @param cipher [TTTLS13::Cryptograph::Aead]
@@ -772,11 +813,12 @@ module TTTLS13
772
813
  # @raise [TTTLS13::Error::ErrorAlerts]
773
814
  #
774
815
  # @return [TTTLS13::Message::CertificateVerify]
816
+ # @return [String]
775
817
  def recv_certificate_verify(cipher)
776
- cv = recv_message(receivable_ccs: true, cipher: cipher)
818
+ cv, orig_msg = recv_message(receivable_ccs: true, cipher: cipher)
777
819
  terminate(:unexpected_message) unless cv.is_a?(Message::CertificateVerify)
778
820
 
779
- cv
821
+ [cv, orig_msg]
780
822
  end
781
823
 
782
824
  # @param cipher [TTTLS13::Cryptograph::Aead]
@@ -784,11 +826,12 @@ module TTTLS13
784
826
  # @raise [TTTLS13::Error::ErrorAlerts]
785
827
  #
786
828
  # @return [TTTLS13::Message::Finished]
829
+ # @return [String]
787
830
  def recv_finished(cipher)
788
- sf = recv_message(receivable_ccs: true, cipher: cipher)
831
+ sf, orig_msg = recv_message(receivable_ccs: true, cipher: cipher)
789
832
  terminate(:unexpected_message) unless sf.is_a?(Message::Finished)
790
833
 
791
- sf
834
+ [sf, orig_msg]
792
835
  end
793
836
 
794
837
  # @param cipher [TTTLS13::Cryptograph::Aead]
@@ -16,7 +16,7 @@ module TTTLS13
16
16
  @ap_wcipher = Cryptograph::Passer.new
17
17
  @ap_rcipher = Cryptograph::Passer.new
18
18
  @alert_wcipher = Cryptograph::Passer.new
19
- @message_queue = [] # Array of TTTLS13::Message::$Object
19
+ @message_queue = [] # Array of [TTTLS13::Message::$Object, String]
20
20
  @binary_buffer = '' # deposit Record.surplus_binary
21
21
  @cipher_suite = nil # TTTLS13::CipherSuite
22
22
  @named_group = nil # TTTLS13::NamedGroup
@@ -42,7 +42,7 @@ module TTTLS13
42
42
 
43
43
  message = nil
44
44
  loop do
45
- message = recv_message(receivable_ccs: false, cipher: @ap_rcipher)
45
+ message, = recv_message(receivable_ccs: false, cipher: @ap_rcipher)
46
46
  # At any time after the server has received the client Finished
47
47
  # message, it MAY send a NewSessionTicket message.
48
48
  break unless message.is_a?(Message::NewSessionTicket)
@@ -220,13 +220,15 @@ module TTTLS13
220
220
  # @raise [TTTLS13::Error::ErrorAlerts
221
221
  #
222
222
  # @return [TTTLS13::Message::$Object]
223
+ # @return [String]
223
224
  # rubocop: disable Metrics/CyclomaticComplexity
224
225
  def recv_message(receivable_ccs:, cipher:)
225
226
  return @message_queue.shift unless @message_queue.empty?
226
227
 
227
228
  messages = nil
229
+ orig_msgs = []
228
230
  loop do
229
- record = recv_record(cipher)
231
+ record, orig_msgs = recv_record(cipher)
230
232
  case record.type
231
233
  when Message::ContentType::HANDSHAKE,
232
234
  Message::ContentType::APPLICATION_DATA
@@ -243,20 +245,22 @@ module TTTLS13
243
245
  end
244
246
  end
245
247
 
246
- @message_queue += messages[1..]
248
+ @message_queue += messages[1..].zip(orig_msgs[1..])
247
249
  message = messages.first
250
+ orig_msg = orig_msgs.first
248
251
  if message.is_a?(Message::Alert)
249
252
  handle_received_alert(message)
250
253
  return nil
251
254
  end
252
255
 
253
- message
256
+ [message, orig_msg]
254
257
  end
255
258
  # rubocop: enable Metrics/CyclomaticComplexity
256
259
 
257
260
  # @param cipher [TTTLS13::Cryptograph::Aead, Passer]
258
261
  #
259
262
  # @return [TTTLS13::Message::Record]
263
+ # @return [Array of String]
260
264
  def recv_record(cipher)
261
265
  binary = @socket.read(5)
262
266
  record_len = Convert.bin2i(binary.slice(3, 2))
@@ -264,9 +268,13 @@ module TTTLS13
264
268
 
265
269
  begin
266
270
  buffer = @binary_buffer
267
- record = Message::Record.deserialize(binary, cipher, buffer,
268
- @recv_record_size)
269
- @binary_buffer = record.surplus_binary
271
+ record, orig_msgs, surplus_binary = Message::Record.deserialize(
272
+ binary,
273
+ cipher,
274
+ buffer,
275
+ @recv_record_size
276
+ )
277
+ @binary_buffer = surplus_binary
270
278
  rescue Error::ErrorAlerts => e
271
279
  terminate(e.message.to_sym)
272
280
  end
@@ -278,7 +286,7 @@ module TTTLS13
278
286
  end
279
287
 
280
288
  logger.debug("receive \n" + record.pretty_inspect)
281
- record
289
+ [record, orig_msgs]
282
290
  end
283
291
 
284
292
  # @param ch1 [TTTLS13::Message::ClientHello]
@@ -292,11 +300,9 @@ module TTTLS13
292
300
  # TODO: ext binder
293
301
  hash_len = OpenSSL::Digest.new(digest).digest_length
294
302
  tt = Transcript.new
295
- tt.merge!(
296
- CH1 => ch1,
297
- HRR => hrr,
298
- CH => ch
299
- )
303
+ tt[CH1] = [ch1, ch1.serialize] unless ch1.nil?
304
+ tt[HRR] = [hrr, hrr.serialize] unless hrr.nil?
305
+ tt[CH] = [ch, ch.serialize]
300
306
  # transcript-hash (CH1 + HRR +) truncated-CH
301
307
  hash = tt.truncate_hash(digest, CH, hash_len + 3)
302
308
  OpenSSL::HMAC.digest(digest, binder_key, hash)
@@ -500,11 +506,10 @@ module TTTLS13
500
506
  return false if san.nil?
501
507
 
502
508
  ostr = OpenSSL::ASN1.decode(san.to_der).value.last
503
- matching = OpenSSL::ASN1.decode(ostr.value).map(&:value)
504
- .map { |s| s.gsub('.', '\.').gsub('*', '.*') }
505
- .any? { |s| name.match(/#{s}/) }
506
-
507
- matching
509
+ OpenSSL::ASN1.decode(ostr.value)
510
+ .map(&:value)
511
+ .map { |s| s.gsub('.', '\.').gsub('*', '.*') }
512
+ .any? { |s| name.match(/#{s}/) }
508
513
  end
509
514
 
510
515
  # @param signature_algorithms [Array of SignatureAlgorithms]
@@ -18,6 +18,7 @@ module TTTLS13
18
18
  ExtensionType::CLIENT_CERTIFICATE_TYPE,
19
19
  ExtensionType::SERVER_CERTIFICATE_TYPE,
20
20
  ExtensionType::PADDING,
21
+ ExtensionType::COMPRESS_CERTIFICATE,
21
22
  ExtensionType::RECORD_SIZE_LIMIT,
22
23
  ExtensionType::PWD_PROTECT,
23
24
  ExtensionType::PWD_CLEAR,
@@ -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
@@ -27,9 +27,12 @@ module TTTLS13
27
27
 
28
28
  # @return [String]
29
29
  def serialize
30
- binary = @protocol_name_list.map(&:prefix_uint8_length).join
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.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.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.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