tttls1.3 0.2.18 → 0.3.0

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +8 -5
  3. data/Gemfile +2 -0
  4. data/README.md +6 -3
  5. data/example/helper.rb +5 -2
  6. data/example/https_client_using_0rtt.rb +1 -1
  7. data/example/https_client_using_ech.rb +32 -0
  8. data/example/https_client_using_grease_ech.rb +26 -0
  9. data/example/https_client_using_grease_psk.rb +66 -0
  10. data/example/https_client_using_hrr_and_ech.rb +32 -0
  11. data/example/https_client_using_hrr_and_ticket.rb +1 -1
  12. data/example/https_client_using_ticket.rb +1 -1
  13. data/interop/client_spec.rb +3 -2
  14. data/interop/server_spec.rb +1 -3
  15. data/interop/{helper.rb → spec_helper.rb} +12 -5
  16. data/lib/tttls1.3/client.rb +553 -32
  17. data/lib/tttls1.3/connection.rb +9 -8
  18. data/lib/tttls1.3/cryptograph/aead.rb +1 -1
  19. data/lib/tttls1.3/error.rb +1 -1
  20. data/lib/tttls1.3/hpke.rb +91 -0
  21. data/lib/tttls1.3/key_schedule.rb +111 -8
  22. data/lib/tttls1.3/message/alert.rb +2 -1
  23. data/lib/tttls1.3/message/client_hello.rb +2 -1
  24. data/lib/tttls1.3/message/encrypted_extensions.rb +2 -1
  25. data/lib/tttls1.3/message/extension/alpn.rb +4 -5
  26. data/lib/tttls1.3/message/extension/compress_certificate.rb +1 -1
  27. data/lib/tttls1.3/message/extension/ech.rb +241 -0
  28. data/lib/tttls1.3/message/extension/key_share.rb +2 -4
  29. data/lib/tttls1.3/message/extension/server_name.rb +1 -1
  30. data/lib/tttls1.3/message/extensions.rb +20 -7
  31. data/lib/tttls1.3/message/record.rb +1 -1
  32. data/lib/tttls1.3/message/server_hello.rb +3 -5
  33. data/lib/tttls1.3/message.rb +3 -1
  34. data/lib/tttls1.3/named_group.rb +1 -1
  35. data/lib/tttls1.3/server.rb +2 -2
  36. data/lib/tttls1.3/utils.rb +8 -0
  37. data/lib/tttls1.3/version.rb +1 -1
  38. data/lib/tttls1.3.rb +4 -0
  39. data/spec/client_spec.rb +40 -0
  40. data/spec/connection_spec.rb +22 -7
  41. data/spec/ech_spec.rb +81 -0
  42. data/spec/extensions_spec.rb +1 -2
  43. data/spec/key_schedule_spec.rb +2 -2
  44. data/spec/server_spec.rb +22 -7
  45. data/spec/spec_helper.rb +41 -5
  46. data/tttls1.3.gemspec +2 -0
  47. metadata +39 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a07aded25aecad8bd61ff9fd49a70df15c8abf356d4747891486dd81386b68d
4
- data.tar.gz: 4637b3288dab22caae951cc43c283057fd3ed215fc5fa86e318becc3369ac7b2
3
+ metadata.gz: fd89bebc90f5379d37e4fd3d1397168b6df9fcbfe5d1ad05f3ae852ba7d071d1
4
+ data.tar.gz: 45372c096b46a5c37c9e05d22dfad8d53e24278d5d195b1b7305aa86b49bdfe9
5
5
  SHA512:
