tttls1.3 0.2.9 → 0.2.14

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +32 -0
  3. data/.rubocop.yml +9 -2
  4. data/Gemfile +1 -1
  5. data/README.md +5 -1
  6. data/Rakefile +66 -7
  7. data/example/helper.rb +6 -8
  8. data/example/https_client.rb +1 -1
  9. data/example/https_client_using_0rtt.rb +3 -3
  10. data/example/https_client_using_hrr.rb +1 -1
  11. data/example/https_client_using_hrr_and_ticket.rb +2 -2
  12. data/example/https_client_using_status_request.rb +31 -0
  13. data/example/https_client_using_ticket.rb +2 -2
  14. data/example/https_server.rb +6 -5
  15. data/interop/client_spec.rb +8 -8
  16. data/interop/helper.rb +10 -2
  17. data/interop/server_spec.rb +14 -10
  18. data/lib/tttls1.3.rb +1 -0
  19. data/lib/tttls1.3/client.rb +97 -12
  20. data/lib/tttls1.3/connection.rb +45 -12
  21. data/lib/tttls1.3/cryptograph.rb +1 -1
  22. data/lib/tttls1.3/cryptograph/aead.rb +20 -7
  23. data/lib/tttls1.3/message.rb +1 -1
  24. data/lib/tttls1.3/message/alert.rb +2 -2
  25. data/lib/tttls1.3/message/extension/status_request.rb +73 -17
  26. data/lib/tttls1.3/message/extensions.rb +35 -12
  27. data/lib/tttls1.3/server.rb +40 -13
  28. data/lib/tttls1.3/utils.rb +15 -0
  29. data/lib/tttls1.3/version.rb +1 -1
  30. data/spec/extensions_spec.rb +16 -0
  31. data/spec/fixtures/rsa_rsa.crt +15 -15
  32. data/spec/fixtures/rsa_rsa.key +25 -25
  33. data/spec/fixtures/rsa_rsa_ocsp.crt +18 -0
  34. data/spec/fixtures/rsa_rsa_ocsp.key +27 -0
  35. data/spec/server_hello_spec.rb +1 -1
  36. data/spec/spec_helper.rb +35 -1
  37. data/spec/status_request_spec.rb +77 -10
  38. data/tttls1.3.gemspec +1 -1
  39. metadata +14 -10
  40. data/.travis.yml +0 -18
  41. data/interop/Dockerfile +0 -28
@@ -8,7 +8,7 @@ module TTTLS13
8
8
  FATAL = "\x02"
9
9
  end
10
10
 
11
- # rubocop: disable Layout/AlignHash
11
+ # rubocop: disable Layout/HashAlignment
12
12
  ALERT_DESCRIPTION = {
13
13
  close_notify: "\x00",
14
14
  unexpected_message: "\x0a",
@@ -38,7 +38,7 @@ module TTTLS13
38
38
  certificate_required: "\x74",
39
39
  no_application_protocol: "\x78"
40
40
  }.freeze
41
- # rubocop: enable Layout/AlignHash
41
+ # rubocop: enable Layout/HashAlignment
42
42
 
43
43
  class Alert
44
44
  attr_reader :level
@@ -9,23 +9,20 @@ module TTTLS13
9
9
  OCSP = "\x01"
10
10
  end
11
11
 
12
- class StatusRequest
12
+ class OCSPStatusRequest
13
13
  attr_reader :extension_type
14
14
  attr_reader :responder_id_list
15
15
  attr_reader :request_extensions
16
16
 
17
- # @param responder_id_list [Array of String]
18
- # @param request_extensions [String]
17
+ # @param responder_id_list [Array of OpenSSL::ASN1::ASN1Data]
18
+ # @param request_extensions [Array of OpenSSL::ASN1::ASN1Data]
19
19
  #
20
20
  # @example
21
- # StatusRequest.new(
22
- # responder_id_list: [],
23
- # request_extensions: []
24
- # )
25
- def initialize(responder_id_list: [], request_extensions: '')
21
+ # OCSPStatusRequest.new
22
+ def initialize(responder_id_list: [], request_extensions: [])
26
23
  @extension_type = ExtensionType::STATUS_REQUEST
27
24
  @responder_id_list = responder_id_list || []
28
- @request_extensions = request_extensions || ''
25
+ @request_extensions = request_extensions || []
29
26
  end
30
27
 
31
28
  # @return [String]
@@ -34,9 +31,9 @@ module TTTLS13
34
31
  binary += CertificateStatusType::OCSP
35
32
  binary += @responder_id_list.length.to_uint16
36
33
  binary += @responder_id_list.map do |id|
37
- id.length.to_uint16 + id
34
+ id.to_der.prefix_uint16_length
38
35
  end.join
39
- binary += @request_extensions.prefix_uint16_length
36
+ binary += @request_extensions.map(&:to_der).join.prefix_uint16_length
40
37
 
41
38
  @extension_type + binary.prefix_uint16_length
42
39
  end
@@ -45,8 +42,9 @@ module TTTLS13
45
42
  #
46
43
  # @raise [TTTLS13::Error::ErrorAlerts]
47
44
  #
48
- # @return [TTTLS13::Message::Extension::StatusRequest, nil]
45
+ # @return [TTTLS13::Message::Extension::OCSPStatusRequest, nil]
49
46
  # rubocop: disable Metrics/CyclomaticComplexity
47
+ # rubocop: disable Metrics/PerceivedComplexity
50
48
  def self.deserialize(binary)
51
49
  raise Error::ErrorAlerts, :internal_error if binary.nil?
52
50
  return nil if binary.length < 5 ||
@@ -64,14 +62,20 @@ module TTTLS13
64
62
 
65
63
  re_len = Convert.bin2i(binary.slice(i, 2))
66
64
  i += 2
67
- request_extensions = binary.slice(i, re_len)
65
+ exs_bin = binary.slice(i, re_len)
66
+ begin
67
+ request_extensions = OpenSSL::ASN1.decode_all(exs_bin)
68
+ rescue OpenSSL::ASN1::ASN1Error
69
+ return nil
70
+ end
68
71
  i += re_len
69
72
  return nil unless i == binary.length
70
73
 
71
- StatusRequest.new(responder_id_list: responder_id_list,
72
- request_extensions: request_extensions)
74
+ OCSPStatusRequest.new(responder_id_list: responder_id_list,
75
+ request_extensions: request_extensions)
73
76
  end
74
77
  # rubocop: enable Metrics/CyclomaticComplexity
78
+ # rubocop: enable Metrics/PerceivedComplexity
75
79
 
76
80
  class << self
77
81
  private
@@ -80,7 +84,7 @@ module TTTLS13
80
84
  #
81
85
  # @raise [TTTLS13::Error::ErrorAlerts]
82
86
  #
83
- # @return [Array of String, nil] received unparsable binary, nil
87
+ # @return [Array of ASN1Data, nil] received unparsable binary, nil
84
88
  def deserialize_request_ids(binary)
85
89
  raise Error::ErrorAlerts, :internal_error if binary.nil?
86
90
 
@@ -92,7 +96,11 @@ module TTTLS13
92
96
  id_len = Convert.bin2i(binary.slice(i, 2))
93
97
  i += 2
94
98
  id = binary.slice(i, id_len)
95
- request_ids += id
99
+ begin
100
+ request_ids += OpenSSL::ASN1.decode(id)
101
+ rescue OpenSSL::ASN1::ASN1Error
102
+ return nil
103
+ end
96
104
  i += id_len
97
105
  end
98
106
  return nil if i != binary.length
@@ -101,6 +109,54 @@ module TTTLS13
101
109
  end
102
110
  end
103
111
  end
112
+
113
+ class OCSPResponse
114
+ attr_reader :extension_type
115
+ attr_reader :ocsp_response
116
+
117
+ # @param ocsp_response [OpenSSL::OCSP::Response]
118
+ #
119
+ # @example
120
+ # OCSPResponse.new(
121
+ # OpenSSL::OCSP::Response.create(status, basic_resp)
122
+ # )
123
+ def initialize(ocsp_response)
124
+ @extension_type = ExtensionType::STATUS_REQUEST
125
+ @ocsp_response = ocsp_response
126
+ end
127
+
128
+ # @return [String]
129
+ def serialize
130
+ binary = ''
131
+ binary += CertificateStatusType::OCSP
132
+ binary += @ocsp_response.to_der.prefix_uint24_length
133
+
134
+ @extension_type + binary.prefix_uint16_length
135
+ end
136
+
137
+ # @param binary [String]
138
+ #
139
+ # @raise [TTTLS13::Error::ErrorAlerts]
140
+ #
141
+ # @return [TTTLS13::Message::Extension::OCSPResponse, nil]
142
+ def self.deserialize(binary)
143
+ raise Error::ErrorAlerts, :internal_error if binary.nil?
144
+ return nil if binary.length < 4 ||
145
+ binary[0] != CertificateStatusType::OCSP
146
+
147
+ res_len = Convert.bin2i(binary.slice(1, 3))
148
+ res = binary.slice(4, res_len)
149
+ ocsp_response = nil
150
+ begin
151
+ ocsp_response = OpenSSL::OCSP::Response.new(res)
152
+ rescue OpenSSL::OCSP::OCSPError
153
+ return nil
154
+ end
155
+ return nil if 4 + res_len != binary.length
156
+
157
+ OCSPResponse.new(ocsp_response)
158
+ end
159
+ end
104
160
  end
