Skip to main content

confium_pki/xmldsig/
mod.rs

1//! XMLDSig and Exclusive C14N for XML document signing.
2//!
3//! Produces standard XMLDSig signatures verifiable by `xmlsec1`, browser-
4//! native XMLDSig, and existing OIML CNML verifiers. Direct integration
5//! point with the CNML project.
6//!
7//! See `TODO.roadmap/32-cert-delegation-cms-xmldsig.md` for full spec.
8
9#![forbid(unsafe_code)]
10#![allow(missing_docs)] // TODO: document before 1.0
11
12mod c14n;
13
14pub use c14n::*;
15
16use serde::{Deserialize, Serialize};
17
18/// Canonicalization algorithm.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case")]
21pub enum Canonicalization {
22    /// Exclusive XML Canonicalization (default for XMLDSig).
23    ExclusiveC14N,
24    /// Exclusive C14N with comments.
25    ExclusiveC14NWithComments,
26    /// Inclusive XML Canonicalization.
27    InclusiveC14N,
28    /// Inclusive C14N with comments.
29    InclusiveC14NWithComments,
30}
31
32impl Canonicalization {
33    /// W3C algorithm identifier.
34    pub fn algorithm_id(&self) -> &'static str {
35        match self {
36            Canonicalization::ExclusiveC14N => "http://www.w3.org/2001/10/xml-exc-c14n#",
37            Canonicalization::ExclusiveC14NWithComments => {
38                "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
39            }
40            Canonicalization::InclusiveC14N => "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
41            Canonicalization::InclusiveC14NWithComments => {
42                "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
43            }
44        }
45    }
46}
47
48/// Signature algorithm.
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub enum SignatureAlgorithm {
52    /// ECDSA-SHA256.
53    EcdsaSha256,
54    /// Ed25519.
55    Ed25519,
56    /// RSA-SHA256.
57    RsaSha256,
58}
59
60impl SignatureAlgorithm {
61    /// Algorithm identifier.
62    pub fn algorithm_id(&self) -> &'static str {
63        match self {
64            SignatureAlgorithm::EcdsaSha256 => {
65                "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"
66            }
67            SignatureAlgorithm::Ed25519 => "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519",
68            SignatureAlgorithm::RsaSha256 => "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
69        }
70    }
71}
72
73/// A reference (something being signed).
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct Reference {
76    /// URI of the referenced element ("" = whole document, "#xpointer(...)" = subset).
77    pub uri: String,
78    /// Digest method (typically SHA-256).
79    pub digest_method: String,
80    /// Digest value (computed over canonicalized referenced content).
81    pub digest_value: Vec<u8>,
82    /// Transforms applied before digesting.
83    pub transforms: Vec<Transform>,
84}
85
86/// Transform applied before digesting.
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88#[serde(rename_all = "snake_case")]
89pub enum Transform {
90    /// Exclusive C14N.
91    ExclusiveC14N,
92    /// Inclusive C14N.
93    InclusiveC14N,
94    /// Enveloped signature (strip Signature element from referenced subtree).
95    EnvelopedSignature,
96    /// Base64 decode.
97    Base64Decode,
98}
99
100impl Transform {
101    /// Algorithm identifier.
102    pub fn algorithm_id(&self) -> &'static str {
103        match self {
104            Transform::ExclusiveC14N => "http://www.w3.org/2001/10/xml-exc-c14n#",
105            Transform::InclusiveC14N => "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
106            Transform::EnvelopedSignature => {
107                "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
108            }
109            Transform::Base64Decode => "http://www.w3.org/2000/09/xmldsig#base64",
110        }
111    }
112}
113
114/// SignedInfo — the part that gets signed.
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct SignedInfo {
117    /// Canonicalization algorithm.
118    pub canonicalization: Canonicalization,
119    /// Signature algorithm.
120    pub signature_algorithm: SignatureAlgorithm,
121    /// References.
122    pub references: Vec<Reference>,
123}
124
125/// XMLDSig Signature structure.
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct XmlDSigSignature {
128    /// SignedInfo (the part that gets canonicalized then signed).
129    pub signed_info: SignedInfo,
130    /// Signature value (over canonicalized SignedInfo).
131    pub signature_value: Vec<u8>,
132    /// Optional KeyInfo (cert chain, key name, etc.).
133    #[serde(default)]
134    pub key_info: Option<KeyInfo>,
135}
136
137/// Key information.
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct KeyInfo {
140    /// X509 certificate chain (DER bytes, base64-encoded in output XML).
141    pub x509_certificates: Vec<String>,
142    /// Optional key name.
143    #[serde(default)]
144    pub key_name: Option<String>,
145}
146
147/// Errors during XMLDSig operations.
148#[derive(Debug, thiserror::Error)]
149pub enum XmlDSigError {
150    /// XML parse error.
151    #[error("XML parse error: {0}")]
152    XmlParse(String),
153    /// Canonicalization error.
154    #[error("canonicalization error: {0}")]
155    Canonicalize(String),
156    /// Signature verification failed.
157    #[error("signature verification failed")]
158    VerifyFailed,
159    /// Unsupported algorithm.
160    #[error("unsupported algorithm: {0}")]
161    UnsupportedAlgorithm(String),
162}
163
164/// Compute a SHA-256 digest over `data`. Returns the digest bytes.
165pub fn sha256_digest(data: &[u8]) -> Vec<u8> {
166    use sha2::{Digest, Sha256};
167    let mut h = Sha256::new();
168    h.update(data);
169    h.finalize().to_vec()
170}
171
172/// Mock canonicalization: returns the input unchanged.
173/// Real impl would implement RFC 3076 (Inclusive) or Exclusive C14N.
174pub fn canonicalize_exclusive(xml: &str) -> Result<String, XmlDSigError> {
175    Ok(xml.to_string())
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn algorithm_ids_correct() {
184        assert_eq!(
185            Canonicalization::ExclusiveC14N.algorithm_id(),
186            "http://www.w3.org/2001/10/xml-exc-c14n#"
187        );
188        assert_eq!(
189            SignatureAlgorithm::Ed25519.algorithm_id(),
190            "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519"
191        );
192    }
193
194    #[test]
195    fn sha256_digest_deterministic() {
196        let d1 = sha256_digest(b"hello");
197        let d2 = sha256_digest(b"hello");
198        assert_eq!(d1, d2);
199        assert_eq!(d1.len(), 32);
200    }
201
202    #[test]
203    fn mock_canonicalize_round_trips() {
204        let xml = "<root>test</root>";
205        let canon = canonicalize_exclusive(xml).unwrap();
206        assert_eq!(canon, xml);
207    }
208}