6
- metadata.gz: 621a8f82c99e21e964cfb6defe14e2f8864f1c42cc94c9af725de2ff73929226d99a694c893ada3fc44c5224be70ec87bfcb291eceab271b33e4759c6c900cd8
7
- data.tar.gz: 9088db06f998013577eb647d064e97035047a2cef7799010bc91f18384787bdf158357fd33c296b92ec1bc07a2ad1b307c98d0217735dfef5c6acf565f3c9433
6
+ metadata.gz: 6b79f03e7eff3d7f2e47e0ca715ee9559c8c2a04f3f6c4869b4c8b915905f8934bb6cf4dd776ae74bdc3e7d9c97dd3a8ee77e46298073bdffd70fc936a954421
7
+ data.tar.gz: c43d3629de31d6ebd8f86d0c7a700d85a9f6e53be351eb13062c7ad0af9d1b8acc1c685f95a48e7d320b22ad1cd83979c4de3b57f07245060d6cc0dacdcca482
@@ -27,8 +27,11 @@ jobs:
27
27
  gem install bundler
28
28
  bundle --version
29
29
  bundle install
30
- - name: Run test
31
- run: |
32
- bundle exec rake
33
- bundle exec rake interop:client
34
- bundle exec rake interop:server
30
+ - name: Run rubocop
31
+ run: bundle exec rake rubocop
32
+ - name: Run rspec
33
+ run: bundle exec rake spec
34
+ - name: Run interop client
35
+ run: bundle exec rake interop:client
36
+ - name: Run interop server
37
+ run: bundle exec rake interop:server
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
 
@@ -92,9 +93,9 @@ tttls1.3 client is configurable using keyword arguments.
92
93
  | `:supported_groups` | Array of TTTLS13::NamedGroup constant | `SECP256R1`, `SECP384R1`, `SECP521R1` | List of named groups offered in ClientHello extensions. |
93
94
  | `:key_share_groups` | Array of TTTLS13::NamedGroup constant | nil | List of named groups offered in KeyShareClientHello. In default, KeyShareClientHello has only a KeyShareEntry of most preferred named group in `:supported_groups`. You can set this to send KeyShareClientHello that has multiple KeyShareEntry. |
94
95
  | `:alpn` | Array of String | nil | List of application protocols offered in ClientHello extensions. If not needed to be present, set nil. |
95
- | `:process_new_session_ticket` | Proc | nil | Proc that processes received NewSessionTicket. Its 3 arguments are TTTLS13::Message::NewSessionTicket, resumption master secret and cipher suite. If not needed to process NewSessionTicket, set nil. |
96
+ | `:process_new_session_ticket` | Proc | nil | Proc that processes received NewSessionTicket. Its 3 arguments are TTTLS13::Message::NewSessionTicket, resumption main secret and cipher suite. If not needed to process NewSessionTicket, set nil. |
96
97
  | `:ticket` | String | nil | The ticket for PSK. |
97
- | `:resumption_master_secret` | String | nil | The resumption master secret. |
98
+ | `:resumption_secret` | String | nil | The resumption main secret. |
98
99
  | `:psk_cipher_suite` | TTTLS13::CipherSuite constant | nil | The cipher suite for PSK. |
99
100
  | `:ticket_nonce` | String | nil | The ticket\_nonce for PSK. |
100
101
  | `:ticket_age_add` | String | nil | The ticket\_age\_add for PSK. |
@@ -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. 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. |
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/helper.rb CHANGED
@@ -3,10 +3,13 @@
3
3
  $LOAD_PATH << __dir__ + '/../lib'
4
4
 
5
5
  require 'socket'
6
- require 'tttls1.3'
6
+ require 'time'
7
7
  require 'webrick'
8
+
8
9
  require 'http/parser'
9
- require 'time'
10
+ require 'svcb_rr_patch'
11
+
12
+ require 'tttls1.3'
10
13
 
11
14
  def simple_http_request(hostname, path = '/')
12
15
  s = <<~REQUEST
