Class: EchSpec::Spec::Spec9
- Inherits:
-
Object
- Object
- EchSpec::Spec::Spec9
- Defined in:
- lib/echspec/spec/9.rb
Class Attribute Summary collapse
-
.description ⇒ Object
readonly
Returns the value of attribute description.
-
.section ⇒ Object
readonly
Returns the value of attribute section.
Class Method Summary collapse
- .fetch_well_known(hostname) ⇒ EchSpec::Ok | Err
-
.parse_origin_svcb(body) ⇒ EchSpec::Ok | Err
{ "regeninterval": 3600, "endpoints": [ { "priority": 1, "params": { "ipv4hint": [ "192.0.2.1", "192.0.2.254" ], "ech": "AD7+DQA6NAAgACCn3zNTeX/WOD...AAA==", "ipv6hint": [ "2001:DB::ec4" ], "alpn": [ "h2", "http/1.1" ] } } ] }.
- .parse_pem(pem) ⇒ EchSpec::Ok<Array<ECHConfig>> | Err
- .resolve_ech_configs(hostname) ⇒ EchSpec::Ok<Array<ECHConfig>> | Err
- .try_get_ech_config(fpath, hostname, force_compliant) ⇒ EchSpec::Ok<ECHConfig> | Err
- .validate_compliant_ech_configs(ech_configs) ⇒ EchSpec::Ok | Err
- .well_known_origin_svcb(hostname) ⇒ EchSpec::Ok<Array<ECHConfig>> | Err
Class Attribute Details
.description ⇒ Object (readonly)
Returns the value of attribute description.
16 17 18 |
# File 'lib/echspec/spec/9.rb', line 16 def description @description end |
.section ⇒ Object (readonly)
Returns the value of attribute section.
16 17 18 |
# File 'lib/echspec/spec/9.rb', line 16 def section @section end |
Class Method Details
.fetch_well_known(hostname) ⇒ EchSpec::Ok | Err
108 109 110 111 112 113 114 115 116 |
# File 'lib/echspec/spec/9.rb', line 108 def fetch_well_known(hostname) uri = URI("https://#{hostname}#{WK_ECH_PATH}") res = Net::HTTP.get_response(uri) return Err.new("GET #{uri} returned #{res.code}.", nil) unless res.is_a?(Net::HTTPSuccess) Ok.new(res.body) rescue StandardError => e Err.new(e., nil) end |
.parse_origin_svcb(body) ⇒ EchSpec::Ok | Err
{ "regeninterval": 3600, "endpoints": [ { "priority": 1, "params": { "ipv4hint": [ "192.0.2.1", "192.0.2.254" ], "ech": "AD7+DQA6NAAgACCn3zNTeX/WOD...AAA==", "ipv6hint": [ "2001:DB::ec4" ], "alpn": [ "h2", "http/1.1" ] } } ] }
https://datatracker.ietf.org/doc/html/draft-ietf-tls-wkech-12#section-5
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/echspec/spec/9.rb', line 138 def parse_origin_svcb(body) h = JSON.parse(body) echs = h['endpoints']&.filter_map { |e| e.dig('params', 'ech') } return Err.new('The origin-svcb well-known resource does NOT have ech SvcParams.', nil) if echs.nil? || echs.empty? # if parsing any one of the ech values fails, return Err ech_configs = echs.flat_map do |ech| octet = Base64.decode64(ech) ECHConfig.decode_vectors(octet.slice(2..)) end Ok.new(ech_configs) rescue StandardError => e Err.new(e., nil) end |
.parse_pem(pem) ⇒ EchSpec::Ok<Array<ECHConfig>> | Err
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/echspec/spec/9.rb', line 157 def parse_pem(pem) s = pem.scan(/-----BEGIN ECHCONFIG-----(.*)-----END ECHCONFIG-----/m) .first .first .gsub("\n", '') b = Base64.decode64(s) ech_configs = ECHConfig.decode_vectors(b.slice(2..)) Ok.new(ech_configs) rescue StandardError # https://datatracker.ietf.org/doc/html/rfc9934#section-3 example = <<~PEM -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VuBCIEICjd4yGRdsoP9gU7YT7My8DHx1Tjme8GYDXrOMCi8v1V -----END PRIVATE KEY----- -----BEGIN ECHCONFIG----- AD7+DQA65wAgACA8wVN2BtscOl3vQheUzHeIkVmKIiydUhDCliA4iyQRCwAEAAEA AQALZXhhbXBsZS5jb20AAA== -----END ECHCONFIG----- PEM Err.new("Failed to parse ECHConfig PEM file, expected ECHConfig PEM like following: \n\n#{example}", nil) end |
.resolve_ech_configs(hostname) ⇒ EchSpec::Ok<Array<ECHConfig>> | Err
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/echspec/spec/9.rb', line 65 def resolve_ech_configs(hostname) begin rr = Resolv::DNS.new.getresource( hostname, Resolv::DNS::Resource::IN::HTTPS ) rescue Resolv::ResolvError => e return Err.new(e., nil) end # https://datatracker.ietf.org/doc/html/rfc9934#section-3 ech = 5 return Err.new("HTTPS resource record for #{hostname} does NOT have ech SvcParams.", nil) if rr.params[ech].nil? octet = rr.params[ech].value Err.new('Failed to parse ECHConfig on HTTPS resource record.', nil) \ unless octet.length == octet.slice(0, 2).unpack1('n') + 2 Ok.new(ECHConfig.decode_vectors(octet.slice(2..))) end |
.try_get_ech_config(fpath, hostname, force_compliant) ⇒ EchSpec::Ok<ECHConfig> | Err
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/echspec/spec/9.rb', line 23 def try_get_ech_config(fpath, hostname, force_compliant) result = if fpath.nil? resolve_ech_configs(hostname) else parse_pem(File.open(fpath).read) end ech_configs = case result in Ok(obj) obj in Err return result end if force_compliant validate_compliant_ech_configs(ech_configs) else Ok.new(ech_configs.first) end end |
.validate_compliant_ech_configs(ech_configs) ⇒ EchSpec::Ok | Err
47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/echspec/spec/9.rb', line 47 def validate_compliant_ech_configs(ech_configs) ech_config = ech_configs.find do |c| kconfig = c.echconfig_contents.key_config valid_kem_id = kconfig.kem_id.uint16 == HPKE::DHKEM_X25519_HKDF_SHA256 valid_cipher_suite = kconfig.cipher_suites.any? do |cs| cs.kdf_id.uint16 == HPKE::HKDF_SHA256 && cs.aead_id.uint16 == HPKE::AES_128_GCM end valid_kem_id && valid_cipher_suite end return Ok.new(ech_config) unless ech_config.nil? Err.new('ECHConfigs does NOT include HPKE cipher suite: KEM: DHKEM(X25519, HKDF-SHA256), KDF: HKDF-SHA256 and AEAD: AES-128-GCM.', nil) end |
.well_known_origin_svcb(hostname) ⇒ EchSpec::Ok<Array<ECHConfig>> | Err
89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/echspec/spec/9.rb', line 89 def well_known_origin_svcb(hostname) res = fetch_well_known(hostname) body = case res in Ok(obj) obj in Err return res end parse_origin_svcb(body) end |