public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs
@ 2023-11-14 14:07 Folke Gleumes
  2023-11-14 14:07 ` [pve-devel] [PATCH acme-rs 1/8] add external account binding Folke Gleumes
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:07 UTC (permalink / raw)
  To: pve-devel

Following the implementation for pve [0], this implements external account
binding for pmg and pbs.

For pmg, the tos endpoint was replaced with a meta endpoint, for pbs
this was not necessary, although it might be in the future if the
functionality is introduced in the gui.

Similar to the pve implementation, the cli will ask for eab credentials
if the ca requires it, or optionally if the user provided a custom
directory url.

The patches were tested against pebble with eab and le-staging + pebble
without eab to ensure no regression have taken place.

[0] https://lists.proxmox.com/pipermail/pve-devel/2023-October/059726.html

acme-rs:
Folke Gleumes (2):
  add external account binding
  add meta fields returned by the directory

 src/account.rs   | 28 +++++++++++++++-----
 src/client.rs    |  6 ++++-
 src/directory.rs | 25 ++++++++++++++++--
 src/eab.rs       | 66 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/error.rs     | 10 ++++++++
 src/lib.rs       |  1 +
 6 files changed, 127 insertions(+), 9 deletions(-)
 create mode 100644 src/eab.rs

backup:
Folke Gleumes (2):
  acme: api: add eab options to api
  cli: acme: add possibility to set eab via the cli

 src/acme/client.rs                     |  9 +++-
 src/api2/config/acme.rs                | 35 +++++++++++++--
 src/bin/proxmox_backup_manager/acme.rs | 61 +++++++++++++++++++++-----
 3 files changed, 89 insertions(+), 16 deletions(-)

perl-rs:
Folke Gleumes (1):
  acme: add eab fields for pmg

 pmg-rs/src/acme.rs | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

pmg-api:
Folke Gleumes (3):
  api: acme: add eab parameters
  api: acme: deprecate tos endpoint in favor of new meta endpoint
  cli: acme: expose acme eab options on the cli

 src/PMG/API2/ACME.pm     | 75 ++++++++++++++++++++++++++++++++++++++--
 src/PMG/CLI/pmgconfig.pm | 29 ++++++++++++++--
 2 files changed, 99 insertions(+), 5 deletions(-)
-- 
2.39.2





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [pve-devel] [PATCH acme-rs 1/8] add external account binding
  2023-11-14 14:07 [pve-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:07 ` Folke Gleumes
  2023-11-14 14:07 ` [pve-devel] [PATCH acme-rs 2/8] add meta fields returned by the directory Folke Gleumes
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:07 UTC (permalink / raw)
  To: pve-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] 11+ messages in thread

* [pve-devel] [PATCH acme-rs 2/8] add meta fields returned by the directory
  2023-11-14 14:07 [pve-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:07 ` [pve-devel] [PATCH acme-rs 1/8] add external account binding Folke Gleumes
@ 2023-11-14 14:07 ` Folke Gleumes
  2023-11-14 14:07 ` [pve-devel] [PATCH] expand helper function by eab credentials Folke Gleumes
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:07 UTC (permalink / raw)
  To: pve-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] 11+ messages in thread

* [pve-devel] [PATCH] expand helper function by eab credentials
  2023-11-14 14:07 [pve-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:07 ` [pve-devel] [PATCH acme-rs 1/8] add external account binding Folke Gleumes
  2023-11-14 14:07 ` [pve-devel] [PATCH acme-rs 2/8] add meta fields returned by the directory Folke Gleumes
@ 2023-11-14 14:07 ` Folke Gleumes
  2023-11-14 14:07 ` [pve-devel] [PATCH backup 3/8] acme: api: add eab options to api Folke Gleumes
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:07 UTC (permalink / raw)
  To: pve-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] 11+ messages in thread

* [pve-devel] [PATCH backup 3/8] acme: api: add eab options to api
  2023-11-14 14:07 [pve-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:07 ` [pve-devel] [PATCH] expand helper function by eab credentials Folke Gleumes
@ 2023-11-14 14:07 ` Folke Gleumes
  2023-11-14 14:07 ` [pve-devel] [PATCH backup 4/8] cli: acme: add possibility to set eab via the cli Folke Gleumes
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:07 UTC (permalink / raw)
  To: pve-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] 11+ messages in thread

* [pve-devel] [PATCH backup 4/8] cli: acme: add possibility to set eab via the cli
  2023-11-14 14:07 [pve-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:07 ` [pve-devel] [PATCH backup 3/8] acme: api: add eab options to api Folke Gleumes
@ 2023-11-14 14:07 ` Folke Gleumes
  2023-11-14 14:07 ` [pve-devel] [PATCH perl-rs 5/8] acme: add eab fields for pmg Folke Gleumes
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:07 UTC (permalink / raw)
  To: pve-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] 11+ messages in thread

