use alloc::vec::Vec;
use crate::iso20::messages::{
MeteringConfirmationReq, PnCAReqAuthorizationMode, SignedMeteringData,
};
use crate::session::SessionId;
use crate::iso20::common::{
CanonicalizationMethod, DigestMethod, Reference, Signature, SignatureMethod, SignatureValue,
SignedInfo, Transform, Transforms,
};
use super::{
CANONICAL_EXI, GenChallenge, Hash, PncError, Sign, Signed, Suite, Verify,
check_canonicalization, check_echo, check_reference, check_session, check_suite,
check_transforms, pair,
};
pub const SUITES: &[Suite] = &[Suite::EcdsaSha256, Suite::EcdsaSha512];
pub fn sign(
suite: Suite,
elements: &[Signed<'_>],
hash: &impl Hash,
signer: &impl Sign,
) -> Result<Signature, PncError> {
let signed_info = build_signed_info(suite, elements, hash);
let canonical = signed_info.to_xmldsig_fragment()?;
let value = signer.sign(suite, &canonical)?;
Ok(Signature { id: None, signed_info, signature_value: SignatureValue { id: None, value } })
}
#[must_use]
pub fn build_signed_info(suite: Suite, elements: &[Signed<'_>], hash: &impl Hash) -> SignedInfo {
SignedInfo {
id: None,
canonicalization_method: CanonicalizationMethod { algorithm: CANONICAL_EXI.into() },
signature_method: SignatureMethod {
algorithm: suite.signature_algorithm().into(),
hmac_output_length: None,
},
reference: elements
.iter()
.map(|e| Reference {
id: None,
r#type: None,
uri: Some(super::reference_uri(e.id)),
transforms: Some(Transforms {
transform: alloc::vec![Transform { algorithm: CANONICAL_EXI.into() }],
}),
digest_method: DigestMethod { algorithm: suite.digest_algorithm().into() },
digest_value: hash.digest(suite, e.fragment),
})
.collect(),
}
}
pub fn verify(
signature: &Signature,
elements: &[Signed<'_>],
accepted: &[Suite],
hash: &impl Hash,
verifier: &impl Verify,
) -> Result<(), PncError> {
let info = &signature.signed_info;
check_canonicalization(&info.canonicalization_method.algorithm)?;
let suite = check_suite(
&info.signature_method.algorithm,
info.signature_method.hmac_output_length,
accepted,
)?;
for (reference, element) in pair(&info.reference, |r| r.uri.as_deref(), elements)? {
check_transforms(transform_algorithms(reference.transforms.as_ref()))?;
check_reference(
suite,
&reference.digest_method.algorithm,
&reference.digest_value,
element,
hash,
)?;
}
let canonical = info.to_xmldsig_fragment()?;
verifier.verify(suite, &canonical, &signature.signature_value.value)
}
fn transform_algorithms(transforms: Option<&Transforms>) -> Option<impl Iterator<Item = &str>> {
Some(transforms?.transform.iter().map(|t| t.algorithm.as_str()))
}
pub fn canonical_signed_info(signature: &Signature) -> Result<Vec<u8>, PncError> {
Ok(signature.signed_info.to_xmldsig_fragment()?)
}
pub fn sign_authorization(
suite: Suite,
mode: &mut PnCAReqAuthorizationMode,
challenge: &GenChallenge,
hash: &impl Hash,
signer: &impl Sign,
) -> Result<Signature, PncError> {
if mode.id.is_empty() {
mode.id = super::DEFAULT_ID.into();
}
mode.gen_challenge = challenge.as_bytes().to_vec();
let id = mode.id.clone();
let fragment = mode.to_fragment()?;
sign(suite, &[Signed::new(&id, &fragment)], hash, signer)
}
pub fn verify_authorization(
mode: &PnCAReqAuthorizationMode,
signature: &Signature,
challenge: &GenChallenge,
accepted: &[Suite],
hash: &impl Hash,
verifier: &impl Verify,
) -> Result<(), PncError> {
if !challenge.matches(&mode.gen_challenge) {
return Err(PncError::ChallengeMismatch);
}
if mode.id.is_empty() {
return Err(PncError::MissingId);
}
let fragment = mode.to_fragment()?;
verify(signature, &[Signed::new(&mode.id, &fragment)], accepted, hash, verifier)
}
pub fn sign_metering_data(
suite: Suite,
data: &mut SignedMeteringData,
hash: &impl Hash,
signer: &impl Sign,
) -> Result<Signature, PncError> {
if data.id.is_empty() {
data.id = super::DEFAULT_ID.into();
}
let id = data.id.clone();
let fragment = data.to_fragment()?;
sign(suite, &[Signed::new(&id, &fragment)], hash, signer)
}
pub fn verify_metering_data(
data: &SignedMeteringData,
signature: &Signature,
session: SessionId,
accepted: &[Suite],
hash: &impl Hash,
verifier: &impl Verify,
) -> Result<(), PncError> {
check_session(&data.session_id, session)?;
if data.id.is_empty() {
return Err(PncError::MissingId);
}
let fragment = data.to_fragment()?;
verify(signature, &[Signed::new(&data.id, &fragment)], accepted, hash, verifier)
}
pub fn verify_metering_confirmation(
request: &MeteringConfirmationReq,
issued: &SignedMeteringData,
) -> Result<(), PncError> {
check_echo(&request.signed_metering_data, issued, "SignedMeteringData")
}