acme-client 2.0.25 → 2.0.26

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: 57864d566a88b4298a243bfaa6f34b5556a7aa1f36f41fd4e61636d0e7fae74e
4
- data.tar.gz: c68a9476baa8fad93f9373e16d3dad6249edb2cc7f007d0d1bf4386cf0cf7f6e
3
+ metadata.gz: f24f8baac91c1bff36891f14b0ec6d1fb3a9265cf771f2ae943b49dac5a96d2e
4
+ data.tar.gz: 4f84da04feeea6ae55ab875ab835194bf71182e8cabf7d56e574d79d80e988ae
5
5
  SHA512:
6
- metadata.gz: c2cb942f81bfb49f952f955b9eea415b86718c8fe4fb3426cf7cdc1fcb8925b97dfe20e07300480fef894b6438ab5b2046d670564f8bc66a8a80bd5f14e2a282
7
- data.tar.gz: f5343edaa0fb6543d2f37e6017732969e63e687c9db877ff8ba188444244e1a180b59cedfbbef12d021a702f65cb089fc08f0ec79ab2363b660343d9df421f5c
6
+ metadata.gz: 1e4a73d22af2fec3e323859f5386308c046c32b443952f81c9ce1811562eae0ec92c0a3bd683c0806bd85ca605bda5468bdf7534b224b94a65a7fe1b1222cce4
7
+ data.tar.gz: 84722131dd304475f45459ff78b4a0eda6ccf1a6d0aa2ffbdb2092cb17446ffc62db288ba429ee6a2080779dde273487718cddf27cc7d07b1850210401222b63
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## `2.0.26`
2
+
3
+ * Add support for dns-account-01 challenge (RFC draft-ietf-acme-dns-account-label-01)
4
+
1
5
  ## `2.0.25`
2
6
 
3
7
  * Add support for profiles extension
data/README.md CHANGED
@@ -120,9 +120,11 @@ To order a new certificate, the client must provide a list of identifiers.
120
120
 
121
121
  The returned order will contain a list of `Authorization` that need to be completed in other to finalize the order, generally one per identifier.
122
122
 
123
- Each authorization contains multiple challenges, typically a `dns-01` and a `http-01` challenge. The applicant is only required to complete one of the challenges.
123
+ Each authorization contains multiple challenges, typically a `dns-01`, `dns-account-01`, and a `http-01` challenge. The applicant is only required to complete one of the challenges.
124
124
 
125
- You can access the challenge you wish to complete using the `#dns` or `#http` method.
125
+ The `dns-account-01` challenge prepends an account-specific label before `_acme-challenge`, producing a record name of the form `_<label>._acme-challenge` so different clients can validate the same domain concurrently.
126
+
127
+ You can access the challenge you wish to complete using the `#dns`, `#dns_account`, or `#http` methods.
126
128
 
127
129
  ```ruby
128
130
  order = client.new_order(identifiers: ['example.com'])
@@ -165,6 +167,25 @@ dns_challenge.record_type # => 'TXT'
165
167
  dns_challenge.record_content # => 'HRV3PS5sRDyV-ous4HJk4z24s5JjmUTjcCaUjFt28-8'
166
168
  ```
167
169
 
170
+ ### Preparing for DNS-Account-01 challenge
171
+
172
+ To complete the DNS-Account-01 challenge, you must set a DNS TXT record using an account-specific name. This allows multiple ACME clients to validate the same domain concurrently without conflicts.
173
+
174
+ The DNSAccount01 object has utility methods to generate the required DNS record:
175
+
176
+ ```ruby
177
+ dns_account_challenge = authorization.dns_account
178
+
179
+ dns_account_challenge.record_name # => '_ujmmovf2vn55tgye._acme-challenge'
180
+ dns_account_challenge.record_type # => 'TXT'
181
+ dns_account_challenge.record_content # => 'HRV3PS5sRDyV-ous4HJk4z24s5JjmUTjcCaUjFt28-8'
182
+ ```
183
+
184
+ The record name includes an account-specific label derived from your account URL, ensuring different clients can validate simultaneously:
185
+
186
+ - **DNS-01**: `_acme-challenge.example.com` (shared)
187
+ - **DNS-Account-01**: `_ujmmovf2vn55tgye._acme-challenge.example.com` (account-specific)
188
+
168
189
  ### Requesting a challenge verification
