tttls1.3 0.3.1 → 0.3.3

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/.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'