Class: EchSpec::Spec::Spec9

Inherits:
Object
  • Object
show all
Defined in:
lib/echspec/spec/9.rb

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.descriptionObject (readonly)

Returns the value of attribute description.



16
17
18
# File 'lib/echspec/spec/9.rb', line 16

def description
  @description
end

.sectionObject (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

.parse_pem(pem) ⇒ EchSpec::Ok<Array of ECHConfig> | Err

Parameters:

  • pem (String)

Returns:



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/echspec/spec/9.rb', line 89

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/draft-farrell-tls-pemesni-08#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 of ECHConfig> | Err

Parameters:

  • hostname (String)

Returns:



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.message, nil)
  end

  # https://datatracker.ietf.org/doc/html/draft-ietf-tls-svcb-ech-01#section-6
  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

Parameters:

  • fpath (String | NilClass)
  • hostname (String)
  • force_compliant (Boolean)

Returns:



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<ECHConfig> | Err

Parameters:

  • ech_configs (Array of ECHConfig)

Returns:



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 == 0x0020
    valid_cipher_suite = kconfig.cipher_suites.any? do |cs|
      cs.kdf_id.uint16 == 0x0001 && cs.aead_id.uint16 == 0x0001
    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