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;
#[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")]
contact: Vec<String>,
terms_of_service_agreed: bool,
key: Option<PKey<Private>>,
+ eab_credentials: Option<(String, PKey<Private>)>,
}
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)?;
/// [`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,
} 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,
--- /dev/null
+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()?)
+ }
+}
/// 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),
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),
}
}
}
Error::Api(e)
}
}
+
+impl From<base64::DecodeError> for Error {
+ fn from(e: base64::DecodeError) -> Self {
+ Error::BadBase64(e)
+ }
+}