tttls1.3 0.2.19 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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