ech_config 0.0.5 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5f6d7fc30ca60ee845fcc56eceadad14e4307fca0e95b5eb9274185410bedbc
4
- data.tar.gz: 7c887ae00fce9af2a5d96bf2a27d9a163c999385d9fdcf57f36a0c6cab3c894e
3
+ metadata.gz: 0f6f2b4668e0fbb692d0db6c03b8a4a78320fa3147d8770310ab9428b6ece0bf
4
+ data.tar.gz: 41c4f5b773ce66afd9f2a72ef2b64779c3fc9817bdb7684ccbcd2b966667eda4
5
5
  SHA512:
6
- metadata.gz: d64fae03b94b58435cc31aba2a22bc3f95c5d5676b9bffdb7ac8bd642cf9ba3ee88828c8039bbb33c737bf82ad516af05d3e378ccbb350c6bdfa28236c32ecfd
7
- data.tar.gz: e0e133cab1b650e184e68bdae49357ccec39c87ab25fa5e5651e12bc158ecce9a3fe52cdb4c14e660e921c7ddb46253bfd210c5bc1e73785d7487600fa12cb03
6
+ metadata.gz: e9ebeb59a85b89e629cc18ec1a4c41f7edaa41584ab33ab470f2c3a5077ea329987772a77da3ad5794c1ad955d8e6716a45078e9ce6503eb167d2c79d30fb075
7
+ data.tar.gz: dd8a35d615ce762ae37bb3658e1ab9695992572bd36bf58af451e9e0ae48d96ac5ba6165b7f027635de0c2d9e5772a237365b63223604b5188ca97e219e21e31
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ech_config (0.0.5)
4
+ ech_config (0.1.0)
5
5
  base64
6
6
 
7
7
  GEM
@@ -172,7 +172,7 @@ CHECKSUMS
172
172
  csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
173
173
  diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
174
174
  drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
175
- ech_config (0.0.5)
175
+ ech_config (0.1.0)
176
176
  ffi (1.17.3) sha256=0e9f39f7bb3934f77ad6feab49662be77e87eedcdeb2a3f5c0234c2938563d4c
177
177
  ffi (1.17.3-aarch64-linux-gnu) sha256=28ad573df26560f0aedd8a90c3371279a0b2bd0b4e834b16a2baa10bd7a97068