* [pve-devel] [PATCH perl-rs 5/8] acme: add eab fields for pmg
  2023-11-14 14:07 [pve-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:07 ` [pve-devel] [PATCH backup 4/8] cli: acme: add possibility to set eab via the cli Folke Gleumes
@ 2023-11-14 14:07 ` Folke Gleumes
  2023-11-14 14:07 ` [pve-devel] [PATCH pmg-api 6/8] api: acme: add eab parameters Folke Gleumes
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:07 UTC (permalink / raw)
  To: pve-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] 11+ messages in thread

* [pve-devel] [PATCH pmg-api 6/8] api: acme: add eab parameters
  2023-11-14 14:07 [pve-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:07 ` [pve-devel] [PATCH perl-rs 5/8] acme: add eab fields for pmg Folke Gleumes
@ 2023-11-14 14:07 ` Folke Gleumes
  2023-11-14 14:07 ` [pve-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; 11+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:07 UTC (permalink / raw)
  To: pve-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] 11+ messages in thread

* [pve-devel] [PATCH pmg-api 7/8] api: acme: deprecate tos endpoint in favor of new meta endpoint
  2023-11-14 14:07 [pve-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:07 ` [pve-devel] [PATCH pmg-api 6/8] api: acme: add eab parameters Folke Gleumes
@ 2023-11-14 14:07 ` Folke Gleumes
  2023-11-14 14:07 ` [pve-devel] [PATCH pmg-api 8/8] cli: acme: expose acme eab options on the cli Folke Gleumes
  2023-11-14 14:08 ` [pve-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
  9 siblings, 0 replies; 11+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:07 UTC (permalink / raw)
  To: pve-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] 11+ messages in thread

* [pve-devel] [PATCH pmg-api 8/8] cli: acme: expose acme eab options on the cli
  2023-11-14 14:07 [pve-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:07 ` [pve-devel] [PATCH pmg-api 7/8] api: acme: deprecate tos endpoint in favor of new meta endpoint Folke Gleumes
@ 2023-11-14 14:07 ` Folke Gleumes
  2023-11-14 14:08 ` [pve-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes
  9 siblings, 0 replies; 11+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:07 UTC (permalink / raw)
  To: pve-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] 11+ messages in thread

* Re: [pve-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs
  2023-11-14 14:07 [pve-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:07 ` [pve-devel] [PATCH pmg-api 8/8] cli: acme: expose acme eab options on the cli Folke Gleumes
@ 2023-11-14 14:08 ` Folke Gleumes
  9 siblings, 0 replies; 11+ messages in thread
From: Folke Gleumes @ 2023-11-14 14:08 UTC (permalink / raw)
  To: pve-devel

Please ignore this, wrong mailing list

On Tue, 2023-11-14 at 15:07 +0100, Folke Gleumes wrote:
> Following the implementation for pve [0], this implements external
> account
> binding for pmg and pbs.
> 
> For pmg, the tos endpoint was replaced with a meta endpoint, for pbs
> this was not necessary, although it might be in the future if the
> functionality is introduced in the gui.
> 
> Similar to the pve implementation, the cli will ask for eab
> credentials
> if the ca requires it, or optionally if the user provided a custom
> directory url.
> 
> The patches were tested against pebble with eab and le-staging +
> pebble
> without eab to ensure no regression have taken place.
> 
> [0]
> https://lists.proxmox.com/pipermail/pve-devel/2023-October/059726.html
> 
> acme-rs:
> Folke Gleumes (2):
>   add external account binding
>   add meta fields returned by the directory
> 
>  src/account.rs   | 28 +++++++++++++++-----
>  src/client.rs    |  6 ++++-
>  src/directory.rs | 25 ++++++++++++++++--
>  src/eab.rs       | 66
> ++++++++++++++++++++++++++++++++++++++++++++++++
>  src/error.rs     | 10 ++++++++
>  src/lib.rs       |  1 +
>  6 files changed, 127 insertions(+), 9 deletions(-)
>  create mode 100644 src/eab.rs
> 
> backup:
> Folke Gleumes (2):
>   acme: api: add eab options to api
>   cli: acme: add possibility to set eab via the cli
> 
>  src/acme/client.rs                     |  9 +++-
>  src/api2/config/acme.rs                | 35 +++++++++++++--
>  src/bin/proxmox_backup_manager/acme.rs | 61 +++++++++++++++++++++---
> --
>  3 files changed, 89 insertions(+), 16 deletions(-)
> 
> perl-rs:
> Folke Gleumes (1):
>   acme: add eab fields for pmg
> 
>  pmg-rs/src/acme.rs | 18 +++++++++++++-----
>  1 file changed, 13 insertions(+), 5 deletions(-)
> 
> pmg-api:
> Folke Gleumes (3):
>   api: acme: add eab parameters
>   api: acme: deprecate tos endpoint in favor of new meta endpoint
>   cli: acme: expose acme eab options on the cli
> 
>  src/PMG/API2/ACME.pm     | 75
> ++++++++++++++++++++++++++++++++++++++--
>  src/PMG/CLI/pmgconfig.pm | 29 ++++++++++++++--
>  2 files changed, 99 insertions(+), 5 deletions(-)





^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2023-11-14 14:08 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-11-14 14:07 [pve-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:07 ` [pve-devel] [PATCH acme-rs 1/8] add external account binding Folke Gleumes
2023-11-14 14:07 ` [pve-devel] [PATCH acme-rs 2/8] add meta fields returned by the directory Folke Gleumes
2023-11-14 14:07 ` [pve-devel] [PATCH] expand helper function by eab credentials Folke Gleumes
2023-11-14 14:07 ` [pve-devel] [PATCH backup 3/8] acme: api: add eab options to api Folke Gleumes
2023-11-14 14:07 ` [pve-devel] [PATCH backup 4/8] cli: acme: add possibility to set eab via the cli Folke Gleumes
2023-11-14 14:07 ` [pve-devel] [PATCH perl-rs 5/8] acme: add eab fields for pmg Folke Gleumes
2023-11-14 14:07 ` [pve-devel] [PATCH pmg-api 6/8] api: acme: add eab parameters Folke Gleumes
2023-11-14 14:07 ` [pve-devel] [PATCH pmg-api 7/8] api: acme: deprecate tos endpoint in favor of new meta endpoint Folke Gleumes
2023-11-14 14:07 ` [pve-devel] [PATCH pmg-api 8/8] cli: acme: expose acme eab options on the cli Folke Gleumes
2023-11-14 14:08 ` [pve-devel] [PATCH acme-rs/backup/perl-rs/pmg-api 0/8] add external account binding to pmg and pbs Folke Gleumes

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal