* [pmg-devel] [PATCH acme-rs 1/8] add external account binding
2023-11-14 14:13 [pmg-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
@ 2023-11-14 14:14 ` Folke Gleumes
2023-11-14 14:14 ` [pmg-devel] [PATCH acme-rs 2/8] add meta fields returned by the directory Folke Gleumes
` (8 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:14 UTC (permalink / raw)
To: pmg-devel
Functionality was added as a additional setter function, which hopefully
prevents any breakages. Since a placeholder Option an the AccountData
was already present, but has never been used, replacing the field with
an Option of a fully defined type should also be minimally intrusive.
Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
---
src/account.rs | 28 ++++++++++++++++-----
src/eab.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++
src/error.rs | 10 ++++++++
src/lib.rs | 1 +
4 files changed, 99 insertions(+), 6 deletions(-)
create mode 100644 src/eab.rs
diff --git a/src/account.rs b/src/account.rs
index 8144d39..9f3af26 100644
--- a/src/account.rs
+++ b/src/account.rs
@@ -11,8 +11,9 @@ use serde_json::Value;
use crate::authorization::{Authorization, GetAuthorization};
use crate::b64u;
use crate::directory::Directory;
+use crate::eab::ExternalAccountBinding;
use crate::jws::Jws;
-use crate::key::PublicKey;
+use crate::key::{Jwk, PublicKey};
use crate::order::{NewOrder, Order, OrderData};
use crate::request::Request;
use crate::Error;
@@ -336,10 +337,9 @@ pub struct AccountData {
#[serde(skip_serializing_if = "Option::is_none")]
pub terms_of_service_agreed: Option<bool>,
- /// External account information. This is currently not directly supported in any way and only
- /// stored to completeness.
+ /// External account information.
#[serde(skip_serializing_if = "Option::is_none")]
- pub external_account_binding: Option<Value>,
+ pub external_account_binding: Option<ExternalAccountBinding>,
/// This is only used by the client when querying an account.
#[serde(default = "default_true", skip_serializing_if = "is_false")]
@@ -375,6 +375,7 @@ pub struct AccountCreator {
contact: Vec<String>,
terms_of_service_agreed: bool,
key: Option<PKey<Private>>,
+ eab_credentials: Option<(String, PKey<Private>)>,
}
impl AccountCreator {
@@ -402,6 +403,13 @@ impl AccountCreator {
self
}
+ /// Set the EAB credentials for the account registration
+ pub fn set_eab_credentials(mut self, kid: String, hmac_key: String) -> Result<Self, Error> {
+ let hmac_key = PKey::hmac(&base64::decode(hmac_key)?)?;
+ self.eab_credentials = Some((kid, hmac_key));
+ Ok(self)
+ }
+
/// Generate a new RSA key of the specified key size.
pub fn generate_rsa_key(self, bits: u32) -> Result<Self, Error> {
let key = openssl::rsa::Rsa::generate(bits)?;
@@ -431,6 +439,15 @@ impl AccountCreator {
/// [`response`](AccountCreator::response()) will render the account unusable!
pub fn request(&self, directory: &Directory, nonce: &str) -> Result<Request, Error> {
let key = self.key.as_deref().ok_or(Error::MissingKey)?;
+ let url = directory.new_account_url();
+
+ let external_account_binding = self
+ .eab_credentials
+ .as_ref()
+ .map(|cred| {
+ ExternalAccountBinding::new(&cred.0, &cred.1, Jwk::try_from(key)?, url.to_string())
+ })
+ .transpose()?;
let data = AccountData {
orders: None,
@@ -441,12 +458,11 @@ impl AccountCreator {
} else {
None
},
- external_account_binding: None,
+ external_account_binding,
only_return_existing: false,
extra: HashMap::new(),
};
- let url = directory.new_account_url();
let body = serde_json::to_string(&Jws::new(
key,
None,
diff --git a/src/eab.rs b/src/eab.rs
new file mode 100644
index 0000000..a4c0642
--- /dev/null
+++ b/src/eab.rs
@@ -0,0 +1,66 @@
+use openssl::hash::MessageDigest;
+use openssl::pkey::{HasPrivate, PKeyRef};
+use openssl::sign::Signer;
+use serde::{Deserialize, Serialize};
+
+use crate::key::Jwk;
+use crate::{b64u, Error};
+
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+struct Protected {
+ alg: &'static str,
+ url: String,
+ kid: String,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct ExternalAccountBinding {
+ protected: String,
+ payload: String,
+ signature: String,
+}
+
+impl ExternalAccountBinding {
+ pub fn new<P>(
+ eab_kid: &str,
+ eab_hmac_key: &PKeyRef<P>,
+ jwk: Jwk,
+ url: String,
+ ) -> Result<Self, Error>
+ where
+ P: HasPrivate,
+ {
+ let protected = Protected {
+ alg: "HS256",
+ kid: eab_kid.to_string(),
+ url,
+ };
+ let payload = b64u::encode(serde_json::to_string(&jwk)?.as_bytes());
+ let protected_data = b64u::encode(serde_json::to_string(&protected)?.as_bytes());
+ let signature = {
+ let protected = protected_data.as_bytes();
+ let payload = payload.as_bytes();
+ Self::sign_hmac(eab_hmac_key, protected, payload)?
+ };
+
+ let signature = b64u::encode(&signature);
+ Ok(ExternalAccountBinding {
+ protected: protected_data,
+ payload,
+ signature,
+ })
+ }
+
+ fn sign_hmac<P>(key: &PKeyRef<P>, protected: &[u8], payload: &[u8]) -> Result<Vec<u8>, Error>
+ where
+ P: HasPrivate,
+ {
+ let mut signer = Signer::new(MessageDigest::sha256(), key)?;
+ signer.update(protected)?;
+ signer.update(b".")?;
+ signer.update(payload)?;
+ Ok(signer.sign_to_vec()?)
+ }
+}
diff --git a/src/error.rs b/src/error.rs
index bcfaed0..59da3ea 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -59,6 +59,9 @@ pub enum Error {
/// An otherwise uncaught serde error happened.
Json(serde_json::Error),
+ /// Failed to parse
+ BadBase64(base64::DecodeError),
+
/// Can be used by the user for textual error messages without having to downcast to regular
/// acme errors.
Custom(String),
@@ -121,6 +124,7 @@ impl fmt::Display for Error {
Error::HttpClient(err) => fmt::Display::fmt(err, f),
Error::Client(err) => fmt::Display::fmt(err, f),
Error::Csr(err) => fmt::Display::fmt(err, f),
+ Error::BadBase64(err) => fmt::Display::fmt(err, f),
}
}
}
@@ -142,3 +146,9 @@ impl From<crate::request::ErrorResponse> for Error {
Error::Api(e)
}
}
+
+impl From<base64::DecodeError> for Error {
+ fn from(e: base64::DecodeError) -> Self {
+ Error::BadBase64(e)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 3533b29..98ad04e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,6 +14,7 @@
#![deny(missing_docs)]
mod b64u;
+mod eab;
mod json;
mod jws;
mod key;
--
2.39.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [pmg-devel] [PATCH acme-rs 2/8] add meta fields returned by the directory
2023-11-14 14:13 [pmg-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
2023-11-14 14:14 ` [pmg-devel] [PATCH acme-rs 1/8] add external account binding Folke Gleumes
@ 2023-11-14 14:14 ` Folke Gleumes
2023-12-04 10:56 ` [pmg-devel] applied: " Wolfgang Bumiller
2023-11-14 14:14 ` [pmg-devel] [PATCH] expand helper function by eab credentials Folke Gleumes
` (7 subsequent siblings)
9 siblings, 1 reply; 15+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:14 UTC (permalink / raw)
To: pmg-devel
According to the rfc, the meta field contains additional fields that
weren't covered by the Meta struct. Of the additional fields, only
external_account_required will be used in the near future, but others
were added for completeness and the case that they might be used in the
future.
Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
---
src/directory.rs | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/src/directory.rs b/src/directory.rs
index 755ea8c..a9d31f2 100644
--- a/src/directory.rs
+++ b/src/directory.rs
@@ -47,6 +47,18 @@ pub struct Meta {
/// The terms of service. This is typically in the form of an URL.
#[serde(skip_serializing_if = "Option::is_none")]
pub terms_of_service: Option<String>,
+
+ /// Flag indicating if EAB is required, None is equivalent to false
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub external_account_required: Option<bool>,
+
+ /// Website with information about the ACME Server
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub website: Option<String>,
+
+ /// List of hostnames used by the CA, intended for the use with caa dns records
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub caa_identities: Option<Vec<String>>,
}
impl Directory {
@@ -64,6 +76,17 @@ impl Directory {
}
}
+ /// Get if external account binding is required
+ pub fn external_account_binding_required(&self) -> bool {
+ matches!(
+ &self.data.meta,
+ Some(Meta {
+ external_account_required: Some(true),
+ ..
+ })
+ )
+ }
+
/// Get the "newNonce" URL. Use `HEAD` requests on this to get a new nonce.
pub fn new_nonce_url(&self) -> &str {
&self.data.new_nonce
@@ -78,8 +101,6 @@ impl Directory {
}
/// Access to the in the Acme spec defined metadata structure.
- /// Currently only contains the ToS URL already exposed via the `terms_of_service_url()`
- /// method.
pub fn meta(&self) -> Option<&Meta> {
self.data.meta.as_ref()
}
--
2.39.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [pmg-devel] applied: [PATCH acme-rs 2/8] add meta fields returned by the directory
2023-11-14 14:14 ` [pmg-devel] [PATCH acme-rs 2/8] add meta fields returned by the directory Folke Gleumes
@ 2023-12-04 10:56 ` Wolfgang Bumiller
0 siblings, 0 replies; 15+ messages in thread
From: Wolfgang Bumiller @ 2023-12-04 10:56 UTC (permalink / raw)
To: Folke Gleumes; +Cc: pmg-devel
applied the acme-rs patches
Note: proxmox-acme-rs.git has now been merged into proxmox.git.
Also, the `-rs` suffix has been dropped from the crate name.
On Tue, Nov 14, 2023 at 03:14:01PM +0100, Folke Gleumes wrote:
> According to the rfc, the meta field contains additional fields that
> weren't covered by the Meta struct. Of the additional fields, only
> external_account_required will be used in the near future, but others
> were added for completeness and the case that they might be used in the
> future.
>
> Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
> ---
> src/directory.rs | 25 +++++++++++++++++++++++--
> 1 file changed, 23 insertions(+), 2 deletions(-)
>
> diff --git a/src/directory.rs b/src/directory.rs
> index 755ea8c..a9d31f2 100644
> --- a/src/directory.rs
> +++ b/src/directory.rs
> @@ -47,6 +47,18 @@ pub struct Meta {
> /// The terms of service. This is typically in the form of an URL.
> #[serde(skip_serializing_if = "Option::is_none")]
> pub terms_of_service: Option<String>,
> +
> + /// Flag indicating if EAB is required, None is equivalent to false
> + #[serde(skip_serializing_if = "Option::is_none")]
> + pub external_account_required: Option<bool>,
> +
> + /// Website with information about the ACME Server
> + #[serde(skip_serializing_if = "Option::is_none")]
> + pub website: Option<String>,
> +
> + /// List of hostnames used by the CA, intended for the use with caa dns records
> + #[serde(skip_serializing_if = "Option::is_none")]
> + pub caa_identities: Option<Vec<String>>,
^ I dropped the `Option<>` there and added `#[serde(default)]` so
deserializing works without it.
^ permalink raw reply [flat|nested] 15+ messages in thread
* [pmg-devel] [PATCH] expand helper function by eab credentials
2023-11-14 14:13 [pmg-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
2023-11-14 14:14 ` [pmg-devel] [PATCH acme-rs 1/8] add external account binding Folke Gleumes
2023-11-14 14:14 ` [pmg-devel] [PATCH acme-rs 2/8] add meta fields returned by the directory Folke Gleumes
@ 2023-11-14 14:14 ` Folke Gleumes
2023-12-04 10:57 ` [pmg-devel] applied: " Wolfgang Bumiller
2023-11-14 14:14 ` [pmg-devel] [PATCH backup 3/8] acme: api: add eab options to api Folke Gleumes
` (6 subsequent siblings)
9 siblings, 1 reply; 15+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:14 UTC (permalink / raw)
To: pmg-devel
Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
---
src/client.rs | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/client.rs b/src/client.rs
index 78c83a2..53f2688 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -367,10 +367,14 @@ impl Client {
contact: Vec<String>,
tos_agreed: bool,
rsa_bits: Option<u32>,
+ eab_creds: Option<(String, String)>,
) -> Result<&Account, Error> {
- let account = Account::creator()
+ let mut account = Account::creator()
.set_contacts(contact)
.agree_to_tos(tos_agreed);
+ if let Some((eab_kid, eab_hmac_key)) = eab_creds {
+ account = account.set_eab_credentials(eab_kid, eab_hmac_key)?;
+ }
let account = if let Some(bits) = rsa_bits {
account.generate_rsa_key(bits)?
} else {
--
2.39.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [pmg-devel] [PATCH backup 3/8] acme: api: add eab options to api
2023-11-14 14:13 [pmg-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
` (2 preceding siblings ...)
2023-11-14 14:14 ` [pmg-devel] [PATCH] expand helper function by eab credentials Folke Gleumes
@ 2023-11-14 14:14 ` Folke Gleumes
2023-11-14 14:14 ` [pmg-devel] [PATCH backup 4/8] cli: acme: add possibility to set eab via the cli Folke Gleumes
` (5 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:14 UTC (permalink / raw)
To: pmg-devel
Optionally allow for setting external account binding credentials at the
account registration endpoint.
Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
---
src/acme/client.rs | 7 +++++-
src/api2/config/acme.rs | 35 +++++++++++++++++++++++---
src/bin/proxmox_backup_manager/acme.rs | 12 ++++++---
3 files changed, 47 insertions(+), 7 deletions(-)
diff --git a/src/acme/client.rs b/src/acme/client.rs
index 46566210..1396eb2c 100644
--- a/src/acme/client.rs
+++ b/src/acme/client.rs
@@ -116,6 +116,7 @@ impl AcmeClient {
tos_agreed: bool,
contact: Vec<String>,
rsa_bits: Option<u32>,
+ eab_creds: Option<(String, String)>,
) -> Result<&'a Account, anyhow::Error> {
self.tos = if tos_agreed {
self.terms_of_service_url().await?.map(str::to_owned)
@@ -123,10 +124,14 @@ impl AcmeClient {
None
};
- let account = Account::creator()
+ let mut account = Account::creator()
.set_contacts(contact)
.agree_to_tos(tos_agreed);
+ if let Some((eab_kid, eab_hmac_key)) = eab_creds {
+ account = account.set_eab_credentials(eab_kid, eab_hmac_key)?;
+ }
+
let account = if let Some(bits) = rsa_bits {
account.generate_rsa_key(bits)?
} else {
diff --git a/src/api2/config/acme.rs b/src/api2/config/acme.rs
index 1954318b..8f010027 100644
--- a/src/api2/config/acme.rs
+++ b/src/api2/config/acme.rs
@@ -182,6 +182,16 @@ fn account_contact_from_string(s: &str) -> Vec<String> {
description: "The ACME Directory.",
optional: true,
},
+ eab_kid: {
+ type: String,
+ description: "Key Identifier for External Account Binding.",
+ optional: true,
+ },
+ eab_hmac_key: {
+ type: String,
+ description: "HMAC Key for External Account Binding.",
+ optional: true,
+ }
},
},
access: {
@@ -196,6 +206,8 @@ fn register_account(
contact: String,
tos_url: Option<String>,
directory: Option<String>,
+ eab_kid: Option<String>,
+ eab_hmac_key: Option<String>,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<String, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
@@ -204,6 +216,15 @@ fn register_account(
AcmeAccountName::from_string_unchecked("default".to_string())
});
+ // TODO: this should be done via the api definition, but
+ // the api macro currently lacks this ability (2023-11-06)
+ if eab_kid.is_some() ^ eab_hmac_key.is_some() {
+ http_bail!(
+ BAD_REQUEST,
+ "either both or none of 'eab_kid' and 'eab_hmac_key' have to be set."
+ );
+ }
+
if Path::new(&crate::config::acme::account_path(&name)).exists() {
http_bail!(BAD_REQUEST, "account {} already exists", name);
}
@@ -224,8 +245,15 @@ fn register_account(
task_log!(worker, "Registering ACME account '{}'...", &name);
- let account =
- do_register_account(&mut client, &name, tos_url.is_some(), contact, None).await?;
+ let account = do_register_account(
+ &mut client,
+ &name,
+ tos_url.is_some(),
+ contact,
+ None,
+ eab_kid.zip(eab_hmac_key),
+ )
+ .await?;
task_log!(
worker,
@@ -244,10 +272,11 @@ pub async fn do_register_account<'a>(
agree_to_tos: bool,
contact: String,
rsa_bits: Option<u32>,
+ eab_creds: Option<(String, String)>,
) -> Result<&'a Account, Error> {
let contact = account_contact_from_string(&contact);
client
- .new_account(name, agree_to_tos, contact, rsa_bits)
+ .new_account(name, agree_to_tos, contact, rsa_bits, eab_creds)
.await
}
diff --git a/src/bin/proxmox_backup_manager/acme.rs b/src/bin/proxmox_backup_manager/acme.rs
index de48a420..17ca5958 100644
--- a/src/bin/proxmox_backup_manager/acme.rs
+++ b/src/bin/proxmox_backup_manager/acme.rs
@@ -156,9 +156,15 @@ async fn register_account(
println!("Attempting to register account with {:?}...", directory);
- let account =
- api2::config::acme::do_register_account(&mut client, &name, tos_agreed, contact, None)
- .await?;
+ let account = api2::config::acme::do_register_account(
+ &mut client,
+ &name,
+ tos_agreed,
+ contact,
+ None,
+ None,
+ )
+ .await?;
println!("Registration successful, account URL: {}", account.location);
--
2.39.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [pmg-devel] [PATCH backup 4/8] cli: acme: add possibility to set eab via the cli
2023-11-14 14:13 [pmg-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
` (3 preceding siblings ...)
2023-11-14 14:14 ` [pmg-devel] [PATCH backup 3/8] acme: api: add eab options to api Folke Gleumes
@ 2023-11-14 14:14 ` Folke Gleumes
2023-11-14 14:14 ` [pmg-devel] [PATCH perl-rs 5/8] acme: add eab fields for pmg Folke Gleumes
` (4 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:14 UTC (permalink / raw)
To: pmg-devel
If the ca demands external account binding credentials, the user will be
asked for them. If a custom directory is used, the user will be asked if
eab should be used.
Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
---
src/acme/client.rs | 2 +-
src/bin/proxmox_backup_manager/acme.rs | 51 +++++++++++++++++++++-----
2 files changed, 43 insertions(+), 10 deletions(-)
diff --git a/src/acme/client.rs b/src/acme/client.rs
index 1396eb2c..6130748b 100644
--- a/src/acme/client.rs
+++ b/src/acme/client.rs
@@ -577,7 +577,7 @@ impl AcmeClient {
Self::execute(&mut self.http_client, request, &mut self.nonce).await
}
- async fn directory(&mut self) -> Result<&Directory, Error> {
+ pub async fn directory(&mut self) -> Result<&Directory, Error> {
Ok(Self::get_directory(
&mut self.http_client,
&self.directory_url,
diff --git a/src/bin/proxmox_backup_manager/acme.rs b/src/bin/proxmox_backup_manager/acme.rs
index 17ca5958..f3e62115 100644
--- a/src/bin/proxmox_backup_manager/acme.rs
+++ b/src/bin/proxmox_backup_manager/acme.rs
@@ -103,8 +103,8 @@ async fn register_account(
contact: String,
directory: Option<String>,
) -> Result<(), Error> {
- let directory = match directory {
- Some(directory) => directory,
+ let (directory_url, custom_directory) = match directory {
+ Some(directory) => (directory, true),
None => {
println!("Directory endpoints:");
for (i, dir) in KNOWN_ACME_DIRECTORIES.iter().enumerate() {
@@ -122,12 +122,12 @@ async fn register_account(
match input.trim().parse::<usize>() {
Ok(n) if n < KNOWN_ACME_DIRECTORIES.len() => {
- break KNOWN_ACME_DIRECTORIES[n].url.to_owned();
+ break (KNOWN_ACME_DIRECTORIES[n].url.to_owned(), false);
}
Ok(n) if n == KNOWN_ACME_DIRECTORIES.len() => {
input.clear();
std::io::stdin().read_line(&mut input)?;
- break input.trim().to_owned();
+ break (input.trim().to_owned(), true);
}
_ => eprintln!("Invalid selection."),
}
@@ -140,9 +140,13 @@ async fn register_account(
}
};
- println!("Attempting to fetch Terms of Service from {:?}", directory);
- let mut client = AcmeClient::new(directory.clone());
- let tos_agreed = if let Some(tos_url) = client.terms_of_service_url().await? {
+ println!(
+ "Attempting to fetch Terms of Service from {:?}",
+ directory_url
+ );
+ let mut client = AcmeClient::new(directory_url.clone());
+ let directory = client.directory().await?;
+ let tos_agreed = if let Some(tos_url) = directory.terms_of_service_url() {
println!("Terms of Service: {}", tos_url);
print!("Do you agree to the above terms? [y|N]: ");
std::io::stdout().flush()?;
@@ -154,7 +158,36 @@ async fn register_account(
true
};
- println!("Attempting to register account with {:?}...", directory);
+ let mut eab_enabled = directory.external_account_binding_required();
+ if !eab_enabled && custom_directory {
+ print!("Do you want to use external account binding? [y|N]: ");
+ std::io::stdout().flush()?;
+ let mut input = String::new();
+ std::io::stdin().read_line(&mut input)?;
+ eab_enabled = input.trim().eq_ignore_ascii_case("y");
+ } else if eab_enabled {
+ println!("The CA requires external account binding.");
+ }
+
+ let eab_creds = if eab_enabled {
+ println!("You should have received a key id and a key from your CA.");
+
+ print!("Enter EAB key id: ");
+ std::io::stdout().flush()?;
+ let mut eab_kid = String::new();
+ std::io::stdin().read_line(&mut eab_kid)?;
+
+ print!("Enter EAB key: ");
+ std::io::stdout().flush()?;
+ let mut eab_hmac_key = String::new();
+ std::io::stdin().read_line(&mut eab_hmac_key)?;
+
+ Some((eab_kid.trim().to_owned(), eab_hmac_key.trim().to_owned()))
+ } else {
+ None
+ };
+
+ println!("Attempting to register account with {:?}...", directory_url);
let account = api2::config::acme::do_register_account(
&mut client,
@@ -162,7 +195,7 @@ async fn register_account(
tos_agreed,
contact,
None,
- None,
+ eab_creds,
)
.await?;
--
2.39.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [pmg-devel] [PATCH perl-rs 5/8] acme: add eab fields for pmg
2023-11-14 14:13 [pmg-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
` (4 preceding siblings ...)
2023-11-14 14:14 ` [pmg-devel] [PATCH backup 4/8] cli: acme: add possibility to set eab via the cli Folke Gleumes
@ 2023-11-14 14:14 ` Folke Gleumes
2023-12-06 11:37 ` [pmg-devel] applied: " Wolfgang Bumiller
2023-11-14 14:14 ` [pmg-devel] [PATCH pmg-api 6/8] api: acme: add eab parameters Folke Gleumes
` (3 subsequent siblings)
9 siblings, 1 reply; 15+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:14 UTC (permalink / raw)
To: pmg-devel
Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
---
pmg-rs/src/acme.rs | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/pmg-rs/src/acme.rs b/pmg-rs/src/acme.rs
index b38e1ea..fe1e465 100644
--- a/pmg-rs/src/acme.rs
+++ b/pmg-rs/src/acme.rs
@@ -79,6 +79,7 @@ impl Inner {
tos_agreed: bool,
contact: Vec<String>,
rsa_bits: Option<u32>,
+ eab_creds: Option<(String, String)>,
) -> Result<(), Error> {
self.tos = if tos_agreed {
self.client.terms_of_service_url()?.map(str::to_owned)
@@ -86,7 +87,9 @@ impl Inner {
None
};
- let _account = self.client.new_account(contact, tos_agreed, rsa_bits)?;
+ let _account = self
+ .client
+ .new_account(contact, tos_agreed, rsa_bits, eab_creds)?;
let file = OpenOptions::new()
.write(true)
.create(true)
@@ -238,11 +241,16 @@ pub mod export {
tos_agreed: bool,
contact: Vec<String>,
rsa_bits: Option<u32>,
+ eab_kid: Option<String>,
+ eab_hmac_key: Option<String>,
) -> Result<(), Error> {
- this.inner
- .lock()
- .unwrap()
- .new_account(account_path, tos_agreed, contact, rsa_bits)
+ this.inner.lock().unwrap().new_account(
+ account_path,
+ tos_agreed,
+ contact,
+ rsa_bits,
+ eab_kid.zip(eab_hmac_key),
+ )
}
/// Get the directory's meta information.
--
2.39.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [pmg-devel] applied: [PATCH perl-rs 5/8] acme: add eab fields for pmg
2023-11-14 14:14 ` [pmg-devel] [PATCH perl-rs 5/8] acme: add eab fields for pmg Folke Gleumes
@ 2023-12-06 11:37 ` Wolfgang Bumiller
0 siblings, 0 replies; 15+ messages in thread
From: Wolfgang Bumiller @ 2023-12-06 11:37 UTC (permalink / raw)
To: Folke Gleumes; +Cc: pmg-devel
applied
On Tue, Nov 14, 2023 at 03:14:05PM +0100, Folke Gleumes wrote:
> Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
> ---
> pmg-rs/src/acme.rs | 18 +++++++++++++-----
> 1 file changed, 13 insertions(+), 5 deletions(-)
>
> diff --git a/pmg-rs/src/acme.rs b/pmg-rs/src/acme.rs
> index b38e1ea..fe1e465 100644
> --- a/pmg-rs/src/acme.rs
> +++ b/pmg-rs/src/acme.rs
> @@ -79,6 +79,7 @@ impl Inner {
> tos_agreed: bool,
> contact: Vec<String>,
> rsa_bits: Option<u32>,
> + eab_creds: Option<(String, String)>,
> ) -> Result<(), Error> {
> self.tos = if tos_agreed {
> self.client.terms_of_service_url()?.map(str::to_owned)
> @@ -86,7 +87,9 @@ impl Inner {
> None
> };
>
> - let _account = self.client.new_account(contact, tos_agreed, rsa_bits)?;
> + let _account = self
> + .client
> + .new_account(contact, tos_agreed, rsa_bits, eab_creds)?;
> let file = OpenOptions::new()
> .write(true)
> .create(true)
> @@ -238,11 +241,16 @@ pub mod export {
> tos_agreed: bool,
> contact: Vec<String>,
> rsa_bits: Option<u32>,
> + eab_kid: Option<String>,
> + eab_hmac_key: Option<String>,
> ) -> Result<(), Error> {
> - this.inner
> - .lock()
> - .unwrap()
> - .new_account(account_path, tos_agreed, contact, rsa_bits)
> + this.inner.lock().unwrap().new_account(
> + account_path,
> + tos_agreed,
> + contact,
> + rsa_bits,
> + eab_kid.zip(eab_hmac_key),
> + )
> }
>
> /// Get the directory's meta information.
> --
> 2.39.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [pmg-devel] [PATCH pmg-api 6/8] api: acme: add eab parameters
2023-11-14 14:13 [pmg-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
` (5 preceding siblings ...)
2023-11-14 14:14 ` [pmg-devel] [PATCH perl-rs 5/8] acme: add eab fields for pmg Folke Gleumes
@ 2023-11-14 14:14 ` Folke Gleumes
2023-11-14 14:14 ` [pmg-devel] [PATCH pmg-api 7/8] api: acme: deprecate tos endpoint in favor of new meta endpoint Folke Gleumes
` (2 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:14 UTC (permalink / raw)
To: pmg-devel
Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
---
src/PMG/API2/ACME.pm | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/PMG/API2/ACME.pm b/src/PMG/API2/ACME.pm
index 42c9f4e..9e3eb8d 100644
--- a/src/PMG/API2/ACME.pm
+++ b/src/PMG/API2/ACME.pm
@@ -132,6 +132,18 @@ __PACKAGE__->register_method ({
default => $acme_default_directory_url,
optional => 1,
}),
+ 'eab-kid' => {
+ type => 'string',
+ description => 'Key Identifier for External Account Binding.',
+ requires => 'eab-hmac-key',
+ optional => 1,
+ },
+ 'eab-hmac-key' => {
+ type => 'string',
+ description => 'HMAC key for External Account Binding.',
+ requires => 'eab-kid',
+ optional => 1,
+ },
},
},
returns => {
@@ -151,6 +163,8 @@ __PACKAGE__->register_method ({
my $directory = extract_param($param, 'directory') // $acme_default_directory_url;
my $contact = $account_contact_from_param->($param);
+ my $eab_kid = extract_param($param, 'eab-kid');
+ my $eab_hmac_key = extract_param($param, 'eab-hmac-key');
my $realcmd = sub {
PMG::CertHelpers::lock_acme($account_name, 10, sub {
@@ -160,7 +174,7 @@ __PACKAGE__->register_method ({
print "Registering new ACME account..\n";
my $acme = PMG::RS::Acme->new($directory);
eval {
- $acme->new_account($account_file, defined($param->{tos_url}), $contact, undef);
+ $acme->new_account($account_file, defined($param->{tos_url}), $contact, undef, $eab_kid, $eab_hmac_key);
};
if (my $err = $@) {
unlink $account_file;
--
2.39.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [pmg-devel] [PATCH pmg-api 7/8] api: acme: deprecate tos endpoint in favor of new meta endpoint
2023-11-14 14:13 [pmg-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
` (6 preceding siblings ...)
2023-11-14 14:14 ` [pmg-devel] [PATCH pmg-api 6/8] api: acme: add eab parameters Folke Gleumes
@ 2023-11-14 14:14 ` Folke Gleumes
2023-11-14 14:14 ` [pmg-devel] [PATCH pmg-api 8/8] cli: acme: expose acme eab options on the cli Folke Gleumes
2023-12-06 11:59 ` [pmg-devel] applied-series: [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Wolfgang Bumiller
9 siblings, 0 replies; 15+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:14 UTC (permalink / raw)
To: pmg-devel
The ToS endpoint ignored data that is needed to detect if EAB needs to
be used. Instead of adding a new endpoint that does the same request,
the tos endpoint is deprecated and replaced by the meta endpoint,
that returns all information returned by the directory.
Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
---
src/PMG/API2/ACME.pm | 59 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)
diff --git a/src/PMG/API2/ACME.pm b/src/PMG/API2/ACME.pm
index 9e3eb8d..8795c33 100644
--- a/src/PMG/API2/ACME.pm
+++ b/src/PMG/API2/ACME.pm
@@ -67,6 +67,7 @@ __PACKAGE__->register_method ({
return [
{ name => 'account' },
{ name => 'tos' },
+ { name => 'meta' },
{ name => 'directories' },
{ name => 'plugins' },
{ name => 'challenge-schema' },
@@ -353,11 +354,12 @@ __PACKAGE__->register_method ({
return $update_account->($param, 'deactivate', $force_deactivate, status => 'deactivated');
}});
+# TODO: deprecated, remove with pmg 9
__PACKAGE__->register_method ({
name => 'get_tos',
path => 'tos',
method => 'GET',
- description => "Retrieve ACME TermsOfService URL from CA.",
+ description => "Retrieve ACME TermsOfService URL from CA. Deprecated, please use /config/acme/meta.",
permissions => { user => 'all' },
parameters => {
additionalProperties => 0,
@@ -384,6 +386,61 @@ __PACKAGE__->register_method ({
return $meta ? $meta->{termsOfService} : undef;
}});
+__PACKAGE__->register_method ({
+ name => 'get_meta',
+ path => 'meta',
+ method => 'GET',
+ description => "Retrieve ACME Directory Meta Information",
+ permissions => { user => 'all' },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ directory => get_standard_option('pmg-acme-directory-url', {
+ default => $acme_default_directory_url,
+ optional => 1,
+ }),
+ },
+ },
+ returns => {
+ type => 'object',
+ additionalProperties => 1,
+ properties => {
+ termsOfService => {
+ description => 'ACME TermsOfService URL.',
+ type => 'string',
+ optional => 1,
+ },
+ externalAccountRequired => {
+ description => 'EAB Required',
+ type => 'boolean',
+ optional => 1,
+ },
+ website => {
+ description => 'URL to more information about the ACME server.',
+ type => 'string',
+ optional => 1,
+ },
+ caaIdentities => {
+ description => 'Hostnames referring to the ACME servers.',
+ type => 'array',
+ items => {
+ type => 'string',
+ },
+ optional => 1,
+ },
+ },
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $directory = extract_param($param, 'directory') // $acme_default_directory_url;
+
+ my $acme = PVE::ACME->new(undef, $directory);
+ my $meta = $acme->get_meta();
+
+ return $meta;
+ }});
+
__PACKAGE__->register_method ({
name => 'get_directories',
path => 'directories',
--
2.39.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [pmg-devel] [PATCH pmg-api 8/8] cli: acme: expose acme eab options on the cli
2023-11-14 14:13 [pmg-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
` (7 preceding siblings ...)
2023-11-14 14:14 ` [pmg-devel] [PATCH pmg-api 7/8] api: acme: deprecate tos endpoint in favor of new meta endpoint Folke Gleumes
@ 2023-11-14 14:14 ` Folke Gleumes
2023-12-06 11:41 ` [pmg-devel] applied: " Wolfgang Bumiller
2023-12-06 11:59 ` [pmg-devel] applied-series: [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Wolfgang Bumiller
9 siblings, 1 reply; 15+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:14 UTC (permalink / raw)
To: pmg-devel
interactively ask for external account binding credentials if either:
* the ca requests it
* a custom ca is used
Signed-off-by: Folke Gleumes <f.gleumes@proxmox.com>
---
src/PMG/CLI/pmgconfig.pm | 29 ++++++++++++++++++++++++++---
1 file changed, 26 insertions(+), 3 deletions(-)
diff --git a/src/PMG/CLI/pmgconfig.pm b/src/PMG/CLI/pmgconfig.pm
index cc036fa..2a7a7a1 100644
--- a/src/PMG/CLI/pmgconfig.pm
+++ b/src/PMG/CLI/pmgconfig.pm
@@ -244,6 +244,7 @@ __PACKAGE__->register_method({
code => sub {
my ($param) = @_;
+ my $custom_directory = 1;
if (!$param->{directory}) {
my $directories = PMG::API2::ACME->get_directories({});
print "Directory endpoints:\n";
@@ -264,6 +265,7 @@ __PACKAGE__->register_method({
return;
} elsif ($selection < $i && $selection >= 0) {
$param->{directory} = $directories->[$selection]->{url};
+ $custom_directory = 0;
return;
}
}
@@ -277,11 +279,13 @@ __PACKAGE__->register_method({
$attempts++;
}
}
+
print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n";
- my $tos = PMG::API2::ACME->get_tos({ directory => $param->{directory} });
- if ($tos) {
+ my $meta = PMG::API2::ACME->get_meta({ directory => $param->{directory} });
+ if ($meta->{termsOfService}) {
+ my $tos = $meta->{termsOfService};
print "Terms of Service: $tos\n";
- my $term = Term::ReadLine->new('pvenode');
+ my $term = Term::ReadLine->new('pmgconfig');
my $agreed = $term->readline('Do you agree to the above terms? [y|N]: ');
die "Cannot continue without agreeing to ToS, aborting.\n"
if ($agreed !~ /^y$/i);
@@ -290,6 +294,25 @@ __PACKAGE__->register_method({
} else {
print "No Terms of Service found, proceeding.\n";
}
+
+ my $eab_enabled = $meta->{externalAccountRequired};
+ if (!$eab_enabled && $custom_directory) {
+ my $term = Term::ReadLine->new('pmgconfig');
+ my $agreed = $term->readline('Do you want to use external account binding? [y|N]: ');
+ $eab_enabled = ($agreed =~ /^y$/i);
+ } elsif ($eab_enabled) {
+ print "The CA requires external account binding.\n";
+ }
+ if ($eab_enabled) {
+ print "You should have received a key id and a key from your CA.\n";
+ my $term = Term::ReadLine->new('pmgconfig');
+ my $eab_kid = $term->readline('Enter EAB key id: ');
+ my $eab_hmac_key = $term->readline('Enter EAB key: ');
+
+ $param->{'eab-kid'} = $eab_kid;
+ $param->{'eab-hmac-key'} = $eab_hmac_key;
+ }
+
print "\nAttempting to register account with '$param->{directory}'..\n";
$upid_exit->(PMG::API2::ACME->register_account($param));
--
2.39.2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [pmg-devel] applied-series: [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs
2023-11-14 14:13 [pmg-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
` (8 preceding siblings ...)
2023-11-14 14:14 ` [pmg-devel] [PATCH pmg-api 8/8] cli: acme: expose acme eab options on the cli Folke Gleumes
@ 2023-12-06 11:59 ` Wolfgang Bumiller
9 siblings, 0 replies; 15+ messages in thread
From: Wolfgang Bumiller @ 2023-12-06 11:59 UTC (permalink / raw)
To: Folke Gleumes; +Cc: pmg-devel
applied the remaining patches, thanks
^ permalink raw reply [flat|nested] 15+ messages in thread