use alloc::vec::Vec;
use crate::iso2::{
AuthorizationReq, CanonicalizationMethod, DigestMethod, MeterInfo, MeteringReceiptReq,
Reference, Signature, SignatureMethod, SignatureValue, SignedInfo, Transform, Transforms,
};
use crate::session::SessionId;
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 SUITE: Suite = Suite::EcdsaSha256;
pub fn sign(
elements: &[Signed<'_>],
hash: &impl Hash,
signer: &impl Sign,
) -> Result<Signature, PncError> {
let signed_info = build_signed_info(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(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<'_>],
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,
&[SUITE],
)?;
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(
request: &mut AuthorizationReq,
challenge: &GenChallenge,
hash: &impl Hash,
signer: &impl Sign,
) -> Result<Signature, PncError> {
request.id.get_or_insert_with(|| super::DEFAULT_ID.into());
request.gen_challenge = Some(challenge.as_bytes().to_vec());
let id = request.id.clone().unwrap_or_default();
let fragment = authorization_fragment(request)?;
sign(&[Signed::new(&id, &fragment)], hash, signer)
}
pub fn verify_authorization(
request: &AuthorizationReq,
signature: &Signature,
challenge: &GenChallenge,
hash: &impl Hash,
verifier: &impl Verify,
) -> Result<(), PncError> {
let echoed = request.gen_challenge.as_deref().ok_or(PncError::MissingChallenge)?;
if !challenge.matches(echoed) {
return Err(PncError::ChallengeMismatch);
}
let id = request.id.as_deref().ok_or(PncError::MissingId)?;
let fragment = authorization_fragment(request)?;
verify(signature, &[Signed::new(id, &fragment)], hash, verifier)
}
fn authorization_fragment(request: &AuthorizationReq) -> Result<Vec<u8>, PncError> {
Ok(request.to_fragment()?)
}
pub fn sign_metering_receipt(
receipt: &mut MeteringReceiptReq,
hash: &impl Hash,
signer: &impl Sign,
) -> Result<Signature, PncError> {
receipt.id.get_or_insert_with(|| super::DEFAULT_ID.into());
let id = receipt.id.clone().unwrap_or_default();
let fragment = receipt.to_fragment()?;
sign(&[Signed::new(&id, &fragment)], hash, signer)
}
pub fn verify_metering_receipt(
receipt: &MeteringReceiptReq,
signature: &Signature,
issued: &MeterInfo,
session: SessionId,
hash: &impl Hash,
verifier: &impl Verify,
) -> Result<(), PncError> {
check_session(&receipt.session_id, session)?;
check_echo(&receipt.meter_info, issued, "MeterInfo")?;
let id = receipt.id.as_deref().ok_or(PncError::MissingId)?;
let fragment = receipt.to_fragment()?;
verify(signature, &[Signed::new(id, &fragment)], hash, verifier)
}