#![allow(clippy::module_name_repetitions)]
use std::{borrow::Cow, io::BufRead, num::ParseIntError, str::FromStr};
use quick_xml::DeError;
use serde::{Deserialize, Serialize};
use crate::common::{ActionType, ActivityId, BinarySecurityToken, Header, RequestId, TokenType};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WstepResponse<'a> {
pub envelope: ResponseEnvelope<'a>,
}
impl<'a> WstepResponse<'a> {
#[must_use]
pub fn new(header: Header<'a>, body: ResponseBody<'a>) -> Self {
Self {
envelope: ResponseEnvelope::new(header, body),
}
}
#[must_use]
pub fn new_issued_x509v3(
activity_id: ActivityId<'a>,
relates_to: &'a str,
request_id: &'a str,
issuing_ca: BinarySecurityToken<'a>,
issued_certificate: BinarySecurityToken<'a>,
) -> Self {
Self::new(
Header::new_response_header(
ActionType::RequestSecurityTokenResponseCollection,
activity_id,
relates_to,
),
ResponseBody {
value: ResponseOutcome::Success(RequestSecurityTokenResponseCollection::new(
RequestSecurityTokenResponse::new_issued_x509v3(
request_id,
issuing_ca,
issued_certificate,
),
)),
},
)
}
#[must_use]
pub fn new_key_exchange_token(
activity_id: ActivityId<'a>,
relates_to: &'a str,
key_exchange_token: BinarySecurityToken<'a>,
) -> Self {
Self::new(
Header::new_response_header(ActionType::KeyExchangeTokenFinal, activity_id, relates_to),
ResponseBody {
value: ResponseOutcome::Success(RequestSecurityTokenResponseCollection::new(
RequestSecurityTokenResponse::new_key_exchange_token(key_exchange_token),
)),
},
)
}
#[must_use]
pub fn new_fault(
fault_type: FaultType,
activity_id: ActivityId<'a>,
relates_to: &'a str,
fault: Fault<'a>,
) -> Self {
Self::new(
Header::new_response_header(ActionType::from(fault_type), activity_id, relates_to),
ResponseBody {
value: ResponseOutcome::Fault(fault),
},
)
}
pub fn new_from_soap_xml_str(xml: &str) -> Result<Self, WstepResponseError> {
let envelope = quick_xml::de::from_str(xml)?;
Ok(Self { envelope })
}
pub fn new_from_soap_xml_reader(reader: impl BufRead) -> Result<Self, WstepResponseError> {
let envelope = quick_xml::de::from_reader(reader)?;
Ok(Self { envelope })
}
pub const fn requested_token_collection(
&self,
) -> Result<&RequestSecurityTokenResponseCollection<'_>, &Fault<'_>> {
match &self.envelope.body.value {
ResponseOutcome::Success(collection) => Ok(collection),
ResponseOutcome::Fault(fault) => Err(fault),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FaultType {
DispatcherFault,
DetailFault,
SoapFault,
}
impl From<FaultType> for ActionType {
fn from(value: FaultType) -> Self {
match value {
FaultType::DispatcherFault => Self::Fault,
FaultType::DetailFault => Self::FaultDetail,
FaultType::SoapFault => Self::SoapFault,
}
}
}
#[derive(Debug, Clone)]
pub enum WstepResponseError {
Parse(DeError),
}
impl From<DeError> for WstepResponseError {
fn from(value: DeError) -> Self {
Self::Parse(value)
}
}
impl std::fmt::Display for WstepResponseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Parse(error) => write!(f, "parse error: {error}"),
}
}
}
impl std::error::Error for WstepResponseError {}
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename = "s:Envelope")]
pub struct ResponseEnvelope<'a> {
#[serde(rename = "@xmlns:s")]
pub xmlns_s: Cow<'a, str>,
#[serde(rename = "@xmlns:a")]
pub xmlns_a: Cow<'a, str>,
#[serde(rename = "s:Header")]
#[serde(alias = "Header")]
pub header: Header<'a>,
#[serde(rename = "Body")]
pub body: ResponseBody<'a>,
}
impl<'a> ResponseEnvelope<'a> {
const XMLNS_S: &'static str = "http://www.w3.org/2003/05/soap-envelope";
const XMLNS_A: &'static str = "http://www.w3.org/2005/08/addressing";
#[must_use]
pub fn new(header: Header<'a>, body: ResponseBody<'a>) -> Self {
Self {
xmlns_s: Self::XMLNS_S.into(),
xmlns_a: Self::XMLNS_A.into(),
header,
body,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct ResponseBody<'a> {
#[serde(rename = "$value")]
pub value: ResponseOutcome<'a>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "PascalCase")]
#[allow(clippy::large_enum_variant)]
pub enum ResponseOutcome<'a> {
#[serde(rename = "RequestSecurityTokenResponseCollection")]
Success(RequestSecurityTokenResponseCollection<'a>),
#[serde(rename = "Fault")]
Fault(Fault<'a>),
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename = "RequestSecurityTokenResponseCollection")]
pub struct RequestSecurityTokenResponseCollection<'a> {
#[serde(rename = "@xmlns")]
pub xmlns: Cow<'a, str>,
#[serde(rename = "RequestSecurityTokenResponse")]
pub request_security_token_response: RequestSecurityTokenResponse<'a>,
}
impl<'a> RequestSecurityTokenResponseCollection<'a> {
const XMLNS: &'a str = "http://docs.oasis-open.org/ws-sx/ws-trust/200512";
#[must_use]
pub fn new(request_security_token_response: RequestSecurityTokenResponse<'a>) -> Self {
Self {
xmlns: Self::XMLNS.into(),
request_security_token_response,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct RequestSecurityTokenResponse<'a> {
pub requested_security_token: RequestedSecurityToken<'a>,
pub token_type: TokenType<'a>,
pub binary_security_token: Option<BinarySecurityToken<'a>>,
pub disposition_message: Option<DispositionMessage<'a>>,
#[serde(rename = "RequestID")]
pub request_id: Option<RequestId<'a>>,
}
impl<'a> RequestSecurityTokenResponse<'a> {
#[must_use]
pub fn new_issued_x509v3(
request_id: &'a str,
issuing_ca: BinarySecurityToken<'a>,
issued_certificate: BinarySecurityToken<'a>,
) -> Self {
Self {
requested_security_token: RequestedSecurityToken::BinarySecurityToken {
binary_security_token: issued_certificate,
},
token_type: TokenType::x509v3(),
binary_security_token: Some(issuing_ca),
disposition_message: Some(DispositionMessage::issued()),
request_id: Some(RequestId::new_with_id(request_id)),
}
}
#[must_use]
pub fn new_key_exchange_token(key_exchange_token: BinarySecurityToken<'a>) -> Self {
Self {
requested_security_token: RequestedSecurityToken::KeyExchangeToken {
key_exchange_token: KeyExchangeToken {
binary_security_token: key_exchange_token,
},
},
token_type: TokenType::x509v3(),
binary_security_token: None,
disposition_message: None,
request_id: None,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct DispositionMessage<'a> {
#[serde(rename = "@lang")]
pub lang: Cow<'a, str>,
#[serde(rename = "@xmlns")]
pub xmlns: Cow<'a, str>,
#[serde(rename = "$text")]
pub value: Cow<'a, str>,
}
impl DispositionMessage<'_> {
const LANG: &'static str = "en-US";
const XMLNS: &'static str = "http://schemas.microsoft.com/windows/pki/2009/01/enrollment";
const MSG_ISSUED: &'static str = "Issued";
#[must_use]
pub fn issued() -> Self {
Self {
lang: Self::LANG.into(),
xmlns: Self::XMLNS.into(),
value: Self::MSG_ISSUED.into(),
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(untagged)]
pub enum RequestedSecurityToken<'a> {
#[serde(rename_all = "PascalCase")]
BinarySecurityToken {
binary_security_token: BinarySecurityToken<'a>,
},
#[serde(rename_all = "PascalCase")]
KeyExchangeToken {
key_exchange_token: KeyExchangeToken<'a>,
},
}
impl<'a> RequestedSecurityToken<'a> {
#[must_use]
pub const fn binary_security_token_value(&self) -> &Cow<'a, str> {
match self {
RequestedSecurityToken::BinarySecurityToken {
binary_security_token,
} => &binary_security_token.value,
RequestedSecurityToken::KeyExchangeToken { key_exchange_token } => {
&key_exchange_token.binary_security_token.value
}
}
}
}
impl std::fmt::Display for RequestedSecurityToken<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let bst = self.binary_security_token_value();
write!(f, "{bst}")
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct KeyExchangeToken<'a> {
pub binary_security_token: BinarySecurityToken<'a>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Fault<'a> {
#[serde(rename = "s:Code")]
#[serde(alias = "Code")]
pub code: FaultCode<'a>,
#[serde(rename = "s:Reason")]
#[serde(alias = "Reason")]
pub reason: FaultReason<'a>,
#[serde(rename = "s:Detail", skip_serializing_if = "Option::is_none")]
#[serde(alias = "Detail")]
pub detail: Option<FaultDetail<'a>>,
}
impl std::fmt::Display for Fault<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for Fault<'_> {}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct FaultCode<'a> {
#[serde(rename = "s:Value")]
#[serde(alias = "Value")]
pub value: FaultCodeValue<'a>,
#[serde(rename = "s:Subcode", skip_serializing_if = "Option::is_none")]
#[serde(alias = "Subcode")]
pub subcode: Option<FaultSubcode<'a>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct FaultCodeValue<'a> {
#[serde(rename = "$text")]
pub value: Cow<'a, str>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct FaultSubcode<'a> {
#[serde(rename = "s:Value")]
#[serde(alias = "Value")]
pub value: FaultSubcodeValue<'a>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct FaultSubcodeValue<'a> {
#[serde(rename = "@xmlns:a", skip_serializing_if = "Option::is_none")]
pub xmlns_a: Option<Cow<'a, str>>,
#[serde(rename = "$text")]
pub value: Cow<'a, str>,
}
impl<'a> FaultSubcodeValue<'a> {
const XMLNS_A: &'static str =
"http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher";
#[must_use]
pub fn new(value: &'a str) -> Self {
Self {
xmlns_a: Some(Self::XMLNS_A.into()),
value: value.into(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct FaultReason<'a> {
#[serde(rename = "s:Text")]
#[serde(alias = "Text")]
pub text: FaultReasonText<'a>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct FaultReasonText<'a> {
#[serde(rename = "@lang")]
pub lang: Cow<'a, str>,
#[serde(rename = "$text", skip_serializing_if = "Option::is_none")]
pub value: Option<Cow<'a, str>>,
}
impl<'a> FaultReasonText<'a> {
const LANG_EN_US: &'static str = "en-US";
#[must_use]
pub fn new(value: Option<&'a str>) -> Self {
Self {
lang: Self::LANG_EN_US.into(),
value: value.map(Into::into),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct FaultDetail<'a> {
#[serde(rename = "CertificateEnrollmentWSDetail")]
pub certificate_enrollment_ws_detail: CertificateEnrollmentWsDetail<'a>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct CertificateEnrollmentWsDetail<'a> {
#[serde(rename = "@xmlns")]
pub xmlns: Cow<'a, str>,
#[serde(rename = "@xmlns:xsd")]
pub xmlns_xsd: Cow<'a, str>,
#[serde(rename = "@xmlns:xsi")]
pub xmlns_xsi: Cow<'a, str>,
#[serde(rename = "BinaryResponse")]
pub binary_response: BinaryResponse<'a>,
#[serde(rename = "ErrorCode")]
pub error_code: ErrorCode,
#[serde(rename = "InvalidRequest")]
pub invalid_request: InvalidRequest,
#[serde(rename = "RequestID")]
pub request_id: RequestId<'a>,
}
impl<'a> CertificateEnrollmentWsDetail<'a> {
const XMLNS: &'static str = "http://schemas.microsoft.com/windows/pki/2009/01/enrollment";
const XMLNS_XSD: &'static str = "http://www.w3.org/2001/XMLSchema";
const XMLNS_XSI: &'static str = "http://www.w3.org/2001/XMLSchema-instance";
#[must_use]
pub fn new(
binary_response: BinaryResponse<'a>,
error_code: ErrorCode,
invalid_request: InvalidRequest,
request_id: RequestId<'a>,
) -> Self {
Self {
xmlns: Self::XMLNS.into(),
xmlns_xsd: Self::XMLNS_XSD.into(),
xmlns_xsi: Self::XMLNS_XSI.into(),
binary_response,
error_code,
invalid_request,
request_id,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct BinaryResponse<'a> {
#[serde(rename = "$text")]
pub value: Cow<'a, str>,
}
impl<'a> BinaryResponse<'a> {
#[must_use]
pub fn new(value: &'a str) -> Self {
Self {
value: value.into(),
}
}
}
#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq)]
pub enum ErrorCode {
Asn1Eod,
BadRenewalCertAttribute,
BadRequestSubject,
BadTemplateVersion,
EnrollDenied,
RestrictedOfficer,
TemplateConflict,
Other(u32),
}
impl From<u32> for ErrorCode {
fn from(code: u32) -> Self {
match code {
0x8009_3102 => Self::Asn1Eod,
0x8009_400E => Self::BadRenewalCertAttribute,
0x8009_4001 => Self::BadRequestSubject,
0x8009_4807 => Self::BadTemplateVersion,
0x8009_4011 => Self::EnrollDenied,
0x8009_4009 => Self::RestrictedOfficer,
0x8009_4802 => Self::TemplateConflict,
unknown_code => Self::Other(unknown_code),
}
}
}
impl FromStr for ErrorCode {
type Err = ErrorCodeParseError;
#[allow(clippy::cast_sign_loss)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from(
i32::from_str(s).map_err(ErrorCodeParseError)? as u32
))
}
}
impl<'de> Deserialize<'de> for ErrorCode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
(<&str>::deserialize(deserializer)?)
.parse()
.map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ErrorCodeParseError(ParseIntError);
impl std::fmt::Display for ErrorCodeParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "failed to parse MS-WSTEP Fault ErrorCode: {}", self.0)
}
}
impl std::error::Error for ErrorCodeParseError {}
impl std::fmt::Display for ErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Asn1Eod => write!(f, "ASN1 unexpected end of data."),
Self::BadRenewalCertAttribute => write!(
f,
"The request contains an invalid renewal certificate attribute."
),
Self::BadRequestSubject => {
write!(f, "The request subject name is invalid or too long.")
}
Self::BadTemplateVersion => write!(
f,
"The request template version is newer than the supported template version."
),
Self::EnrollDenied => write!(
f,
"The permissions on this certification authority do not allow the current user to enroll for certificates."
),
Self::RestrictedOfficer => write!(f, "The operation is denied. It can only be performed by a certificate manager that is allowed to manage certificates for the current requester."),
Self::TemplateConflict => write!(f, "The request contains conflicting template information."),
Self::Other(code) => write!(f, "Unknown error description for code {code}."),
}
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct InvalidRequest {
#[serde(rename = "$text")]
pub value: bool,
}
impl InvalidRequest {
#[must_use]
pub const fn new(invalid: bool) -> Self {
Self { value: invalid }
}
}