ech_config 0.0.4 → 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 +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +35 -1
- data/lib/ech_config/ech_config_contents/extensions/ech_auth.rb +123 -0
- data/lib/ech_config/ech_config_contents/extensions/ech_authinfo.rb +66 -0
- data/lib/ech_config/ech_config_contents/extensions/ech_config_extension.rb +30 -0
- data/lib/ech_config/ech_config_contents/extensions/unknown_extension.rb +34 -0
- data/lib/ech_config/ech_config_contents/extensions.rb +89 -11
- data/lib/ech_config/ech_config_contents.rb +2 -2
- data/lib/ech_config/version.rb +1 -1
- data/lib/ech_config.rb +33 -0
- data/sig/generated/ech_config/ech_config_contents/extensions/ech_auth.rbs +63 -0
- data/sig/generated/ech_config/ech_config_contents/extensions/ech_authinfo.rbs +33 -0
- data/sig/generated/ech_config/ech_config_contents/extensions/ech_config_extension.rbs +23 -0
- data/sig/generated/ech_config/ech_config_contents/extensions/unknown_extension.rbs +25 -0
- data/sig/generated/ech_config/ech_config_contents/extensions.rbs +38 -6
- data/sig/generated/ech_config.rbs +13 -0
- metadata +10 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0f6f2b4668e0fbb692d0db6c03b8a4a78320fa3147d8770310ab9428b6ece0bf
|
|
4
|
+
data.tar.gz: 41c4f5b773ce66afd9f2a72ef2b64779c3fc9817bdb7684ccbcd2b966667eda4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
|
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` ~ `
|
|
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
|
-
|
|
6
|
-
|
|
5
|
+
# @rbs inherits Hash[Integer, ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension]
|
|
6
|
+
class ECHConfig::ECHConfigContents::Extensions < Hash
|
|
7
|
+
# define class
|
|
8
|
+
end
|
|
7
9
|
|
|
8
|
-
|
|
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(
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
18
|
-
|
|
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.
|
|
24
|
-
|
|
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.
|
|
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.
|
|
65
|
+
extensions = Extensions.decode(octet.slice(i, ex_len) || '')
|
|
66
66
|
new(
|
|
67
67
|
key_config,
|
|
68
68
|
maximum_name_length,
|
data/lib/ech_config/version.rb
CHANGED
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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: (
|
|
11
|
+
def initialize: (?Array[ECHConfig::ECHConfigContents::Extensions::ECHConfigExtension] extensions) -> void
|
|
9
12
|
|
|
10
13
|
# @rbs return: String
|
|
11
|
-
def
|
|
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.
|
|
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
|
|
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
|
|
@@ -95,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
95
103
|
- !ruby/object:Gem::Version
|
|
96
104
|
version: '0'
|
|
97
105
|
requirements: []
|
|
98
|
-
rubygems_version: 4.0.
|
|
106
|
+
rubygems_version: 4.0.3
|
|
99
107
|
specification_version: 4
|
|
100
108
|
summary: ECHConfig
|
|
101
109
|
test_files: []
|