acme-client 2.0.12 → 2.0.14
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/CHANGELOG.md +9 -1
- data/README.md +10 -1
- data/lib/acme/client/http_client.rb +162 -0
- data/lib/acme/client/jwk/hmac.rb +30 -0
- data/lib/acme/client/jwk.rb +1 -0
- data/lib/acme/client/resources/directory.rb +2 -7
- data/lib/acme/client/util.rb +2 -2
- data/lib/acme/client/version.rb +1 -1
- data/lib/acme/client.rb +21 -25
- metadata +9 -8
- data/lib/acme/client/faraday_middleware.rb +0 -111
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6238349e171138af08de444bf08268ab3bebab0c4d0497bee91d9d4a30d397ab
|
4
|
+
data.tar.gz: 9928049d9997c284cec6d75e12bf6b5f07150459775d8f852adbc09b7c923178
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8543f742ee8fe3822c8c989a7e5f8b1f75473f01a36d7a2218c44b43ebec55fa538beabc4545c4798a7b35005ad1fddbd0ed59dbae6942413812fae9ce625f92
|
7
|
+
data.tar.gz: 77cd41773aa293bcb65ebdb9489c500a06b144e0efb2dc5af23baa8dec8c36c1af32fd9f1c97eb77e40eb4a521a9c904448d5765f64dcbe56a67f4907225aaf3
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,12 @@
|
|
1
|
-
## `2.0.
|
1
|
+
## `2.0.14`
|
2
|
+
|
3
|
+
* Fix Faraday HTTP exceptions leaking out, always raise `Acme::Client::Error` instead
|
4
|
+
|
5
|
+
## `2.0.13`
|
6
|
+
|
7
|
+
* Add support for External Account Binding
|
8
|
+
|
9
|
+
## `2.0.12`
|
2
10
|
|
3
11
|
* Update test matrix to current Ruby versions (2.7 to 3.2)
|
4
12
|
* Support for Faraday retry 2.x
|
data/README.md
CHANGED
@@ -106,6 +106,15 @@ client.kid
|
|
106
106
|
=> "https://acme-staging-v02.api.letsencrypt.org/acme/acct/000000"
|
107
107
|
```
|
108
108
|
|
109
|
+
## External Account Binding support
|
110
|
+
|
111
|
+
You can use External Account Binding by providing a `external_account_binding` with a `kid` and `hmac_key`.
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
client = Acme::Client.new(private_key: private_key, directory: 'https://acme.zerossl.com/v2/DV90')
|
115
|
+
account = client.new_account(contact: 'mailto:info@example.com', terms_of_service_agreed: true, external_account_binding: { kid: "your kid", hmac_key: "your hmac key"})
|
116
|
+
```
|
117
|
+
|
109
118
|
## Obtaining a certificate
|
110
119
|
### Ordering a certificate
|
111
120
|
|
@@ -235,7 +244,7 @@ To change the key used for an account you can call `#account_key_change` with th
|
|
235
244
|
```ruby
|
236
245
|
require 'openssl'
|
237
246
|
new_private_key = OpenSSL::PKey::RSA.new(4096)
|
238
|
-
client.account_key_change(
|
247
|
+
client.account_key_change(new_private_key: new_private_key)
|
239
248
|
```
|
240
249
|
|
241
250
|
## Requirements
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Acme::Client::HTTPClient
|
4
|
+
# Creates and returns a new HTTP client, with default settings.
|
5
|
+
#
|
6
|
+
# @param url [URI:HTTPS]
|
7
|
+
# @param options [Hash]
|
8
|
+
# @return [Faraday::Connection]
|
9
|
+
def self.new_connection(url:, options: {})
|
10
|
+
Faraday.new(url, options) do |configuration|
|
11
|
+
configuration.use Acme::Client::HTTPClient::ErrorMiddleware
|
12
|
+
|
13
|
+
yield(configuration) if block_given?
|
14
|
+
|
15
|
+
configuration.headers[:user_agent] = Acme::Client::USER_AGENT
|
16
|
+
configuration.adapter Faraday.default_adapter
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates and returns a new HTTP client designed for the Acme-protocol, with default settings.
|
21
|
+
#
|
22
|
+
# @param url [URI:HTTPS]
|
23
|
+
# @param client [Acme::Client]
|
24
|
+
# @param mode [Symbol]
|
25
|
+
# @param options [Hash]
|
26
|
+
# @param bad_nonce_retry [Integer]
|
27
|
+
# @return [Faraday::Connection]
|
28
|
+
def self.new_acme_connection(url:, client:, mode:, options: {}, bad_nonce_retry: 0)
|
29
|
+
new_connection(url: url, options: options) do |configuration|
|
30
|
+
if bad_nonce_retry > 0
|
31
|
+
configuration.request(:retry,
|
32
|
+
max: bad_nonce_retry,
|
33
|
+
methods: Faraday::Connection::METHODS,
|
34
|
+
exceptions: [Acme::Client::Error::BadNonce])
|
35
|
+
end
|
36
|
+
|
37
|
+
configuration.use Acme::Client::HTTPClient::AcmeMiddleware, client: client, mode: mode
|
38
|
+
|
39
|
+
yield(configuration) if block_given?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# ErrorMiddleware ensures the HTTP Client would not raise exceptions outside the Acme namespace.
|
44
|
+
#
|
45
|
+
# Exceptions are rescued and re-packaged as Acme exceptions.
|
46
|
+
class ErrorMiddleware < Faraday::Middleware
|
47
|
+
# Implements the Rack-alike Faraday::Middleware interface.
|
48
|
+
def call(env)
|
49
|
+
@app.call(env)
|
50
|
+
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
|
51
|
+
raise Acme::Client::Error::Timeout
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# AcmeMiddleware implements the Acme-protocol requirements for JWK requests.
|
56
|
+
class AcmeMiddleware < Faraday::Middleware
|
57
|
+
attr_reader :env, :response, :client
|
58
|
+
|
59
|
+
CONTENT_TYPE = 'application/jose+json'
|
60
|
+
|
61
|
+
def initialize(app, options)
|
62
|
+
super(app)
|
63
|
+
@client = options.fetch(:client)
|
64
|
+
@mode = options.fetch(:mode)
|
65
|
+
end
|
66
|
+
|
67
|
+
def call(env)
|
68
|
+
@env = env
|
69
|
+
@env[:request_headers]['Content-Type'] = CONTENT_TYPE
|
70
|
+
|
71
|
+
if @env.method != :get
|
72
|
+
@env.body = client.jwk.jws(header: jws_header, payload: env.body)
|
73
|
+
end
|
74
|
+
|
75
|
+
@app.call(env).on_complete { |response_env| on_complete(response_env) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def on_complete(env)
|
79
|
+
@env = env
|
80
|
+
|
81
|
+
raise_on_not_found!
|
82
|
+
store_nonce
|
83
|
+
env.body = decode_body
|
84
|
+
env.response_headers['Link'] = decode_link_headers
|
85
|
+
|
86
|
+
return if env.success?
|
87
|
+
|
88
|
+
raise_on_error!
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def jws_header
|
94
|
+
headers = { nonce: pop_nonce, url: env.url.to_s }
|
95
|
+
headers[:kid] = client.kid if @mode == :kid
|
96
|
+
headers
|
97
|
+
end
|
98
|
+
|
99
|
+
def raise_on_not_found!
|
100
|
+
raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
|
101
|
+
end
|
102
|
+
|
103
|
+
def raise_on_error!
|
104
|
+
raise error_class, error_message
|
105
|
+
end
|
106
|
+
|
107
|
+
def error_message
|
108
|
+
if env.body.is_a? Hash
|
109
|
+
env.body['detail']
|
110
|
+
else
|
111
|
+
"Error message: #{env.body}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def error_class
|
116
|
+
Acme::Client::Error::ACME_ERRORS.fetch(error_name, Acme::Client::Error)
|
117
|
+
end
|
118
|
+
|
119
|
+
def error_name
|
120
|
+
return unless env.body.is_a?(Hash)
|
121
|
+
return unless env.body.key?('type')
|
122
|
+
env.body['type']
|
123
|
+
end
|
124
|
+
|
125
|
+
def decode_body
|
126
|
+
content_type = env.response_headers['Content-Type'].to_s
|
127
|
+
|
128
|
+
if content_type.start_with?('application/json', 'application/problem+json')
|
129
|
+
JSON.load(env.body)
|
130
|
+
else
|
131
|
+
env.body
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def decode_link_headers
|
136
|
+
return unless env.response_headers.key?('Link')
|
137
|
+
link_header = env.response_headers['Link']
|
138
|
+
Acme::Client::Util.decode_link_headers(link_header)
|
139
|
+
end
|
140
|
+
|
141
|
+
def store_nonce
|
142
|
+
nonce = env.response_headers['replay-nonce']
|
143
|
+
nonces << nonce if nonce
|
144
|
+
end
|
145
|
+
|
146
|
+
def pop_nonce
|
147
|
+
if nonces.empty?
|
148
|
+
get_nonce
|
149
|
+
end
|
150
|
+
|
151
|
+
nonces.pop
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_nonce
|
155
|
+
client.get_nonce
|
156
|
+
end
|
157
|
+
|
158
|
+
def nonces
|
159
|
+
client.nonces
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Acme::Client::JWK::HMAC < Acme::Client::JWK::Base
|
4
|
+
# Instantiate a new HMAC JWS.
|
5
|
+
#
|
6
|
+
# key - A string.
|
7
|
+
#
|
8
|
+
# Returns nothing.
|
9
|
+
def initialize(key)
|
10
|
+
@key = key
|
11
|
+
end
|
12
|
+
|
13
|
+
# Sign a message with the private key.
|
14
|
+
#
|
15
|
+
# message - A String message to sign.
|
16
|
+
#
|
17
|
+
# Returns a String signature.
|
18
|
+
def sign(message)
|
19
|
+
OpenSSL::HMAC.digest('SHA256', @key, message)
|
20
|
+
end
|
21
|
+
|
22
|
+
# The name of the algorithm as needed for the `alg` member of a JWS object.
|
23
|
+
#
|
24
|
+
# Returns a String.
|
25
|
+
def jwa_alg
|
26
|
+
# https://tools.ietf.org/html/rfc7518#section-3.1
|
27
|
+
# HMAC using SHA-256
|
28
|
+
'HS256'
|
29
|
+
end
|
30
|
+
end
|
data/lib/acme/client/jwk.rb
CHANGED
@@ -68,13 +68,8 @@ class Acme::Client::Resources::Directory
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def fetch_directory
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
configuration.adapter Faraday.default_adapter
|
75
|
-
end
|
76
|
-
connection.headers[:user_agent] = Acme::Client::USER_AGENT
|
77
|
-
response = connection.get(@url)
|
71
|
+
http_client = Acme::Client::HTTPClient.new_acme_connection(url: @directory, options: @connection_options, client: nil, mode: nil)
|
72
|
+
response = http_client.get(@url)
|
78
73
|
response.body
|
79
74
|
end
|
80
75
|
end
|
data/lib/acme/client/util.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
module Acme::Client::Util
|
2
|
+
extend self
|
3
|
+
|
2
4
|
def urlsafe_base64(data)
|
3
5
|
Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '')
|
4
6
|
end
|
@@ -30,6 +32,4 @@ module Acme::Client::Util
|
|
30
32
|
raise ArgumentError, 'priv must be EC or RSA'
|
31
33
|
end
|
32
34
|
end
|
33
|
-
|
34
|
-
extend self
|
35
35
|
end
|
data/lib/acme/client/version.rb
CHANGED
data/lib/acme/client.rb
CHANGED
@@ -14,10 +14,10 @@ module Acme; end
|
|
14
14
|
class Acme::Client; end
|
15
15
|
|
16
16
|
require 'acme/client/version'
|
17
|
+
require 'acme/client/http_client'
|
17
18
|
require 'acme/client/certificate_request'
|
18
19
|
require 'acme/client/self_sign_certificate'
|
19
20
|
require 'acme/client/resources'
|
20
|
-
require 'acme/client/faraday_middleware'
|
21
21
|
require 'acme/client/jwk'
|
22
22
|
require 'acme/client/error'
|
23
23
|
require 'acme/client/util'
|
@@ -50,7 +50,8 @@ class Acme::Client
|
|
50
50
|
|
51
51
|
attr_reader :jwk, :nonces
|
52
52
|
|
53
|
-
def new_account(contact:, terms_of_service_agreed: nil)
|
53
|
+
def new_account(contact:, terms_of_service_agreed: nil, external_account_binding: nil)
|
54
|
+
new_account_endpoint = endpoint_for(:new_account)
|
54
55
|
payload = {
|
55
56
|
contact: Array(contact)
|
56
57
|
}
|
@@ -59,7 +60,18 @@ class Acme::Client
|
|
59
60
|
payload[:termsOfServiceAgreed] = terms_of_service_agreed
|
60
61
|
end
|
61
62
|
|
62
|
-
|
63
|
+
if external_account_binding
|
64
|
+
kid, hmac_key = external_account_binding.values_at(:kid, :hmac_key)
|
65
|
+
if kid.nil? || hmac_key.nil?
|
66
|
+
raise ArgumentError, 'must specify kid and hmac_key key for external_account_binding'
|
67
|
+
end
|
68
|
+
|
69
|
+
hmac = Acme::Client::JWK::HMAC.new(Base64.urlsafe_decode64(hmac_key))
|
70
|
+
external_account_payload = hmac.jws(header: { kid: kid, url: new_account_endpoint }, payload: @jwk)
|
71
|
+
payload[:externalAccountBinding] = JSON.parse(external_account_payload)
|
72
|
+
end
|
73
|
+
|
74
|
+
response = post(new_account_endpoint, payload: payload, mode: :jws)
|
63
75
|
@kid = response.headers.fetch(:location)
|
64
76
|
|
65
77
|
if response.body.nil? || response.body.empty?
|
@@ -211,8 +223,8 @@ class Acme::Client
|
|
211
223
|
end
|
212
224
|
|
213
225
|
def get_nonce
|
214
|
-
|
215
|
-
response =
|
226
|
+
http_client = Acme::Client::HTTPClient.new_connection(url: endpoint_for(:new_nonce))
|
227
|
+
response = http_client.head(nil, nil)
|
216
228
|
nonces << response.headers['replay-nonce']
|
217
229
|
true
|
218
230
|
end
|
@@ -320,28 +332,12 @@ class Acme::Client
|
|
320
332
|
def connection_for(url:, mode:)
|
321
333
|
uri = URI(url)
|
322
334
|
endpoint = "#{uri.scheme}://#{uri.hostname}:#{uri.port}"
|
335
|
+
|
323
336
|
@connections ||= {}
|
324
337
|
@connections[mode] ||= {}
|
325
|
-
@connections[mode][endpoint] ||= new_acme_connection(
|
326
|
-
|
327
|
-
|
328
|
-
def new_acme_connection(endpoint:, mode:)
|
329
|
-
new_connection(endpoint: endpoint) do |configuration|
|
330
|
-
configuration.use Acme::Client::FaradayMiddleware, client: self, mode: mode
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
def new_connection(endpoint:)
|
335
|
-
Faraday.new(endpoint, **@connection_options) do |configuration|
|
336
|
-
if @bad_nonce_retry > 0
|
337
|
-
configuration.request(:retry,
|
338
|
-
max: @bad_nonce_retry,
|
339
|
-
methods: Faraday::Connection::METHODS,
|
340
|
-
exceptions: [Acme::Client::Error::BadNonce])
|
341
|
-
end
|
342
|
-
yield(configuration) if block_given?
|
343
|
-
configuration.adapter Faraday.default_adapter
|
344
|
-
end
|
338
|
+
@connections[mode][endpoint] ||= Acme::Client::HTTPClient.new_acme_connection(
|
339
|
+
url: URI(endpoint), mode: mode, client: self, options: @connection_options, bad_nonce_retry: @bad_nonce_retry
|
340
|
+
)
|
345
341
|
end
|
346
342
|
|
347
343
|
def fetch_chain(response, limit = 10)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acme-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charles Barbier
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-06-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -134,7 +134,7 @@ dependencies:
|
|
134
134
|
- - "<"
|
135
135
|
- !ruby/object:Gem::Version
|
136
136
|
version: 3.0.0
|
137
|
-
description:
|
137
|
+
description:
|
138
138
|
email:
|
139
139
|
- unixcharles@gmail.com
|
140
140
|
executables: []
|
@@ -161,10 +161,11 @@ files:
|
|
161
161
|
- lib/acme/client/certificate_request/ec_key_patch.rb
|
162
162
|
- lib/acme/client/chain_identifier.rb
|
163
163
|
- lib/acme/client/error.rb
|
164
|
-
- lib/acme/client/
|
164
|
+
- lib/acme/client/http_client.rb
|
165
165
|
- lib/acme/client/jwk.rb
|
166
166
|
- lib/acme/client/jwk/base.rb
|
167
167
|
- lib/acme/client/jwk/ecdsa.rb
|
168
|
+
- lib/acme/client/jwk/hmac.rb
|
168
169
|
- lib/acme/client/jwk/rsa.rb
|
169
170
|
- lib/acme/client/resources.rb
|
170
171
|
- lib/acme/client/resources/account.rb
|
@@ -183,7 +184,7 @@ homepage: http://github.com/unixcharles/acme-client
|
|
183
184
|
licenses:
|
184
185
|
- MIT
|
185
186
|
metadata: {}
|
186
|
-
post_install_message:
|
187
|
+
post_install_message:
|
187
188
|
rdoc_options: []
|
188
189
|
require_paths:
|
189
190
|
- lib
|
@@ -198,8 +199,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
198
199
|
- !ruby/object:Gem::Version
|
199
200
|
version: '0'
|
200
201
|
requirements: []
|
201
|
-
rubygems_version: 3.
|
202
|
-
signing_key:
|
202
|
+
rubygems_version: 3.0.3.1
|
203
|
+
signing_key:
|
203
204
|
specification_version: 4
|
204
205
|
summary: Client for the ACME protocol.
|
205
206
|
test_files: []
|
@@ -1,111 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Acme::Client::FaradayMiddleware < Faraday::Middleware
|
4
|
-
attr_reader :env, :response, :client
|
5
|
-
|
6
|
-
CONTENT_TYPE = 'application/jose+json'
|
7
|
-
|
8
|
-
def initialize(app, options)
|
9
|
-
super(app)
|
10
|
-
@client = options.fetch(:client)
|
11
|
-
@mode = options.fetch(:mode)
|
12
|
-
end
|
13
|
-
|
14
|
-
def call(env)
|
15
|
-
@env = env
|
16
|
-
@env[:request_headers]['User-Agent'] = Acme::Client::USER_AGENT
|
17
|
-
@env[:request_headers]['Content-Type'] = CONTENT_TYPE
|
18
|
-
|
19
|
-
if @env.method != :get
|
20
|
-
@env.body = client.jwk.jws(header: jws_header, payload: env.body)
|
21
|
-
end
|
22
|
-
|
23
|
-
@app.call(env).on_complete { |response_env| on_complete(response_env) }
|
24
|
-
rescue Faraday::TimeoutError, Faraday::ConnectionFailed
|
25
|
-
raise Acme::Client::Error::Timeout
|
26
|
-
end
|
27
|
-
|
28
|
-
def on_complete(env)
|
29
|
-
@env = env
|
30
|
-
|
31
|
-
raise_on_not_found!
|
32
|
-
store_nonce
|
33
|
-
env.body = decode_body
|
34
|
-
env.response_headers['Link'] = decode_link_headers
|
35
|
-
|
36
|
-
return if env.success?
|
37
|
-
|
38
|
-
raise_on_error!
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def jws_header
|
44
|
-
headers = { nonce: pop_nonce, url: env.url.to_s }
|
45
|
-
headers[:kid] = client.kid if @mode == :kid
|
46
|
-
headers
|
47
|
-
end
|
48
|
-
|
49
|
-
def raise_on_not_found!
|
50
|
-
raise Acme::Client::Error::NotFound, env.url.to_s if env.status == 404
|
51
|
-
end
|
52
|
-
|
53
|
-
def raise_on_error!
|
54
|
-
raise error_class, error_message
|
55
|
-
end
|
56
|
-
|
57
|
-
def error_message
|
58
|
-
if env.body.is_a? Hash
|
59
|
-
env.body['detail']
|
60
|
-
else
|
61
|
-
"Error message: #{env.body}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def error_class
|
66
|
-
Acme::Client::Error::ACME_ERRORS.fetch(error_name, Acme::Client::Error)
|
67
|
-
end
|
68
|
-
|
69
|
-
def error_name
|
70
|
-
return unless env.body.is_a?(Hash)
|
71
|
-
return unless env.body.key?('type')
|
72
|
-
env.body['type']
|
73
|
-
end
|
74
|
-
|
75
|
-
def decode_body
|
76
|
-
content_type = env.response_headers['Content-Type'].to_s
|
77
|
-
|
78
|
-
if content_type.start_with?('application/json', 'application/problem+json')
|
79
|
-
JSON.load(env.body)
|
80
|
-
else
|
81
|
-
env.body
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def decode_link_headers
|
86
|
-
return unless env.response_headers.key?('Link')
|
87
|
-
link_header = env.response_headers['Link']
|
88
|
-
Acme::Client::Util.decode_link_headers(link_header)
|
89
|
-
end
|
90
|
-
|
91
|
-
def store_nonce
|
92
|
-
nonce = env.response_headers['replay-nonce']
|
93
|
-
nonces << nonce if nonce
|
94
|
-
end
|
95
|
-
|
96
|
-
def pop_nonce
|
97
|
-
if nonces.empty?
|
98
|
-
get_nonce
|
99
|
-
end
|
100
|
-
|
101
|
-
nonces.pop
|
102
|
-
end
|
103
|
-
|
104
|
-
def get_nonce
|
105
|
-
client.get_nonce
|
106
|
-
end
|
107
|
-
|
108
|
-
def nonces
|
109
|
-
client.nonces
|
110
|
-
end
|
111
|
-
end
|