tttls1.3 0.3.1 → 0.3.3

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/Gemfile +1 -0
  4. data/example/helper.rb +43 -0
  5. data/example/https_client_using_0rtt.rb +5 -3
  6. data/example/https_client_using_ech.rb +6 -7
  7. data/example/https_client_using_grease_ech.rb +0 -2
  8. data/example/https_client_using_hrr.rb +2 -1
  9. data/example/https_client_using_hrr_and_ech.rb +6 -7
  10. data/example/https_client_using_hrr_and_ticket.rb +4 -2
  11. data/example/https_client_using_status_request.rb +2 -1
  12. data/example/https_client_using_ticket.rb +4 -2
  13. data/example/https_client_using_ticket_and_ech.rb +57 -0
  14. data/example/https_server.rb +14 -1
  15. data/lib/tttls1.3/client.rb +205 -418
  16. data/lib/tttls1.3/connection.rb +21 -362
  17. data/lib/tttls1.3/ech.rb +426 -0
  18. data/lib/tttls1.3/endpoint.rb +276 -0
  19. data/lib/tttls1.3/message/certificate_verify.rb +1 -1
  20. data/lib/tttls1.3/message/extension/ech.rb +21 -24
  21. data/lib/tttls1.3/message/extension/ech_outer_extensions.rb +52 -0
  22. data/lib/tttls1.3/message/extension/signature_algorithms.rb +2 -2
  23. data/lib/tttls1.3/message/extension/supported_versions.rb +3 -3
  24. data/lib/tttls1.3/message/extension/unknown_extension.rb +2 -2
  25. data/lib/tttls1.3/message/extensions.rb +30 -0
  26. data/lib/tttls1.3/message.rb +1 -0
  27. data/lib/tttls1.3/server.rb +125 -63
  28. data/lib/tttls1.3/utils.rb +37 -0
  29. data/lib/tttls1.3/version.rb +1 -1
  30. data/lib/tttls1.3.rb +2 -1
  31. data/spec/client_spec.rb +21 -60
  32. data/spec/ech_outer_extensions_spec.rb +42 -0
  33. data/spec/ech_spec.rb +41 -0
  34. data/spec/{connection_spec.rb → endpoint_spec.rb} +41 -49
  35. data/spec/extensions_spec.rb +65 -0
  36. data/spec/server_spec.rb +12 -12
  37. data/spec/spec_helper.rb +4 -0
  38. metadata +11 -6
  39. data/lib/tttls1.3/hpke.rb +0 -91
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbb7e4290064777d30371999409395c0f2db398d1ee7a67eaaf5fa500de27954
4
- data.tar.gz: 3a00195088a78054fd4abdbf77e11fff3898f23b539d90e3ce141a0ef045f227
3
+ metadata.gz: fa5d7f448e0c984d75be22a995278853b4d4b0c505aaaa92fc5e99f48371fc82
4
+ data.tar.gz: 4a0d95465cabfd048ea1d7190c1c617dcc26a5395e06f7acc0173fcbbfe0bd04
5
5
  SHA512:
6
- metadata.gz: 3186fcebd41a40b21c5a4d1de53d13af3ea57dd4d0f3e6baff476882223cc370cb610d9c13cc4df43c3b5b13dd10063b702f39636dd447e9386062de7c566bbc
7
- data.tar.gz: 4c8f6a076e042b1c9059dea5b9598d5bd5d80640b2b72cc859a76d2e020edc047b8309c7821ca849b445c51c41fbcac600363c34881074b28996774d05cc8ffb
6
+ metadata.gz: 4035648fa81ae715315e1efbddd9b0b0506395180d0c7e994d6c8dd49d714e0754717b0cfa93bee702b4ef5a4d8e29bb765fae6045fdb7b24a3e25a1098eca38
7
+ data.tar.gz: 3c9d5286f1724b4998325ab811bf2e6ce44dbbbebb3ffb3c6c01ffe76f2d730797a599196b1ec6c3a894265b16ed0b12a78f2eeca53a36b52980de85db76f247
data/.rubocop.yml CHANGED
@@ -4,6 +4,9 @@ AllCops:
4
4
  Gemspec/RequiredRubyVersion:
