tttls1.3 0.2.19 → 0.3.1

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 (45) 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 +2 -0
  6. data/README.md +4 -1
  7. data/example/README.md +1 -1
  8. data/example/helper.rb +6 -2
  9. data/example/https_client.rb +4 -4
  10. data/example/https_client_using_0rtt.rb +5 -4
  11. data/example/https_client_using_ech.rb +31 -0
  12. data/example/https_client_using_grease_ech.rb +24 -0
  13. data/example/https_client_using_grease_psk.rb +58 -0
  14. data/example/https_client_using_hrr.rb +5 -4
  15. data/example/https_client_using_hrr_and_ech.rb +32 -0
  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/lib/tttls1.3/client.rb +534 -24
  20. data/lib/tttls1.3/connection.rb +3 -0
  21. data/lib/tttls1.3/cryptograph/aead.rb +1 -1
  22. data/lib/tttls1.3/error.rb +1 -1
  23. data/lib/tttls1.3/hpke.rb +91 -0
  24. data/lib/tttls1.3/key_schedule.rb +71 -3
  25. data/lib/tttls1.3/message/alert.rb +2 -1
  26. data/lib/tttls1.3/message/client_hello.rb +2 -1
  27. data/lib/tttls1.3/message/encrypted_extensions.rb +2 -1
  28. data/lib/tttls1.3/message/extension/alpn.rb +4 -5
  29. data/lib/tttls1.3/message/extension/compress_certificate.rb +1 -1
  30. data/lib/tttls1.3/message/extension/ech.rb +241 -0
  31. data/lib/tttls1.3/message/extension/server_name.rb +1 -1
  32. data/lib/tttls1.3/message/extensions.rb +20 -7
  33. data/lib/tttls1.3/message/record.rb +1 -1
  34. data/lib/tttls1.3/message/server_hello.rb +3 -5
  35. data/lib/tttls1.3/message.rb +3 -1
  36. data/lib/tttls1.3/named_group.rb +1 -1
  37. data/lib/tttls1.3/server.rb +1 -1
  38. data/lib/tttls1.3/utils.rb +8 -0
  39. data/lib/tttls1.3/version.rb +1 -1
  40. data/lib/tttls1.3.rb +4 -0
  41. data/spec/client_spec.rb +40 -0
  42. data/spec/ech_spec.rb +81 -0
  43. data/spec/spec_helper.rb +41 -5
  44. data/tttls1.3.gemspec +3 -1
  45. metadata +40 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60aaa0dddc8e01d6ee1c89a81de02e7cd9e05e0169e11381ebb68aa919644f11
4
- data.tar.gz: 974b5c89009c2a63a6d99a608b32463cb0b6dc4bb0ed9e915cd03cca45ce2ea9
3
+ metadata.gz: fbb7e4290064777d30371999409395c0f2db398d1ee7a67eaaf5fa500de27954
4
+ data.tar.gz: 3a00195088a78054fd4abdbf77e11fff3898f23b539d90e3ce141a0ef045f227
5
5
  SHA512:
6
- metadata.gz: b9ab939f9010481de463c2fbf81dc230cdd653dac47286e1fd61f8820da796a09b0675837dc119ebd8f1ddd137383ccc87aec18edd4945312cb0923ebbe77e52
7
- data.tar.gz: 74d0635bba0274cfaf9ed980d2f0cef3351ab1f820a17720424dececaeb86c6ea36d6f9c3d7b69c8e81b52c4790b1796ba59d02a95a30d4afe90bad35767d442
6
+ metadata.gz: 3186fcebd41a40b21c5a4d1de53d13af3ea57dd4d0f3e6baff476882223cc370cb610d9c13cc4df43c3b5b13dd10063b702f39636dd447e9386062de7c566bbc
7
+ data.tar.gz: 4c8f6a076e042b1c9059dea5b9598d5bd5d80640b2b72cc859a76d2e020edc047b8309c7821ca849b445c51c41fbcac600363c34881074b28996774d05cc8ffb
@@ -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
@@ -2,6 +2,7 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'ech_config', '~> 0.0.3'
5
6
  gem 'logger'