105
161
  end
106
162
  end
@@ -1,7 +1,8 @@
1
1
  # encoding: ascii-8bit
2
2
  # frozen_string_literal: true
3
3
 
4
- Dir[File.dirname(__FILE__) + '/extension/*.rb'].each { |f| require f }
4
+ # signature_algorithms_cert.rb needs signature_algorithms.rb so that `sort`
5
+ Dir[File.dirname(__FILE__) + '/extension/*.rb'].sort.each { |f| require f }
5
6
 
6
7
  module TTTLS13
7
8
  using Refinements
@@ -43,10 +44,11 @@ module TTTLS13
43
44
  #
44
45
  # @return [TTTLS13::Message::Extensions]
45
46
  # rubocop: disable Metrics/CyclomaticComplexity
47
+ # rubocop: disable Metrics/PerceivedComplexity
46
48
  def self.deserialize(binary, msg_type)
47
49
  raise Error::ErrorAlerts, :internal_error if binary.nil?
48
50
 
49
- extensions = []
51
+ exs = Extensions.new
50
52
  i = 0
51
53
  while i < binary.length
52
54
  raise Error::ErrorAlerts, :decode_error if i + 4 > binary.length
@@ -65,32 +67,41 @@ module TTTLS13
65
67
  ex = Extension::UnknownExtension.new(extension_type: extension_type,
66
68
  extension_data: ex_bin)
67
69
  end
68
- extensions << ex
70
+
71
+ # There MUST NOT be more than one extension of the same type in a
72
+ # given extension block.
73
+ raise Error::ErrorAlerts, :unsupported_extension \
74
+ if exs.include?(extension_type)
75
+
76
+ exs[extension_type] = ex
69
77
  i += ex_len
70
78
  end
71
79
  raise Error::ErrorAlerts, :decode_error unless i == binary.length
72
80
 
73
- Extensions.new(extensions)
81
+ exs
74
82
  end
75
83
  # rubocop: enable Metrics/CyclomaticComplexity
84
+ # rubocop: enable Metrics/PerceivedComplexity
76
85
 
77
86
  # @param key [TTTLS13::Message::ExtensionType]
87
+ # @param default
78
88
  #
79
89
  # @return [TTTLS13::Message::Extension::$Object]
80
- def [](key)
90
+ def fetch(key, default = nil)
81
91
  return nil if super_fetch(key, nil).is_a?(Extension::UnknownExtension)
82
92
 
83
- super_fetch(key, nil)
93
+ super_fetch(key, default)
84
94
  end
85
95
 
86
- # @param key [TTTLS13::Message::ExtensionType]
87
- # @param default
96
+ def [](key)
97
+ fetch(key)
98
+ end
99
+
100
+ # @param ex [TTTLS13::Message::Extension::$Object]
88
101
  #
89
102
  # @return [TTTLS13::Message::Extension::$Object]
90
- def fetch(key, default = nil)
91
- return nil if super_fetch(key, nil).is_a?(Extension::UnknownExtension)
92
-
93
- super_fetch(key, default)
103
+ def <<(ex)
104
+ store(ex.extension_type, ex)
94
105
  end
95
106
 
96
107
  class << self
@@ -109,12 +120,23 @@ module TTTLS13
109
120
  #
110
121
  # @return [TTTLS13::Message::Extension::$Object, nil]
111
122
  # rubocop: disable Metrics/CyclomaticComplexity
123
+ # rubocop: disable Metrics/MethodLength
112
124
  def deserialize_extension(binary, extension_type, msg_type)
113
125
  raise Error::ErrorAlerts, :internal_error if binary.nil?
114
126
 
115
127
  case extension_type
116
128
  when ExtensionType::SERVER_NAME
117
129
  Extension::ServerName.deserialize(binary)
130
+ when ExtensionType::STATUS_REQUEST
131
+ if msg_type == HandshakeType::CLIENT_HELLO
132
+ return Extension::OCSPStatusRequest.deserialize(binary)
133
+ end
134
+
135
+ if msg_type == HandshakeType::CERTIFICATE
136
+ return Extension::OCSPResponse.deserialize(binary)
137
+ end
138
+
139
+ Extension::UnknownExtension.deserialize(binary, extension_type)
118
140
  when ExtensionType::SUPPORTED_GROUPS
119
141
  Extension::SupportedGroups.deserialize(binary)