5
5
  Enabled: false
6
6
 
7
+ Semicolon:
8
+ AllowAsExpressionSeparator: true
9
+
7
10
  Style/ConditionalAssignment:
8
11
  Enabled: false
9
12
 
data/Gemfile CHANGED
@@ -3,6 +3,7 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gem 'ech_config', '~> 0.0.3'
6
+ gem 'hpke'
6
7
  gem 'logger'
7
8
  gem 'openssl'
8
9
  gem 'rake'
data/example/helper.rb CHANGED
@@ -7,6 +7,7 @@ require 'time'
7
7
  require 'uri'
8
8
  require 'webrick'
9
9
 
10
+ require 'ech_config'
10
11
  require 'http/parser'
11
12
  require 'svcb_rr_patch'
12
13
 
@@ -62,3 +63,45 @@ def recv_http_response(client)
62
63
  parser << client.read until client.eof?
63
64
  buf
64
65
  end
66
+
67
+ def transcript_htmlize(transcript)
68
+ m = {
69
+ TTTLS13::CH1 => 'ClientHello',
70
+ TTTLS13::HRR => 'HelloRetryRequest',
71
+ TTTLS13::CH => 'ClientHello',
72
+ TTTLS13::SH => 'ServerHello',
73
+ TTTLS13::EE => 'EncryptedExtensions',
74
+ TTTLS13::CR => 'CertificateRequest',
75
+ TTTLS13::CT => 'Certificate',
76
+ TTTLS13::CV => 'CertificateVerify',
77
+ TTTLS13::SF => 'Finished',
78
+ TTTLS13::EOED => 'EndOfEarlyData',
79
+ TTTLS13::CCT => 'Certificate',
80
+ TTTLS13::CCV => 'CertificateVerify',
81
+ TTTLS13::CF => 'Finished'
82
+ }.map { |k, v| [k, '<details><summary>' + v + '</summary>%s</details>'] }.to_h
83
+ transcript.map do |k, v|
84
+ format(m[k], TTTLS13::Convert.obj2html(v.first))
85
+ end.join('<br>')
86
+ end
87
+
88
+ def parse_echconfigs_pem(pem)
89
+ s = pem.gsub(/-----(BEGIN|END) ECH CONFIGS-----/, '')
90
+ .gsub("\n", '')
91
+ b = Base64.decode64(s)
92
+ raise 'failed to parse ECHConfigs' \
93
+ unless b.length == b.slice(0, 2).unpack1('n') + 2
94
+
95
+ ECHConfig.decode_vectors(b.slice(2..))
96
+ end
97
+
98
+ def resolve_echconfig(hostname)
99
+ rr = Resolv::DNS.new.getresources(
100
+ hostname,
101
+ Resolv::DNS::Resource::IN::HTTPS
102
+ )
103
+ raise "failed to resolve echconfig via #{hostname} HTTPS RR" \
104
+ if rr.first.nil? || !rr.first.svc_params.keys.include?('ech')
105
+
106
+ rr.first.svc_params['ech'].echconfiglist.first
107
+ end
@@ -9,13 +9,14 @@ req = simple_http_request(uri.host, uri.path)
9
9
 
10
10
  settings_2nd = {
11
11
  ca_file: File.exist?(ca_file) ? ca_file : nil,
12
- alpn: ['http/1.1']
12
+ alpn: ['http/1.1'],
13
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
13
14
  }
14
15
  process_new_session_ticket = lambda do |nst, rms, cs|
15
16
  return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
16
17
 
17
18
  settings_2nd[:ticket] = nst.ticket
18
- settings_2nd[:resumption_main_secret] = rms
19
+ settings_2nd[:resumption_secret] = rms
19
20
  settings_2nd[:psk_cipher_suite] = cs
20
21
  settings_2nd[:ticket_nonce] = nst.ticket_nonce
21
22
  settings_2nd[:ticket_age_add] = nst.ticket_age_add
@@ -24,7 +25,8 @@ end
24
25
  settings_1st = {
25
26
  ca_file: File.exist?(ca_file) ? ca_file : nil,
26
27
  alpn: ['http/1.1'],
27
- process_new_session_ticket: process_new_session_ticket
28
+ process_new_session_ticket: process_new_session_ticket,
29
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
28
30
  }
