add external account binding
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>
This commit is contained in:
parent
7e6aa2733a
commit
88f7e190ec
@ -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,
|
||||
|
66
src/eab.rs
Normal file
66
src/eab.rs
Normal file
@ -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()?)
|
||||
}
|
||||
}
|
10
src/error.rs
10
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)
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
#![deny(missing_docs)]
|
||||
|
||||
mod b64u;
|
||||
mod eab;
|
||||
mod json;
|
||||
mod jws;
|
||||
mod key;
|
||||
|
Loading…
Reference in New Issue
Block a user