120
142
  when ExtensionType::SIGNATURE_ALGORITHMS
@@ -142,6 +164,7 @@ module TTTLS13
142
164
  end
143
165
  end
144
166
  # rubocop: enable Metrics/CyclomaticComplexity
167
+ # rubocop: enable Metrics/MethodLength
145
168
  end
146
169
  end
147
170
  end
@@ -46,11 +46,13 @@ module TTTLS13
46
46
 
47
47
  DEFAULT_SERVER_SETTINGS = {
48
48
  crt_file: nil,
49
+ chain_files: nil,
49
50
  key_file: nil,
50
51
  cipher_suites: DEFAULT_SP_CIPHER_SUITES,
51
52
  signature_algorithms: DEFAULT_SP_SIGNATURE_ALGORITHMS,
52
53
  supported_groups: DEFAULT_SP_NAMED_GROUP_LIST,
53
54
  alpn: nil,
55
+ process_ocsp_response: nil,
54
56
  compatibility_mode: true,
55
57
  loglevel: Logger::WARN
56
58
  }.freeze
@@ -75,6 +77,14 @@ module TTTLS13
75
77
  klass = @crt.public_key.class
76
78
  @key = klass.new(File.read(@settings[:key_file]))
77
79
  raise Error::ConfigError unless @crt.check_private_key(@key)
80
+
81
+ @chain = @settings[:chain_files]&.map do |f|
82
+ OpenSSL::X509::Certificate.new(File.read(f))
83
+ end
84
+ @chain ||= []
85
+ ([@crt] + @chain).each_cons(2) do |cert, sign|
86
+ raise Error::ConfigError unless cert.verify(sign.public_key)
87
+ end
78
88
  end
79
89
 
80
90
  # NOTE:
@@ -227,10 +237,14 @@ module TTTLS13
227
237
 
228
238
  ch = transcript[CH]
229
239
  rsl = @send_record_size \
230
- unless ch.extensions[Message::ExtensionType::RECORD_SIZE_LIMIT].nil?
240
+ if ch.extensions.include?(Message::ExtensionType::RECORD_SIZE_LIMIT)
231
241
  ee = transcript[EE] = gen_encrypted_extensions(ch, @alpn, rsl)
232
242
  # TODO: [Send CertificateRequest]
233
- ct = transcript[CT] = gen_certificate(@crt)
243
+
244
+ # status_request
245
+ ocsp_response = fetch_ocsp_response \
246
+ if ch.extensions.include?(Message::ExtensionType::STATUS_REQUEST)
247
+ ct = transcript[CT] = gen_certificate(@crt, @chain, ocsp_response)
234
248
  digest = CipherSuite.digest(@cipher_suite)
