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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +939 -987
  3. data/Cargo.toml +0 -1
  4. data/Rakefile +18 -5
  5. data/ext/itsi_acme/Cargo.toml +2 -1
  6. data/ext/itsi_acme/src/acceptor.rs +1 -1
  7. data/ext/itsi_acme/src/acme.rs +31 -3
  8. data/ext/itsi_acme/src/http_challenge.rs +81 -0
  9. data/ext/itsi_acme/src/https_helper.rs +3 -1
  10. data/ext/itsi_acme/src/jose.rs +6 -2
  11. data/ext/itsi_acme/src/lib.rs +2 -0
  12. data/ext/itsi_acme/src/resolver.rs +27 -4
  13. data/ext/itsi_acme/src/state.rs +183 -22
  14. data/ext/itsi_scheduler/Cargo.toml +1 -1
  15. data/ext/itsi_scheduler/src/itsi_scheduler.rs +115 -64
  16. data/ext/itsi_scheduler/src/lib.rs +2 -1
  17. data/ext/itsi_server/Cargo.lock +2 -2
  18. data/ext/itsi_server/Cargo.toml +2 -1
  19. data/ext/itsi_server/src/lib.rs +15 -0
  20. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +9 -0
  21. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +95 -0
  22. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +22 -1
  23. data/ext/itsi_server/src/ruby_types/itsi_server.rs +100 -0
  24. data/ext/itsi_server/src/server/binds/listener.rs +9 -24
  25. data/ext/itsi_server/src/server/binds/tls.rs +372 -67
  26. data/ext/itsi_server/src/services/itsi_http_service.rs +46 -2
  27. data/lib/itsi/http_request.rb +10 -0
  28. data/lib/itsi/server/rack_interface.rb +45 -2
  29. data/lib/itsi/server/version.rb +1 -1
  30. data/lib/itsi/server.rb +24 -0
  31. metadata +3 -20
  32. data/vendor/rb-sys-build/.cargo-ok +0 -1
  33. data/vendor/rb-sys-build/.cargo_vcs_info.json +0 -6
  34. data/vendor/rb-sys-build/Cargo.lock +0 -294
  35. data/vendor/rb-sys-build/Cargo.toml +0 -71
  36. data/vendor/rb-sys-build/Cargo.toml.orig +0 -32
  37. data/vendor/rb-sys-build/LICENSE-APACHE +0 -190
  38. data/vendor/rb-sys-build/LICENSE-MIT +0 -21
  39. data/vendor/rb-sys-build/src/bindings/sanitizer.rs +0 -185
  40. data/vendor/rb-sys-build/src/bindings/stable_api.rs +0 -247
  41. data/vendor/rb-sys-build/src/bindings/wrapper.h +0 -71
  42. data/vendor/rb-sys-build/src/bindings.rs +0 -280
  43. data/vendor/rb-sys-build/src/cc.rs +0 -421
  44. data/vendor/rb-sys-build/src/lib.rs +0 -12
  45. data/vendor/rb-sys-build/src/rb_config/flags.rs +0 -101
  46. data/vendor/rb-sys-build/src/rb_config/library.rs +0 -132
  47. data/vendor/rb-sys-build/src/rb_config/search_path.rs +0 -57
  48. data/vendor/rb-sys-build/src/rb_config.rs +0 -906
  49. data/vendor/rb-sys-build/src/utils.rs +0 -53
data/Cargo.toml CHANGED
@@ -8,4 +8,3 @@ resolver = "2"
8
8
 
9
9
  [patch.crates-io]
10
10
  magnus = { git = "https://github.com/matsadler/magnus.git", rev = "1ed232edb2b75a2eed9b1def34ad57e55c411a5c" }
11
- rb-sys-build = { path = "vendor/rb-sys-build" }
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
- require "rb_sys/extensiontask"
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
- ext.lib_dir = "lib/itsi/server"
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]
@@ -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(crate) fn new(resolver: Arc<ResolvesServerCertAcme>) -> Self {
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);
@@ -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 key_auth = key_authorization_sha256(&self.key_pair, &challenge.token)?;
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.request(method, url.as_ref());
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)
@@ -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 jwk = Jwk::new(key);
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,
@@ -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(crate) fn new() -> Arc<Self> {
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(crate) fn set_cert(&self, cert: Arc<CertifiedKey>) {
30
+ pub fn set_cert(&self, cert: Arc<CertifiedKey>) {
29
31
  self.inner.lock().unwrap().cert = Some(cert);
30
32
  }
31
- pub(crate) fn set_auth_key(&self, domain: String, cert: Arc<CertifiedKey>) {
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().cert.clone()
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
  }
@@ -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: ResolvesServerCertAcme::new(),
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
- self.resolver.set_cert(Arc::new(cert));
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
- .authorizations
247
- .iter()
248
- .map(|url| Self::authorize(&config, &resolver, &account, url));
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, challenge_url) = match auth.status {
331
+ let (domain, challenges) = match auth.status {
297
332
  AuthStatus::Pending => {
298
333
  let Identifier::Dns(domain) = auth.identifier;
299
- log::info!("trigger challenge for {}", &domain);
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(&auth.challenges, domain.clone())?;
302
- resolver.set_auth_key(domain.clone(), Arc::new(auth_key));
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
- (domain, challenge.url.clone())
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
- AuthStatus::Valid => return Ok(()),
309
- _ => return Err(OrderError::BadAuth(auth)),
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, url).await?;
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", &domain);
454
+ log::info!("authorization for {} still pending", domain);
317
455
  account
318
- .challenge(&config.client_config, &challenge_url)
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
- Err(OrderError::TooManyAttemptsAuth(domain))
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(config.clone(), resolver.clone(), account_key)
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
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-scheduler"
3
- version = "0.2.25"
3
+ version = "0.2.27-rc1"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"