1use base64::Engine;
9use getrandom::SysRng;
10use p256::ecdsa::{Signature as P256Signature, SigningKey as P256SigningKey};
11use p256::pkcs8::DecodePrivateKey;
12use p384::ecdsa::{Signature as P384Signature, SigningKey as P384SigningKey};
13use roxmltree::{Document, Node};
14use rsa::RsaPrivateKey;
15use rsa::pkcs1v15::Signature as RsaPkcs1v15Signature;
16use rsa::pkcs1v15::SigningKey as RsaPkcs1v15SigningKey;
17use rsa::signature::{RandomizedSigner, SignatureEncoding, Signer};
18use sha2::{Sha256, Sha384, Sha512};
19use std::collections::HashSet;
20
21use crate::c14n::canonicalize;
22
23use super::builder::{SignatureBuilder, SignatureBuilderError};
24use super::digest::{DigestAlgorithm, compute_digest};
25use super::mutation::{
26 XmlMutationError, append_signature_to_root, fill_signature_value,
27 fill_signed_info_digest_values,
28};
29use super::parse::{SignatureAlgorithm, XMLDSIG_NS, parse_signed_info};
30use super::transforms::{Transform, execute_transforms, parse_transforms};
31use super::types::TransformError;
32use super::uri::UriReferenceResolver;
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36#[must_use = "use the computed digest value to fill the corresponding <DigestValue>"]
37pub struct ComputedReferenceDigest {
38 pub index: usize,
40 pub uri: String,
42 pub digest_method: DigestAlgorithm,
44 pub digest_value: String,
46}
47
48#[derive(Debug, thiserror::Error)]
50pub enum SigningDigestError {
51 #[error("XML parse error: {0}")]
53 XmlParse(#[from] roxmltree::Error),
54
55 #[error("missing required element: <{element}>")]
57 MissingElement {
58 element: &'static str,
60 },
61
62 #[error("invalid signing template: {0}")]
64 InvalidStructure(String),
65
66 #[error("unsupported digest algorithm: {uri}")]
68 UnsupportedAlgorithm {
69 uri: String,
71 },
72
73 #[error("digest algorithm is disabled for signing: {uri}")]
75 SigningAlgorithmDisabled {
76 uri: &'static str,
78 },
79
80 #[error("reference processing error: {0}")]
82 Transform(#[from] TransformError),
83
84 #[error("XML mutation error: {0}")]
86 XmlMutation(#[from] XmlMutationError),
87}
88
89#[derive(Debug, thiserror::Error)]
91pub enum SigningError {
92 #[error("signing digest pass failed: {0}")]
94 Digest(#[from] SigningDigestError),
95
96 #[error("failed to parse SignedInfo after digest fill: {0}")]
98 ParseSignedInfo(#[from] super::parse::ParseError),
99
100 #[error("SignedInfo canonicalization failed: {0}")]
102 Canonicalization(#[from] crate::c14n::C14nError),
103
104 #[error("signing key error: {0}")]
106 Key(#[from] SigningKeyError),
107
108 #[error("XML mutation error: {0}")]
110 XmlMutation(#[from] XmlMutationError),
111
112 #[error("signature template error: {0}")]
114 Template(#[from] SignatureBuilderError),
115}
116
117#[derive(Debug, thiserror::Error)]
119#[non_exhaustive]
120pub enum SigningKeyError {
121 #[error("invalid PEM private key")]
123 InvalidKeyPem,
124
125 #[error("invalid key format: expected PRIVATE KEY PEM, got {label}")]
127 InvalidKeyFormat {
128 label: String,
130 },
131
132 #[error("invalid PKCS#8 private key DER")]
134 InvalidKeyDer,
135
136 #[error("signing key does not support algorithm: {uri}")]
138 UnsupportedAlgorithm {
139 uri: String,
141 },
142
143 #[error("private-key signing operation failed")]
145 SigningFailed,
146}
147
148pub trait SigningKey {
150 fn sign(
152 &self,
153 algorithm: SignatureAlgorithm,
154 canonical_signed_info: &[u8],
155 ) -> Result<Vec<u8>, SigningKeyError>;
156}
157
158pub struct RsaSigningKey {
160 key: RsaPrivateKey,
161}
162
163impl RsaSigningKey {
164 pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
166 let private_key_der = parse_private_key_pem(private_key_pem)?;
167 Self::from_pkcs8_der(&private_key_der)
168 }
169
170 pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
172 let key = RsaPrivateKey::from_pkcs8_der(private_key_der)
173 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
174 Ok(Self { key })
175 }
176}
177
178impl SigningKey for RsaSigningKey {
179 fn sign(
180 &self,
181 algorithm: SignatureAlgorithm,
182 canonical_signed_info: &[u8],
183 ) -> Result<Vec<u8>, SigningKeyError> {
184 match algorithm {
185 SignatureAlgorithm::RsaSha256 => sign_rsa_pkcs1v15_with_rng(
186 RsaPkcs1v15SigningKey::<Sha256>::new(self.key.clone()),
187 canonical_signed_info,
188 ),
189 SignatureAlgorithm::RsaSha384 => sign_rsa_pkcs1v15_with_rng(
190 RsaPkcs1v15SigningKey::<Sha384>::new(self.key.clone()),
191 canonical_signed_info,
192 ),
193 SignatureAlgorithm::RsaSha512 => sign_rsa_pkcs1v15_with_rng(
194 RsaPkcs1v15SigningKey::<Sha512>::new(self.key.clone()),
195 canonical_signed_info,
196 ),
197 _ => Err(SigningKeyError::UnsupportedAlgorithm {
198 uri: algorithm.uri().to_string(),
199 }),
200 }
201 }
202}
203
204fn sign_rsa_pkcs1v15_with_rng(
205 key: impl RandomizedSigner<RsaPkcs1v15Signature>,
206 canonical_signed_info: &[u8],
207) -> Result<Vec<u8>, SigningKeyError> {
208 let signature = key
209 .try_sign_with_rng(&mut SysRng, canonical_signed_info)
210 .map_err(|_| SigningKeyError::SigningFailed)?;
211 Ok(signature.to_vec())
212}
213
214pub struct EcdsaP256SigningKey {
216 key: P256SigningKey,
217}
218
219impl EcdsaP256SigningKey {
220 pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
222 let private_key_der = parse_private_key_pem(private_key_pem)?;
223 Self::from_pkcs8_der(&private_key_der)
224 }
225
226 pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
228 let key = P256SigningKey::from_pkcs8_der(private_key_der)
229 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
230 Ok(Self { key })
231 }
232}
233
234impl SigningKey for EcdsaP256SigningKey {
235 fn sign(
236 &self,
237 algorithm: SignatureAlgorithm,
238 canonical_signed_info: &[u8],
239 ) -> Result<Vec<u8>, SigningKeyError> {
240 if algorithm != SignatureAlgorithm::EcdsaP256Sha256 {
241 return Err(SigningKeyError::UnsupportedAlgorithm {
242 uri: algorithm.uri().to_string(),
243 });
244 }
245 let signature: P256Signature = self
246 .key
247 .try_sign(canonical_signed_info)
248 .map_err(|_| SigningKeyError::SigningFailed)?;
249 Ok(signature.to_bytes().to_vec())
250 }
251}
252
253pub struct EcdsaP384SigningKey {
255 key: P384SigningKey,
256}
257
258impl EcdsaP384SigningKey {
259 pub fn from_pkcs8_pem(private_key_pem: &str) -> Result<Self, SigningKeyError> {
261 let private_key_der = parse_private_key_pem(private_key_pem)?;
262 Self::from_pkcs8_der(&private_key_der)
263 }
264
265 pub fn from_pkcs8_der(private_key_der: &[u8]) -> Result<Self, SigningKeyError> {
267 let key = P384SigningKey::from_pkcs8_der(private_key_der)
268 .map_err(|_| SigningKeyError::InvalidKeyDer)?;
269 Ok(Self { key })
270 }
271}
272
273impl SigningKey for EcdsaP384SigningKey {
274 fn sign(
275 &self,
276 algorithm: SignatureAlgorithm,
277 canonical_signed_info: &[u8],
278 ) -> Result<Vec<u8>, SigningKeyError> {
279 if algorithm != SignatureAlgorithm::EcdsaP384Sha384 {
280 return Err(SigningKeyError::UnsupportedAlgorithm {
281 uri: algorithm.uri().to_string(),
282 });
283 }
284 let signature: P384Signature = self
285 .key
286 .try_sign(canonical_signed_info)
287 .map_err(|_| SigningKeyError::SigningFailed)?;
288 Ok(signature.to_bytes().to_vec())
289 }
290}
291
292pub struct SignContext<'a> {
294 signing_key: &'a dyn SigningKey,
295}
296
297impl<'a> SignContext<'a> {
298 pub fn new(signing_key: &'a dyn SigningKey) -> Self {
300 Self { signing_key }
301 }
302
303 pub fn sign_template(&self, xml: &str) -> Result<String, SigningError> {
310 let with_digests = fill_reference_digest_values(xml)?;
311 let (algorithm, canonical_signed_info) = canonicalize_signed_info(&with_digests)?;
312 let signature_value = self.signing_key.sign(algorithm, &canonical_signed_info)?;
313 let signature_b64 = base64::engine::general_purpose::STANDARD.encode(signature_value);
314 Ok(fill_signature_value(&with_digests, &signature_b64)?)
315 }
316
317 pub fn sign_with_builder(
319 &self,
320 xml: &str,
321 builder: &SignatureBuilder,
322 ) -> Result<String, SigningError> {
323 let template = builder.build_template()?;
324 let templated = append_signature_to_root(xml, &template)?;
325 self.sign_template(&templated)
326 }
327}
328
329#[derive(Debug)]
330struct SigningReference {
331 uri: String,
332 transforms: Vec<Transform>,
333 digest_method: DigestAlgorithm,
334}
335
336pub fn compute_reference_digest_values(
342 xml: &str,
343) -> Result<Vec<ComputedReferenceDigest>, SigningDigestError> {
344 let doc = Document::parse(xml)?;
345 let signature = find_single_signature_node(&doc)?;
346 let signed_info = find_required_child(signature, "SignedInfo")?;
347 let references = parse_signing_references(signed_info)?;
348 let resolver = UriReferenceResolver::new(&doc);
349
350 references
351 .into_iter()
352 .enumerate()
353 .map(|(index, reference)| {
354 let initial_data = resolver.dereference(&reference.uri)?;
355 let pre_digest = execute_transforms(signature, initial_data, &reference.transforms)?;
356 let digest = compute_digest(reference.digest_method, &pre_digest);
357 let digest_value = base64::engine::general_purpose::STANDARD.encode(digest);
358 Ok(ComputedReferenceDigest {
359 index,
360 uri: reference.uri,
361 digest_method: reference.digest_method,
362 digest_value,
363 })
364 })
365 .collect()
366}
367
368pub fn fill_reference_digest_values(xml: &str) -> Result<String, SigningDigestError> {
375 let digest_values = compute_reference_digest_values(xml)?
376 .into_iter()
377 .map(|digest| digest.digest_value);
378 Ok(fill_signed_info_digest_values(xml, digest_values)?)
379}
380
381fn canonicalize_signed_info(xml: &str) -> Result<(SignatureAlgorithm, Vec<u8>), SigningError> {
382 let doc = Document::parse(xml).map_err(SigningDigestError::XmlParse)?;
383 let signature = find_single_signature_node(&doc).map_err(SigningError::Digest)?;
384 let signed_info_node =
385 find_required_child(signature, "SignedInfo").map_err(SigningError::Digest)?;
386 let signed_info = parse_signed_info(signed_info_node)?;
387 let signed_info_subtree: HashSet<_> = signed_info_node
388 .descendants()
389 .map(|node: Node<'_, '_>| node.id())
390 .collect();
391 let mut canonical_signed_info = Vec::new();
392 canonicalize(
393 &doc,
394 Some(&|node| signed_info_subtree.contains(&node.id())),
395 &signed_info.c14n_method,
396 &mut canonical_signed_info,
397 )?;
398 Ok((signed_info.signature_method, canonical_signed_info))
399}
400
401fn parse_private_key_pem(private_key_pem: &str) -> Result<Vec<u8>, SigningKeyError> {
402 let (rest, pem) = x509_parser::pem::parse_x509_pem(private_key_pem.as_bytes())
403 .map_err(|_| SigningKeyError::InvalidKeyPem)?;
404 if !rest.iter().all(|byte| byte.is_ascii_whitespace()) {
405 return Err(SigningKeyError::InvalidKeyPem);
406 }
407 if pem.label != "PRIVATE KEY" {
408 return Err(SigningKeyError::InvalidKeyFormat { label: pem.label });
409 }
410 Ok(pem.contents)
411}
412
413fn find_single_signature_node<'a>(
414 doc: &'a Document<'a>,
415) -> Result<Node<'a, 'a>, SigningDigestError> {
416 let mut signatures = doc.descendants().filter(|node| {
417 node.is_element()
418 && node.tag_name().name() == "Signature"
419 && node.tag_name().namespace() == Some(XMLDSIG_NS)
420 });
421 let signature = signatures
422 .next()
423 .ok_or(SigningDigestError::MissingElement {
424 element: "Signature",
425 })?;
426 if signatures.next().is_some() {
427 return Err(SigningDigestError::InvalidStructure(
428 "expected exactly one <ds:Signature> element".into(),
429 ));
430 }
431 Ok(signature)
432}
433
434fn parse_signing_references(
435 signed_info: Node<'_, '_>,
436) -> Result<Vec<SigningReference>, SigningDigestError> {
437 verify_ds_element(signed_info, "SignedInfo")?;
438 let mut children = element_children(signed_info);
439
440 let c14n_node = children.next().ok_or(SigningDigestError::MissingElement {
441 element: "CanonicalizationMethod",
442 })?;
443 verify_ds_element(c14n_node, "CanonicalizationMethod")?;
444 required_algorithm_attr(c14n_node, "CanonicalizationMethod")?;
445
446 let signature_method_node = children.next().ok_or(SigningDigestError::MissingElement {
447 element: "SignatureMethod",
448 })?;
449 verify_ds_element(signature_method_node, "SignatureMethod")?;
450 required_algorithm_attr(signature_method_node, "SignatureMethod")?;
451
452 let mut references = Vec::new();
453 for child in children {
454 verify_ds_element(child, "Reference")?;
455 references.push(parse_signing_reference(child)?);
456 }
457 if references.is_empty() {
458 return Err(SigningDigestError::MissingElement {
459 element: "Reference",
460 });
461 }
462 Ok(references)
463}
464
465fn parse_signing_reference(
466 reference_node: Node<'_, '_>,
467) -> Result<SigningReference, SigningDigestError> {
468 let uri = reference_node
469 .attribute("URI")
470 .ok_or_else(|| {
471 SigningDigestError::InvalidStructure(
472 "signing Reference must include URI attribute".into(),
473 )
474 })?
475 .to_string();
476 let mut children = element_children(reference_node);
477
478 let mut transforms = Vec::new();
479 let mut next = children.next().ok_or(SigningDigestError::MissingElement {
480 element: "DigestMethod",
481 })?;
482 if next.tag_name().name() == "Transforms" && next.tag_name().namespace() == Some(XMLDSIG_NS) {
483 transforms = parse_transforms(next)?;
484 next = children.next().ok_or(SigningDigestError::MissingElement {
485 element: "DigestMethod",
486 })?;
487 }
488
489 verify_ds_element(next, "DigestMethod")?;
490 let digest_uri = required_algorithm_attr(next, "DigestMethod")?;
491 let digest_method = DigestAlgorithm::from_uri(digest_uri).ok_or_else(|| {
492 SigningDigestError::UnsupportedAlgorithm {
493 uri: digest_uri.to_string(),
494 }
495 })?;
496 if !digest_method.signing_allowed() {
497 return Err(SigningDigestError::SigningAlgorithmDisabled {
498 uri: digest_method.uri(),
499 });
500 }
501
502 let digest_value_node = children.next().ok_or(SigningDigestError::MissingElement {
503 element: "DigestValue",
504 })?;
505 verify_ds_element(digest_value_node, "DigestValue")?;
506
507 if let Some(unexpected) = children.next() {
508 return Err(SigningDigestError::InvalidStructure(format!(
509 "unexpected element <{}> after <DigestValue> in <Reference>",
510 unexpected.tag_name().name()
511 )));
512 }
513
514 Ok(SigningReference {
515 uri,
516 transforms,
517 digest_method,
518 })
519}
520
521fn find_required_child<'a>(
522 parent: Node<'a, 'a>,
523 child_name: &'static str,
524) -> Result<Node<'a, 'a>, SigningDigestError> {
525 parent
526 .children()
527 .find(|node| {
528 node.is_element()
529 && node.tag_name().name() == child_name
530 && node.tag_name().namespace() == Some(XMLDSIG_NS)
531 })
532 .ok_or(SigningDigestError::MissingElement {
533 element: child_name,
534 })
535}
536
537fn element_children<'a>(node: Node<'a, 'a>) -> impl Iterator<Item = Node<'a, 'a>> {
538 node.children().filter(Node::is_element)
539}
540
541fn verify_ds_element(
542 node: Node<'_, '_>,
543 expected_name: &'static str,
544) -> Result<(), SigningDigestError> {
545 if !node.is_element() {
546 return Err(SigningDigestError::InvalidStructure(format!(
547 "expected element <{expected_name}>, got non-element node"
548 )));
549 }
550 let tag = node.tag_name();
551 if tag.name() != expected_name || tag.namespace() != Some(XMLDSIG_NS) {
552 return Err(SigningDigestError::InvalidStructure(format!(
553 "expected <ds:{expected_name}>, got <{}>",
554 tag.name()
555 )));
556 }
557 Ok(())
558}
559
560fn required_algorithm_attr<'a>(
561 node: Node<'a, 'a>,
562 element_name: &'static str,
563) -> Result<&'a str, SigningDigestError> {
564 node.attribute("Algorithm").ok_or_else(|| {
565 SigningDigestError::InvalidStructure(format!(
566 "missing Algorithm attribute on <{element_name}>"
567 ))
568 })
569}