Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Sympl
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Packages & Registries
Packages & Registries
Package Registry
Container Registry
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Ian Eiloart
Sympl
Commits
24a14e8d
Commit
24a14e8d
authored
Oct 23, 2019
by
Paul Cammish
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'buster-testing' into 'buster'
Buster testing -> Buster See merge request
!144
parents
73f14ea5
8936bad9
Changes
22
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
876 additions
and
458 deletions
+876
-458
core/debian/changelog
core/debian/changelog
+6
-0
core/lib/acme/client.rb
core/lib/acme/client.rb
+238
-63
core/lib/acme/client/certificate.rb
core/lib/acme/client/certificate.rb
+0
-30
core/lib/acme/client/certificate_request.rb
core/lib/acme/client/certificate_request.rb
+0
-2
core/lib/acme/client/error.rb
core/lib/acme/client/error.rb
+52
-13
core/lib/acme/client/faraday_middleware.rb
core/lib/acme/client/faraday_middleware.rb
+28
-28
core/lib/acme/client/jwk/base.rb
core/lib/acme/client/jwk/base.rb
+7
-6
core/lib/acme/client/jwk/ecdsa.rb
core/lib/acme/client/jwk/ecdsa.rb
+0
-2
core/lib/acme/client/resources.rb
core/lib/acme/client/resources.rb
+4
-2
core/lib/acme/client/resources/account.rb
core/lib/acme/client/resources/account.rb
+49
-0
core/lib/acme/client/resources/authorization.rb
core/lib/acme/client/resources/authorization.rb
+62
-32
core/lib/acme/client/resources/challenges.rb
core/lib/acme/client/resources/challenges.rb
+20
-5
core/lib/acme/client/resources/challenges/base.rb
core/lib/acme/client/resources/challenges/base.rb
+34
-22
core/lib/acme/client/resources/challenges/dns01.rb
core/lib/acme/client/resources/challenges/dns01.rb
+1
-1
core/lib/acme/client/resources/challenges/http01.rb
core/lib/acme/client/resources/challenges/http01.rb
+1
-1
core/lib/acme/client/resources/challenges/tls_sni01.rb
core/lib/acme/client/resources/challenges/tls_sni01.rb
+0
-25
core/lib/acme/client/resources/directory.rb
core/lib/acme/client/resources/directory.rb
+76
-0
core/lib/acme/client/resources/order.rb
core/lib/acme/client/resources/order.rb
+58
-0
core/lib/acme/client/resources/registration.rb
core/lib/acme/client/resources/registration.rb
+0
-37
core/lib/acme/client/version.rb
core/lib/acme/client/version.rb
+1
-1
core/lib/symbiosis/ssl/letsencrypt.rb
core/lib/symbiosis/ssl/letsencrypt.rb
+73
-22
core/test.d/tc_ssl_letsencrypt.rb
core/test.d/tc_ssl_letsencrypt.rb
+166
-166
No files found.
core/debian/changelog
View file @
24a14e8d
sympl-core (10.0.191017.0) stable; urgency=medium
* Updated sympl-ssl to use Let's Encrypt ACME v02 API
-- Paul Cammish <sympl@kelduum.net> Thu, 17 Oct 2019 13:45:01 +0100
sympl-core (10.0.190908.0) stable; urgency=medium
* Set default threshold for LE cert renewal to 30 days.
...
...
core/lib/acme/client.rb
View file @
24a14e8d
...
...
@@ -7,12 +7,12 @@ require 'digest'
require
'forwardable'
require
'base64'
require
'time'
require
'uri'
module
Acme
;
end
class
Acme::Client
;
end
require
'acme/client/version'
require
'acme/client/certificate'
require
'acme/client/certificate_request'
require
'acme/client/self_sign_certificate'
require
'acme/client/resources'
...
...
@@ -22,15 +22,14 @@ require 'acme/client/error'
require
'acme/client/util'
class
Acme::Client
DEFAULT_ENDPOINT
=
'http://127.0.0.1:4000'
.
freeze
DIRECTORY_DEFAULT
=
{
'new-authz'
=>
'/acme/new-authz'
,
'new-cert'
=>
'/acme/new-cert'
,
'new-reg'
=>
'/acme/new-reg'
,
'revoke-cert'
=>
'/acme/revoke-cert'
}.
freeze
def
initialize
(
jwk:
nil
,
private_key:
nil
,
endpoint:
DEFAULT_ENDPOINT
,
directory_uri:
nil
,
connection_options:
{})
DEFAULT_DIRECTORY
=
'http://127.0.0.1:4000/directory'
.
freeze
repo_url
=
'https://github.com/unixcharles/acme-client'
USER_AGENT
=
"Acme::Client v
#{
Acme
::
Client
::
VERSION
}
(
#{
repo_url
}
)"
.
freeze
CONTENT_TYPES
=
{
pem:
'application/pem-certificate-chain'
}
def
initialize
(
jwk:
nil
,
kid:
nil
,
private_key:
nil
,
directory:
DEFAULT_DIRECTORY
,
connection_options:
{},
bad_nonce_retry:
0
)
if
jwk
.
nil?
&&
private_key
.
nil?
raise
ArgumentError
,
'must specify jwk or private_key'
end
...
...
@@ -41,93 +40,269 @@ class Acme::Client
Acme
::
Client
::
JWK
.
from_private_key
(
private_key
)
end
@endpoint
,
@directory_uri
,
@connection_options
=
endpoint
,
directory_uri
,
connection_options
@kid
,
@connection_options
=
kid
,
connection_options
@bad_nonce_retry
=
bad_nonce_retry
@directory
=
Acme
::
Client
::
Resources
::
Directory
.
new
(
URI
(
directory
),
@connection_options
)
@nonces
||=
[]
load_directory!
end
attr_reader
:jwk
,
:nonces
,
:endpoint
,
:directory_uri
,
:operation_endpoints
attr_reader
:jwk
,
:nonces
def
register
(
contact
:
)
def
new_account
(
contact
:,
terms_of_service_agreed:
nil
)
payload
=
{
resource:
'new-reg'
,
contact:
Array
(
contact
)
contact:
Array
(
contact
)
}
response
=
connection
.
post
(
@operation_endpoints
.
fetch
(
'new-reg'
),
payload
)
::
Acme
::
Client
::
Resources
::
Registration
.
new
(
self
,
response
)
if
terms_of_service_agreed
payload
[
:termsOfServiceAgreed
]
=
terms_of_service_agreed
end
response
=
post
(
endpoint_for
(
:new_account
),
payload:
payload
,
mode: :jws
)
@kid
=
response
.
headers
.
fetch
(
:location
)
if
response
.
body
.
nil?
||
response
.
body
.
empty?
account
else
arguments
=
attributes_from_account_response
(
response
)
Acme
::
Client
::
Resources
::
Account
.
new
(
self
,
url:
@kid
,
**
arguments
)
end
end
def
authorize
(
domain
:)
payload
=
{
resource:
'new-authz'
,
identifier:
{
type:
'dns'
,
value:
domain
}
}
def
account_update
(
contact:
nil
,
terms_of_service_agreed:
nil
)
payload
=
{}
payload
[
:contact
]
=
Array
(
contact
)
if
contact
payload
[
:termsOfServiceAgreed
]
=
terms_of_service_agreed
if
terms_of_service_agreed
response
=
connection
.
post
(
@operation_endpoints
.
fetch
(
'new-authz'
),
payload
)
::
Acme
::
Client
::
Resources
::
Authorization
.
new
(
self
,
response
.
headers
[
'Location'
],
response
)
response
=
post
(
kid
,
payload:
payload
)
arguments
=
attributes_from_account_response
(
response
)
Acme
::
Client
::
Resources
::
Account
.
new
(
self
,
url:
kid
,
**
arguments
)
end
def
fetch_authorization
(
uri
)
response
=
connection
.
get
(
uri
)
::
Acme
::
Client
::
Resources
::
Authorization
.
new
(
self
,
uri
,
response
)
def
account_deactivate
response
=
post
(
kid
,
payload:
{
status:
'deactivated'
})
arguments
=
attributes_from_account_response
(
response
)
Acme
::
Client
::
Resources
::
Account
.
new
(
self
,
url:
kid
,
**
arguments
)
end
def
new_certificate
(
csr
)
payload
=
{
res
ource:
'new-cert'
,
csr:
Base64
.
urlsafe_encode64
(
csr
.
to_der
)
}
def
account
@kid
||=
begin
res
ponse
=
post
(
endpoint_for
(
:new_account
),
payload:
{
onlyReturnExisting:
true
},
mode: :jwk
)
response
.
headers
.
fetch
(
:location
)
end
response
=
connection
.
post
(
@operation_endpoints
.
fetch
(
'new-cert'
),
payload
)
::
Acme
::
Client
::
Certificate
.
new
(
OpenSSL
::
X509
::
Certificate
.
new
(
response
.
body
),
response
.
headers
[
'location'
],
fetch_chain
(
response
),
csr
)
response
=
post
(
@kid
)
arguments
=
attributes_from_account_response
(
response
)
Acme
::
Client
::
Resources
::
Account
.
new
(
self
,
url:
@kid
,
**
arguments
)
end
def
revoke_certificate
(
certificate
)
payload
=
{
resource:
'revoke-cert'
,
certificate:
Base64
.
urlsafe_encode64
(
certificate
.
to_der
)
}
endpoint
=
@operation_endpoints
.
fetch
(
'revoke-cert'
)
response
=
connection
.
post
(
endpoint
,
payload
)
response
.
success?
def
kid
@kid
||=
account
.
kid
end
def
new_order
(
identifiers
:,
not_before:
nil
,
not_after:
nil
)
payload
=
{}
payload
[
'identifiers'
]
=
if
identifiers
.
is_a?
(
Hash
)
identifiers
else
Array
(
identifiers
).
map
do
|
identifier
|
{
type:
'dns'
,
value:
identifier
}
end
end
payload
[
'notBefore'
]
=
not_before
if
not_before
payload
[
'notAfter'
]
=
not_after
if
not_after
response
=
post
(
endpoint_for
(
:new_order
),
payload:
payload
)
arguments
=
attributes_from_order_response
(
response
)
Acme
::
Client
::
Resources
::
Order
.
new
(
self
,
**
arguments
)
end
def
self
.
revoke_certificate
(
certificate
,
*
arguments
)
client
=
new
(
*
arguments
)
client
.
revoke_certificate
(
certificate
)
def
order
(
url
:)
response
=
get
(
url
)
arguments
=
attributes_from_order_response
(
response
)
Acme
::
Client
::
Resources
::
Order
.
new
(
self
,
**
arguments
.
merge
(
url:
url
))
end
def
connection
@connection
||=
Faraday
.
new
(
@endpoint
,
**
@connection_options
)
do
|
configuration
|
configuration
.
use
Acme
::
Client
::
FaradayMiddleware
,
client:
self
configuration
.
adapter
Faraday
.
default_adapter
def
finalize
(
url
:,
csr
:)
unless
csr
.
respond_to?
(
:to_der
)
raise
ArgumentError
,
'csr must respond to `#to_der`'
end
base64_der_csr
=
Acme
::
Client
::
Util
.
urlsafe_base64
(
csr
.
to_der
)
response
=
post
(
url
,
payload:
{
csr:
base64_der_csr
})
arguments
=
attributes_from_order_response
(
response
)
Acme
::
Client
::
Resources
::
Order
.
new
(
self
,
**
arguments
)
end
def
certificate
(
url
:)
response
=
download
(
url
,
format: :pem
)
response
.
body
end
def
authorization
(
url
:)
response
=
get
(
url
)
arguments
=
attributes_from_authorization_response
(
response
)
Acme
::
Client
::
Resources
::
Authorization
.
new
(
self
,
url:
url
,
**
arguments
)
end
def
deactivate_authorization
(
url
:)
response
=
post
(
url
,
payload:
{
status:
'deactivated'
})
arguments
=
attributes_from_authorization_response
(
response
)
Acme
::
Client
::
Resources
::
Authorization
.
new
(
self
,
url:
url
,
**
arguments
)
end
def
challenge
(
url
:)
response
=
get
(
url
)
arguments
=
attributes_from_challenge_response
(
response
)
Acme
::
Client
::
Resources
::
Challenges
.
new
(
self
,
**
arguments
)
end
def
request_challenge_validation
(
url
:,
key_authorization
:)
response
=
post
(
url
,
payload:
{
keyAuthorization:
key_authorization
})
arguments
=
attributes_from_challenge_response
(
response
)
Acme
::
Client
::
Resources
::
Challenges
.
new
(
self
,
**
arguments
)
end
def
revoke
(
certificate
:,
reason:
nil
)
der_certificate
=
if
certificate
.
respond_to?
(
:to_der
)
certificate
.
to_der
else
OpenSSL
::
X509
::
Certificate
.
new
(
certificate
).
to_der
end
base64_der_certificate
=
Acme
::
Client
::
Util
.
urlsafe_base64
(
der_certificate
)
payload
=
{
certificate:
base64_der_certificate
}
payload
[
:reason
]
=
reason
unless
reason
.
nil?
response
=
post
(
endpoint_for
(
:revoke_certificate
),
payload:
payload
)
response
.
success?
end
def
get_nonce
connection
=
new_connection
(
endpoint:
endpoint_for
(
:new_nonce
))
response
=
connection
.
head
(
nil
,
nil
,
'User-Agent'
=>
USER_AGENT
)
nonces
<<
response
.
headers
[
'replay-nonce'
]
true
end
def
meta
@directory
.
meta
end
def
terms_of_service
@directory
.
terms_of_service
end
def
website
@directory
.
website
end
def
caa_identities
@directory
.
caa_identities
end
def
external_account_required
@directory
.
external_account_required
end
private
def
attributes_from_account_response
(
response
)
extract_attributes
(
response
.
body
,
:status
,
[
:term_of_service
,
'termsOfServiceAgreed'
],
:contact
)
end
def
attributes_from_order_response
(
response
)
attributes
=
extract_attributes
(
response
.
body
,
:status
,
:expires
,
[
:finalize_url
,
'finalize'
],
[
:authorization_urls
,
'authorizations'
],
[
:certificate_url
,
'certificate'
],
:identifiers
)
attributes
[
:url
]
=
response
.
headers
[
:location
]
if
response
.
headers
[
:location
]
attributes
end
def
attributes_from_authorization_response
(
response
)
extract_attributes
(
response
.
body
,
:identifier
,
:status
,
:expires
,
:challenges
,
:wildcard
)
end
def
attributes_from_challenge_response
(
response
)
extract_attributes
(
response
.
body
,
:status
,
:url
,
:token
,
:type
,
:error
)
end
def
extract_attributes
(
input
,
*
attributes
)
attributes
.
map
{
|
fields
|
Array
(
fields
)
}
.
each_with_object
({})
{
|
(
key
,
field
),
hash
|
field
||=
key
.
to_s
hash
[
key
]
=
input
[
field
]
}
end
def
post
(
url
,
payload:
{},
mode: :kid
)
connection
=
connection_for
(
url:
url
,
mode:
mode
)
connection
.
post
(
url
,
payload
)
end
def
get
(
url
,
mode: :kid
)
connection
=
connection_for
(
url:
url
,
mode:
mode
)
connection
.
get
(
url
)
end
def
download
(
url
,
format
:)
connection
=
connection_for
(
url:
url
,
mode: :download
)
connection
.
get
do
|
request
|
request
.
url
(
url
)
request
.
headers
[
'Accept'
]
=
CONTENT_TYPES
.
fetch
(
format
)
end
end
def
connection_for
(
url
:,
mode
:)
uri
=
URI
(
url
)
endpoint
=
"
#{
uri
.
scheme
}
://
#{
uri
.
hostname
}
:
#{
uri
.
port
}
"
@connections
||=
{}
@connections
[
mode
]
||=
{}
@connections
[
mode
][
endpoint
]
||=
new_acme_connection
(
endpoint:
endpoint
,
mode:
mode
)
end
def
new_acme_connection
(
endpoint
:,
mode
:)
new_connection
(
endpoint:
endpoint
)
do
|
configuration
|
configuration
.
use
Acme
::
Client
::
FaradayMiddleware
,
client:
self
,
mode:
mode
end
end
def
new_connection
(
endpoint
:)
Faraday
.
new
(
endpoint
,
**
@connection_options
)
do
|
configuration
|
if
@bad_nonce_retry
>
0
configuration
.
request
(
:retry
,
max:
@bad_nonce_retry
,
methods:
Faraday
::
Connection
::
METHODS
,
exceptions:
[
Acme
::
Client
::
Error
::
BadNonce
])
end
yield
(
configuration
)
if
block_given?
configuration
.
adapter
Faraday
.
default_adapter
end
end
def
fetch_chain
(
response
,
limit
=
10
)
links
=
response
.
headers
[
'link'
]
if
limit
.
zero?
||
links
.
nil?
||
links
[
'up'
].
nil?
[]
else
issuer
=
connection
.
get
(
links
[
'up'
])
issuer
=
get
(
links
[
'up'
])
[
OpenSSL
::
X509
::
Certificate
.
new
(
issuer
.
body
),
*
fetch_chain
(
issuer
,
limit
-
1
)]
end
end
def
load_directory!
@operation_endpoints
=
if
@directory_uri
response
=
connection
.
get
(
@directory_uri
)
body
=
response
.
body
{
'new-reg'
=>
body
.
fetch
(
'new-reg'
),
'new-authz'
=>
body
.
fetch
(
'new-authz'
),
'new-cert'
=>
body
.
fetch
(
'new-cert'
),
'revoke-cert'
=>
body
.
fetch
(
'revoke-cert'
),
}
else
DIRECTORY_DEFAULT
end
def
endpoint_for
(
key
)
@directory
.
endpoint_for
(
key
)
end
end
core/lib/acme/client/certificate.rb
deleted
100644 → 0
View file @
73f14ea5
class
Acme::Client::Certificate
extend
Forwardable
attr_reader
:x509
,
:x509_chain
,
:request
,
:private_key
,
:url
def_delegators
:x509
,
:to_pem
,
:to_der
def
initialize
(
certificate
,
url
,
chain
,
request
)
@x509
=
certificate
@url
=
url
@x509_chain
=
chain
@request
=
request
end
def
chain_to_pem
x509_chain
.
map
(
&
:to_pem
).
join
end
def
x509_fullchain
[
x509
,
*
x509_chain
]
end
def
fullchain_to_pem
x509_fullchain
.
map
(
&
:to_pem
).
join
end
def
common_name
x509
.
subject
.
to_a
.
find
{
|
name
,
_
,
_
|
name
==
'CN'
}[
1
]
end
end
core/lib/acme/client/certificate_request.rb
View file @
24a14e8d
...
...
@@ -104,8 +104,6 @@ class Acme::Client::CertificateRequest
end
def
add_extension
(
csr
)
return
if
@names
.
size
<=
1
extension
=
OpenSSL
::
X509
::
ExtensionFactory
.
new
.
create_extension
(
'subjectAltName'
,
@names
.
map
{
|
name
|
"DNS:
#{
name
}
"
}.
join
(
', '
),
false
)
...
...
core/lib/acme/client/error.rb
View file @
24a14e8d
class
Acme::Client::Error
<
StandardError
class
NotFound
<
Acme
::
Client
::
Error
;
end
class
BadCSR
<
Acme
::
Client
::
Error
;
end
class
BadNonce
<
Acme
::
Client
::
Error
;
end
class
Connection
<
Acme
::
Client
::
Error
;
end
class
Dnssec
<
Acme
::
Client
::
Error
;
end
class
Malformed
<
Acme
::
Client
::
Error
;
end
class
ServerInternal
<
Acme
::
Client
::
Error
;
end
class
Acme::Tls
<
Acme
::
Client
::
Error
;
end
class
Unauthorized
<
Acme
::
Client
::
Error
;
end
class
UnknownHost
<
Acme
::
Client
::
Error
;
end
class
Timeout
<
Acme
::
Client
::
Error
;
end
class
RateLimited
<
Acme
::
Client
::
Error
;
end
class
RejectedIdentifier
<
Acme
::
Client
::
Error
;
end
class
UnsupportedIdentifier
<
Acme
::
Client
::
Error
;
end
class
ClientError
<
Acme
::
Client
::
Error
;
end
class
InvalidDirectory
<
ClientError
;
end
class
UnsupportedOperation
<
ClientError
;
end
class
UnsupportedChallengeType
<
ClientError
;
end
class
NotFound
<
ClientError
;
end
class
CertificateNotReady
<
ClientError
;
end
class
ServerError
<
Acme
::
Client
::
Error
;
end
class
BadCSR
<
ServerError
;
end
class
BadNonce
<
ServerError
;
end
class
BadSignatureAlgorithm
<
ServerError
;
end
class
InvalidContact
<
ServerError
;
end
class
UnsupportedContact
<
ServerError
;
end
class
ExternalAccountRequired
<
ServerError
;
end
class
AccountDoesNotExist
<
ServerError
;
end
class
Malformed
<
ServerError
;
end
class
RateLimited
<
ServerError
;
end
class
RejectedIdentifier
<
ServerError
;
end
class
ServerInternal
<
ServerError
;
end
class
Unauthorized
<
ServerError
;
end
class
UnsupportedIdentifier
<
ServerError
;
end
class
UserActionRequired
<
ServerError
;
end
class
BadRevocationReason
<
ServerError
;
end
class
Caa
<
ServerError
;
end
class
Dns
<
ServerError
;
end
class
Connection
<
ServerError
;
end
class
Tls
<
ServerError
;
end
class
IncorrectResponse
<
ServerError
;
end
ACME_ERRORS
=
{
'urn:ietf:params:acme:error:badCSR'
=>
BadCSR
,
'urn:ietf:params:acme:error:badNonce'
=>
BadNonce
,
'urn:ietf:params:acme:error:badSignatureAlgorithm'
=>
BadSignatureAlgorithm
,
'urn:ietf:params:acme:error:invalidContact'
=>
InvalidContact
,
'urn:ietf:params:acme:error:unsupportedContact'
=>
UnsupportedContact
,
'urn:ietf:params:acme:error:externalAccountRequired'
=>
ExternalAccountRequired
,
'urn:ietf:params:acme:error:accountDoesNotExist'
=>
AccountDoesNotExist
,
'urn:ietf:params:acme:error:malformed'
=>
Malformed
,
'urn:ietf:params:acme:error:rateLimited'
=>
RateLimited
,
'urn:ietf:params:acme:error:rejectedIdentifier'
=>
RejectedIdentifier
,
'urn:ietf:params:acme:error:serverInternal'
=>
ServerInternal
,
'urn:ietf:params:acme:error:unauthorized'
=>
Unauthorized
,
'urn:ietf:params:acme:error:unsupportedIdentifier'
=>
UnsupportedIdentifier
,
'urn:ietf:params:acme:error:userActionRequired'
=>
UserActionRequired
,
'urn:ietf:params:acme:error:badRevocationReason'
=>
BadRevocationReason
,
'urn:ietf:params:acme:error:caa'
=>
Caa
,
'urn:ietf:params:acme:error:dns'
=>
Dns
,
'urn:ietf:params:acme:error:connection'
=>
Connection
,
'urn:ietf:params:acme:error:tls'
=>
Tls
,
'urn:ietf:params:acme:error:incorrectResponse'
=>
IncorrectResponse
}
end
core/lib/acme/client/faraday_middleware.rb
View file @
24a14e8d
...
...
@@ -3,20 +3,25 @@
class
Acme::Client::FaradayMiddleware
<
Faraday
::
Middleware
attr_reader
:env
,
:response
,
:client
repo_url
=
'https://github.com/unixcharles/acme-client'
USER_AGENT
=
"Acme::Client v
#{
Acme
::
Client
::
VERSION
}
(
#{
repo_url
}
)"
.
freeze
CONTENT_TYPE
=
'application/jose+json'
def
initialize
(
app
,
client
:)
def
initialize
(
app
,
client
:
,
mode
:
)
super
(
app
)
@client
=
client
@mode
=
mode
end
def
call
(
env
)
@env
=
env
@env
[
:request_headers
][
'User-Agent'
]
=
USER_AGENT
@env
.
body
=
client
.
jwk
.
jws
(
header:
{
nonce:
pop_nonce
},
payload:
env
.
body
)
@env
[
:request_headers
][
'User-Agent'
]
=
Acme
::
Client
::
USER_AGENT
@env
[
:request_headers
][
'Content-Type'
]
=
CONTENT_TYPE
if
@env
.
method
!=
:get
@env
.
body
=
client
.
jwk
.
jws
(
header:
jws_header
,
payload:
env
.
body
)
end
@app
.
call
(
env
).
on_complete
{
|
response_env
|
on_complete
(
response_env
)
}
rescue
Faraday
::
TimeoutError
rescue
Faraday
::
TimeoutError
,
Faraday
::
ConnectionFailed
raise
Acme
::
Client
::
Error
::
Timeout
end
...
...
@@ -35,6 +40,12 @@ class Acme::Client::FaradayMiddleware < Faraday::Middleware
private
def
jws_header
headers
=
{
nonce:
pop_nonce
,
url:
env
.
url
.
to_s
}
headers
[
:kid
]
=
client
.
kid
if
@mode
==
:kid
headers
end
def
raise_on_not_found!
raise
Acme
::
Client
::
Error
::
NotFound
,
env
.
url
.
to_s
if
env
.
status
==
404
end
...
...
@@ -52,30 +63,19 @@ class Acme::Client::FaradayMiddleware < Faraday::Middleware
end
def
error_class
if
error_name
&&
!
error_name
.
empty?
&&
Acme
::
Client
::
Error
.
const_defined?
(
error_name
)
Object
.
const_get
(
"Acme::Client::Error::
#{
error_name
}
"
)
else
Acme
::
Client
::
Error
end
Acme
::
Client
::
Error
::
ACME_ERRORS
.
fetch
(
error_name
,
Acme
::
Client
::
Error
)
end
def
error_name
@error_name
||=
begin
return
unless
env
.
body
.
is_a?
(
Hash
)
return
unless
env
.
body
.
key?
(
'type'
)
error_type_to_klass
env
.
body
[
'type'
]
end
end
def
error_type_to_klass
(
type
)
type
.
gsub
(
'urn:acme:error:'
,
''
).
split
(
/[_-]/
).
map
{
|
type_part
|
type_part
[
0
].
upcase
+
type_part
[
1
..-
1
]
}.
join
return
unless
env
.
body
.
is_a?
(
Hash
)
return
unless
env
.
body
.
key?
(
'type'
)
env
.
body
[
'type'
]
end
def
decode_body
content_type
=
env
.
response_headers
[
'Content-Type'
]
content_type
=
env
.
response_headers
[
'Content-Type'
]
.
to_s
if
content_type
==
'application/json'
||
content_type
==
'application/problem+json'
if
content_type
.
start_with?
(
'application/json'
,
'application/problem+json'
)
JSON
.
load
(
env
.
body
)
else
env
.
body
...
...
@@ -97,20 +97,20 @@ class Acme::Client::FaradayMiddleware < Faraday::Middleware
end
def
store_nonce
nonces
<<
env
.
response_headers
[
'replay-nonce'
]
nonce
=
env
.
response_headers
[
'replay-nonce'
]
nonces
<<
nonce
if
nonce
end
def
pop_nonce
if
nonces
.
empty?
get_nonce
else
nonces
.
pop
end
nonces
.
pop
end
def
get_nonce
response
=
Faraday
.
head
(
env
.
url
,
nil
,
'User-Agent'
=>
USER_AGENT
)
response
.
headers
[
'replay-nonce'
]
client
.
get_nonce
end
def
nonces
...
...
core/lib/acme/client/jwk/base.rb
View file @
24a14e8d
...
...
@@ -15,7 +15,7 @@ class Acme::Client::JWK::Base
#
# Returns a JSON String.
def
jws
(
header:
{},
payload:
{})
header
=
jws_header
.
merge
(
header
)
header
=
jws_header
(
header
)
encoded_header
=
Acme
::
Client
::
Util
.
urlsafe_base64
(
header
.
to_json
)
encoded_payload
=
Acme
::
Client
::
Util
.
urlsafe_base64
(
payload
.
to_json
)
...
...
@@ -56,12 +56,13 @@ class Acme::Client::JWK::Base
# typ: - Value for the `typ` field. Default 'JWT'.
#
# Returns a Hash.
def
jws_header
{