itsi-server 0.2.25 → 0.2.27.rc1
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/Cargo.lock +939 -987
- data/Cargo.toml +0 -1
- data/Rakefile +18 -5
- data/ext/itsi_acme/Cargo.toml +2 -1
- data/ext/itsi_acme/src/acceptor.rs +1 -1
- data/ext/itsi_acme/src/acme.rs +31 -3
- data/ext/itsi_acme/src/http_challenge.rs +81 -0
- data/ext/itsi_acme/src/https_helper.rs +3 -1
- data/ext/itsi_acme/src/jose.rs +6 -2
- data/ext/itsi_acme/src/lib.rs +2 -0
- data/ext/itsi_acme/src/resolver.rs +27 -4
- data/ext/itsi_acme/src/state.rs +183 -22
- data/ext/itsi_scheduler/Cargo.toml +1 -1
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +115 -64
- data/ext/itsi_scheduler/src/lib.rs +2 -1
- data/ext/itsi_server/Cargo.lock +2 -2
- data/ext/itsi_server/Cargo.toml +2 -1
- data/ext/itsi_server/src/lib.rs +15 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +9 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +95 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +22 -1
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +100 -0
- data/ext/itsi_server/src/server/binds/listener.rs +9 -24
- data/ext/itsi_server/src/server/binds/tls.rs +372 -67
- data/ext/itsi_server/src/services/itsi_http_service.rs +46 -2
- data/lib/itsi/http_request.rb +10 -0
- data/lib/itsi/server/rack_interface.rb +45 -2
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +24 -0
- metadata +3 -20
- data/vendor/rb-sys-build/.cargo-ok +0 -1
- data/vendor/rb-sys-build/.cargo_vcs_info.json +0 -6
- data/vendor/rb-sys-build/Cargo.lock +0 -294
- data/vendor/rb-sys-build/Cargo.toml +0 -71
- data/vendor/rb-sys-build/Cargo.toml.orig +0 -32
- data/vendor/rb-sys-build/LICENSE-APACHE +0 -190
- data/vendor/rb-sys-build/LICENSE-MIT +0 -21
- data/vendor/rb-sys-build/src/bindings/sanitizer.rs +0 -185
- data/vendor/rb-sys-build/src/bindings/stable_api.rs +0 -247
- data/vendor/rb-sys-build/src/bindings/wrapper.h +0 -71
- data/vendor/rb-sys-build/src/bindings.rs +0 -280
- data/vendor/rb-sys-build/src/cc.rs +0 -421
- data/vendor/rb-sys-build/src/lib.rs +0 -12
- data/vendor/rb-sys-build/src/rb_config/flags.rs +0 -101
- data/vendor/rb-sys-build/src/rb_config/library.rs +0 -132
- data/vendor/rb-sys-build/src/rb_config/search_path.rs +0 -57
- data/vendor/rb-sys-build/src/rb_config.rs +0 -906
- data/vendor/rb-sys-build/src/utils.rs +0 -53
data/Cargo.toml
CHANGED
data/Rakefile
CHANGED
|
@@ -29,6 +29,16 @@ SMOKE_TEST_GLOBS = %w[
|
|
|
29
29
|
test/middleware/string_rewrite.rb
|
|
30
30
|
].freeze
|
|
31
31
|
|
|
32
|
+
def native_build_tasks_requested?
|
|
33
|
+
requested = Rake.application.top_level_tasks
|
|
34
|
+
return true if requested.empty?
|
|
35
|
+
|
|
36
|
+
requested.any? do |task_name|
|
|
37
|
+
task_name == "default" ||
|
|
38
|
+
task_name.start_with?("build", "compile", "cross", "clobber")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
32
42
|
def configure_test_task(task_name, test_globs)
|
|
33
43
|
Minitest::TestTask.create(task_name) do |t|
|
|
34
44
|
t.libs << "test"
|
|
@@ -41,17 +51,20 @@ end
|
|
|
41
51
|
|
|
42
52
|
configure_test_task(:test, ["test/**/*.rb"])
|
|
43
53
|
configure_test_task("test:smoke", SMOKE_TEST_GLOBS)
|
|
54
|
+
configure_test_task("test:acme", ["test/acme/**/*.rb"])
|
|
44
55
|
|
|
45
56
|
task "test:full" => :test
|
|
46
57
|
|
|
47
|
-
|
|
58
|
+
if native_build_tasks_requested?
|
|
59
|
+
require "rb_sys/extensiontask"
|
|
48
60
|
|
|
49
|
-
task build: :compile
|
|
61
|
+
task build: :compile
|
|
50
62
|
|
|
51
|
-
GEMSPEC = Gem::Specification.load("itsi-server.gemspec")
|
|
63
|
+
GEMSPEC = Gem::Specification.load("itsi-server.gemspec")
|
|
52
64
|
|
|
53
|
-
RbSys::ExtensionTask.new("itsi-server", GEMSPEC) do |ext|
|
|
54
|
-
|
|
65
|
+
RbSys::ExtensionTask.new("itsi-server", GEMSPEC) do |ext|
|
|
66
|
+
ext.lib_dir = "lib/itsi/server"
|
|
67
|
+
end
|
|
55
68
|
end
|
|
56
69
|
|
|
57
70
|
task default: %i[compile test rubocop]
|
data/ext/itsi_acme/Cargo.toml
CHANGED
|
@@ -31,13 +31,14 @@ async-trait = "0.1.53"
|
|
|
31
31
|
rustls = { version = "0.23", default-features = false, features = ["ring"] }
|
|
32
32
|
time = "0.3.36" # force the transitive dependency to a more recent minimal version. The build fails with 0.3.20
|
|
33
33
|
|
|
34
|
-
tokio = { version = "1.20.1", default-features = false }
|
|
34
|
+
tokio = { version = "1.20.1", default-features = false, features = ["fs", "io-util", "rt", "sync", "time"] }
|
|
35
35
|
tokio-rustls = { version = "0.26", default-features = false, features = [
|
|
36
36
|
"tls12",
|
|
37
37
|
] }
|
|
38
38
|
reqwest = { version = "0.12", default-features = false, features = [
|
|
39
39
|
"rustls-tls",
|
|
40
40
|
] }
|
|
41
|
+
parking_lot = "0.12"
|
|
41
42
|
|
|
42
43
|
# Axum
|
|
43
44
|
axum-server = { version = "0.7", features = ["tokio-rustls"], optional = true }
|
|
@@ -16,7 +16,7 @@ pub struct AcmeAcceptor {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
impl AcmeAcceptor {
|
|
19
|
-
pub
|
|
19
|
+
pub fn new(resolver: Arc<ResolvesServerCertAcme>) -> Self {
|
|
20
20
|
let mut config = ServerConfig::builder()
|
|
21
21
|
.with_no_client_auth()
|
|
22
22
|
.with_cert_resolver(resolver);
|
data/ext/itsi_acme/src/acme.rs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use std::sync::Arc;
|
|
2
2
|
|
|
3
3
|
use crate::https_helper::{https, HttpsRequestError, Method, Response};
|
|
4
|
-
use crate::jose::{key_authorization_sha256, sign, sign_eab, JoseError};
|
|
4
|
+
use crate::jose::{key_authorization, key_authorization_sha256, sign, sign_eab, JoseError};
|
|
5
5
|
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
|
6
6
|
use base64::Engine;
|
|
7
7
|
use rcgen::{CustomExtension, Error as RcgenError, PKCS_ECDSA_P256_SHA256};
|
|
@@ -191,7 +191,11 @@ impl Account {
|
|
|
191
191
|
None => return Err(AcmeError::NoTlsAlpn01Challenge),
|
|
192
192
|
};
|
|
193
193
|
let mut params = rcgen::CertificateParams::new(vec![domain])?;
|
|
194
|
-
let
|
|
194
|
+
let token = challenge
|
|
195
|
+
.token
|
|
196
|
+
.as_deref()
|
|
197
|
+
.ok_or(AcmeError::MissingChallengeToken)?;
|
|
198
|
+
let key_auth = key_authorization_sha256(&self.key_pair, token)?;
|
|
195
199
|
params.custom_extensions = vec![CustomExtension::new_acme_identifier(key_auth.as_ref())];
|
|
196
200
|
|
|
197
201
|
let key_pair = rcgen::KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256)?;
|
|
@@ -204,6 +208,24 @@ impl Account {
|
|
|
204
208
|
let certified_key = CertifiedKey::new(vec![cert.der().clone()], pk);
|
|
205
209
|
Ok((challenge, certified_key))
|
|
206
210
|
}
|
|
211
|
+
|
|
212
|
+
pub fn http_01<'a>(
|
|
213
|
+
&self,
|
|
214
|
+
challenges: &'a [Challenge],
|
|
215
|
+
) -> Result<(&'a Challenge, String), AcmeError> {
|
|
216
|
+
let challenge = challenges.iter().find(|c| c.typ == ChallengeType::Http01);
|
|
217
|
+
|
|
218
|
+
let challenge = match challenge {
|
|
219
|
+
Some(challenge) => challenge,
|
|
220
|
+
None => return Err(AcmeError::NoHttp01Challenge),
|
|
221
|
+
};
|
|
222
|
+
let token = challenge
|
|
223
|
+
.token
|
|
224
|
+
.as_deref()
|
|
225
|
+
.ok_or(AcmeError::MissingChallengeToken)?;
|
|
226
|
+
|
|
227
|
+
Ok((challenge, key_authorization(&self.key_pair, token)?))
|
|
228
|
+
}
|
|
207
229
|
}
|
|
208
230
|
|
|
209
231
|
#[derive(Debug, Clone, Deserialize)]
|
|
@@ -253,6 +275,8 @@ pub enum ChallengeType {
|
|
|
253
275
|
Dns01,
|
|
254
276
|
#[serde(rename = "tls-alpn-01")]
|
|
255
277
|
TlsAlpn01,
|
|
278
|
+
#[serde(other)]
|
|
279
|
+
Unknown,
|
|
256
280
|
}
|
|
257
281
|
|
|
258
282
|
#[derive(Debug, Deserialize)]
|
|
@@ -305,7 +329,7 @@ pub struct Challenge {
|
|
|
305
329
|
#[serde(rename = "type")]
|
|
306
330
|
pub typ: ChallengeType,
|
|
307
331
|
pub url: String,
|
|
308
|
-
pub token: String
|
|
332
|
+
pub token: Option<String>,
|
|
309
333
|
pub error: Option<Problem>,
|
|
310
334
|
}
|
|
311
335
|
|
|
@@ -335,6 +359,10 @@ pub enum AcmeError {
|
|
|
335
359
|
Crypto(#[from] Unspecified),
|
|
336
360
|
#[error("acme service response is missing {0} header")]
|
|
337
361
|
MissingHeader(&'static str),
|
|
362
|
+
#[error("selected challenge is missing token")]
|
|
363
|
+
MissingChallengeToken,
|
|
364
|
+
#[error("no http-01 challenge found")]
|
|
365
|
+
NoHttp01Challenge,
|
|
338
366
|
#[error("no tls-alpn-01 challenge found")]
|
|
339
367
|
NoTlsAlpn01Challenge,
|
|
340
368
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
use parking_lot::RwLock;
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
use std::sync::Arc;
|
|
4
|
+
|
|
5
|
+
const ACME_CHALLENGE_PREFIX: &str = "/.well-known/acme-challenge/";
|
|
6
|
+
|
|
7
|
+
#[derive(Debug, Clone, Default)]
|
|
8
|
+
pub struct Http01Handler {
|
|
9
|
+
challenges: Arc<RwLock<HashMap<String, String>>>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
impl Http01Handler {
|
|
13
|
+
pub fn new() -> Self {
|
|
14
|
+
Self::default()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
pub fn add_challenge(&self, token: String, key_authorization: String) {
|
|
18
|
+
self.challenges.write().insert(token, key_authorization);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pub fn remove_challenge(&self, token: &str) {
|
|
22
|
+
self.challenges.write().remove(token);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pub fn handle_challenge_request(&self, path: &str) -> Option<String> {
|
|
26
|
+
let token = path.strip_prefix(ACME_CHALLENGE_PREFIX)?;
|
|
27
|
+
if token.is_empty()
|
|
28
|
+
|| !token
|
|
29
|
+
.chars()
|
|
30
|
+
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
|
|
31
|
+
{
|
|
32
|
+
return None;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
self.challenges.read().get(token).cloned()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[cfg(test)]
|
|
40
|
+
mod tests {
|
|
41
|
+
use super::Http01Handler;
|
|
42
|
+
|
|
43
|
+
#[test]
|
|
44
|
+
fn serves_registered_key_authorization() {
|
|
45
|
+
let handler = Http01Handler::new();
|
|
46
|
+
handler.add_challenge("token_123".to_string(), "token_123.thumbprint".to_string());
|
|
47
|
+
|
|
48
|
+
assert_eq!(
|
|
49
|
+
handler.handle_challenge_request("/.well-known/acme-challenge/token_123"),
|
|
50
|
+
Some("token_123.thumbprint".to_string())
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn rejects_invalid_paths_and_tokens() {
|
|
56
|
+
let handler = Http01Handler::new();
|
|
57
|
+
handler.add_challenge("token_123".to_string(), "token_123.thumbprint".to_string());
|
|
58
|
+
|
|
59
|
+
assert_eq!(handler.handle_challenge_request("/not-acme"), None);
|
|
60
|
+
assert_eq!(
|
|
61
|
+
handler.handle_challenge_request("/.well-known/acme-challenge/"),
|
|
62
|
+
None
|
|
63
|
+
);
|
|
64
|
+
assert_eq!(
|
|
65
|
+
handler.handle_challenge_request("/.well-known/acme-challenge/invalid token"),
|
|
66
|
+
None
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[test]
|
|
71
|
+
fn removes_tokens() {
|
|
72
|
+
let handler = Http01Handler::new();
|
|
73
|
+
handler.add_challenge("token_123".to_string(), "token_123.thumbprint".to_string());
|
|
74
|
+
handler.remove_challenge("token_123");
|
|
75
|
+
|
|
76
|
+
assert_eq!(
|
|
77
|
+
handler.handle_challenge_request("/.well-known/acme-challenge/token_123"),
|
|
78
|
+
None
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -30,7 +30,9 @@ pub(crate) async fn https(
|
|
|
30
30
|
let client = reqwest::ClientBuilder::new()
|
|
31
31
|
.use_preconfigured_tls(client_config.clone())
|
|
32
32
|
.build()?;
|
|
33
|
-
let mut request = client
|
|
33
|
+
let mut request = client
|
|
34
|
+
.request(method, url.as_ref())
|
|
35
|
+
.header("User-Agent", concat!("itsi-acme/", env!("CARGO_PKG_VERSION")));
|
|
34
36
|
if let Some(body) = body {
|
|
35
37
|
request = request
|
|
36
38
|
.body(body)
|
data/ext/itsi_acme/src/jose.rs
CHANGED
|
@@ -53,11 +53,15 @@ pub(crate) fn key_authorization_sha256(
|
|
|
53
53
|
key: &EcdsaKeyPair,
|
|
54
54
|
token: &str,
|
|
55
55
|
) -> Result<Digest, JoseError> {
|
|
56
|
-
let
|
|
57
|
-
let key_authorization = format!("{}.{}", token, jwk.thumb_sha256_base64()?);
|
|
56
|
+
let key_authorization = key_authorization(key, token)?;
|
|
58
57
|
Ok(digest(&SHA256, key_authorization.as_bytes()))
|
|
59
58
|
}
|
|
60
59
|
|
|
60
|
+
pub(crate) fn key_authorization(key: &EcdsaKeyPair, token: &str) -> Result<String, JoseError> {
|
|
61
|
+
let jwk = Jwk::new(key);
|
|
62
|
+
Ok(format!("{}.{}", token, jwk.thumb_sha256_base64()?))
|
|
63
|
+
}
|
|
64
|
+
|
|
61
65
|
#[derive(Serialize)]
|
|
62
66
|
pub(crate) struct Body {
|
|
63
67
|
protected: String,
|
data/ext/itsi_acme/src/lib.rs
CHANGED
|
@@ -126,6 +126,7 @@ pub mod axum;
|
|
|
126
126
|
mod cache;
|
|
127
127
|
pub mod caches;
|
|
128
128
|
mod config;
|
|
129
|
+
mod http_challenge;
|
|
129
130
|
mod https_helper;
|
|
130
131
|
mod incoming;
|
|
131
132
|
mod jose;
|
|
@@ -137,6 +138,7 @@ pub use tokio_rustls;
|
|
|
137
138
|
pub use acceptor::*;
|
|
138
139
|
pub use cache::*;
|
|
139
140
|
pub use config::*;
|
|
141
|
+
pub use http_challenge::*;
|
|
140
142
|
pub use incoming::*;
|
|
141
143
|
pub use resolver::*;
|
|
142
144
|
pub use state::*;
|
|
@@ -13,24 +13,40 @@ pub struct ResolvesServerCertAcme {
|
|
|
13
13
|
#[derive(Debug)]
|
|
14
14
|
struct Inner {
|
|
15
15
|
cert: Option<Arc<CertifiedKey>>,
|
|
16
|
+
certs: BTreeMap<String, Arc<CertifiedKey>>,
|
|
16
17
|
auth_keys: BTreeMap<String, Arc<CertifiedKey>>,
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
impl ResolvesServerCertAcme {
|
|
20
|
-
pub
|
|
21
|
+
pub fn new() -> Arc<Self> {
|
|
21
22
|
Arc::new(Self {
|
|
22
23
|
inner: Mutex::new(Inner {
|
|
23
24
|
cert: None,
|
|
25
|
+
certs: Default::default(),
|
|
24
26
|
auth_keys: Default::default(),
|
|
25
27
|
}),
|
|
26
28
|
})
|
|
27
29
|
}
|
|
28
|
-
pub
|
|
30
|
+
pub fn set_cert(&self, cert: Arc<CertifiedKey>) {
|
|
29
31
|
self.inner.lock().unwrap().cert = Some(cert);
|
|
30
32
|
}
|
|
31
|
-
pub
|
|
33
|
+
pub fn set_cert_for_domain(&self, domain: String, cert: Arc<CertifiedKey>) {
|
|
34
|
+
let mut inner = self.inner.lock().unwrap();
|
|
35
|
+
if inner.cert.is_none() {
|
|
36
|
+
inner.cert = Some(cert.clone());
|
|
37
|
+
}
|
|
38
|
+
inner.certs.insert(domain, cert);
|
|
39
|
+
}
|
|
40
|
+
pub fn remove_cert_for_domain(&self, domain: &str) {
|
|
41
|
+
self.inner.lock().unwrap().certs.remove(domain);
|
|
42
|
+
}
|
|
43
|
+
pub fn set_auth_key(&self, domain: String, cert: Arc<CertifiedKey>) {
|
|
32
44
|
self.inner.lock().unwrap().auth_keys.insert(domain, cert);
|
|
33
45
|
}
|
|
46
|
+
|
|
47
|
+
pub fn remove_auth_key(&self, domain: &str) {
|
|
48
|
+
self.inner.lock().unwrap().auth_keys.remove(domain);
|
|
49
|
+
}
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
impl ResolvesServerCert for ResolvesServerCertAcme {
|
|
@@ -53,7 +69,14 @@ impl ResolvesServerCert for ResolvesServerCertAcme {
|
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
71
|
} else {
|
|
56
|
-
self.inner.lock().unwrap()
|
|
72
|
+
let inner = self.inner.lock().unwrap();
|
|
73
|
+
match client_hello.server_name() {
|
|
74
|
+
Some(domain) => {
|
|
75
|
+
let domain = AsRef::<str>::as_ref(&domain);
|
|
76
|
+
inner.certs.get(domain).cloned().or_else(|| inner.cert.clone())
|
|
77
|
+
}
|
|
78
|
+
None => inner.cert.clone(),
|
|
79
|
+
}
|
|
57
80
|
}
|
|
58
81
|
}
|
|
59
82
|
}
|
data/ext/itsi_acme/src/state.rs
CHANGED
|
@@ -20,8 +20,9 @@ use x509_parser::parse_x509_certificate;
|
|
|
20
20
|
|
|
21
21
|
use crate::acceptor::AcmeAcceptor;
|
|
22
22
|
use crate::acme::{
|
|
23
|
-
Account, AcmeError, Auth, AuthStatus, Directory, Identifier, Order, OrderStatus,
|
|
23
|
+
Account, AcmeError, Auth, AuthStatus, Challenge, Directory, Identifier, Order, OrderStatus,
|
|
24
24
|
};
|
|
25
|
+
use crate::http_challenge::Http01Handler;
|
|
25
26
|
use crate::{AcmeConfig, Incoming, ResolvesServerCertAcme};
|
|
26
27
|
|
|
27
28
|
type Timer = std::pin::Pin<Box<Sleep>>;
|
|
@@ -36,6 +37,9 @@ pub struct AcmeState<EC: Debug = Infallible, EA: Debug = EC> {
|
|
|
36
37
|
config: Arc<AcmeConfig<EC, EA>>,
|
|
37
38
|
resolver: Arc<ResolvesServerCertAcme>,
|
|
38
39
|
account_key: Option<Vec<u8>>,
|
|
40
|
+
http01_handler: Arc<Http01Handler>,
|
|
41
|
+
http01_enabled: bool,
|
|
42
|
+
managed_domain: Option<String>,
|
|
39
43
|
|
|
40
44
|
early_action: Option<BoxFuture<Event<EC, EA>>>,
|
|
41
45
|
load_cert: Option<BoxFuture<Result<Option<Vec<u8>>, EC>>>,
|
|
@@ -128,12 +132,29 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
|
|
|
128
132
|
pub fn resolver(&self) -> Arc<ResolvesServerCertAcme> {
|
|
129
133
|
self.resolver.clone()
|
|
130
134
|
}
|
|
135
|
+
pub fn http01_handler(&self) -> Arc<Http01Handler> {
|
|
136
|
+
self.http01_handler.clone()
|
|
137
|
+
}
|
|
138
|
+
pub fn set_http01_enabled(&mut self, enabled: bool) {
|
|
139
|
+
self.http01_enabled = enabled;
|
|
140
|
+
}
|
|
131
141
|
pub fn new(config: AcmeConfig<EC, EA>) -> Self {
|
|
142
|
+
Self::new_with_resolver(config, ResolvesServerCertAcme::new(), Arc::new(Http01Handler::new()), None)
|
|
143
|
+
}
|
|
144
|
+
pub fn new_with_resolver(
|
|
145
|
+
config: AcmeConfig<EC, EA>,
|
|
146
|
+
resolver: Arc<ResolvesServerCertAcme>,
|
|
147
|
+
http01_handler: Arc<Http01Handler>,
|
|
148
|
+
managed_domain: Option<String>,
|
|
149
|
+
) -> Self {
|
|
132
150
|
let config = Arc::new(config);
|
|
133
151
|
Self {
|
|
134
152
|
config: config.clone(),
|
|
135
|
-
resolver
|
|
153
|
+
resolver,
|
|
136
154
|
account_key: None,
|
|
155
|
+
http01_handler,
|
|
156
|
+
http01_enabled: false,
|
|
157
|
+
managed_domain,
|
|
137
158
|
early_action: None,
|
|
138
159
|
load_cert: Some(Box::pin({
|
|
139
160
|
let config = config.clone();
|
|
@@ -195,7 +216,11 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
|
|
|
195
216
|
}
|
|
196
217
|
}
|
|
197
218
|
};
|
|
198
|
-
|
|
219
|
+
let cert = Arc::new(cert);
|
|
220
|
+
match self.managed_domain.as_ref() {
|
|
221
|
+
Some(domain) => self.resolver.set_cert_for_domain(domain.clone(), cert),
|
|
222
|
+
None => self.resolver.set_cert(cert),
|
|
223
|
+
}
|
|
199
224
|
let wait_duration = (validity[1] - (validity[1] - validity[0]) / 3 - Utc::now())
|
|
200
225
|
.max(chrono::Duration::zero())
|
|
201
226
|
.to_std()
|
|
@@ -220,6 +245,8 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
|
|
|
220
245
|
async fn order(
|
|
221
246
|
config: Arc<AcmeConfig<EC, EA>>,
|
|
222
247
|
resolver: Arc<ResolvesServerCertAcme>,
|
|
248
|
+
http01_handler: Arc<Http01Handler>,
|
|
249
|
+
http01_enabled: bool,
|
|
223
250
|
key_pair: Vec<u8>,
|
|
224
251
|
) -> Result<Vec<u8>, OrderError> {
|
|
225
252
|
let directory = Directory::discover(&config.client_config, &config.directory_url).await?;
|
|
@@ -242,10 +269,16 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
|
|
|
242
269
|
loop {
|
|
243
270
|
match order.status {
|
|
244
271
|
OrderStatus::Pending => {
|
|
245
|
-
let auth_futures = order
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
272
|
+
let auth_futures = order.authorizations.iter().map(|url| {
|
|
273
|
+
Self::authorize(
|
|
274
|
+
&config,
|
|
275
|
+
&resolver,
|
|
276
|
+
&http01_handler,
|
|
277
|
+
http01_enabled,
|
|
278
|
+
&account,
|
|
279
|
+
url,
|
|
280
|
+
)
|
|
281
|
+
});
|
|
249
282
|
try_join_all(auth_futures).await?;
|
|
250
283
|
log::info!("completed all authorizations");
|
|
251
284
|
order = account.order(&config.client_config, &order_url).await?;
|
|
@@ -289,40 +322,147 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
|
|
|
289
322
|
async fn authorize(
|
|
290
323
|
config: &AcmeConfig<EC, EA>,
|
|
291
324
|
resolver: &ResolvesServerCertAcme,
|
|
325
|
+
http01_handler: &Http01Handler,
|
|
326
|
+
http01_enabled: bool,
|
|
292
327
|
account: &Account,
|
|
293
328
|
url: &String,
|
|
294
329
|
) -> Result<(), OrderError> {
|
|
295
330
|
let auth = account.auth(&config.client_config, url).await?;
|
|
296
|
-
let (domain,
|
|
331
|
+
let (domain, challenges) = match auth.status {
|
|
297
332
|
AuthStatus::Pending => {
|
|
298
333
|
let Identifier::Dns(domain) = auth.identifier;
|
|
299
|
-
|
|
334
|
+
(domain, auth.challenges)
|
|
335
|
+
}
|
|
336
|
+
AuthStatus::Valid => return Ok(()),
|
|
337
|
+
_ => return Err(OrderError::BadAuth(auth)),
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
log::info!("trigger challenge for {}", &domain);
|
|
341
|
+
|
|
342
|
+
let primary = if http01_enabled {
|
|
343
|
+
ChallengeKind::Http01
|
|
344
|
+
} else {
|
|
345
|
+
ChallengeKind::TlsAlpn01
|
|
346
|
+
};
|
|
347
|
+
let secondary = match primary {
|
|
348
|
+
ChallengeKind::Http01 => ChallengeKind::TlsAlpn01,
|
|
349
|
+
ChallengeKind::TlsAlpn01 => ChallengeKind::Http01,
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
match Self::attempt_authorization(
|
|
353
|
+
config,
|
|
354
|
+
resolver,
|
|
355
|
+
http01_handler,
|
|
356
|
+
account,
|
|
357
|
+
&domain,
|
|
358
|
+
url,
|
|
359
|
+
&challenges,
|
|
360
|
+
primary,
|
|
361
|
+
)
|
|
362
|
+
.await?
|
|
363
|
+
{
|
|
364
|
+
AttemptOutcome::Validated => return Ok(()),
|
|
365
|
+
AttemptOutcome::Unavailable | AttemptOutcome::RetryableFailure => {}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
match Self::attempt_authorization(
|
|
369
|
+
config,
|
|
370
|
+
resolver,
|
|
371
|
+
http01_handler,
|
|
372
|
+
account,
|
|
373
|
+
&domain,
|
|
374
|
+
url,
|
|
375
|
+
&challenges,
|
|
376
|
+
secondary,
|
|
377
|
+
)
|
|
378
|
+
.await?
|
|
379
|
+
{
|
|
380
|
+
AttemptOutcome::Validated => Ok(()),
|
|
381
|
+
AttemptOutcome::Unavailable | AttemptOutcome::RetryableFailure => {
|
|
382
|
+
Err(OrderError::TooManyAttemptsAuth(domain))
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async fn attempt_authorization(
|
|
388
|
+
config: &AcmeConfig<EC, EA>,
|
|
389
|
+
resolver: &ResolvesServerCertAcme,
|
|
390
|
+
http01_handler: &Http01Handler,
|
|
391
|
+
account: &Account,
|
|
392
|
+
domain: &str,
|
|
393
|
+
auth_url: &str,
|
|
394
|
+
challenges: &[Challenge],
|
|
395
|
+
kind: ChallengeKind,
|
|
396
|
+
) -> Result<AttemptOutcome, OrderError> {
|
|
397
|
+
match kind {
|
|
398
|
+
ChallengeKind::TlsAlpn01 => {
|
|
300
399
|
let (challenge, auth_key) =
|
|
301
|
-
account.tls_alpn_01(
|
|
302
|
-
|
|
400
|
+
match account.tls_alpn_01(challenges, domain.to_string()) {
|
|
401
|
+
Ok(value) => value,
|
|
402
|
+
Err(AcmeError::NoTlsAlpn01Challenge) => {
|
|
403
|
+
return Ok(AttemptOutcome::Unavailable);
|
|
404
|
+
}
|
|
405
|
+
Err(error) => return Err(OrderError::Acme(error)),
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
resolver.set_auth_key(domain.to_string(), Arc::new(auth_key));
|
|
303
409
|
account
|
|
304
410
|
.challenge(&config.client_config, &challenge.url)
|
|
305
411
|
.await?;
|
|
306
|
-
|
|
412
|
+
let outcome =
|
|
413
|
+
Self::poll_authorization(config, account, domain, auth_url, &challenge.url)
|
|
414
|
+
.await?;
|
|
415
|
+
resolver.remove_auth_key(domain);
|
|
416
|
+
Ok(outcome)
|
|
307
417
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
418
|
+
ChallengeKind::Http01 => {
|
|
419
|
+
let (challenge, key_authorization) = match account.http_01(challenges) {
|
|
420
|
+
Ok(value) => value,
|
|
421
|
+
Err(AcmeError::NoHttp01Challenge) => return Ok(AttemptOutcome::Unavailable),
|
|
422
|
+
Err(error) => return Err(OrderError::Acme(error)),
|
|
423
|
+
};
|
|
424
|
+
let token = challenge
|
|
425
|
+
.token
|
|
426
|
+
.clone()
|
|
427
|
+
.ok_or(OrderError::Acme(AcmeError::MissingChallengeToken))?;
|
|
428
|
+
|
|
429
|
+
http01_handler.add_challenge(token.clone(), key_authorization);
|
|
430
|
+
account
|
|
431
|
+
.challenge(&config.client_config, &challenge.url)
|
|
432
|
+
.await?;
|
|
433
|
+
let outcome =
|
|
434
|
+
Self::poll_authorization(config, account, domain, auth_url, &challenge.url)
|
|
435
|
+
.await?;
|
|
436
|
+
http01_handler.remove_challenge(&token);
|
|
437
|
+
Ok(outcome)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async fn poll_authorization(
|
|
443
|
+
config: &AcmeConfig<EC, EA>,
|
|
444
|
+
account: &Account,
|
|
445
|
+
domain: &str,
|
|
446
|
+
auth_url: &str,
|
|
447
|
+
challenge_url: &str,
|
|
448
|
+
) -> Result<AttemptOutcome, OrderError> {
|
|
311
449
|
for i in 0u64..5 {
|
|
312
450
|
after(Duration::from_secs(1u64 << i)).await;
|
|
313
|
-
let auth = account.auth(&config.client_config,
|
|
451
|
+
let auth = account.auth(&config.client_config, auth_url).await?;
|
|
314
452
|
match auth.status {
|
|
315
453
|
AuthStatus::Pending => {
|
|
316
|
-
log::info!("authorization for {} still pending",
|
|
454
|
+
log::info!("authorization for {} still pending", domain);
|
|
317
455
|
account
|
|
318
|
-
.challenge(&config.client_config,
|
|
319
|
-
.await
|
|
456
|
+
.challenge(&config.client_config, challenge_url)
|
|
457
|
+
.await?;
|
|
320
458
|
}
|
|
321
|
-
AuthStatus::Valid => return Ok(
|
|
459
|
+
AuthStatus::Valid => return Ok(AttemptOutcome::Validated),
|
|
460
|
+
AuthStatus::Invalid => return Ok(AttemptOutcome::RetryableFailure),
|
|
322
461
|
_ => return Err(OrderError::BadAuth(auth)),
|
|
323
462
|
}
|
|
324
463
|
}
|
|
325
|
-
|
|
464
|
+
|
|
465
|
+
Ok(AttemptOutcome::RetryableFailure)
|
|
326
466
|
}
|
|
327
467
|
fn poll_next_infinite(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Event<EC, EA>> {
|
|
328
468
|
loop {
|
|
@@ -408,13 +548,34 @@ impl<EC: 'static + Debug, EA: 'static + Debug> AcmeState<EC, EA> {
|
|
|
408
548
|
};
|
|
409
549
|
let config = self.config.clone();
|
|
410
550
|
let resolver = self.resolver.clone();
|
|
551
|
+
let http01_handler = self.http01_handler.clone();
|
|
552
|
+
let http01_enabled = self.http01_enabled;
|
|
411
553
|
self.order = Some(Box::pin({
|
|
412
|
-
Self::order(
|
|
554
|
+
Self::order(
|
|
555
|
+
config.clone(),
|
|
556
|
+
resolver.clone(),
|
|
557
|
+
http01_handler,
|
|
558
|
+
http01_enabled,
|
|
559
|
+
account_key,
|
|
560
|
+
)
|
|
413
561
|
}));
|
|
414
562
|
}
|
|
415
563
|
}
|
|
416
564
|
}
|
|
417
565
|
|
|
566
|
+
#[derive(Clone, Copy, Debug)]
|
|
567
|
+
enum ChallengeKind {
|
|
568
|
+
Http01,
|
|
569
|
+
TlsAlpn01,
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
573
|
+
enum AttemptOutcome {
|
|
574
|
+
Validated,
|
|
575
|
+
RetryableFailure,
|
|
576
|
+
Unavailable,
|
|
577
|
+
}
|
|
578
|
+
|
|
418
579
|
impl<EC: 'static + Debug, EA: 'static + Debug> Stream for AcmeState<EC, EA> {
|
|
419
580
|
type Item = Event<EC, EA>;
|
|
420
581
|
|