169
190
 
170
191
  Once you are ready to complete the challenge, you can request the server perform the verification.
@@ -38,6 +38,13 @@ class Acme::Client::Resources::Authorization
38
38
  end
39
39
  alias_method :dns, :dns01
40
40
 
41
+ def dns_account_01
42
+ @dns_account_01 ||= challenges.find { |challenge|
43
+ challenge.is_a?(Acme::Client::Resources::Challenges::DNSAccount01)
44
+ }
45
+ end
46
+ alias_method :dns_account, :dns_account_01
47
+
41
48
  def to_h
42
49
  {
43
50
  url: url,
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # DNS-Account-01 challenge following draft-ietf-acme-dns-account-label-01
4
+ # Enables multiple ACME clients to validate the same domain concurrently
5
+ class Acme::Client::Resources::Challenges::DNSAccount01 < Acme::Client::Resources::Challenges::Base
6
+ CHALLENGE_TYPE = 'dns-account-01'.freeze
7
+ RECORD_PREFIX = '_'.freeze
8
+ RECORD_SUFFIX = '._acme-challenge'.freeze
9
+ RECORD_TYPE = 'TXT'.freeze
10
+ DIGEST = OpenSSL::Digest::SHA256
11
+ BASE32_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567'.freeze # RFC 4648 lowercase alphabet
12
+
13
+ # Generates account-specific DNS record name using SHA256(account_url) + Base32
14
+ # Format: _<base32_label>._acme-challenge
15
+ def record_name
16
+ digest = DIGEST.digest(@client.kid)[0, 10] # First 10 octets for label
17
+ bits = digest.unpack1('B*')
18
+ label = bits.scan(/.{5}/).map { |chunk| BASE32_ALPHABET[chunk.to_i(2)] }.join
19
+ "#{RECORD_PREFIX}#{label}#{RECORD_SUFFIX}"
20
+ end
21
+
22
+ def record_type
23
+ RECORD_TYPE
24
+ end
25
+
26
+ def record_content
27
+ Acme::Client::Util.urlsafe_base64(DIGEST.digest(key_authorization))
28
+ end
29
+ end
30
+
@@ -4,11 +4,13 @@ module Acme::Client::Resources::Challenges
4
4
  require 'acme/client/resources/challenges/base'
5
5
  require 'acme/client/resources/challenges/http01'
6
6
  require 'acme/client/resources/challenges/dns01'
7
+ require 'acme/client/resources/challenges/dns_account01'
7
8
  require 'acme/client/resources/challenges/unsupported_challenge'
8
9
 
9
10
  CHALLENGE_TYPES = {
10
11
  'http-01' => Acme::Client::Resources::Challenges::HTTP01,
11
- 'dns-01' => Acme::Client::Resources::Challenges::DNS01
12
+ 'dns-01' => Acme::Client::Resources::Challenges::DNS01,
13
+ 'dns-account-01' => Acme::Client::Resources::Challenges::DNSAccount01
12
14
  }
13
15
 
14
16
  def self.new(client, type:, **arguments)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Acme
4
4
  class Client
5
- VERSION = '2.0.25'.freeze
5
+ VERSION = '2.0.26'.freeze
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acme-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.25
4
+ version: 2.0.26
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Barbier
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-08-07 00:00:00.000000000 Z
10
+ date: 2025-09-24 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rake
@@ -167,6 +167,7 @@ files:
167
167
  - lib/acme/client/resources/challenges.rb
168
168
  - lib/acme/client/resources/challenges/base.rb
169
169
  - lib/acme/client/resources/challenges/dns01.rb
170
+ - lib/acme/client/resources/challenges/dns_account01.rb
170
171
  - lib/acme/client/resources/challenges/http01.rb
171
172
  - lib/acme/client/resources/challenges/unsupported_challenge.rb
172
173
  - lib/acme/client/resources/directory.rb