235
249
  cv = transcript[CV] = gen_certificate_verify(
236
250
  @key,
@@ -341,7 +355,7 @@ module TTTLS13
341
355
  #
342
356
  # @return [TTTLS13::Message::ServerHello]
343
357
  def send_hello_retry_request(ch1, cipher_suite)
344
- exs = []
358
+ exs = Message::Extensions.new
345
359
  # supported_versions
346
360
  exs << Message::Extension::SupportedVersions.new(
347
361
  msg_type: Message::HandshakeType::SERVER_HELLO
@@ -363,7 +377,7 @@ module TTTLS13
363
377
  random: Message::HRR_RANDOM,
364
378
  legacy_session_id_echo: ch1.legacy_session_id,
365
379
  cipher_suite: cipher_suite,
366
- extensions: Message::Extensions.new(exs)
380
+ extensions: exs
367
381
  )
368
382
  send_handshakes(Message::ContentType::HANDSHAKE, [sh],
369
383
  Cryptograph::Passer.new)
@@ -394,11 +408,18 @@ module TTTLS13
394
408
  end
395
409
 
396
410
  # @param crt [OpenSSL::X509::Certificate]
411
+ # @param chain [Array of OpenSSL::X509::Certificate]
412
+ # @param ocsp_response [OpenSSL::OCSP::Response]
397
413
  #
398
414
  # @return [TTTLS13::Message::Certificate, nil]
399
- def gen_certificate(crt)
400
- ce = Message::CertificateEntry.new(crt)
401
- Message::Certificate.new(certificate_list: [ce])
415
+ def gen_certificate(crt, chain = [], ocsp_response = nil)
416
+ exs = Message::Extensions.new
417
+ # status_request
418
+ exs << Message::Extension::OCSPResponse.new(ocsp_response) \
419
+ unless ocsp_response.nil?
420
+ ces = [Message::CertificateEntry.new(crt, exs)] \
421
+ + (chain || []).map { |c| Message::CertificateEntry.new(c) }
422
+ Message::Certificate.new(certificate_list: ces)
402
423
  end
403
424
 
404
425
  # @param key [OpenSSL::PKey::PKey]
@@ -433,7 +454,7 @@ module TTTLS13
433
454
  # @return [TTTLS13::Message::Extensions]
434
455
  # @return [OpenSSL::PKey::EC.$Object]
435
456
  def gen_sh_extensions(named_group)
436
- exs = []
457
+ exs = Message::Extensions.new
437
458
  # supported_versions: only TLS 1.3
438
459
  exs << Message::Extension::SupportedVersions.new(
439
460
  msg_type: Message::HandshakeType::SERVER_HELLO
@@ -444,7 +465,7 @@ module TTTLS13
444
465
  = Message::Extension::KeyShare.gen_sh_key_share(named_group)
445
466
  exs << key_share
446
467
 
447
- [Message::Extensions.new(exs), priv_key]
468
+ [exs, priv_key]
448
469
  end
449
470
 
450
471
  # @param ch [TTTLS13::Message::ClientHello]
@@ -453,15 +474,16 @@ module TTTLS13
453
474
  #
454
475
  # @return [TTTLS13::Message::Extensions]
455
476
  def gen_ee_extensions(ch, alpn, record_size_limit)
456
- exs = []
477
+ exs = Message::Extensions.new
457
478
 
458
479
  # server_name
459
480
  exs << Message::Extension::ServerName.new('') \
460
481
  if ch.extensions.include?(Message::ExtensionType::SERVER_NAME)
461
482
 
462
483
  # supported_groups
463
- exs \
464
- << Message::Extension::SupportedGroups.new(@settings[:supported_groups])
484
+ exs << Message::Extension::SupportedGroups.new(
485
+ @settings[:supported_groups]
486
+ )
465
487
 
466
488
  # alpn
467
489
  exs << Message::Extension::Alpn.new([alpn]) unless alpn.nil?
@@ -470,7 +492,7 @@ module TTTLS13
470
492
  exs << Message::Extension::RecordSizeLimit.new(record_size_limit) \
471
493
  unless record_size_limit.nil?
472
494
 
473
- Message::Extensions.new(exs)
495
+ exs
474
496
  end
475
497
 
476
498
  # @param key [OpenSSL::PKey::PKey]
@@ -532,6 +554,11 @@ module TTTLS13
532
554
 
533
555
  matching_san?(crt, server_name)
534
556
  end
557
+
558
+ # @return [OpenSSL::OCSP::Response, nil]
559
+ def fetch_ocsp_response
560
+ @settings[:process_ocsp_response]&.call
561
+ end
535
562
  end
536
563
  # rubocop: enable Metrics/ClassLength
537
564
  end
@@ -61,6 +61,21 @@ module TTTLS13
61
61
  length.to_uint64 + self
62
62
  end
63
63
  end
64
+
65
+ refine OpenSSL::X509::Certificate do
66
+ unless method_defined?(:ocsp_uris)
67
+ define_method(:ocsp_uris) do
68
+ aia = extensions.find { |ex| ex.oid == 'authorityInfoAccess' }
69
+ return nil if aia.nil?
70
+
71
+ ostr = OpenSSL::ASN1.decode(aia.to_der).value.last
72
+ ocsp = OpenSSL::ASN1.decode(ostr.value)
73
+ .map(&:value)
74
+ .select { |des| des.first.value == 'OCSP' }
75
+ ocsp&.map { |o| o[1].value }
76
+ end
77
+ end
78
+ end
64
79
  end
65
80
 
66
81
  module Convert
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TTTLS13
4
- VERSION = '0.2.9'
4
+ VERSION = '0.2.14'
5
5
  end
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative 'spec_helper'
5
+ using Refinements
5
6
 
6
7
  RSpec.describe Extensions do
7
8
  context 'empty extensions' do
@@ -167,4 +168,19 @@ RSpec.describe Extensions do
167
168
  expect(extensions).to include ExtensionType::RECORD_SIZE_LIMIT
168
169
  end
169
170
  end
171
+
172
+ context 'duplicated extension_type' do
173
+ let(:server_name) do
174
+ ServerName.new('example.com')
175
+ end
176
+
177
+ let(:testbinary) do
178
+ server_name.serialize * 2
179
+ end
180
+
181
+ it 'should raise error, if extension_type get duplicated' do
182
+ expect { Extensions.deserialize(testbinary, HandshakeType::CLIENT_HELLO) }
183
+ .to raise_error(ErrorAlerts)
184
+ end
185
+ end
170
186
  end