tttls1.3 0.3.0 → 0.3.2

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/.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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd89bebc90f5379d37e4fd3d1397168b6df9fcbfe5d1ad05f3ae852ba7d071d1
4
- data.tar.gz: 45372c096b46a5c37c9e05d22dfad8d53e24278d5d195b1b7305aa86b49bdfe9
3
+ metadata.gz: 7245602faa9087e83b3e47484aa88dc368b153e98c76fd00e36ebb1a863af6ef
4
+ data.tar.gz: c7362f39fc26763f712cdf61a29b3796686c50034d2a6431788d8dbf7dd505c9
5
5
  SHA512:
6
- metadata.gz: 6b79f03e7eff3d7f2e47e0ca715ee9559c8c2a04f3f6c4869b4c8b915905f8934bb6cf4dd776ae74bdc3e7d9c97dd3a8ee77e46298073bdffd70fc936a954421
7
- data.tar.gz: c43d3629de31d6ebd8f86d0c7a700d85a9f6e53be351eb13062c7ad0af9d1b8acc1c685f95a48e7d320b22ad1cd83979c4de3b57f07245060d6cc0dacdcca482
6
+ metadata.gz: 33168ef27007f9e6d73197ed3e1bae9b58dadfdf3042e732ddc6a05913db0aad48fb653ea7b7222716d70ebe42739f2d9dddee0ec75ee45eb33daba4be6f0229
7
+ data.tar.gz: d05c73a49e0f5d568785c3b6af3d9bc3c286b35f1110c5f31860f8cf788a9f9e019112fd8647394672c279808e20c4f02d438a58c44f5bab9af1d5fb851ef41f
@@ -13,14 +13,14 @@ jobs:
13
13
  runs-on: ubuntu-latest
14
14
  strategy:
15
15
  matrix:
16
- ruby-version: ['2.7.x', '3.0.x', '3.1.x']
16
+ ruby-version: ['3.1', '3.2', '3.3']
17
17
  steps:
18
18
  - uses: actions/checkout@v3
19
19
  - uses: docker://thekuwayama/openssl:latest
20
20
  - name: Set up Ruby
21
21
  uses: ruby/setup-ruby@v1
22
22
  with:
23
- ruby-version: ${{ matrix.ruby }}
23
+ ruby-version: ${{ matrix.ruby-version }}
24
24
  - name: Install dependencies
25
25
  run: |
26
26
  gem --version
data/.rubocop.yml CHANGED
@@ -1,6 +1,9 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.7
3
3
 
4
+ Gemspec/RequiredRubyVersion:
5
+ Enabled: false
6
+
4
7
  Style/ConditionalAssignment:
5
8
  Enabled: false
6
9
 
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.1.2
1
+ 3.2.2
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/README.md CHANGED
@@ -104,8 +104,8 @@ tttls1.3 client is configurable using keyword arguments.
104
104
  | `:check_certificate_status` | Boolean | false | If needed to check certificate status, set true. |
105
105
  | `: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. |
106
106
  | `:compress_certificate_algorithms` | Array of TTTLS13::Message::Extension::CertificateCompressionAlgorithm constant | `ZLIB` | The compression algorithms are supported for compressing the Certificate message. |
107
- | `:ech_config` | ECHConfig | nil | ECHConfig to use ECH. If needed to use ECH, set TTTLS13::STANDARD\_CLIENT\_ECH_HPKE\_SYMMETRIC\_CIPHER\_SUITES, for example. See [ech_config](https://github.com/thekuwayama/ech_config). |
108
- | `:ech_hpke_cipher_suites` | Array of ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite | nil | If needed to use ECH, set client preference HPKE cipher suites. |
107
+ | `:ech_config` | ECHConfig | nil | ECHConfig to use ECH. See [ech_config](https://github.com/thekuwayama/ech_config). |
108
+ | `:ech_hpke_cipher_suites` | Array of ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite | nil | If needed to use ECH, set client preference HPKE cipher suites. For example, you can set TTTLS13::STANDARD\_CLIENT\_ECH_HPKE\_SYMMETRIC\_CIPHER\_SUITES. |
109
109
  | `:compatibility_mode` | Boolean | true | If needed to send ChangeCipherSpec, set true. |
110
110
  | `:sslkeylogfile` | String | nil | If needed to log SSLKEYLOGFILE, set the file path. |
111
111
  | `:loglevel` | Logger constant | Logger::WARN | If needed to print verbose, set Logger::DEBUG. |
data/example/README.md CHANGED
@@ -13,7 +13,7 @@ The examples run as follows:
13
13
  ```bash
14
14
  $ ruby https_client.rb
15
15
 
