use std::collections::HashMap;
use openssl::{
sha::sha256,
};
use serde::{self, Serialize, Deserialize};
use serde_json::Value;
use crate::{
directory::{AcmeBoundDirectory},
error::{Error, Result},
helper::b64,
identifier::AcmeIdentifier,
key::KeyAlg,
problem::AcmeProblem,
};
pub const CHALLENGE_TYPE_DNS_01: &str = "dns-01";
pub const CHALLENGE_TYPE_HTTP_01: &str = "http-01";
pub const CHALLENGE_TYPE_TLS_ALPN_01: &str = "tls-alpn-01";
#[derive(Debug, Serialize, Deserialize)]
pub struct AcmeAuthorization {
pub identifier: AcmeIdentifier,
pub status: String,
pub expires: Option<String>,
pub challenges: Vec<AcmeChallenge>,
pub wildcard: Option<bool>,
}
impl AcmeAuthorization {
pub fn get_challenge_by_type<'a, 'b>(&'a self, challenge_type: &'b str) -> Option<&'a AcmeChallenge> {
for ref challenge in &self.challenges {
if challenge.challenge_type == challenge_type {
return Some(challenge)
}
}
None
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AcmeChallenge {
#[serde(rename="type")]
pub challenge_type: String,
pub url: String,
pub status: String,
pub validated: Option<String>,
pub error: Option<AcmeProblem>,
pub token: Option<String>,
#[serde(flatten)]
pub additional_fields: HashMap<String, Value>,
}
impl AcmeChallenge {
pub fn is_dns_01(&self) -> bool {
self.challenge_type == CHALLENGE_TYPE_DNS_01
}
pub fn is_http_01(&self) -> bool {
self.challenge_type == CHALLENGE_TYPE_HTTP_01
}
pub fn is_tls_alpn_01(&self) -> bool {
self.challenge_type == CHALLENGE_TYPE_TLS_ALPN_01
}
pub fn get_txt_record(&self, key: &KeyAlg) -> Result<String> {
if self.is_dns_01() {
if let Some(ref token) = self.token {
let key_authorization = key.get_key_authorization(token)?;
let challenge_value = format!("\"{}\"", b64(sha256(&key_authorization.as_bytes())));
Ok(challenge_value)
} else {
Err(Error::missing_token(format!("ACME server did not send a token for challenge {:#}", self.challenge_type)))
}
} else {
Err(Error::challenge_type_not_applicable(format!("Challenge type {:#} does not use TXT records", self.challenge_type)))
}
}
pub async fn respond(&self, dir: &AcmeBoundDirectory) -> Result<AcmeChallenge> {
dir.respond_challenge(&self.url).await
}
}