29
31
 
30
32
  succeed_early_data = false
@@ -2,22 +2,21 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative 'helper'
5
- HpkeSymmetricCipherSuite = \
6
- ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
5
 
8
6
  uri = URI.parse(ARGV[0] || 'https://localhost:4433')
9
7
  ca_file = __dir__ + '/../tmp/ca.crt'
10
8
  req = simple_http_request(uri.host, uri.path)
9
+ ech_config = if ARGV.length > 1
10
+ parse_echconfigs_pem(File.open(ARGV[1]).read).first
11
+ else
12
+ resolve_echconfig(uri.host)
13
+ end
11
14
 
12
- rr = Resolv::DNS.new.getresources(
13
- uri.host,
14
- Resolv::DNS::Resource::IN::HTTPS
15
- )
16
15
  socket = TCPSocket.new(uri.host, uri.port)
17
16
  settings = {
18
17
  ca_file: File.exist?(ca_file) ? ca_file : nil,
19
18
  alpn: ['http/1.1'],
20
- ech_config: rr.first.svc_params['ech'].echconfiglist.first,
19
+ ech_config: ech_config,
21
20
  ech_hpke_cipher_suites:
22
21
  TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
23
22
  sslkeylogfile: '/tmp/sslkeylogfile.log'
@@ -2,8 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative 'helper'
5
- HpkeSymmetricCipherSuite = \
6
- ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
5
 
8
6
  uri = URI.parse(ARGV[0] || 'https://localhost:4433')
9
7
  ca_file = __dir__ + '/../tmp/ca.crt'
@@ -11,7 +11,8 @@ socket = TCPSocket.new(uri.host, uri.port)
11
11
  settings = {
12
12
  ca_file: File.exist?(ca_file) ? ca_file : nil,
13
13
  key_share_groups: [], # empty KeyShareClientHello.client_shares
14
- alpn: ['http/1.1']
14
+ alpn: ['http/1.1'],
15
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
15
16
  }
16
17
  client = TTTLS13::Client.new(socket, uri.host, **settings)
17
18
  client.connect
@@ -2,23 +2,22 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative 'helper'
5
- HpkeSymmetricCipherSuite = \
6
- ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
5
 
8
6
  uri = URI.parse(ARGV[0] || 'https://localhost:4433')
9
7
  ca_file = __dir__ + '/../tmp/ca.crt'
10
8
  req = simple_http_request(uri.host, uri.path)
9
+ ech_config = if ARGV.length > 1
10
+ parse_echconfigs_pem(File.open(ARGV[1]).read).first
11
+ else
12
+ resolve_echconfig(uri.host)
13
+ end
11
14
 
12
- rr = Resolv::DNS.new.getresources(
13
- uri.host,
14
- Resolv::DNS::Resource::IN::HTTPS
15
- )
16
15
  socket = TCPSocket.new(uri.host, uri.port)
17
16
  settings = {
18
17
  ca_file: File.exist?(ca_file) ? ca_file : nil,
19
18
  key_share_groups: [], # empty KeyShareClientHello.client_shares
20
19
  alpn: ['http/1.1'],
21
- ech_config: rr.first.svc_params['ech'].echconfiglist.first,
20
+ ech_config: ech_config,
22
21
  ech_hpke_cipher_suites:
23
22
  TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
24
23
  sslkeylogfile: '/tmp/sslkeylogfile.log'
@@ -9,7 +9,8 @@ req = simple_http_request(uri.host, uri.path)
9
9
 
10
10
  settings_2nd = {
11
11
  ca_file: File.exist?(ca_file) ? ca_file : nil,
12
- alpn: ['http/1.1']
12
+ alpn: ['http/1.1'],
13
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
13
14
  }
14
15
  process_new_session_ticket = lambda do |nst, rms, cs|
15
16
  return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
@@ -25,7 +26,8 @@ end
25
26
  settings_1st = {
26
27
  ca_file: File.exist?(ca_file) ? ca_file : nil,
27
28
  alpn: ['http/1.1'],
28
- process_new_session_ticket: process_new_session_ticket
29
+ process_new_session_ticket: process_new_session_ticket,
30
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
29
31
  }
30
32
 
31
33
  [
@@ -19,7 +19,8 @@ settings = {
19
19
  ca_file: File.exist?(ca_file) ? ca_file : nil,
20
20
  alpn: ['http/1.1'],
21
21
  check_certificate_status: true,
22
- process_certificate_status: process_certificate_status
22
+ process_certificate_status: process_certificate_status,
23
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
23
24
  }
24
25
  client = TTTLS13::Client.new(socket, uri.host, **settings)
25
26
  client.connect
@@ -9,7 +9,8 @@ req = simple_http_request(uri.host, uri.path)
9
9
 
10
10
  settings_2nd = {
11
11
  ca_file: File.exist?(ca_file) ? ca_file : nil,
12
- alpn: ['http/1.1']
12
+ alpn: ['http/1.1'],
13
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
13
14
  }
14
15
  process_new_session_ticket = lambda do |nst, rms, cs|
15
16
  return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
@@ -24,7 +25,8 @@ end
24
25
  settings_1st = {
25
26
  ca_file: File.exist?(ca_file) ? ca_file : nil,
26
27
  alpn: ['http/1.1'],
27
- process_new_session_ticket: process_new_session_ticket
28
+ process_new_session_ticket: process_new_session_ticket,
29
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
28
30
  }
29
31
 
30
32
  [
@@ -0,0 +1,57 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'helper'
5
+
6
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
7
+ ca_file = __dir__ + '/../tmp/ca.crt'
8
+ req = simple_http_request(uri.host, uri.path)
9
+ ech_config = if ARGV.length > 1
10
+ parse_echconfigs_pem(File.open(ARGV[1]).read).first
11
+ else
12
+ resolve_echconfig(uri.host)
13
+ end
14
+
15
+ settings_2nd = {
16
+ ca_file: File.exist?(ca_file) ? ca_file : nil,
17
+ alpn: ['http/1.1'],
18
+ ech_config: ech_config,
19
+ ech_hpke_cipher_suites:
20
+ TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
21
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
22
+ }
23
+ process_new_session_ticket = lambda do |nst, rms, cs|
24
+ return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
25
+
26
+ settings_2nd[:ticket] = nst.ticket
27
+ settings_2nd[:resumption_main_secret] = rms
28
+ settings_2nd[:psk_cipher_suite] = cs
29
+ settings_2nd[:ticket_nonce] = nst.ticket_nonce
30
+ settings_2nd[:ticket_age_add] = nst.ticket_age_add
31
+ settings_2nd[:ticket_timestamp] = nst.timestamp
32
+ end
33
+ settings_1st = {
34
+ ca_file: File.exist?(ca_file) ? ca_file : nil,
35
+ alpn: ['http/1.1'],
36
+ ech_config: ech_config,
37
+ ech_hpke_cipher_suites:
38
+ TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
39
+ process_new_session_ticket: process_new_session_ticket,
40
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
41
+ }
42
+
43
+ [
44
+ # Initial Handshake:
45
+ settings_1st,
46
+ # Subsequent Handshake:
47
+ settings_2nd
48
+ ].each do |settings|
49
+ socket = TCPSocket.new(uri.host, uri.port)
50
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
51
+ client.connect
52
+ client.write(req)
53
+
54
+ print recv_http_response(client)
55
+ client.close unless client.eof?
56
+ socket.close
57
+ end
@@ -30,7 +30,20 @@ Etc.nprocessors.times do
30
30
  parser.on_message_complete = lambda do
31
31
  if !parser.http_method.nil?
32
32
  logger.info 'Receive Request'
33
- server.write(simple_http_response('TEST'))
33
+ html = <<HTML
34
+ <!DOCTYPE html>
35
+ <html>
36
+ <head>
37
+ <meta charset="UTF-8" />
38
+ <title>tttls1.3 test server</title>
39
+ </head>
40
+ <body>
41
+ %s
42
+ </body>
43
+ </html>
44
+ HTML
45
+ html = format(html, transcript_htmlize(server.transcript))
46
+ server.write(simple_http_response(html))
34
47
  server.close
35
48
  else
36
49
  logger.warn 'Not Request'