Module: Acme::Client::Util

Extended by:
Util
Included in:
Util
Defined in:
lib/acme/client/util.rb

Constant Summary collapse

/<(.*?)>\s?;\s?rel="([\w-]+)"/

Instance Method Summary collapse

Instance Method Details

#ari_certificate_identifier(certificate) ⇒ Object

Generates a certificate identifier for ACME Renewal Information (ARI) as per RFC 9773. The identifier is constructed by extracting the Authority Key Identifier (AKI) from the certificate extension, and the DER-encoded serial number (without tag and length bytes). Both values are base64url-encoded and concatenated with a period separator.

certificate - An OpenSSL::X509::Certificate instance or PEM string.

Returns a string in the format: base64url(AKI).base64url(serial)

Raises:

  • (ArgumentError)


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/acme/client/util.rb', line 62

def ari_certificate_identifier(certificate)
  cert = if certificate.is_a?(OpenSSL::X509::Certificate)
    certificate
  else
    OpenSSL::X509::Certificate.new(certificate)
  end

  aki_ext = cert.extensions.find { |ext| ext.oid == 'authorityKeyIdentifier' }
  raise ArgumentError, 'Certificate does not have an Authority Key Identifier extension' unless aki_ext

  aki_value = aki_ext.value
  hex_string = if aki_value =~ /keyid:([0-9A-Fa-f:]+)/
    $1
  elsif aki_value =~ /^[0-9A-Fa-f:]+$/
    aki_value
  else
    raise ArgumentError, 'Could not parse Authority Key Identifier'
  end

  key_identifier = hex_string.split(':').map { |hex| hex.to_i(16).chr }.join
  serial_der = OpenSSL::ASN1::Integer.new(cert.serial).to_der
  serial_value = OpenSSL::ASN1.decode(serial_der).value.to_s(2)

  aki_b64 = urlsafe_base64(key_identifier)
  serial_b64 = urlsafe_base64(serial_value)

  "#{aki_b64}.#{serial_b64}"
end


29
30
31
32
33
34
35
# File 'lib/acme/client/util.rb', line 29

def decode_link_headers(link_header)
  link_header.split(',').each_with_object({}) { |entry, hash|
    _, link, name = *entry.match(LINK_MATCH)
    hash[name] ||= []
    hash[name].push(link)
  }
end

#parse_retry_after(value) ⇒ Object

Parses a Retry-After header value into a Time. RFC 7231 ยง7.1.3: the value is either delay-seconds or an HTTP-date. Returns a Time, or nil if the value is nil or unparseable.



9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/acme/client/util.rb', line 9

def parse_retry_after(value)
  return nil if value.nil?

  value = value.to_s
  Integer(value, 10).then { |seconds| Time.now + seconds }
rescue ArgumentError, RangeError
  begin
    Time.httpdate(value)
  rescue ArgumentError
    nil
  end
end

#set_public_key(obj, priv) ⇒ Object

Sets public key on CSR or cert.

obj - An OpenSSL::X509::Certificate or OpenSSL::X509::Request instance. priv - An OpenSSL::PKey::EC or OpenSSL::PKey::RSA instance.

Returns nothing.



43
44
45
46
47
48
49
50
51
52
# File 'lib/acme/client/util.rb', line 43

def set_public_key(obj, priv)
  case priv
  when OpenSSL::PKey::EC
    obj.public_key = priv
  when OpenSSL::PKey::RSA
    obj.public_key = priv.public_key
  else
    raise ArgumentError, 'priv must be EC or RSA'
  end
end

#urlsafe_base64(data) ⇒ Object



22
23
24
# File 'lib/acme/client/util.rb', line 22

def urlsafe_base64(data)
  Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '')
end