From: Manuel Federanko <m.federanko@proxmox.com>
To: pbs-devel@lists.proxmox.com, pdm-devel@lists.proxmox.com
Subject: [PATCH proxmox 1/7] acme: client: add methods to fetch renewal information.
Date: Thu, 25 Jun 2026 16:13:31 +0200 [thread overview]
Message-ID: <20260625141337.181684-2-m.federanko@proxmox.com> (raw)
In-Reply-To: <20260625141337.181684-1-m.federanko@proxmox.com>
Add new structs representing the renewal information returned by the
server.
Introduce a method on the client that fetches the renewal information
from the server. A helper method computes the certificate ID passed to
this method.
Signed-off-by: Manuel Federanko <m.federanko@proxmox.com>
---
proxmox-acme-api/src/certificate_helpers.rs | 30 +++++++++++++++++++
proxmox-acme-api/src/lib.rs | 5 +++-
proxmox-acme/src/async_client.rs | 31 +++++++++++++++++++
proxmox-acme/src/directory.rs | 8 +++++
proxmox-acme/src/lib.rs | 3 ++
proxmox-acme/src/renewal.rs | 33 +++++++++++++++++++++
6 files changed, 109 insertions(+), 1 deletion(-)
create mode 100644 proxmox-acme/src/renewal.rs
diff --git a/proxmox-acme-api/src/certificate_helpers.rs b/proxmox-acme-api/src/certificate_helpers.rs
index 3921b18e..7dc06c2d 100644
--- a/proxmox-acme-api/src/certificate_helpers.rs
+++ b/proxmox-acme-api/src/certificate_helpers.rs
@@ -31,6 +31,36 @@ pub struct OrderedCertificate {
pub private_key_pem: Vec<u8>,
}
+pub fn compute_ari_certificate_id(cert: &openssl::x509::X509) -> Option<String> {
+ let authority_key_identifier = match cert.authority_key_id() {
+ Some(v) => v.as_slice(),
+ None => return None,
+ };
+ let mut serial_number = (match cert.serial_number().to_bn() {
+ Ok(v) => v,
+ Err(_) => return None,
+ })
+ .to_vec();
+ if !serial_number.is_empty() && (serial_number[0] & 0x80) > 0 {
+ // check for negative numbers and prepend leading 0
+ serial_number.insert(0, 0);
+ }
+
+ let authority_key_identifier = proxmox_base64::url::encode_no_pad(authority_key_identifier);
+ let serial_number = proxmox_base64::url::encode_no_pad(serial_number);
+ Some(format!("{authority_key_identifier}.{serial_number}"))
+}
+
+pub async fn get_renewal_info(
+ acme_config: &AcmeConfig,
+ certificate_id: &str,
+) -> Result<Option<proxmox_acme::renewal::RenewalInformation>, Error> {
+ let mut acme = super::account_config::load_account_config(&acme_config.account)
+ .await?
+ .client();
+ acme.get_renewal_info(certificate_id).await
+}
+
pub async fn order_certificate(
worker: Arc<WorkerTask>,
acme_config: &AcmeConfig,
diff --git a/proxmox-acme-api/src/lib.rs b/proxmox-acme-api/src/lib.rs
index c315d137..89a5e9a2 100644
--- a/proxmox-acme-api/src/lib.rs
+++ b/proxmox-acme-api/src/lib.rs
@@ -45,7 +45,10 @@ pub(crate) mod acme_plugin;
#[cfg(feature = "impl")]
mod certificate_helpers;
#[cfg(feature = "impl")]
-pub use certificate_helpers::{create_self_signed_cert, order_certificate, revoke_certificate};
+pub use certificate_helpers::{
+ compute_ari_certificate_id, create_self_signed_cert, get_renewal_info, order_certificate,
+ revoke_certificate,
+};
#[cfg(feature = "impl")]
pub mod completion {
diff --git a/proxmox-acme/src/async_client.rs b/proxmox-acme/src/async_client.rs
index bba92023..6670bc24 100644
--- a/proxmox-acme/src/async_client.rs
+++ b/proxmox-acme/src/async_client.rs
@@ -335,6 +335,37 @@ impl AcmeClient {
}
}
+ /// Get the renewal information for a certificate
+ /// Returns None if the server does not support ARI
+ pub async fn get_renewal_info(
+ &mut self,
+ certificate_id: &str, // computed according to rfc9773 section 4.1
+ ) -> Result<Option<crate::renewal::RenewalInformation>, anyhow::Error> {
+ let directory = self.directory().await?;
+ if directory.renewal_info_url().is_none() {
+ return Ok(None);
+ }
+ let url = format!(
+ "{}/{}",
+ directory.renewal_info_url().unwrap(),
+ certificate_id,
+ );
+ let request = crate::request::Request {
+ url,
+ method: "GET",
+ content_type: crate::request::JSON_CONTENT_TYPE,
+ body: "".into(),
+ expected: &[crate::request::http_status::OK],
+ };
+ match Self::execute(&mut self.http_client, request, &mut self.nonce).await {
+ Ok(response) => {
+ let data: crate::renewal::RenewalInformationData = response.json()?;
+ Ok(Some(crate::renewal::RenewalInformation { data }))
+ }
+ Err(err) => Err(err.into()),
+ }
+ }
+
fn need_account(account: &Option<Account>) -> Result<&Account, anyhow::Error> {
account
.as_ref()
diff --git a/proxmox-acme/src/directory.rs b/proxmox-acme/src/directory.rs
index b940901a..bbce4da5 100644
--- a/proxmox-acme/src/directory.rs
+++ b/proxmox-acme/src/directory.rs
@@ -38,6 +38,10 @@ pub struct DirectoryData {
#[serde(skip_serializing_if = "Option::is_none")]
pub key_change: Option<String>,
+ /// URL to get renewal information
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub renewal_info: Option<String>,
+
/// Metadata object, for additional information which aren't directly part of the API
/// itself, such as the terms of service.
#[serde(skip_serializing_if = "Option::is_none")]
@@ -104,6 +108,10 @@ impl Directory {
self.data.new_order.as_deref()
}
+ pub(crate) fn renewal_info_url(&self) -> Option<&str> {
+ self.data.renewal_info.as_deref()
+ }
+
/// Access to the in the Acme spec defined metadata structure.
pub fn meta(&self) -> Option<&Meta> {
self.data.meta.as_ref()
diff --git a/proxmox-acme/src/lib.rs b/proxmox-acme/src/lib.rs
index 6b774746..370d5ec0 100644
--- a/proxmox-acme/src/lib.rs
+++ b/proxmox-acme/src/lib.rs
@@ -43,6 +43,9 @@ pub mod error;
#[cfg(feature = "impl")]
pub mod order;
+#[cfg(feature = "impl")]
+pub mod renewal;
+
#[cfg(feature = "impl")]
pub mod util;
diff --git a/proxmox-acme/src/renewal.rs b/proxmox-acme/src/renewal.rs
new file mode 100644
index 00000000..eb4ff96a
--- /dev/null
+++ b/proxmox-acme/src/renewal.rs
@@ -0,0 +1,33 @@
+//! Acme renewal information
+use serde::{Deserialize, Serialize};
+
+/// The suggested renewal time window
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SuggestedWindowData {
+ /// RFC3339 encoded time strings
+ pub start: String,
+ /// RFC3339 encoded time strings
+ pub end: String,
+}
+
+/// This contains the renewal information data returned by the ACME server
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct RenewalInformationData {
+ /// the suggested time window to renew the certificate
+ pub suggested_window: SuggestedWindowData,
+
+ /// explanatory URL why the windows is suggested
+ #[serde(skip_serializing_if = "Option::is_none")]
+ #[serde(rename = "explanatoryURL")]
+ pub explanation_url: Option<String>,
+}
+
+/// Renewal and retry information
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+#[serde(rename_all = "kebab-case")]
+pub struct RenewalInformation {
+ /// the actual response of the acme server
+ pub data: RenewalInformationData,
+}
--
2.47.3
next prev parent reply other threads:[~2026-06-25 14:14 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-25 14:13 [PATCH proxmox{,-backup,-datacenter-manager} 0/7] acme: fix #6372 implement basic ARI support Manuel Federanko
2026-06-25 14:13 ` Manuel Federanko [this message]
2026-06-25 14:13 ` [PATCH proxmox 2/7] acme: add retry-after header to renewal information Manuel Federanko
2026-06-25 14:13 ` [PATCH proxmox 3/7] acme: allow specifying the certificate that is replaced by an order Manuel Federanko
2026-06-25 14:13 ` [PATCH proxmox 4/7] acme: cert: add dedicated ari_id field to the certificate info Manuel Federanko
2026-06-25 14:13 ` [PATCH proxmox-backup 5/7] acme: add ari_id to cert info Manuel Federanko
2026-06-25 14:13 ` [PATCH proxmox-backup 6/7] acme: fix #6372 implement ARI renewal information fetching Manuel Federanko
2026-06-25 14:13 ` [PATCH proxmox-datacenter-manager 7/7] acme: fix #6372 use ARI for renewal if available Manuel Federanko
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260625141337.181684-2-m.federanko@proxmox.com \
--to=m.federanko@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
--cc=pdm-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.