1mod 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)]
31pub struct XMLSignatureError(#[from] XMLSignatureErrorSignOrVerify);
33
34#[derive(Error, Debug)]
35#[allow(dead_code)]
36enum 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)]
63pub enum VerifyXMLSignatureResult {
65 Success,
67 DigestWrong,
69 SignatureWrong,
71}
72
73impl VerifyXMLSignatureResult {
74 pub fn is_ok(&self) -> bool {
75 self == &VerifyXMLSignatureResult::Success
76 }
77}
78
79pub 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#[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
102pub 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}