16
- $ ruby https_client.rb localhost:4433
16
+ $ ruby https_client.rb https://localhost:4433
17
17
  ```
18
18
 
19
19
  Note that `https_server.rb` requires PEM files of certificate and private key.
data/example/helper.rb CHANGED
@@ -4,6 +4,7 @@ $LOAD_PATH << __dir__ + '/../lib'
4
4
 
5
5
  require 'socket'
6
6
  require 'time'
7
+ require 'uri'
7
8
  require 'webrick'
8
9
 
9
10
  require 'http/parser'
@@ -61,3 +62,24 @@ def recv_http_response(client)
61
62
  parser << client.read until client.eof?
62
63
  buf
63
64
  end
65
+
66
+ def transcript_htmlize(transcript)
67
+ m = {
68
+ TTTLS13::CH1 => 'ClientHello',
69
+ TTTLS13::HRR => 'HelloRetryRequest',
70
+ TTTLS13::CH => 'ClientHello',
71
+ TTTLS13::SH => 'ServerHello',
72
+ TTTLS13::EE => 'EncryptedExtensions',
73
+ TTTLS13::CR => 'CertificateRequest',
74
+ TTTLS13::CT => 'Certificate',
75
+ TTTLS13::CV => 'CertificateVerify',
76
+ TTTLS13::SF => 'Finished',
77
+ TTTLS13::EOED => 'EndOfEarlyData',
78
+ TTTLS13::CCT => 'Certificate',
79
+ TTTLS13::CCV => 'CertificateVerify',
80
+ TTTLS13::CF => 'Finished'
81
+ }.map { |k, v| [k, '<details><summary>' + v + '</summary>%s</details>'] }.to_h
82
+ transcript.map do |k, v|
83
+ format(m[k], TTTLS13::Convert.obj2html(v.first))
84
+ end.join('<br>')
85
+ end
@@ -3,17 +3,17 @@
3
3
 
4
4
  require_relative 'helper'
5
5
 
6
- hostname, port = (ARGV[0] || 'localhost:4433').split(':')
6
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
7
7
  ca_file = __dir__ + '/../tmp/ca.crt'
8
- req = simple_http_request(hostname)
8
+ req = simple_http_request(uri.host, uri.path)
9
9
 
10
- socket = TCPSocket.new(hostname, port)
10
+ socket = TCPSocket.new(uri.host, uri.port)
11
11
  settings = {
12
12
  ca_file: File.exist?(ca_file) ? ca_file : nil,
13
13
  alpn: ['http/1.1'],
14
14
  sslkeylogfile: '/tmp/sslkeylogfile.log'
15
15
  }
16
- client = TTTLS13::Client.new(socket, hostname, **settings)
16
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
17
17
  client.connect
18
18
  client.write(req)
19
19
 
@@ -3,9 +3,9 @@
3
3
 
4
4
  require_relative 'helper'
5
5
 
6
- hostname, port = (ARGV[0] || 'localhost:4433').split(':')
6
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
7
7
  ca_file = __dir__ + '/../tmp/ca.crt'
8
- req = simple_http_request(hostname)
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,
@@ -15,7 +15,7 @@ process_new_session_ticket = lambda do |nst, rms, cs|
15
15
  return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
16
16
 
17
17
  settings_2nd[:ticket] = nst.ticket
18
- settings_2nd[:resumption_main_secret] = rms
18
+ settings_2nd[:resumption_secret] = rms
19
19
  settings_2nd[:psk_cipher_suite] = cs
20
20
  settings_2nd[:ticket_nonce] = nst.ticket_nonce
21
21
  settings_2nd[:ticket_age_add] = nst.ticket_age_add
@@ -35,14 +35,15 @@ succeed_early_data = false
35
35
  # Subsequent Handshake:
36
36
  settings_2nd
37
37
  ].each_with_index do |settings, i|
38
- socket = TCPSocket.new(hostname, port)
39
- client = TTTLS13::Client.new(socket, hostname, **settings)
38
+ socket = TCPSocket.new(uri.host, uri.port)
39
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
40
40
 
41
41
  # send message using early data; 0-RTT
42
42
  client.early_data(req) if i == 1 && settings.include?(:ticket)
43
43
  client.connect
44
44
  # send message after Simple 1-RTT Handshake
45
45
  client.write(req) if i.zero? || !client.succeed_early_data?
46
+
46
47
  print recv_http_response(client)
47
48
  client.close unless client.eof?
48
49
  socket.close
@@ -5,16 +5,15 @@ require_relative 'helper'
5
5
  HpkeSymmetricCipherSuite = \
6
6
  ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
7
 
8
- hostname = 'crypto.cloudflare.com'
9
- port = 443
8
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
10
9
  ca_file = __dir__ + '/../tmp/ca.crt'
11
- req = simple_http_request(hostname, '/cdn-cgi/trace')
10
+ req = simple_http_request(uri.host, uri.path)
12
11
 
13
12
  rr = Resolv::DNS.new.getresources(
14
- hostname,
13
+ uri.host,
15
14
  Resolv::DNS::Resource::IN::HTTPS
16
15
  )
17
- socket = TCPSocket.new(hostname, port)
16
+ socket = TCPSocket.new(uri.host, uri.port)
18
17
  settings = {
19
18
  ca_file: File.exist?(ca_file) ? ca_file : nil,
20
19
  alpn: ['http/1.1'],
@@ -23,7 +22,7 @@ settings = {
23
22
  TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
24
23
  sslkeylogfile: '/tmp/sslkeylogfile.log'
25
24
  }
26
- client = TTTLS13::Client.new(socket, hostname, **settings)
25
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
27
26
  client.connect
28
27
  client.write(req)
29
28
 
@@ -5,11 +5,10 @@ require_relative 'helper'
5
5
  HpkeSymmetricCipherSuite = \
6
6
  ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
7
 
8
- hostname = 'crypto.cloudflare.com'
9
- port = 443
8
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
10
9
  ca_file = __dir__ + '/../tmp/ca.crt'
11
10
 
12
- socket = TCPSocket.new(hostname, port)
11
+ socket = TCPSocket.new(uri.host, uri.port)
13
12
  settings = {
14
13
  ca_file: File.exist?(ca_file) ? ca_file : nil,
15
14
  alpn: ['http/1.1'],
@@ -17,10 +16,9 @@ settings = {
17
16
  TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
18
17
  sslkeylogfile: '/tmp/sslkeylogfile.log'
19
18
  }
20
- client = TTTLS13::Client.new(socket, hostname, **settings)
19
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
21
20
  client.connect
22
21
 
23
22
  print client.retry_configs if client.rejected_ech?
24
-
25
23
  client.close unless client.eof?
26
24
  socket.close
@@ -5,13 +5,12 @@ require_relative 'helper'
5
5
  HpkeSymmetricCipherSuite = \
6
6
  ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
7
 
8
- hostname = 'crypto.cloudflare.com'
9
- port = 443
8
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
10
9
  ca_file = __dir__ + '/../tmp/ca.crt'
11
- req = simple_http_request(hostname, '/cdn-cgi/trace')
10
+ req = simple_http_request(uri.host, uri.path)
12
11
 
13
12
  rr = Resolv::DNS.new.getresources(
14
- hostname,
13
+ uri.host,
15
14
  Resolv::DNS::Resource::IN::HTTPS
16
15
  )
17
16
  settings_2nd = {
@@ -37,16 +36,8 @@ settings_1st = {
37
36
  alpn: ['http/1.1'],
38
37
  process_new_session_ticket: process_new_session_ticket,
39
38
  ech_config: rr.first.svc_params['ech'].echconfiglist.first,
40
- ech_hpke_cipher_suites: [
41
- HpkeSymmetricCipherSuite.new(
42
- HpkeSymmetricCipherSuite::HpkeKdfId.new(
43
- TTTLS13::Hpke::KdfId::HKDF_SHA256
44
- ),
45
- HpkeSymmetricCipherSuite::HpkeAeadId.new(
46
- TTTLS13::Hpke::AeadId::AES_128_GCM
47
- )
48
- )
49
- ],
39
+ ech_hpke_cipher_suites:
40
+ TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
50
41
  sslkeylogfile: '/tmp/sslkeylogfile.log'
51
42
  }
52
43
 
@@ -56,10 +47,11 @@ settings_1st = {
56
47
  # Subsequent Handshake:
57
48
  settings_2nd
58
49
  ].each do |settings|
59
- socket = TCPSocket.new(hostname, port)
60
- client = TTTLS13::Client.new(socket, hostname, **settings)
50
+ socket = TCPSocket.new(uri.host, uri.port)
51
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
61
52
  client.connect
62
53
  client.write(req)
54
+
63
55
  print recv_http_response(client)
64
56
  client.close unless client.eof?
65
57
  socket.close
@@ -3,19 +3,20 @@
3
3
 
4
4
  require_relative 'helper'
5
5
 
6
- hostname, port = (ARGV[0] || 'localhost:4433').split(':')
6
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
7
7
  ca_file = __dir__ + '/../tmp/ca.crt'
8
- req = simple_http_request(hostname)
8
+ req = simple_http_request(uri.host, uri.path)
9
9
 
10
- socket = TCPSocket.new(hostname, port)
10
+ 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
14
  alpn: ['http/1.1']
15
15
  }
16
- client = TTTLS13::Client.new(socket, hostname, **settings)
16
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
17
17
  client.connect
18
18
  client.write(req)
19
+
19
20
  print recv_http_response(client)
20
21
  client.close unless client.eof?
21
22
  socket.close
@@ -5,16 +5,15 @@ require_relative 'helper'
5
5
  HpkeSymmetricCipherSuite = \
6
6
  ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
7
 
8
- hostname = 'crypto.cloudflare.com'
9
- port = 443
8
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
10
9
  ca_file = __dir__ + '/../tmp/ca.crt'
11
- req = simple_http_request(hostname, '/cdn-cgi/trace')
10
+ req = simple_http_request(uri.host, uri.path)
12
11
 
13
12
  rr = Resolv::DNS.new.getresources(
14
- hostname,
13
+ uri.host,
15
14
  Resolv::DNS::Resource::IN::HTTPS
16
15
  )
17
- socket = TCPSocket.new(hostname, port)
16
+ socket = TCPSocket.new(uri.host, uri.port)
18
17
  settings = {
19
18
  ca_file: File.exist?(ca_file) ? ca_file : nil,
20
19
  key_share_groups: [], # empty KeyShareClientHello.client_shares
@@ -24,9 +23,10 @@ settings = {
24
23
  TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
25
24
  sslkeylogfile: '/tmp/sslkeylogfile.log'
26
25
  }
27
- client = TTTLS13::Client.new(socket, hostname, **settings)
26
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
28
27
  client.connect
29
28
  client.write(req)
29
+
30
30
  print recv_http_response(client)
31
31
  client.close unless client.eof?
32
32
  socket.close
@@ -3,9 +3,9 @@
3
3
 
4
4
  require_relative 'helper'
5
5
 
6
- hostname, port = (ARGV[0] || 'localhost:4433').split(':')
6
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
7
7
  ca_file = __dir__ + '/../tmp/ca.crt'
8
- req = simple_http_request(hostname)
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,
@@ -34,10 +34,11 @@ settings_1st = {
34
34
  # Subsequent Handshake:
35
35
  settings_2nd
36
36
  ].each do |settings|
37
- socket = TCPSocket.new(hostname, port)
38
- client = TTTLS13::Client.new(socket, hostname, **settings)
37
+ socket = TCPSocket.new(uri.host, uri.port)
38
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
39
39
  client.connect
40
40
  client.write(req)
41
+
41
42
  print recv_http_response(client)
42
43
  client.close unless client.eof?
43
44
  socket.close
@@ -3,10 +3,11 @@
3
3
 
4
4
  require_relative 'helper'
5
5
 
6
- hostname, port = (ARGV[0] || 'localhost:4433').split(':')
6
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
7
7
  ca_file = __dir__ + '/../tmp/ca.crt'
8
- req = simple_http_request(hostname)
8
+ req = simple_http_request(uri.host, uri.path)
9
9
 
10
+ socket = TCPSocket.new(uri.host, uri.port)
10
11
  process_certificate_status = lambda do |res, cert, chain|
11
12
  puts 'stapled OCSPResponse: '
12
13
  puts res.basic.status.pretty_inspect unless res.nil?
@@ -14,15 +15,13 @@ process_certificate_status = lambda do |res, cert, chain|
14
15
 
15
16
  TTTLS13::Client.softfail_check_certificate_status(res, cert, chain)
16
17
  end
17
-
18
- socket = TCPSocket.new(hostname, port)
19
18
  settings = {
20
19
  ca_file: File.exist?(ca_file) ? ca_file : nil,
21
20
  alpn: ['http/1.1'],
22
21
  check_certificate_status: true,
23
22
  process_certificate_status: process_certificate_status
24
23
  }
25
- client = TTTLS13::Client.new(socket, hostname, **settings)
24
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
26
25
  client.connect
27
26
  client.write(req)
28
27
 
@@ -3,9 +3,9 @@
3
3
 
4
4
  require_relative 'helper'
5
5
 
6
- hostname, port = (ARGV[0] || 'localhost:4433').split(':')
6
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
7
7
  ca_file = __dir__ + '/../tmp/ca.crt'
8
- req = simple_http_request(hostname)
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,
@@ -33,10 +33,11 @@ settings_1st = {
33
33
  # Subsequent Handshake:
34
34
  settings_2nd
35
35
  ].each do |settings|
36
- socket = TCPSocket.new(hostname, port)
37
- client = TTTLS13::Client.new(socket, hostname, **settings)
36
+ socket = TCPSocket.new(uri.host, uri.port)
37
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
38
38
  client.connect
39
39
  client.write(req)
40
+
40
41
  print recv_http_response(client)
41
42
  client.close unless client.eof?
42
43
  socket.close
@@ -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'