add more of the ACME workflow to 'Account'

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2021-03-18 11:36:42 +01:00
parent 02ecbb499c
commit afc59f6d15
3 changed files with 136 additions and 1 deletions

View File

@ -5,11 +5,12 @@ use openssl::pkey::{PKey, Private};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::authorization::{Authorization, GetAuthorization};
use crate::b64u;
use crate::directory::Directory;
use crate::jws::Jws;
use crate::key::PublicKey;
use crate::order::{NewOrder, OrderData};
use crate::order::{NewOrder, Order, OrderData};
use crate::request::Request;
use crate::Error;
@ -117,6 +118,31 @@ impl Account {
})
}
/// Prepare a JSON POST request.
fn post_request_raw_payload(
&self,
url: &str,
nonce: &str,
payload: String,
) -> Result<Request, Error> {
let key = PKey::private_key_from_pem(self.private_key.as_bytes())?;
let body = serde_json::to_string(&Jws::new_full(
&key,
Some(self.location.clone()),
url.to_owned(),
nonce.to_owned(),
payload,
)?)?;
Ok(Request {
url: url.to_owned(),
method: "POST",
content_type: crate::request::JSON_CONTENT_TYPE,
body,
expected: 200,
})
}
/// Get the "key authorization" for a token.
pub fn key_authorization(&self, token: &str) -> Result<String, Error> {
let key = PKey::private_key_from_pem(self.private_key.as_bytes())?;
@ -131,6 +157,67 @@ impl Account {
let digest = openssl::sha::sha256(key_authorization.as_bytes());
Ok(b64u::encode(&digest))
}
/// Prepare a request to update account data.
///
/// This is a rather low level interface. You should know what you're doing.
pub fn update_account_request<T: Serialize>(
&self,
nonce: &str,
data: &T,
) -> Result<Request, Error> {
self.post_request(&self.location, nonce, data)
}
/// Prepare a request to deactivate this account.
///
/// This is a rather low level interface. You should know what you're doing.
pub fn deactivate_account_request<T: Serialize>(&self, nonce: &str) -> Result<Request, Error> {
self.post_request_raw_payload(
&self.location,
nonce,
r#"{"status":"deactivated"}"#.to_string(),
)
}
/// Prepare a request to query an Authorization for an Order.
///
/// Returns `Ok(None)` if `auth_index` is out of out of range. You can query the number of
/// authorizations from via [`Order::authorization_len`] or by manually inspecting its
/// `.data.authorization` vector.
pub fn get_authorization(
&self,
order: &Order,
auth_index: usize,
nonce: &str,
) -> Result<Option<GetAuthorization>, Error> {
match order.authorization(auth_index) {
None => Ok(None),
Some(url) => Ok(Some(GetAuthorization::new(self.get_request(url, nonce)?))),
}
}
/// Prepare a request to validate a Challenge from an Authorization.
///
/// Returns `Ok(None)` if `challenge_index` is out of out of range. You can query the number of
/// challenges from via [`Authorization::challenge_len`] or by manually inspecting its
/// `.challenges` vector.
///
/// This returns a raw `Request` since validation takes some time and the `Authorization`
/// object has to be re-queried and its `status` inspected.
pub fn validate_challenge(
&self,
authorization: &Authorization,
challenge_index: usize,
nonce: &str,
) -> Result<Option<Request>, Error> {
match authorization.challenges.get(challenge_index) {
None => Ok(None),
Some(challenge) => self
.post_request_raw_payload(&challenge.url, nonce, "{}".to_string())
.map(Some),
}
}
}
#[derive(Clone, Copy, Eq, PartialEq, Deserialize, Serialize)]

View File

@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::order::Identifier;
use crate::request::Request;
use crate::Error;
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
@ -32,12 +34,25 @@ pub struct Authorization {
pub wildcard: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ChallengeStatus {
Pending,
Processing,
Valid,
Invalid,
}
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Challenge {
#[serde(rename = "type")]
pub ty: String,
pub status: ChallengeStatus,
pub url: String,
#[serde(flatten)]
pub data: HashMap<String, Value>,
}
@ -55,3 +70,24 @@ impl Challenge {
fn is_false(b: &bool) -> bool {
!*b
}
/// Represents an in-flight query for an authorization.
///
/// This is created via [`Account::get_authorization`].
pub struct GetAuthorization {
//order: OrderData,
pub request: Option<Request>,
}
impl GetAuthorization {
pub(crate) fn new(request: Request) -> Self {
Self {
request: Some(request),
}
}
/// Deal with the response we got from the server.
pub fn response(self, response_body: &[u8]) -> Result<Authorization, Error> {
Ok(serde_json::from_slice(response_body)?)
}
}

View File

@ -100,6 +100,18 @@ pub struct Order {
pub data: OrderData,
}
impl Order {
/// Get an authorization URL (or `None` if the index is out of range).
pub fn authorization(&self, index: usize) -> Option<&str> {
Some(&self.data.authorizations.get(index)?)
}
/// Get the number of authorizations in this object.
pub fn authorization_len(&self) -> usize {
self.data.authorizations.len()
}
}
/// Represents a new in-flight order creation.
///
/// This is created via [`Account::new_order`].