@@ -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_master_secret] = rms
18
+ settings_2nd[:resumption_main_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
@@ -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
+ hostname = 'crypto.cloudflare.com'
9
+ port = 443
10
+ ca_file = __dir__ + '/../tmp/ca.crt'
11
+ req = simple_http_request(hostname, '/cdn-cgi/trace')
12
+
13
+ rr = Resolv::DNS.new.getresources(
14
+ hostname,
15
+ Resolv::DNS::Resource::IN::HTTPS
16
+ )
17
+ socket = TCPSocket.new(hostname, port)
18
+ settings = {
19
+ ca_file: File.exist?(ca_file) ? ca_file : nil,
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, hostname, **settings)
27
+ client.connect
28
+ client.write(req)
29
+
30
+ print recv_http_response(client)
31
+ client.close unless client.eof?
32
+ socket.close
@@ -0,0 +1,26 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'helper'
5
+ HpkeSymmetricCipherSuite = \
6
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
+
8
+ hostname = 'crypto.cloudflare.com'
9
+ port = 443
10
+ ca_file = __dir__ + '/../tmp/ca.crt'
11
+
12
+ socket = TCPSocket.new(hostname, port)
13
+ settings = {
14
+ ca_file: File.exist?(ca_file) ? ca_file : nil,
15
+ alpn: ['http/1.1'],
16
+ ech_hpke_cipher_suites:
17
+ TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
18
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
19
+ }
20
+ client = TTTLS13::Client.new(socket, hostname, **settings)
21
+ client.connect
22
+
23
+ print client.retry_configs if client.rejected_ech?
24
+
25
+ client.close unless client.eof?
26
+ socket.close
@@ -0,0 +1,66 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'helper'
5
+ HpkeSymmetricCipherSuite = \
6
+ ECHConfig::ECHConfigContents::HpkeKeyConfig::HpkeSymmetricCipherSuite
7
+
8
+ hostname = 'crypto.cloudflare.com'
9
+ port = 443
10
+ ca_file = __dir__ + '/../tmp/ca.crt'
11
+ req = simple_http_request(hostname, '/cdn-cgi/trace')
12
+
13
+ rr = Resolv::DNS.new.getresources(
14
+ hostname,
15
+ Resolv::DNS::Resource::IN::HTTPS
16
+ )
17
+ settings_2nd = {
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
+ process_new_session_ticket = lambda do |nst, rms, cs|
26
+ return if Time.now.to_i - nst.timestamp > nst.ticket_lifetime
27
+
28
+ settings_2nd[:ticket] = nst.ticket
29
+ settings_2nd[:resumption_main_secret] = rms
30
+ settings_2nd[:psk_cipher_suite] = cs
31
+ settings_2nd[:ticket_nonce] = nst.ticket_nonce
32
+ settings_2nd[:ticket_age_add] = nst.ticket_age_add
33
+ settings_2nd[:ticket_timestamp] = nst.timestamp
34
+ end
35
+ settings_1st = {
36
+ ca_file: File.exist?(ca_file) ? ca_file : nil,
37
+ alpn: ['http/1.1'],
38
+ process_new_session_ticket: process_new_session_ticket,
39
+ 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
+ ],
50
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
51
+ }
52
+
53
+ [
54
+ # Initial Handshake:
55
+ settings_1st,
56
+ # Subsequent Handshake:
57
+ settings_2nd
58
+ ].each do |settings|
59
+ socket = TCPSocket.new(hostname, port)
60
+ client = TTTLS13::Client.new(socket, hostname, **settings)
61
+ client.connect
62
+ client.write(req)
63
+ print recv_http_response(client)
64
+ client.close unless client.eof?
65
+ socket.close
66
+ end
@@ -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
+ hostname = 'crypto.cloudflare.com'
9
+ port = 443
10
+ ca_file = __dir__ + '/../tmp/ca.crt'
11
+ req = simple_http_request(hostname, '/cdn-cgi/trace')
12
+
13
+ rr = Resolv::DNS.new.getresources(
14
+ hostname,
15
+ Resolv::DNS::Resource::IN::HTTPS
16
+ )
17
+ socket = TCPSocket.new(hostname, port)
18
+ settings = {
19
+ ca_file: File.exist?(ca_file) ? ca_file : nil,
20
+ key_share_groups: [], # empty KeyShareClientHello.client_shares
21
+ alpn: ['http/1.1'],
22
+ ech_config: rr.first.svc_params['ech'].echconfiglist.first,
23
+ ech_hpke_cipher_suites:
24
+ TTTLS13::STANDARD_CLIENT_ECH_HPKE_SYMMETRIC_CIPHER_SUITES,
25
+ sslkeylogfile: '/tmp/sslkeylogfile.log'
26
+ }
27
+ client = TTTLS13::Client.new(socket, hostname, **settings)
28
+ client.connect
29
+ client.write(req)
30
+ print recv_http_response(client)
31
+ client.close unless client.eof?
32
+ socket.close
@@ -16,7 +16,7 @@ process_new_session_ticket = lambda do |nst, rms, cs|
16
16
 
17
17
  settings_2nd[:key_share_groups] = [] # empty KeyShareClientHello.client_shares
18
18
  settings_2nd[:ticket] = nst.ticket
19
- settings_2nd[:resumption_master_secret] = rms
19
+ settings_2nd[:resumption_main_secret] = rms
20
20
  settings_2nd[:psk_cipher_suite] = cs
21
21
  settings_2nd[:ticket_nonce] = nst.ticket_nonce
22
22
  settings_2nd[:ticket_age_add] = nst.ticket_age_add
@@ -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_master_secret] = rms
18
+ settings_2nd[:resumption_main_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
@@ -1,10 +1,10 @@
1
1
  # encoding: ascii-8bit
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative 'helper'
4
+ require_relative 'spec_helper'
5
5
 
6
6
  FIXTURES_DIR = __dir__ + '/../spec/fixtures'
7
- PORT = 4433
7
+ PORT = 14433
8
8
 
9
9
  RSpec.describe Client do
10
10
  # normal [Boolean] Is this nominal scenarios?
@@ -173,6 +173,7 @@ RSpec.describe Client do
173
173
  + '-tls1_3 ' \
174
174
  + '-www ' \
175
175
  + '-quiet ' \
176
+ + "-accept #{PORT} " \
176
177
  + opt
177
178
  pid = spawn('docker run ' \
178
179
  + "--volume #{FIXTURES_DIR}:/tmp " \
@@ -1,7 +1,7 @@
1
1
  # encoding: ascii-8bit
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative 'helper'
4
+ require_relative 'spec_helper'
5
5
 
6
6
  FIXTURES_DIR = __dir__ + '/../spec/fixtures'
7
7
  PORT = 4433
@@ -187,8 +187,6 @@ RSpec.describe Server do
187
187
 
188
188
  let(:client) do
189
189
  ip = Socket.ip_address_list.find(&:ipv4_private?).ip_address
190
- wait_to_listen(ip, PORT)
191
-
192
190
  cmd = 'echo -n ping | openssl s_client ' \
193
191
  + "-connect local:#{PORT} " \
194
192
  + '-tls1_3 ' \
@@ -13,13 +13,20 @@ include TTTLS13::Error
13
13
  # rubocop: enable Style/MixinUsage
14
14
 
15
15
  def wait_to_listen(host, port)
16
- loop do
17
- s = TCPSocket.open(host, port) # check by TCP handshake
18
- rescue # rubocop: disable Style/RescueStandardError
19
- sleep(0.2)
16
+ 10.times do
17
+ soc = TCPSocket.open(host, port)
18
+ ctx = OpenSSL::SSL::SSLContext.new
19
+ ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
20
+ ssl = OpenSSL::SSL::SSLSocket.new(soc, ctx)
21
+ ssl.sync_close = true
22
+ ssl.connect
23
+ rescue => e # rubocop: disable Style/RescueStandardError
24
+ p e
25
+ soc&.close
26
+ sleep(0.5)
20
27
  next
21
28
  else
22
- s.close
29
+ ssl.close
23
30
  break
24
31
  end
25
32
  end