6
7
  gem 'openssl'
7
8
  gem 'rake'
@@ -11,6 +12,7 @@ group :development do
11
12
  gem 'http_parser.rb'
12
13
  gem 'rspec', '3.9.0'
13
14
  gem 'rubocop', '0.78.0'
15
+ gem 'svcb_rr_patch'
14
16
  gem 'webrick'
15
17
  end
16
18
 
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Actions Status](https://github.com/thekuwayama/tttls1.3/workflows/CI/badge.svg)](https://github.com/thekuwayama/tttls1.3/actions?workflow=CI)
5
5
  [![Maintainability](https://api.codeclimate.com/v1/badges/b5ae1b3a43828142d2fa/maintainability)](https://codeclimate.com/github/thekuwayama/tttls1.3/maintainability)
6
6
 
7
- tttls1.3 is Ruby implementation of [TLS 1.3](https://tools.ietf.org/html/rfc8446) protocol.
7
+ tttls1.3 is Ruby implementation of [TLS 1.3](https://datatracker.ietf.org/doc/rfc8446/) protocol.
8
8
 
9
9
  tttls1.3 uses [openssl](https://github.com/ruby/openssl) for crypto and X.509 operations.
10
10
 
@@ -22,6 +22,7 @@ tttls1.3 provides client API with the following features:
22
22
  * Simple 1-RTT Handshake
23
23
  * HelloRetryRequest
24
24
  * Resumed 0-RTT Handshake (with PSK from NST)
25
+ * [ECH](https://datatracker.ietf.org/doc/draft-ietf-tls-esni/)
25
26
 
26
27
  **NOT supports** certificate with OID RSASSA-PSS, X25519, X448, FFDHE, AES-CCM, Client Authentication, Post-Handshake Authentication, KeyUpdate and external PSKs.
27
28
 
@@ -103,6 +104,8 @@ tttls1.3 client is configurable using keyword arguments.
103
104
  | `:check_certificate_status` | Boolean | false | If needed to check certificate status, set true. |
104
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. |
105
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. 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. |
106
109
  | `:compatibility_mode` | Boolean | true | If needed to send ChangeCipherSpec, set true. |
107
110
  | `:sslkeylogfile` | String | nil | If needed to log SSLKEYLOGFILE, set the file path. |
108
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
@@ -3,10 +3,14 @@
3
3
  $LOAD_PATH << __dir__ + '/../lib'
4
4
 
5
5
  require 'socket'
6
- require 'tttls1.3'
6
+ require 'time'
7
+ require 'uri'
7
8
  require 'webrick'
9
+
8
10
  require 'http/parser'
9
- require 'time'
11
+ require 'svcb_rr_patch'
12
+
13
+ require 'tttls1.3'
10
14
 
11
15
  def simple_http_request(hostname, path = '/')
12
16
  s = <<~REQUEST
@@ -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,
@@ -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
@@ -0,0 +1,31 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'helper'
5
+ HpkeSymmetricCipherSuite = \
6
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
+
8
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
9
+ ca_file = __dir__ + '/../tmp/ca.crt'
10
+ req = simple_http_request(uri.host, uri.path)
11
+
12
+ rr = Resolv::DNS.new.getresources(
13
+ uri.host,
14
+ Resolv::DNS::Resource::IN::HTTPS
15
+ )
16
+ socket = TCPSocket.new(uri.host, uri.port)
17
+ settings = {
18
+ ca_file: File.exist?(ca_file) ? ca_file : nil,
19
+ alpn: ['http/1.1'],
20
+ ech_config: rr.first.svc_params['ech'].echconfiglist.first,
21
+ ech_hpke_cipher_suites:
22
+ TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
23
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
24
+ }
25
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
26
+ client.connect
27
+ client.write(req)
28
+
29
+ print recv_http_response(client)
30
+ client.close unless client.eof?
31
+ socket.close
@@ -0,0 +1,24 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'helper'
5
+ HpkeSymmetricCipherSuite = \
6
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
+
8
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
9
+ ca_file = __dir__ + '/../tmp/ca.crt'
10
+
11
+ socket = TCPSocket.new(uri.host, uri.port)
12
+ settings = {
13
+ ca_file: File.exist?(ca_file) ? ca_file : nil,
14
+ alpn: ['http/1.1'],
15
+ ech_hpke_cipher_suites:
16
+ TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
17
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
18
+ }
19
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
20
+ client.connect
21
+
22
+ print client.retry_configs if client.rejected_ech?
23
+ client.close unless client.eof?
24
+ socket.close
@@ -0,0 +1,58 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'helper'
5
+ HpkeSymmetricCipherSuite = \
6
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
+
8
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
9
+ ca_file = __dir__ + '/../tmp/ca.crt'
10
+ req = simple_http_request(uri.host, uri.path)
11
+
12
+ rr = Resolv::DNS.new.getresources(
13
+ uri.host,
14
+ Resolv::DNS::Resource::IN::HTTPS
15
+ )
16
+ settings_2nd = {
17
+ ca_file: File.exist?(ca_file) ? ca_file : nil,
18
+ alpn: ['http/1.1'],
19
+ ech_config: rr.first.svc_params['ech'].echconfiglist.first,
20
+ ech_hpke_cipher_suites:
21
+ TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
22
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
23
+ }
24
+ process_new_session_ticket = lambda do |nst, rms, cs|
25
+ return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
26
+
27
+ settings_2nd[:ticket] = nst.ticket
28
+ settings_2nd[:resumption_main_secret] = rms
29
+ settings_2nd[:psk_cipher_suite] = cs
30
+ settings_2nd[:ticket_nonce] = nst.ticket_nonce
31
+ settings_2nd[:ticket_age_add] = nst.ticket_age_add
32
+ settings_2nd[:ticket_timestamp] = nst.timestamp
33
+ end
34
+ settings_1st = {
35
+ ca_file: File.exist?(ca_file) ? ca_file : nil,
36
+ alpn: ['http/1.1'],
37
+ process_new_session_ticket: process_new_session_ticket,
38
+ ech_config: rr.first.svc_params['ech'].echconfiglist.first,
39
+ ech_hpke_cipher_suites:
40
+ TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
41
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
42
+ }
43
+
44
+ [
45
+ # Initial Handshake:
46
+ settings_1st,
47
+ # Subsequent Handshake:
48
+ settings_2nd
49
+ ].each do |settings|
50
+ socket = TCPSocket.new(uri.host, uri.port)
51
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
52
+ client.connect
53
+ client.write(req)
54
+
55
+ print recv_http_response(client)
56
+ client.close unless client.eof?
57
+ socket.close
58
+ end
@@ -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
@@ -0,0 +1,32 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'helper'
5
+ HpkeSymmetricCipherSuite = \
6
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
+
8
+ uri = URI.parse(ARGV[0] || 'https://localhost:4433')
9
+ ca_file = __dir__ + '/../tmp/ca.crt'
10
+ req = simple_http_request(uri.host, uri.path)
11
+
12
+ rr = Resolv::DNS.new.getresources(
13
+ uri.host,
14
+ Resolv::DNS::Resource::IN::HTTPS
15
+ )
16
+ socket = TCPSocket.new(uri.host, uri.port)
17
+ settings = {
18
+ ca_file: File.exist?(ca_file) ? ca_file : nil,
19
+ key_share_groups: [], # empty KeyShareClientHello.client_shares
20
+ alpn: ['http/1.1'],
21
+ ech_config: rr.first.svc_params['ech'].echconfiglist.first,
22
+ ech_hpke_cipher_suites:
23
+ TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
24
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
25
+ }
26
+ client = TTTLS13::Client.new(socket, uri.host, **settings)
27
+ client.connect
28
+ client.write(req)
29
+
30
+ print recv_http_response(client)
31
+ client.close unless client.eof?
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