Skip to main content

rust_ev_system_library/chanel_security/xml/
mod.rs

1// Copyright © 2023 Denis Morel
2
3// This program is free software: you can redistribute it and/or modify it under
4// the terms of the GNU General Public License as published by the Free
5// Software Foundation, either version 3 of the License, or (at your option) any
6// later version.
7//
8// This program is distributed in the hope that it will be useful, but WITHOUT
9// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
11// details.
12//
13// You should have received a copy of the GNU General Public License and
14// a copy of the GNU General Public License along with this program. If not, see
15// <https://www.gnu.org/licenses/>.
16
17mod xml_signature;
18
19use rust_ev_crypto_primitives::{
20    ByteArray, EncodeTrait,
21    basic_crypto_functions::{BasisCryptoError, PublicKey, Secretkey, sha256, sign, verify},
22};
23use std::io::Cursor;
24use thiserror::Error;
25use xml_canonicalization::Canonicalizer;
26use xml_signature::{SignedInfo, XMLWithXMLSignature, XMLWithXMLSignatureError};
27use xot::{NamespaceId, Node, PrefixId, Xot};
28
29#[derive(Error, Debug)]
30#[error(transparent)]
31/// Error with tthe xml signature
32pub struct XMLSignatureError(#[from] XMLSignatureErrorSignOrVerify);
33
34#[derive(Error, Debug)]
35#[allow(dead_code)]
36/// Error with dataset
37enum XMLSignatureErrorSignOrVerify {
38    #[error("Error signing the xml")]
39    Sign { source: XMLSignatureErrorRepr },
40    #[error("Error verifying the xml")]
41    Verify { source: XMLSignatureErrorRepr },
42    #[error("Error calculating the digest")]
43    Digest { source: XMLSignatureErrorRepr },
44}
45
46#[derive(Error, Debug)]
47enum XMLSignatureErrorRepr {
48    #[error("Error collecting the xml signature")]
49    GetXMLSignature { source: XMLWithXMLSignatureError },
50    #[error("Error parsing the xml")]
51    Parse { source: XMLWithXMLSignatureError },
52    #[error("No Signature found")]
53    NoSignature,
54    #[error("Error canonicalizing")]
55    C14N { source: Box<dyn std::error::Error> },
56    #[error("Error calculating digest")]
57    SHA256 { source: BasisCryptoError },
58    #[error("Error verfiying the signature")]
59    Verify { source: BasisCryptoError },
60}
61
62#[derive(Clone, Copy, Eq, PartialEq, Debug)]
63/// Result of the verification of the xml signature
64pub enum VerifyXMLSignatureResult {
65    /// Verification is successful
66    Success,
67    /// The digest in the xml signature ist wrong
68    DigestWrong,
69    /// The signature ist wrong
70    SignatureWrong,
71}
72
73impl VerifyXMLSignatureResult {
74    pub fn is_ok(&self) -> bool {
75        self == &VerifyXMLSignatureResult::Success
76    }
77}
78
79/// Verify the xml signature, according to the specifications of Swiss Post
80pub fn verify_xml_signature(
81    d_signed: &str,
82    pk: &PublicKey,
83) -> Result<VerifyXMLSignatureResult, XMLSignatureError> {
84    verify_xml_signature_impl(d_signed, pk)
85        .map_err(|e| XMLSignatureErrorSignOrVerify::Verify { source: e })
86        .map_err(XMLSignatureError)
87}
88
89/// Generate the xml signature, according to the specifications of Swiss Post
90///
91/// It is not working because of the following issues:
92/// - The position of the namespace ds depends of the type in the implementation of Swiss Post
93/// - Problem with the tab vs spaces in output of xot
94/// - Output of xot adds lines within tags, which is no more the same than the original
95#[allow(dead_code)]
96fn gen_xml_signature(d: &str, sk: &Secretkey) -> Result<String, XMLSignatureError> {
97    gen_xml_signature_impl(d, sk)
98        .map_err(|e| XMLSignatureErrorSignOrVerify::Sign { source: e })
99        .map_err(XMLSignatureError)
100}
101
102/// Collect the digest
103///
104/// If the xml signature exists: get the digest in the xml signature
105/// Else: calculation the digest
106pub fn collect_xml_digest(xml: &str) -> Result<ByteArray, XMLSignatureError> {
107    collect_xml_digest_impl(xml)
108        .map_err(|e| XMLSignatureErrorSignOrVerify::Digest { source: e })
109        .map_err(XMLSignatureError)
110}
111
112fn canonicalize(xml: &str) -> Result<String, XMLSignatureErrorRepr> {
113    let mut result = vec![];
114    Canonicalizer::read_from_str(xml)
115        .write_to_writer(Cursor::new(&mut result))
116        .canonicalize(true)
117        .map_err(|e| XMLSignatureErrorRepr::C14N {
118            source: Box::new(e),
119        })?;
120    Ok(String::from_utf8_lossy(&result).to_string())
121}
122
123fn verify_xml_signature_impl(
124    d_signed: &str,
125    pk: &PublicKey,
126) -> Result<VerifyXMLSignatureResult, XMLSignatureErrorRepr> {
127    let d_can = canonicalize(d_signed)?;
128    let xml_signature = XMLWithXMLSignature::from_str(&d_can)
129        .map_err(|e| XMLSignatureErrorRepr::GetXMLSignature { source: e })?;
130    if !xml_signature.has_signature() {
131        return Err(XMLSignatureErrorRepr::NoSignature);
132    }
133    let si = &xml_signature.unwrap_signature().signed_info;
134    let si_can = get_canonalized_signed_info_str(
135        xml_signature.find_signed_info_str().unwrap(),
136        &xml_signature.unwrap_signature_content().namespace_prefix,
137        &xml_signature.unwrap_signature_content().namespace_uri,
138    );
139    let t = xml_signature.remove_signature_from_orig();
140    let d_prime = sha256(&ByteArray::from(t.as_str()))
141        .map_err(|e| XMLSignatureErrorRepr::SHA256 { source: e })?;
142    let d = &si.reference.digest_value;
143    if &d_prime != d {
144        return Ok(VerifyXMLSignatureResult::DigestWrong);
145    }
146    match verify(
147        pk,
148        &ByteArray::from(si_can.as_str()),
149        &xml_signature.unwrap_signature().signature_value,
150    )
151    .map_err(|e| XMLSignatureErrorRepr::Verify { source: e })?
152    {
153        true => Ok(VerifyXMLSignatureResult::Success),
154        false => Ok(VerifyXMLSignatureResult::SignatureWrong),
155    }
156}
157
158fn gen_xml_signature_impl(d: &str, sk: &Secretkey) -> Result<String, XMLSignatureErrorRepr> {
159    let mut si = SignedInfo::default();
160    let d_transformed = XMLWithXMLSignature::from_str(d)
161        .map_err(|e| XMLSignatureErrorRepr::Parse { source: e })?
162        .remove_signature_from_orig();
163    let d_can = canonicalize(&d_transformed)?;
164    si.set_digest_value(
165        sha256(&ByteArray::from(d_can.as_str()))
166            .map_err(|e| XMLSignatureErrorRepr::SHA256 { source: e })?,
167    );
168    let namespace_prefix = "ds";
169    let namespace_uri = "http://www.w3.org/2000/09/xmldsig#";
170    let xml_sig = integrate_signature_xml(&d_transformed, &si, namespace_prefix, namespace_uri);
171    let si_can = get_canonalized_signed_info_str(&xml_sig, namespace_prefix, namespace_uri);
172    let signature = sign(sk, &ByteArray::from(si_can.as_str())).unwrap();
173    let res = xml_sig.replace(
174        "TO_REPLACE_WITH_SIGNATURE",
175        signature.base64_encode().unwrap().as_str(),
176    );
177    Ok(res)
178}
179
180fn get_canonalized_signed_info_str(
181    xml: &str,
182    namespace_prefix: &str,
183    namespace_uri: &str,
184) -> String {
185    xml.replace(
186        format!("<{namespace_prefix}:SignedInfo>").as_str(),
187        format!("<{namespace_prefix}:SignedInfo xmlns:{namespace_prefix}=\"{namespace_uri}\">")
188            .as_str(),
189    )
190}
191
192fn collect_xml_digest_impl(xml: &str) -> Result<ByteArray, XMLSignatureErrorRepr> {
193    let xml_with_sig = XMLWithXMLSignature::from_str(xml)
194        .map_err(|e| XMLSignatureErrorRepr::GetXMLSignature { source: e })?;
195    match xml_with_sig.has_signature() {
196        true => Ok(xml_with_sig
197            .unwrap_signature()
198            .signed_info
199            .reference
200            .digest_value
201            .clone()),
202        false => sha256(&ByteArray::from(canonicalize(xml)?.as_str()))
203            .map_err(|e| XMLSignatureErrorRepr::SHA256 { source: e }),
204    }
205}
206
207fn integrate_signature_xml(
208    xml: &str,
209    sig_info: &SignedInfo,
210    namespace_prefix: &str,
211    namespace_uri: &str,
212) -> String {
213    let mut xot = Xot::new();
214    let document = xot.parse(xml).unwrap();
215    let root = xot.document_element(document).unwrap();
216    let alg_attribute_name = xot.add_name("Algorithm");
217    let prefix = xot.add_prefix(namespace_prefix);
218    let namespace = xot.add_namespace(namespace_uri);
219    let namespace_node = xot.new_namespace_node(prefix, namespace);
220    xot.append_namespace_node(root, namespace_node).unwrap();
221    let name = xot.add_name_ns("Signature", namespace);
222    let signature_el = xot.new_element(name);
223    let name = xot.add_name_ns("SignedInfo", namespace);
224    let sig_info_el = xot.new_element(name);
225    let name = xot.add_name_ns("CanonicalizationMethod", namespace);
226    let elt = xot.new_element(name);
227    let attr = xot.new_attribute_node(alg_attribute_name, sig_info.canonicalization_method.clone());
228    xot.append_attribute_node(elt, attr).unwrap();
229    xot.append(sig_info_el, elt).unwrap();
230    let name = xot.add_name_ns("SignatureMethod", namespace);
231    let elt = xot.new_element(name);
232    let attr = xot.new_attribute_node(alg_attribute_name, sig_info.signature_method.clone());
233    xot.append_attribute_node(elt, attr).unwrap();
234    xot.append(sig_info_el, elt).unwrap();
235    let name = xot.add_name_ns("Reference", namespace);
236    let ref_elt = xot.new_element(name);
237    let name = xot.add_name("URI");
238    let attr = xot.new_attribute_node(name, String::new());
239    xot.append_attribute_node(ref_elt, attr).unwrap();
240    let name = xot.add_name_ns("Transforms", namespace);
241    let transforms_elt = xot.new_element(name);
242    for tr in sig_info.reference.transforms.iter() {
243        let name = xot.add_name_ns("Transform", namespace);
244        let elt = xot.new_element(name);
245        let attr = xot.new_attribute_node(alg_attribute_name, tr.clone());
246        xot.append_attribute_node(elt, attr).unwrap();
247        xot.append(transforms_elt, elt).unwrap();
248    }
249    xot.append(ref_elt, transforms_elt).unwrap();
250    let name = xot.add_name_ns("DigestMethod", namespace);
251    let elt = xot.new_element(name);
252    let attr = xot.new_attribute_node(alg_attribute_name, sig_info.reference.digest_method.clone());
253    xot.append_attribute_node(elt, attr).unwrap();
254    xot.append(ref_elt, elt).unwrap();
255    let name = xot.add_name_ns("DigestValue", namespace);
256    let elt = xot.new_element(name);
257    xot.append_text(
258        elt,
259        sig_info
260            .reference
261            .digest_value
262            .base64_encode()
263            .unwrap()
264            .as_str(),
265    )
266    .unwrap();
267    xot.append(ref_elt, elt).unwrap();
268    xot.append(sig_info_el, ref_elt).unwrap();
269    xot.append(signature_el, sig_info_el).unwrap();
270    let name = xot.add_name_ns("SignatureValue", namespace);
271    let elt = xot.new_element(name);
272    xot.append_text(elt, "TO_REPLACE_WITH_SIGNATURE").unwrap();
273    xot.append(signature_el, elt).unwrap();
274    let node_to_append = get_node_to_add_signature(&mut xot, root).unwrap();
275    xot.append(node_to_append, signature_el).unwrap();
276    xot.to_string(root).unwrap()
277}
278
279fn get_node_to_add_signature(xot: &mut Xot, root: Node) -> Option<Node> {
280    XMLType::detect_type(xot, root).map(|t| t.get_node_to_add_signature(xot, root))
281}
282
283enum XMLType {
284    Ech0222,
285    Config,
286}
287
288impl XMLType {
289    fn detection_str(&self) -> &str {
290        match self {
291            XMLType::Ech0222 => "http://www.ech.ch/xmlns/eCH-0222",
292            XMLType::Config => "www.evoting.ch/xmlns/config",
293        }
294    }
295
296    fn str_to_self(s: &str) -> Option<Self> {
297        match s {
298            s if s.contains(Self::Ech0222.detection_str()) => Some(Self::Ech0222),
299            s if s.contains(Self::Config.detection_str()) => Some(Self::Config),
300            _ => None,
301        }
302    }
303
304    fn detect_type(xot: &Xot, root: Node) -> Option<Self> {
305        let ns = xot.namespace_declarations(root);
306        ns.iter()
307            .find_map(|(_, ns)| Self::str_to_self(xot.namespace_str(*ns)))
308    }
309
310    fn get_prefix_namespace(&self, xot: &Xot, root: Node) -> Option<(PrefixId, NamespaceId)> {
311        let ns = xot.namespace_declarations(root);
312        ns.into_iter()
313            .find(|(_, ns)| xot.namespace_str(*ns).contains(self.detection_str()))
314    }
315
316    fn get_node_to_add_signature(&self, xot: &mut Xot, root: Node) -> Node {
317        match self {
318            XMLType::Ech0222 => {
319                let raw_data_delivery = xot.last_child(root).unwrap();
320                let last_in_raw_data_delivery = xot.last_child(raw_data_delivery).unwrap();
321                if xot.local_name_str(xot.node_name(last_in_raw_data_delivery).unwrap())
322                    == "extension"
323                {
324                    return last_in_raw_data_delivery;
325                }
326                let (_, namespace) = self.get_prefix_namespace(xot, root).unwrap();
327                let ext_name = xot.add_name_ns("extension", namespace);
328                let ext_node = xot.new_element(ext_name);
329                xot.append(raw_data_delivery, ext_node).unwrap();
330                ext_node
331            }
332            XMLType::Config => root,
333        }
334    }
335}
336
337#[cfg(test)]
338mod test_data {
339    use std::path::PathBuf;
340
341    use crate::test_data::{get_test_data_xml, get_test_data_xml_path};
342
343    const CONFIG_FILENAME: &str = "configuration-anonymized.xml";
344    const ECH0222_FILENAME: &str = "eCH-0222_v3-0_NE_20231124_TT05.xml";
345    const ECH0222_WITHOUT_SIG_FILENAME: &str = "eCH-0222_v3-0_NE_20231124_TT05_without_sig.xml";
346    const VERIFIER_KEYSTORE_FILENAME: &str = "local_direct_trust_keystore_verifier.p12";
347    const VERIFIER_KEYSTORE_PWD_FILENAME: &str = "local_direct_trust_pw_verifier.txt";
348    const CANTON_KEYSTORE_FILENAME: &str = "local_direct_trust_keystore_canton.p12";
349    const CANTON_KEYSTORE_PWD_FILENAME: &str = "local_direct_trust_pw_canton.txt";
350    const TALLY_KEYSTORE_FILENAME: &str = "local_direct_trust_keystore_sdm_tally.p12";
351    const TALLY_KEYSTORE_PWD_FILENAME: &str = "local_direct_trust_pw_sdm_tally.txt";
352
353    pub fn get_test_data_config() -> String {
354        get_test_data_xml(CONFIG_FILENAME)
355    }
356
357    pub fn get_test_data_ech0222() -> String {
358        get_test_data_xml(ECH0222_FILENAME)
359    }
360
361    pub fn get_test_data_ech0222_without_sig() -> String {
362        get_test_data_xml(ECH0222_WITHOUT_SIG_FILENAME)
363    }
364
365    pub fn get_verifier_keystore_path() -> PathBuf {
366        get_test_data_xml_path().join(VERIFIER_KEYSTORE_FILENAME)
367    }
368
369    pub fn get_verifier_keystore_pwd_path() -> PathBuf {
370        get_test_data_xml_path().join(VERIFIER_KEYSTORE_PWD_FILENAME)
371    }
372
373    pub fn get_canton_keystore_path() -> PathBuf {
374        get_test_data_xml_path().join(CANTON_KEYSTORE_FILENAME)
375    }
376
377    pub fn get_canton_keystore_pwd_path() -> PathBuf {
378        get_test_data_xml_path().join(CANTON_KEYSTORE_PWD_FILENAME)
379    }
380
381    pub fn get_tally_keystore_path() -> PathBuf {
382        get_test_data_xml_path().join(TALLY_KEYSTORE_FILENAME)
383    }
384
385    pub fn get_tally_keystore_pwd_path() -> PathBuf {
386        get_test_data_xml_path().join(TALLY_KEYSTORE_PWD_FILENAME)
387    }
388}
389
390#[cfg(test)]
391mod test {
392    use super::{test_data::*, *};
393    use rust_ev_crypto_primitives::direct_trust::Keystore;
394    use std::path::Path;
395
396    fn get_public_key(ca: &str) -> PublicKey {
397        let keystore = Keystore::from_pkcs12(
398            &get_verifier_keystore_path(),
399            &get_verifier_keystore_pwd_path(),
400        )
401        .unwrap();
402        keystore
403            .public_certificate(ca)
404            .unwrap()
405            .signing_certificate()
406            .public_key()
407            .unwrap()
408    }
409
410    fn get_private_key(path: &Path, pwd: &Path) -> Secretkey {
411        let keystore = Keystore::from_pkcs12(path, pwd).unwrap();
412        keystore
413            .secret_key_certificate()
414            .unwrap()
415            .signing_certificate()
416            .secret_key()
417            .clone()
418            .unwrap()
419    }
420
421    #[test]
422    fn test_verify_config() {
423        let data = get_test_data_config();
424        let res = verify_xml_signature(data.as_str(), &get_public_key("canton"));
425        assert!(res.is_ok(), "{:?}", res.unwrap_err());
426        assert_eq!(res.unwrap(), VerifyXMLSignatureResult::Success);
427    }
428
429    #[test]
430    fn test_verify_ech0222() {
431        let data = get_test_data_ech0222();
432        let res = verify_xml_signature(data.as_str(), &get_public_key("sdm_tally"));
433        assert!(res.is_ok(), "{:?}", res.unwrap_err());
434        assert_eq!(res.unwrap(), VerifyXMLSignatureResult::Success);
435    }
436
437    #[test]
438    fn test_ech0222_digest() {
439        let data = get_test_data_ech0222();
440        let data_without_sig = get_test_data_ech0222_without_sig();
441        assert!(XMLWithXMLSignature::from_str(&data).is_ok());
442        assert!(XMLWithXMLSignature::from_str(&data_without_sig).is_ok());
443        assert_eq!(
444            collect_xml_digest(&data).unwrap(),
445            collect_xml_digest(&data_without_sig).unwrap()
446        );
447    }
448
449    #[test]
450    #[ignore = "Not working (problem with tabs and position of namespace declaration of ds"]
451    fn test_sign_ech0222() {
452        let data_without_sig = get_test_data_ech0222_without_sig();
453        let sk = get_private_key(&get_tally_keystore_path(), &get_tally_keystore_pwd_path());
454        let signed_xml_res = gen_xml_signature(&data_without_sig, &sk);
455        assert!(signed_xml_res.is_ok(), "{:?}", signed_xml_res.unwrap_err());
456        let res = verify_xml_signature(&signed_xml_res.unwrap(), &get_public_key("sdm_tally"));
457        assert!(res.is_ok(), "{:?}", res.unwrap_err());
458        assert_eq!(res.unwrap(), VerifyXMLSignatureResult::Success);
459    }
460
461    #[test]
462    #[ignore = "Not working (problem with tabs and position of namespace declaration of ds"]
463    fn test_sign_ech0222_with_sig() {
464        let data_without_sig = get_test_data_ech0222();
465        let sk = get_private_key(&get_tally_keystore_path(), &get_tally_keystore_pwd_path());
466        let signed_xml_res = gen_xml_signature(&data_without_sig, &sk);
467        assert!(signed_xml_res.is_ok(), "{:?}", signed_xml_res.unwrap_err());
468        let res = verify_xml_signature(&signed_xml_res.unwrap(), &get_public_key("sdm_tally"));
469        assert!(res.is_ok(), "{:?}", res.unwrap_err());
470        assert_eq!(res.unwrap(), VerifyXMLSignatureResult::Success);
471    }
472
473    #[test]
474    #[ignore = "Not working (problem with tabs and position of namespace declaration of ds"]
475    fn test_sign_config() {
476        let data_without_sig = get_test_data_config();
477        let sk = get_private_key(&get_canton_keystore_path(), &get_canton_keystore_pwd_path());
478        let signed_xml_res = gen_xml_signature(&data_without_sig, &sk);
479        assert!(signed_xml_res.is_ok(), "{:?}", signed_xml_res.unwrap_err());
480        let res = verify_xml_signature(&signed_xml_res.unwrap(), &get_public_key("canton"));
481        assert!(res.is_ok(), "{:?}", res.unwrap_err());
482        assert_eq!(res.unwrap(), VerifyXMLSignatureResult::Success);
483    }
484}