178
178
  ffi (1.17.3-aarch64-linux-musl) sha256=020b33b76775b1abacc3b7d86b287cef3251f66d747092deec592c7f5df764b2
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  `ech_config` is Ruby implementation of [Encrypted ClientHello Configuration](https://datatracker.ietf.org/doc/html/rfc9849.html).
7
7
 
8
- `ech_config` supports `0xfe0b` ~ `0xfe0f` ECHConfig.version.
8
+ `ech_config` supports `0xfe0b` ~ `0xfe0d` ECHConfig.version.
9
9
 
10
10
 
11
11
  ## Installation
@@ -17,6 +17,40 @@ $ gem install ech_config
17
17
  ```
18
18
 
19
19
 
20
+ ## Usage
21
+
22
+ ```ruby
23
+ require 'ech_config'
24
+
25
+ echconfigs = ECHConfig.decode_vectors(octet)
26
+ ```
27
+
28
+ Clients MUST ignore an ECHConfig that carries an unsupported mandatory ECHConfig extension ([RFC 9849 4.2](https://datatracker.ietf.org/doc/html/rfc9849.html#section-4.2-2)). This gem decodes every extension it does not know as `UnknownExtension`, so:
29
+
30
+ ```ruby
31
+ echconfigs.reject do |echconfig|
32
+ echconfig.echconfig_contents.extensions.any_unsupported_mandatory?
33
+ end
34
+ ```
35
+
36
+ `ech_authinfo` and `ech_auth` ([draft-sullivan-tls-signed-ech-updates-02](https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1)) are decoded as `ECHAuthInfo` / `ECHAuth`. `ECHConfig#to_be_signed` returns the bytes an `ech_auth` signature is computed over; validating `not_after`, `trusted_keys` and the signature itself is left to the caller.
37
+
38
+ ```ruby
39
+ ech_auth = echconfig
40
+ .echconfig_contents
41
+ .extensions[ECHConfig::ECHConfigContents::Extensions::ECHAuth::TYPE]
42
+ OpenSSL::PKey.read(ech_auth.spki)
43
+ .verify(nil, ech_auth.signature, echconfig.to_be_signed)
44
+ ```
45
+
46
+ The draft leaves both extension types unassigned (TBD1 / TBD2), so `ech_config` uses placeholder codepoints until IANA assigns them.
47
+
48
+ | Extension | Draft | Placeholder |
49
+ | --- | --- | --- |
50
+ | `ech_authinfo` | TBD1 | `0xfe0e` |
51
+ | `ech_auth` | TBD2 | `0xfe0d` |
52
+
53
+
20
54
  ## License
21
55
 
22
56
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,123 @@
1
+ # encoding: ascii-8bit
2
+ # frozen_string_literal: true
3
+
4
+ # rbs_inline: enabled
5
+
6
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1
7
+ class ECHConfig::ECHConfigContents::Extensions::ECHAuth
8
+ include ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
9
+
10
+ # Value: TBD2 (assigned with the high-order bit set)
11
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-9.1-5.1.1
12
+ #
13
+ # TODO: placeholder until IANA assigns TBD2. 0xfe0d is the codepoint
14
+ # ech-auth-interop uses for ech_auth.
15
+ # https://github.com/grittygrease/ech-auth-interop
16
+ TYPE = 0xfe0d
17
+
18
+ # context_label = "TLS-ECH-AUTH-v1"
19
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1.1-2
20
+ CONTEXT_LABEL = 'TLS-ECH-AUTH-v1'
21
+
22
+ # uint64 not_after, uint8 disable and the length prefix of spki
23
+ FIXED_LENGTH = 8 + 1 + 2
24
+
25
+ attr_reader :not_after, :spki, :algorithm, :signature
26
+
27
+ # @rbs not_after: Integer
28
+ # @rbs disable: Integer
29
+ # @rbs spki: String
30
+ # @rbs algorithm: Integer
31
+ # @rbs signature: String
32
+ # @rbs return: void
33
+ def initialize(not_after, disable, spki, algorithm, signature)
34
+ # Senders MUST encode disable as 0 or 1; clients MUST reject any other
35
+ # value.
36
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1-7
37
+ raise ArgumentError unless [0, 1].include?(disable)
38
+
39
+ # opaque spki<1..2^16-1>;
40
+ raise ArgumentError if spki.empty? || spki.length > 2**16 - 1
41
+
42
+ # opaque signature<0..2^16-1>;
43
+ raise ArgumentError if signature.length > 2**16 - 1
44
+
45
+ @not_after = not_after
46
+ @disable = disable
47
+ @spki = spki
48
+ @algorithm = algorithm
49
+ @signature = signature
50
+ end
51
+
52
+ # @rbs return: Integer
53
+ def type
54
+ TYPE
55
+ end
56
+
57
+ # When set to 1, the client MUST NOT attempt ECH on the retry.
58
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1-7
59
+ #
60
+ # @rbs return: bool
61
+ def disable?
62
+ @disable == 1
63
+ end
64
+
65
+ # @rbs return: String
66
+ def encode
67
+ [TYPE].pack('n') \
68
+ + ([@not_after].pack('Q>') \
69
+ + [@disable].pack('C') \
70
+ + @spki.then { |s| [s.length].pack('n') + s } \
71
+ + [@algorithm].pack('n') \
72
+ + @signature.then { |s| [s.length].pack('n') + s })
73
+ .then { |s| [s.length].pack('n') + s } # ECHConfigExtension.data
74
+ end
75
+
76
+ # The two-byte length prefix of the signature field is encoded as 0x0000 and
77
+ # no signature bytes follow.
78
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1.1-4.1
79
+ #
80
+ # @rbs return: ECHConfig::ECHConfigContents::Extensions::ECHAuth
81
+ def without_signature
82
+ self.class.new(@not_after, @disable, @spki, @algorithm, '')
83
+ end
84
+
85
+ # rubocop:disable Metrics/AbcSize
86
+ # rubocop:disable Metrics/CyclomaticComplexity
87
+ # rubocop:disable Metrics/PerceivedComplexity
88
+ # @rbs octet: String
89
+ # @rbs return: ECHConfig::ECHConfigContents::Extensions::ECHAuth
90
+ def self.decode(octet)
91
+ raise ::ECHConfig::DecodeError if octet.length < FIXED_LENGTH
92
+
93
+ not_after = octet.slice(0, 8)&.unpack1('Q>') #: Integer
94
+ disable = octet.slice(8, 1)&.unpack1('C') #: Integer
95
+ # Senders MUST encode disable as 0 or 1; clients MUST reject any other
96
+ # value.
97
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1-7
98
+ raise ::ECHConfig::DecodeError unless [0, 1].include?(disable)
99
+
100
+ spki_len = octet.slice(9, 2)&.unpack1('n') #: Integer
101
+ i = 11
102
+ # opaque spki<1..2^16-1>;
103
+ raise ::ECHConfig::DecodeError \
104
+ if spki_len.zero? || i + spki_len + 4 > octet.length
105
+
106
+ spki = octet.slice(i, spki_len) #: String
107
+ i += spki_len
108
+ algorithm = octet.slice(i, 2)&.unpack1('n') #: Integer
109
+ i += 2
110
+ sig_len = octet.slice(i, 2)&.unpack1('n') #: Integer
111
+ i += 2
112
+ raise ::ECHConfig::DecodeError if i + sig_len != octet.length
113
+ # The signature field in a wire ECHAuth MUST be non-empty. The zero-length
114
+ # form is used only when constructing ECHConfigTBS.
115
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1-6
116
+ raise ::ECHConfig::DecodeError if sig_len.zero?
117
+
118
+ new(not_after, disable, spki, algorithm, octet.slice(i, sig_len) || '')
119
+ end
120
+ # rubocop:enable Metrics/AbcSize
121
+ # rubocop:enable Metrics/CyclomaticComplexity
122
+ # rubocop:enable Metrics/PerceivedComplexity
123
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1
6
+ class ECHConfig::ECHConfigContents::Extensions::ECHAuthInfo
7
+ include ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
8
+
9
+ # Value: TBD1 (assigned with the high-order bit set)
10
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-9.1-3.1.1
11
+ #
12
+ # TODO: placeholder until IANA assigns TBD1, NOT interoperable.
13
+ TYPE = 0xfe0e
14
+
15
+ # opaque SPKIHash[32];
16
+ SPKI_HASH_LENGTH = 32
17
+
18
+ attr_reader :trusted_keys
19
+
20
+ # @rbs trusted_keys: Array[String]
21
+ # @rbs return: void
22
+ def initialize(trusted_keys)
23
+ # SPKIHash trusted_keys<32..2^16-32>;
24
+ raise ArgumentError unless trusted_keys.all? { |k| k.length == SPKI_HASH_LENGTH }
25
+ raise ArgumentError \
26
+ if trusted_keys.empty? ||
27
+ trusted_keys.length * SPKI_HASH_LENGTH > 2**16 - SPKI_HASH_LENGTH
28
+
29
+ @trusted_keys = trusted_keys
30
+ end
31
+
32
+ # @rbs return: Integer
33
+ def type
34
+ TYPE
35
+ end
36
+
37
+ # @rbs return: String
38
+ def encode
39
+ [TYPE].pack('n') \
40
+ + @trusted_keys.join
41
+ .then { |s| [s.length].pack('n') + s } # ECHAuthInfo.trusted_keys
42
+ .then { |s| [s.length].pack('n') + s } # ECHConfigExtension.data
43
+ end
44
+
45
+ # rubocop:disable Metrics/CyclomaticComplexity
46
+ # rubocop:disable Metrics/PerceivedComplexity
47
+ # @rbs octet: String
48
+ # @rbs return: ECHConfig::ECHConfigContents::Extensions::ECHAuthInfo
49
+ def self.decode(octet)
50
+ raise ::ECHConfig::DecodeError if octet.length < 2
51
+
52
+ # SPKIHash trusted_keys<32..2^16-32>;
53
+ tk_len = octet.slice(0, 2)&.unpack1('n') #: Integer
54
+ raise ::ECHConfig::DecodeError if tk_len + 2 != octet.length
55
+ raise ::ECHConfig::DecodeError \
56
+ if tk_len < SPKI_HASH_LENGTH || tk_len > 2**16 - SPKI_HASH_LENGTH
57
+ raise ::ECHConfig::DecodeError unless (tk_len % SPKI_HASH_LENGTH).zero?
58
+
59
+ trusted_keys = (0...(tk_len / SPKI_HASH_LENGTH)).map do |i|
60
+ octet.slice(2 + i * SPKI_HASH_LENGTH, SPKI_HASH_LENGTH) || ''
61
+ end
62
+ new(trusted_keys)
63
+ end
64
+ # rubocop:enable Metrics/CyclomaticComplexity
65
+ # rubocop:enable Metrics/PerceivedComplexity
66
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ # @rbs module-self _ECHConfigExtension
6
+ #
7
+ # https://datatracker.ietf.org/doc/html/rfc9849.html#section-4.2
8
+ module ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
9
+ # @rbs!
10
+ # interface _ECHConfigExtension
11
+ # def type: () -> Integer
12
+ #
13
+ # def encode: () -> String
14
+ # end
15
+
16
+ # an extension can be tagged as mandatory by using an extension type
17
+ # codepoint with the high order bit set to 1.
18
+ # https://datatracker.ietf.org/doc/html/rfc9849.html#section-4.2-2
19
+ MANDATORY_BIT = 0x8000
20
+
21
+ # @rbs return: bool
22
+ def mandatory?
23
+ !(type & MANDATORY_BIT).zero?
24
+ end
25
+
26
+ # @rbs return: bool
27
+ def supported?
28
+ true
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ class ECHConfig::ECHConfigContents::Extensions::UnknownExtension
6
+ include ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
7
+
8
+ attr_reader :type, :data
9
+
10
+ # @rbs type: Integer
11
+ # @rbs data: String
12
+ # @rbs return: void
13
+ def initialize(type, data = '')
14
+ @type = type
15
+ @data = data
16
+ end
17
+
18
+ # @rbs return: String
19
+ def encode
20
+ [@type].pack('n') + @data.then { |s| [s.length].pack('n') + s }
21
+ end
22
+
23
+ # @rbs return: bool
24
+ def supported?
25
+ false
26
+ end
27
+
28
+ # @rbs octet: String
29
+ # @rbs type: Integer
30
+ # @rbs return: ECHConfig::ECHConfigContents::Extensions::UnknownExtension
31
+ def self.decode(octet, type)
32
+ new(type, octet)
33
+ end
34
+ end
@@ -2,25 +2,103 @@
2
2
 
3
3
  # rbs_inline: enabled
4
4
 
5
- class ECHConfig::ECHConfigContents::Extensions
6
- attr_reader :octet
5
+ # @rbs inherits Hash[Integer, ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension]
6
+ class ECHConfig::ECHConfigContents::Extensions < Hash
7
+ # define class
8
+ end
7
9
 
8
- # @rbs octet: String
10
+ # ech_config_extension.rb needs to be loaded first so that each extension can
11
+ # include it
12
+ require_relative 'extensions/ech_config_extension'
13
+
14
+ Dir["#{File.dirname(__FILE__)}/extensions/*.rb"]
15
+ .sort.each { |f| require f }
16
+
17
+ # https://datatracker.ietf.org/doc/html/rfc9849.html#section-4.2
18
+ class ECHConfig::ECHConfigContents::Extensions
19
+ # @rbs extensions: Array[ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension]
9
20
  # @rbs return: void
10
- def initialize(octet)
11
- # Note taht ECHConfig::ECHConfigContents::Extension only has octets.
12
- # If you need, deserialize octets to get TLS Extension objects.
13
- @octet = octet
21
+ def initialize(extensions = [])
22
+ super()
23
+ extensions.each { |ex| self << ex }
14
24
  end
15
25
 
16
26
  # @rbs return: String
17
- def load
18
- @octet
27
+ def encode
28
+ values.map(&:encode).join
19
29
  end
20
30
 
31
+ # @rbs ex: ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
32
+ # @rbs return: self
33
+ def <<(ex)
34
+ raise ArgumentError unless self.class.valid_ech_auth?(keys | [ex.type])
35
+
36
+ store(ex.type, ex)
37
+ self
38
+ end
39
+
40
+ # A single ECHConfig MUST NOT carry both extensions.
41
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1-2
42
+ #
43
+ # The ech_auth extension MUST be the last extension in the ECHConfig's
44
+ # extension list.
45
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1-3
46
+ #
47
+ # @rbs types: Array[Integer]
48
+ # @rbs return: bool
49
+ def self.valid_ech_auth?(types)
50
+ return true unless types.include?(ECHAuth::TYPE)
51
+
52
+ !types.include?(ECHAuthInfo::TYPE) && types.last == ECHAuth::TYPE
53
+ end
54
+
55
+ # Clients MUST parse the extension list and check for unsupported mandatory extensions.
56
+ # If an unsupported mandatory extension is present, clients MUST ignore the ECHConfig.
57
+ # https://datatracker.ietf.org/doc/html/rfc9849.html#section-4.2-2
58
+ #
59
+ # @rbs return: bool
60
+ def any_unsupported_mandatory?
61
+ values.any? { |ex| ex.mandatory? && !ex.supported? }
62
+ end
63
+
64
+ # rubocop:disable Metrics/CyclomaticComplexity
65
+ # rubocop:disable Metrics/PerceivedComplexity
21
66
  # @rbs octet: String
22
67
  # @rbs return: ECHConfig::ECHConfigContents::Extensions
23
- def self.store(octet)
24
- new(octet)
68
+ def self.decode(octet)
69
+ i = 0
70
+ extensions = new
71
+ while i < octet.length
72
+ raise ::ECHConfig::DecodeError if i + 4 > octet.length
73
+
74
+ type = octet.slice(i, 2)&.unpack1('n') #: Integer
75
+ ex_len = octet.slice(i + 2, 2)&.unpack1('n') #: Integer
76
+ raise ::ECHConfig::DecodeError if i + 4 + ex_len > octet.length
77
+
78
+ # There MUST NOT be more than one extension of the same type.
79
+ raise ::ECHConfig::DecodeError if extensions.include?(type)
80
+
81
+ ex = decode_extension(octet.slice(i + 4, ex_len) || '', type)
82
+ extensions.store(type, ex)
83
+ i += 4 + ex_len
84
+ end
85
+ raise ::ECHConfig::DecodeError if i != octet.length
86
+ raise ::ECHConfig::DecodeError unless valid_ech_auth?(extensions.keys)
87
+
88
+ extensions
89
+ end
90
+ # rubocop:enable Metrics/CyclomaticComplexity
91
+ # rubocop:enable Metrics/PerceivedComplexity
92
+
93
+ # @rbs octet: String
94
+ # @rbs type: Integer
95
+ # @rbs return: ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
96
+ def self.decode_extension(octet, type)
97
+ case type
98
+ when ECHAuth::TYPE then ECHAuth.decode(octet)
99
+ when ECHAuthInfo::TYPE then ECHAuthInfo.decode(octet)
100
+ else UnknownExtension.decode(octet, type)
101
+ end
25
102
  end
103
+ private_class_method :decode_extension
26
104
  end
@@ -32,7 +32,7 @@ class ECHConfig::ECHConfigContents
32
32
  @key_config.encode \
33
33
  + [@maximum_name_length].pack('C') \
34
34
  + @public_name.then { |s| [s.length].pack('C') + s } \
35
- + @extensions.load.then { |s| [s.length].pack('n') + s }
35
+ + @extensions.encode.then { |s| [s.length].pack('n') + s }
36
36
  end
37
37
 
38
38
  # rubocop:disable Metrics/AbcSize
@@ -62,7 +62,7 @@ class ECHConfig::ECHConfigContents
62
62
  i += 2
63
63
  raise ::ECHConfig::DecodeError if i + ex_len != octet.length
64
64
 
65
- extensions = Extensions.store(octet.slice(i, ex_len) || '')
65
+ extensions = Extensions.decode(octet.slice(i, ex_len) || '')
66
66
  new(
67
67
  key_config,
68
68
  maximum_name_length,
@@ -3,5 +3,5 @@
3
3
  # rbs_inline: enabled
4
4
 
5
5
  class ECHConfig
6
- VERSION = '0.0.5'
6
+ VERSION = '0.1.0'
7
7
  end
data/lib/ech_config.rb CHANGED
@@ -34,6 +34,39 @@ class ECHConfig
34
34
  @version + @echconfig_contents.encode.then { |s| [s.length].pack('n') + s }
35
35
  end
36
36
 
37
+ # to_be_signed = context_label || ECHConfigTBS
38
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1.1-2
39
+ #
40
+ # ECHConfigTBS (To-Be-Signed) is a fresh serialization of the ECHConfig
41
+ # structure including the ech_auth extension, but with the signature field
42
+ # within ech_auth set to zero-length. The ech_auth extension data length,
43
+ # ECHConfig extensions vector length, and ECHConfig length field are
44
+ # recomputed for that serialization.
45
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1.1-4.1
46
+ #
47
+ # @rbs return: String
48
+ def to_be_signed
49
+ ech_auth = @echconfig_contents.extensions[ECHConfigContents::Extensions::ECHAuth::TYPE]
50
+ raise ::ECHConfig::Error \
51
+ unless ech_auth.is_a?(ECHConfigContents::Extensions::ECHAuth)
52
+
53
+ extensions = ECHConfigContents::Extensions.new(
54
+ @echconfig_contents.extensions.values.map do |ex|
55
+ ex.equal?(ech_auth) ? ech_auth.without_signature : ex
56
+ end
57
+ )
58
+ ECHConfigContents::Extensions::ECHAuth::CONTEXT_LABEL \
59
+ + self.class.new(
60
+ @version,
61
+ ECHConfigContents.new(
62
+ @echconfig_contents.key_config,
63
+ @echconfig_contents.maximum_name_length,
64
+ @echconfig_contents.public_name,
65
+ extensions
66
+ )
67
+ ).encode
68
+ end
69
+
37
70
  # rubocop:disable Metrics/CyclomaticComplexity
38
71
  # @rbs octet: String
39
72
  # @rbs return: Array[ECHConfig]
@@ -0,0 +1,63 @@
1
+ # Generated from lib/ech_config/ech_config_contents/extensions/ech_auth.rb with RBS::Inline
2
+
3
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1
4
+ class ECHConfig::ECHConfigContents::Extensions::ECHAuth
5
+ include ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
6
+
7
+ # Value: TBD2 (assigned with the high-order bit set)
8
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-9.1-5.1.1
9
+ #
10
+ # TODO: placeholder until IANA assigns TBD2. 0xfe0d is the codepoint
11
+ # ech-auth-interop uses for ech_auth.
12
+ # https://github.com/grittygrease/ech-auth-interop
13
+ TYPE: ::Integer
14
+
15
+ # context_label = "TLS-ECH-AUTH-v1"
16
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1.1-2
17
+ CONTEXT_LABEL: ::String
18
+
19
+ # uint64 not_after, uint8 disable and the length prefix of spki
20
+ FIXED_LENGTH: untyped
21
+
22
+ attr_reader not_after: untyped
23
+
24
+ attr_reader spki: untyped
25
+
26
+ attr_reader algorithm: untyped
27
+
28
+ attr_reader signature: untyped
29
+
30
+ # @rbs not_after: Integer
31
+ # @rbs disable: Integer
32
+ # @rbs spki: String
33
+ # @rbs algorithm: Integer
34
+ # @rbs signature: String
35
+ # @rbs return: void
36
+ def initialize: (Integer not_after, Integer disable, String spki, Integer algorithm, String signature) -> void
37
+
38
+ # @rbs return: Integer
39
+ def type: () -> Integer
40
+
41
+ # When set to 1, the client MUST NOT attempt ECH on the retry.
42
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1-7
43
+ #
44
+ # @rbs return: bool
45
+ def disable?: () -> bool
46
+
47
+ # @rbs return: String
48
+ def encode: () -> String
49
+
50
+ # The two-byte length prefix of the signature field is encoded as 0x0000 and
51
+ # no signature bytes follow.
52
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1.1-4.1
53
+ #
54
+ # @rbs return: ECHConfig::ECHConfigContents::Extensions::ECHAuth
55
+ def without_signature: () -> ECHConfig::ECHConfigContents::Extensions::ECHAuth
56
+
57
+ # rubocop:disable Metrics/AbcSize
58
+ # rubocop:disable Metrics/CyclomaticComplexity
59
+ # rubocop:disable Metrics/PerceivedComplexity
60
+ # @rbs octet: String
61
+ # @rbs return: ECHConfig::ECHConfigContents::Extensions::ECHAuth
62
+ def self.decode: (String octet) -> ECHConfig::ECHConfigContents::Extensions::ECHAuth
63
+ end
@@ -0,0 +1,33 @@
1
+ # Generated from lib/ech_config/ech_config_contents/extensions/ech_authinfo.rb with RBS::Inline
2
+
3
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1
4
+ class ECHConfig::ECHConfigContents::Extensions::ECHAuthInfo
5
+ include ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
6
+
7
+ # Value: TBD1 (assigned with the high-order bit set)
8
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-9.1-3.1.1
9
+ #
10
+ # TODO: placeholder until IANA assigns TBD1, NOT interoperable.
11
+ TYPE: ::Integer
12
+
13
+ # opaque SPKIHash[32];
14
+ SPKI_HASH_LENGTH: ::Integer
15
+
16
+ attr_reader trusted_keys: untyped
17
+
18
+ # @rbs trusted_keys: Array[String]
19
+ # @rbs return: void
20
+ def initialize: (Array[String] trusted_keys) -> void
21
+
22
+ # @rbs return: Integer
23
+ def type: () -> Integer
24
+
25
+ # @rbs return: String
26
+ def encode: () -> String
27
+
28
+ # rubocop:disable Metrics/CyclomaticComplexity
29
+ # rubocop:disable Metrics/PerceivedComplexity
30
+ # @rbs octet: String
31
+ # @rbs return: ECHConfig::ECHConfigContents::Extensions::ECHAuthInfo
32
+ def self.decode: (String octet) -> ECHConfig::ECHConfigContents::Extensions::ECHAuthInfo
33
+ end
@@ -0,0 +1,23 @@
1
+ # Generated from lib/ech_config/ech_config_contents/extensions/ech_config_extension.rb with RBS::Inline
2
+
3
+ # @rbs module-self _ECHConfigExtension
4
+ #
5
+ # https://datatracker.ietf.org/doc/html/rfc9849.html#section-4.2
6
+ module ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension : _ECHConfigExtension
7
+ interface _ECHConfigExtension
8
+ def type: () -> Integer
9
+
10
+ def encode: () -> String
11
+ end
12
+
13
+ # an extension can be tagged as mandatory by using an extension type
14
+ # codepoint with the high order bit set to 1.
15
+ # https://datatracker.ietf.org/doc/html/rfc9849.html#section-4.2-2
16
+ MANDATORY_BIT: ::Integer
17
+
18
+ # @rbs return: bool
19
+ def mandatory?: () -> bool
20
+
21
+ # @rbs return: bool
22
+ def supported?: () -> bool
23
+ end
@@ -0,0 +1,25 @@
1
+ # Generated from lib/ech_config/ech_config_contents/extensions/unknown_extension.rb with RBS::Inline
2
+
3
+ class ECHConfig::ECHConfigContents::Extensions::UnknownExtension
4
+ include ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
5
+
6
+ attr_reader type: untyped
7
+
8
+ attr_reader data: untyped
9
+
10
+ # @rbs type: Integer
11
+ # @rbs data: String
12
+ # @rbs return: void
13
+ def initialize: (Integer type, ?String data) -> void
14
+
15
+ # @rbs return: String
16
+ def encode: () -> String
17
+
18
+ # @rbs return: bool
19
+ def supported?: () -> bool
20
+
21
+ # @rbs octet: String
22
+ # @rbs type: Integer
23
+ # @rbs return: ECHConfig::ECHConfigContents::Extensions::UnknownExtension
24
+ def self.decode: (String octet, Integer type) -> ECHConfig::ECHConfigContents::Extensions::UnknownExtension
25
+ end
@@ -1,16 +1,48 @@
1
1
  # Generated from lib/ech_config/ech_config_contents/extensions.rb with RBS::Inline
2
2
 
3
- class ECHConfig::ECHConfigContents::Extensions
4
- attr_reader octet: untyped
3
+ # @rbs inherits Hash[Integer, ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension]
4
+ class ECHConfig::ECHConfigContents::Extensions < Hash[Integer, ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension]
5
+ end
5
6
 
6
- # @rbs octet: String
7
+ # https://datatracker.ietf.org/doc/html/rfc9849.html#section-4.2
8
+ class ECHConfig::ECHConfigContents::Extensions
9
+ # @rbs extensions: Array[ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension]
7
10
  # @rbs return: void
8
- def initialize: (String octet) -> void
11
+ def initialize: (?Array[ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension] extensions) -> void
9
12
 
10
13
  # @rbs return: String
11
- def load: () -> String
14
+ def encode: () -> String
15
+
16
+ # @rbs ex: ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
17
+ # @rbs return: self
18
+ def <<: (ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension ex) -> self
12
19
 
20
+ # A single ECHConfig MUST NOT carry both extensions.
21
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1-2
22
+ #
23
+ # The ech_auth extension MUST be the last extension in the ECHConfig's
24
+ # extension list.
25
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1-3
26
+ #
27
+ # @rbs types: Array[Integer]
28
+ # @rbs return: bool
29
+ def self.valid_ech_auth?: (Array[Integer] types) -> bool
30
+
31
+ # Clients MUST parse the extension list and check for unsupported mandatory extensions.
32
+ # If an unsupported mandatory extension is present, clients MUST ignore the ECHConfig.
33
+ # https://datatracker.ietf.org/doc/html/rfc9849.html#section-4.2-2
34
+ #
35
+ # @rbs return: bool
36
+ def any_unsupported_mandatory?: () -> bool
37
+
38
+ # rubocop:disable Metrics/CyclomaticComplexity
39
+ # rubocop:disable Metrics/PerceivedComplexity
13
40
  # @rbs octet: String
14
41
  # @rbs return: ECHConfig::ECHConfigContents::Extensions
15
- def self.store: (String octet) -> ECHConfig::ECHConfigContents::Extensions
42
+ def self.decode: (String octet) -> ECHConfig::ECHConfigContents::Extensions
43
+
44
+ # @rbs octet: String
45
+ # @rbs type: Integer
46
+ # @rbs return: ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
47
+ def self.decode_extension: (String octet, Integer type) -> ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension
16
48
  end
@@ -16,6 +16,19 @@ class ECHConfig
16
16
  # @rbs return: String
17
17
  def encode: () -> String
18
18
 
19
+ # to_be_signed = context_label || ECHConfigTBS
20
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1.1-2
21
+ #
22
+ # ECHConfigTBS (To-Be-Signed) is a fresh serialization of the ECHConfig
23
+ # structure including the ech_auth extension, but with the signature field
24
+ # within ech_auth set to zero-length. The ech_auth extension data length,
25
+ # ECHConfig extensions vector length, and ECHConfig length field are
26
+ # recomputed for that serialization.
27
+ # https://datatracker.ietf.org/doc/html/draft-sullivan-tls-signed-ech-updates-02#section-5.1.1-4.1
28
+ #
29
+ # @rbs return: String
30
+ def to_be_signed: () -> String
31
+
19
32
  # rubocop:disable Metrics/CyclomaticComplexity
20
33
  # @rbs octet: String
21
34
  # @rbs return: Array[ECHConfig]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ech_config
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thekuwayama
@@ -58,6 +58,10 @@ files:
58
58
  - lib/ech_config.rb
59
59
  - lib/ech_config/ech_config_contents.rb
60
60
  - lib/ech_config/ech_config_contents/extensions.rb
61
+ - lib/ech_config/ech_config_contents/extensions/ech_auth.rb
62
+ - lib/ech_config/ech_config_contents/extensions/ech_authinfo.rb
63
+ - lib/ech_config/ech_config_contents/extensions/ech_config_extension.rb
64
+ - lib/ech_config/ech_config_contents/extensions/unknown_extension.rb
61
65
  - lib/ech_config/ech_config_contents/hpke_key_config.rb
62
66
  - lib/ech_config/ech_config_contents/hpke_key_config/hpke_kem_id.rb
63
67
  - lib/ech_config/ech_config_contents/hpke_key_config/hpke_public_key.rb
@@ -69,6 +73,10 @@ files:
69
73
  - sig/generated/ech_config.rbs
70
74
  - sig/generated/ech_config/ech_config_contents.rbs
71
75
  - sig/generated/ech_config/ech_config_contents/extensions.rbs
76
+ - sig/generated/ech_config/ech_config_contents/extensions/ech_auth.rbs
77
+ - sig/generated/ech_config/ech_config_contents/extensions/ech_authinfo.rbs
78
+ - sig/generated/ech_config/ech_config_contents/extensions/ech_config_extension.rbs
79
+ - sig/generated/ech_config/ech_config_contents/extensions/unknown_extension.rbs
72
80
  - sig/generated/ech_config/ech_config_contents/hpke_key_config.rbs
73
81
  - sig/generated/ech_config/ech_config_contents/hpke_key_config/hpke_kem_id.rbs
74
82
  - sig/generated/ech_config/ech_config_contents/hpke_key_config/hpke_public_key.rbs