1#[macro_use]
2extern crate serde_derive;
3
4pub mod proto;
5pub mod c14n;
6
7#[inline]
8pub fn x509_name_to_string(name: &openssl::x509::X509NameRef) -> String {
9 name.entries()
10 .map(|e| {
11 format!("{}=\"{}\"",
12 e.object().nid().short_name().unwrap_or_default(),
13 match e.data().as_utf8() {
14 Ok(d) => d.to_string(),
15 Err(_) => String::new()
16 }
17 )
18 })
19 .collect::<Vec<_>>()
20 .join(",")
21}
22
23#[inline]
24pub fn events_to_string(events: &[xml::reader::XmlEvent]) -> String {
25 let mut output = Vec::new();
26 let mut output_writer = xml::writer::EventWriter::new_with_config(
27 &mut output,
28 xml::writer::EmitterConfig {
29 perform_indent: false,
30 perform_escaping: false,
31 write_document_declaration: true,
32 autopad_comments: false,
33 cdata_to_characters: true,
34 line_separator: std::borrow::Cow::Borrowed("\n"),
35 normalize_empty_elements: false,
36 ..std::default::Default::default()
37 },
38 );
39
40 for event in events {
41 if let Some(e) = event.as_writer_event() {
42 output_writer.write(e).unwrap();
43 }
44 }
45
46 String::from_utf8_lossy(&output).to_string()
47}
48
49fn decode_key(key_info: &proto::ds::KeyInfo) -> Result<openssl::pkey::PKey<openssl::pkey::Public>, String> {
50 match key_info.keys_info.first() {
51 Some(proto::ds::KeyInfoType::X509Data(x509data)) => {
52 for x509_datum in &x509data.x509_data {
53 match x509_datum {
54 proto::ds::X509Datum::Certificate(c) => {
55 let c_bin = match base64::decode_config(c.replace("\r", "").replace("\n", ""), base64::STANDARD_NO_PAD) {
56 Ok(d) => d,
57 Err(e) => {
58 return Err(format!("error decoding X509 cert: {}", e));
59 }
60 };
61 let key = match openssl::x509::X509::from_der(&c_bin) {
62 Ok(d) => d,
63 Err(e) => {
64 return Err(format!("error decoding X509 cert: {}", e));
65 }
66 };
67 let pkey = match key.public_key() {
68 Ok(d) => d,
69 Err(e) => {
70 return Err(format!("error decoding X509 cert: {}", e));
71 }
72 };
73 return Ok(pkey);
74 }
75 _ => {}
76 }
77 }
78 Err(format!("unsupported key: {:?}", x509data))
79 }
80 k => {
81 Err(format!("unsupported key: {:?}", k))
82 }
83 }
84}
85
86fn find_events_slice_by_id<'a>(events: &'a [xml::reader::XmlEvent], id: &str) -> Option<&'a [xml::reader::XmlEvent]> {
87 let mut i = 0;
88 let mut elm_i = events.len();
89 let mut elm_end_i = elm_i;
90 let mut elm_name = None;
91 for evt in events {
92 match evt {
93 xml::reader::XmlEvent::StartElement {
94 name, attributes, ..
95 } => {
96 let elm_id = attributes.iter()
97 .filter_map(|a|
98 if a.name.prefix.is_none() && a.name.namespace.is_none()
99 && a.name.local_name.to_lowercase() == "id" {
100 Some(&a.value)
101 } else {
102 None
103 }
104 ).next();
105 if let Some(elm_id) = elm_id {
106 if elm_name.is_none() && elm_id == id {
107 elm_i = i;
108 elm_name = Some(name.clone());
109 }
110 }
111 }
112 xml::reader::XmlEvent::EndElement {
113 name, ..
114 } => {
115 if let Some(elm_name) = &elm_name {
116 if name == elm_name {
117 elm_end_i = i;
118 break;
119 }
120 }
121 }
122 _ => {}
123 }
124 i += 1;
125 }
126
127 if elm_i == events.len() {
128 return None;
129 }
130
131 Some(&events[elm_i..elm_end_i + 1])
132}
133
134fn find_signed_info<'a>(events: &'a [xml::reader::XmlEvent]) -> Option<&'a [xml::reader::XmlEvent]> {
135 let mut i = 0;
136 let mut elm_i = events.len();
137 let mut elm_end_i = elm_i;
138 let mut elm_name = None;
139 for evt in events {
140 match evt {
141 xml::reader::XmlEvent::StartElement {
142 name, ..
143 } => {
144 if elm_name.is_none() && name.namespace.as_deref() == Some("http://www.w3.org/2000/09/xmldsig#") && &name.local_name == "SignedInfo" {
145 elm_i = i;
146 elm_name = Some(name.clone());
147 }
148 }
149 xml::reader::XmlEvent::EndElement {
150 name, ..
151 } => {
152 if let Some(elm_name) = &elm_name {
153 if name == elm_name {
154 elm_end_i = i;
155 break;
156 }
157 }
158 }
159 _ => {}
160 }
161 i += 1;
162 }
163
164 if elm_i == events.len() {
165 return None;
166 }
167
168 Some(&events[elm_i..elm_end_i + 1])
169}
170
171enum InnerAlgorithmData<'a> {
172 NodeSet(&'a [xml::reader::XmlEvent]),
173 OctetStream(&'a str),
174}
175
176#[derive(Debug)]
177enum AlgorithmData<'a> {
178 NodeSet(&'a [xml::reader::XmlEvent]),
179 OctetStream(&'a str),
180 OwnedNodeSet(Vec<xml::reader::XmlEvent>),
181 OwnedOctetStream(String),
182}
183
184impl<'a> AlgorithmData<'a> {
185 fn into_inner_data(&'a self) -> InnerAlgorithmData<'a> {
186 match self {
187 AlgorithmData::NodeSet(n) => InnerAlgorithmData::NodeSet(n),
188 AlgorithmData::OwnedNodeSet(n) => InnerAlgorithmData::NodeSet(n),
189 AlgorithmData::OctetStream(o) => InnerAlgorithmData::OctetStream(o),
190 AlgorithmData::OwnedOctetStream(o) => InnerAlgorithmData::OctetStream(o),
191 }
192 }
193}
194
195fn transform_canonical_xml_1_0<'a>(events: AlgorithmData<'a>) -> Result<AlgorithmData, String> {
196 let events = match events.into_inner_data() {
197 InnerAlgorithmData::NodeSet(e) => e,
198 _ => return Err("unsupported input format for canonical XML 1.0".to_string())
199 };
200
201 let canon_output = c14n::canonical_rfc3076(events, false, 0, false)?;
202
203 Ok(AlgorithmData::OwnedOctetStream(canon_output))
204}
205
206fn transform_canonical_xml_1_0_with_comments<'a>(events: AlgorithmData<'a>) -> Result<AlgorithmData, String> {
207 let events = match events.into_inner_data() {
208 InnerAlgorithmData::NodeSet(e) => e,
209 _ => return Err("unsupported input format for canonical XML 1.0 (with comments)".to_string())
210 };
211
212 let canon_output = c14n::canonical_rfc3076(events, true, 0, false)?;
213
214 Ok(AlgorithmData::OwnedOctetStream(canon_output))
215}
216
217fn transform_canonical_xml_1_1<'a>(events: AlgorithmData<'a>) -> Result<AlgorithmData, String> {
218 let events = match events.into_inner_data() {
219 InnerAlgorithmData::NodeSet(e) => e,
220 _ => return Err("unsupported input format for canonical XML 1.1".to_string())
221 };
222
223 let canon_output = c14n::canonical_rfc3076(events, false, 0, false)?;
224
225 Ok(AlgorithmData::OwnedOctetStream(canon_output))
226}
227
228fn transform_canonical_xml_1_1_with_comments<'a>(events: AlgorithmData<'a>) -> Result<AlgorithmData, String> {
229 let events = match events.into_inner_data() {
230 InnerAlgorithmData::NodeSet(e) => e,
231 _ => return Err("unsupported input format for canonical XML 1.1 (with comments)".to_string())
232 };
233
234 let canon_output = c14n::canonical_rfc3076(events, true, 0, false)?;
235
236 Ok(AlgorithmData::OwnedOctetStream(canon_output))
237}
238
239fn transform_exclusive_canonical_xml_1_0<'a>(events: AlgorithmData<'a>) -> Result<AlgorithmData, String> {
240 let events = match events.into_inner_data() {
241 InnerAlgorithmData::NodeSet(e) => e,
242 _ => return Err("unsupported input format for exclusive canonical XML 1.0".to_string())
243 };
244
245 let canon_output = c14n::canonical_rfc3076(events, false, 0, true)?;
246
247 Ok(AlgorithmData::OwnedOctetStream(canon_output))
248}
249
250fn transform_exclusive_canonical_xml_1_0_with_comments<'a>(events: AlgorithmData<'a>) -> Result<AlgorithmData, String> {
251 let events = match events.into_inner_data() {
252 InnerAlgorithmData::NodeSet(e) => e,
253 _ => return Err("unsupported input format for exclusive canonical XML 1.0 (with comments)".to_string())
254 };
255
256 let canon_output = c14n::canonical_rfc3076(events, true, 0, true)?;
257
258 Ok(AlgorithmData::OwnedOctetStream(canon_output))
259}
260
261fn transform_enveloped_signature<'a>(events: AlgorithmData<'a>) -> Result<AlgorithmData, String> {
262 let events = match events.into_inner_data() {
263 InnerAlgorithmData::NodeSet(e) => e,
264 _ => return Err("unsupported input format for envelopd signature transform".to_string())
265 };
266
267 let mut level = 0;
268 let mut output = vec![];
269 let mut should_output = true;
270
271 for evt in events {
272 match evt {
273 xml::reader::XmlEvent::StartElement {
274 name, attributes, namespace
275 } => {
276 level += 1;
277 if level == 2 && name.namespace.as_deref() == Some("http://www.w3.org/2000/09/xmldsig#") && name.local_name == "Signature" {
278 should_output = false
279 }
280 if should_output {
281 output.push(xml::reader::XmlEvent::StartElement {
282 name: name.to_owned(),
283 attributes: attributes.to_vec(),
284 namespace: namespace.to_owned(),
285 });
286 }
287 }
288 xml::reader::XmlEvent::EndElement {
289 name
290 } => {
291 if should_output {
292 output.push(xml::reader::XmlEvent::EndElement {
293 name: name.to_owned(),
294 });
295 }
296 if level == 2 && name.namespace.as_deref() == Some("http://www.w3.org/2000/09/xmldsig#") && name.local_name == "Signature" {
297 should_output = true;
298 }
299 level -= 1;
300 }
301 e => {
302 if should_output {
303 output.push(e.to_owned());
304 }
305 }
306 }
307 }
308
309 Ok(AlgorithmData::OwnedNodeSet(output))
310}
311
312pub const DIGEST_SHA1: &'static str = "http://www.w3.org/2000/09/xmldsig#sha1";
313pub const DIGEST_SHA256: &'static str = "http://www.w3.org/2001/04/xmlenc#sha256";
314pub const DIGEST_SH224: &'static str = "http://www.w3.org/2001/04/xmldsig-more#sha224";
315pub const DIGEST_SHA384: &'static str = "http://www.w3.org/2001/04/xmldsig-more#sha384";
316pub const DIGEST_SHA512: &'static str = "http://www.w3.org/2001/04/xmlenc#sha512";
317
318pub const TRANSFORM_ENVELOPED_SIGNATURE: &'static str = "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
319
320pub const CANONICAL_1_0: &'static str = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
321pub const CANONICAL_1_0_COMMENTS: &'static str = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
322pub const CANONICAL_1_1: &'static str = "http://www.w3.org/2006/10/xml-c14n11";
323pub const CANONICAL_1_1_COMMENTS: &'static str = "http://www.w3.org/2006/10/xml-c14n11#WithComments";
324pub const CANONICAL_EXCLUSIVE_1_0: &'static str = "http://www.w3.org/2001/10/xml-exc-c14n#";
325pub const CANONICAL_EXCLUSIVE_1_0_COMMENTS: &'static str = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments";
326
327pub const SIGNATURE_RSA_MD5: &'static str = "http://www.w3.org/2001/04/xmldsig-more#rsa-md5";
328pub const SIGNATURE_RSA_SHA1: &'static str = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
329pub const SIGNATURE_RSA_SHA224: &'static str = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224";
330pub const SIGNATURE_RSA_SHA256: &'static str = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
331pub const SIGNATURE_RSA_SHA384: &'static str = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384";
332pub const SIGNATURE_RSA_SHA512: &'static str = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";
333pub const SIGNATURE_RSA_RIPEMD160: &'static str = "http://www.w3.org/2001/04/xmldsig-more#rsa-ripemd160";
334pub const SIGNATURE_ECDSA_SHA1: &'static str = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1";
335pub const SIGNATURE_ECDSA_SHA224: &'static str = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224";
336pub const SIGNATURE_ECDSA_SHA256: &'static str = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256";
337pub const SIGNATURE_ECDSA_SHA384: &'static str = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384";
338pub const SIGNATURE_ECDSA_SHA512: &'static str = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512";
339pub const SIGNATURE_ECDSA_RIPEMD160: &'static str = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-ripemd160";
340pub const SIGNATURE_DSA_SHA1: &'static str = "http://www.w3.org/2000/09/xmldsig#dsa-sha1";
341pub const SIGNATURE_DSA_SHA256: &'static str = "http://www.w3.org/2009/xmldsig11#dsa-sha256";
342
343fn apply_transforms<'a>(reference: &proto::ds::Reference, mut signed_data: AlgorithmData<'a>) -> Result<String, String> {
344 if let Some(transforms) = &reference.transforms {
345 for transform in &transforms.transforms {
346 match transform.algorithm.as_str() {
347 TRANSFORM_ENVELOPED_SIGNATURE => {
348 signed_data = transform_enveloped_signature(signed_data)?;
349 }
350 CANONICAL_1_0 => {
351 signed_data = transform_canonical_xml_1_0(signed_data)?;
352 }
353 CANONICAL_1_0_COMMENTS => {
354 signed_data = transform_canonical_xml_1_0_with_comments(signed_data)?;
355 }
356 CANONICAL_1_1 => {
357 signed_data = transform_canonical_xml_1_1(signed_data)?;
358 }
359 CANONICAL_1_1_COMMENTS => {
360 signed_data = transform_canonical_xml_1_1_with_comments(signed_data)?;
361 }
362 CANONICAL_EXCLUSIVE_1_0 => {
363 signed_data = transform_exclusive_canonical_xml_1_0(signed_data)?;
364 }
365 CANONICAL_EXCLUSIVE_1_0_COMMENTS => {
366 signed_data = transform_exclusive_canonical_xml_1_0_with_comments(signed_data)?;
367 }
368 u => {
369 return Err(format!("unsupported transformation: {}", u));
370 }
371 }
372 }
373 }
374
375 Ok(match signed_data.into_inner_data() {
376 InnerAlgorithmData::OctetStream(o) => o.to_string(),
377 _ => return Err("transforms did not output octet stream".to_string())
378 })
379}
380
381fn map_digest(dm: &proto::ds::DigestMethod) -> Result<openssl::hash::MessageDigest, String> {
382 match dm.algorithm.as_str() {
383 DIGEST_SHA1 => Ok(openssl::hash::MessageDigest::sha1()),
384 DIGEST_SHA256 => Ok(openssl::hash::MessageDigest::sha256()),
385 DIGEST_SH224 => Ok(openssl::hash::MessageDigest::sha224()),
386 DIGEST_SHA384 => Ok(openssl::hash::MessageDigest::sha384()),
387 DIGEST_SHA512 => Ok(openssl::hash::MessageDigest::sha512()),
388 u => {
389 return Err(format!("unsupported digest: {}", u));
390 }
391 }
392}
393
394fn verify_signature(sm: &proto::ds::SignatureMethod, pkey: &openssl::pkey::PKeyRef<openssl::pkey::Public>, sig: &[u8], data: &[u8]) -> bool {
395 let dm = match sm.algorithm.as_str() {
396 SIGNATURE_RSA_MD5 => {
397 if pkey.rsa().is_err() {
398 return false;
399 }
400 openssl::hash::MessageDigest::md5()
401 }
402 SIGNATURE_RSA_SHA1 => {
403 if pkey.rsa().is_err() {
404 return false;
405 }
406 openssl::hash::MessageDigest::sha1()
407 }
408 SIGNATURE_RSA_SHA224 => {
409 if pkey.rsa().is_err() {
410 return false;
411 }
412 openssl::hash::MessageDigest::sha224()
413 }
414 SIGNATURE_RSA_SHA256 => {
415 if pkey.rsa().is_err() {
416 return false;
417 }
418 openssl::hash::MessageDigest::sha256()
419 }
420 SIGNATURE_RSA_SHA384 => {
421 if pkey.rsa().is_err() {
422 return false;
423 }
424 openssl::hash::MessageDigest::sha384()
425 }
426 SIGNATURE_RSA_SHA512 => {
427 if pkey.rsa().is_err() {
428 return false;
429 }
430 openssl::hash::MessageDigest::sha512()
431 }
432 SIGNATURE_RSA_RIPEMD160 => {
433 if pkey.rsa().is_err() {
434 return false;
435 }
436 openssl::hash::MessageDigest::ripemd160()
437 }
438 SIGNATURE_ECDSA_SHA1 => {
439 if pkey.ec_key().is_err() {
440 return false;
441 }
442 openssl::hash::MessageDigest::sha1()
443 }
444 SIGNATURE_ECDSA_SHA224 => {
445 if pkey.ec_key().is_err() {
446 return false;
447 }
448 openssl::hash::MessageDigest::sha224()
449 }
450 SIGNATURE_ECDSA_SHA256 => {
451 if pkey.ec_key().is_err() {
452 return false;
453 }
454 openssl::hash::MessageDigest::sha256()
455 }
456 SIGNATURE_ECDSA_SHA384 => {
457 if pkey.ec_key().is_err() {
458 return false;
459 }
460 openssl::hash::MessageDigest::sha384()
461 }
462 SIGNATURE_ECDSA_SHA512 => {
463 if pkey.ec_key().is_err() {
464 return false;
465 }
466 openssl::hash::MessageDigest::sha512()
467 }
468 SIGNATURE_ECDSA_RIPEMD160 => {
469 if pkey.ec_key().is_err() {
470 return false;
471 }
472 openssl::hash::MessageDigest::ripemd160()
473 }
474 SIGNATURE_DSA_SHA1 => {
475 if pkey.dsa().is_err() {
476 return false;
477 }
478 openssl::hash::MessageDigest::sha1()
479 }
480 SIGNATURE_DSA_SHA256 => {
481 if pkey.dsa().is_err() {
482 return false;
483 }
484 openssl::hash::MessageDigest::sha256()
485 }
486 _ => return false
487 };
488
489 let mut verifier = match openssl::sign::Verifier::new(
490 dm, pkey,
491 ) {
492 Ok(v) => v,
493 Err(_) => return false
494 };
495
496 match verifier.verify_oneshot(sig, data) {
497 Ok(v) => v,
498 Err(_) => false
499 }
500}
501
502#[derive(Debug)]
503pub enum Output {
504 Verified {
505 references: Vec<String>,
506 pkey: openssl::pkey::PKey<openssl::pkey::Public>,
507 },
508 Unsigned(String),
509}
510
511pub fn decode_and_verify_signed_document(source_xml: &str) -> Result<Output, String> {
512 let reader = xml::reader::EventReader::new_with_config(
513 source_xml.as_bytes(),
514 xml::ParserConfig::new()
515 .ignore_comments(false)
516 .trim_whitespace(false)
517 .coalesce_characters(false)
518 .ignore_root_level_whitespace(true),
519 ).into_iter().collect::<Result<Vec<_>, _>>().map_err(|e| format!("unable to decode XML: {}", e))?;
520
521 let mut i = 0;
522 let mut level = 0;
523 let mut seen_level = reader.len();
524 let mut sig_i = seen_level;
525 let mut sig_end_i = seen_level;
526 for evt in &reader {
527 match evt {
528 xml::reader::XmlEvent::StartElement {
529 name, ..
530 } => {
531 level += 1;
532 if level < seen_level && name.namespace.as_deref() == Some("http://www.w3.org/2000/09/xmldsig#") && &name.local_name == "Signature" {
533 seen_level = level;
534 sig_i = i;
535 }
536 }
537 xml::reader::XmlEvent::EndElement {
538 name, ..
539 } => {
540 if level == seen_level && name.namespace.as_deref() == Some("http://www.w3.org/2000/09/xmldsig#") && &name.local_name == "Signature" {
541 seen_level = level;
542 sig_end_i = i;
543 }
544 level -= 1;
545 }
546 _ => {}
547 }
548 i += 1;
549 }
550
551 if sig_i == reader.len() {
552 return Ok(Output::Unsigned(source_xml.to_string()));
553 }
554
555 let sig_elems = reader[sig_i..sig_end_i + 1].iter().map(|e| xml::reader::Result::Ok(e.to_owned())).collect::<Vec<_>>();
556 let sig: proto::ds::OuterSignatre = match xml_serde::from_events(sig_elems.as_slice()) {
557 Ok(s) => s,
558 Err(e) => return Err(format!("unable to decode XML signature: {}", e))
559 };
560
561 let mut verified_outputs = vec![];
562
563 for reference in &sig.signature.signed_info.reference {
564 let u = reference.uri.as_deref().unwrap_or_default();
565 let signed_data = apply_transforms(reference, AlgorithmData::NodeSet(if u == "" {
566 reader.as_slice()
567 } else if u.starts_with("#") {
568 match find_events_slice_by_id(&reader, &u[1..]) {
569 Some(e) => e,
570 None => return Err(format!("unable to find signed element: {}", u))
571 }
572 } else {
573 return Err(format!("unsupported reference URI: {}", u));
574 }))?;
575
576 let provided_digest = match base64::decode(&reference.digest_value) {
577 Ok(d) => d,
578 Err(e) => {
579 return Err(format!("invalid disest base64: {}", e));
580 }
581 };
582
583 let dm = map_digest(&reference.digest_method)?;
584 let digest = match openssl::hash::hash(dm, signed_data.as_bytes()) {
585 Ok(d) => d,
586 Err(e) => {
587 return Err(format!("openssl error: {}", e));
588 }
589 };
590
591 if digest.as_ref() != provided_digest {
592 return Err("digest does not match".to_string());
593 }
594
595 verified_outputs.push(signed_data);
596 }
597
598 let signed_info_events = AlgorithmData::NodeSet(find_signed_info(&reader[sig_i..sig_end_i + 1]).unwrap());
599 let signed_info_data = match match sig.signature.signed_info.canonicalization_method.algorithm.as_str() {
600 CANONICAL_1_0 => {
601 transform_canonical_xml_1_0(signed_info_events)?
602 }
603 CANONICAL_1_0_COMMENTS => {
604 transform_canonical_xml_1_0_with_comments(signed_info_events)?
605 }
606 CANONICAL_1_1 => {
607 transform_canonical_xml_1_1(signed_info_events)?
608 }
609 CANONICAL_1_1_COMMENTS => {
610 transform_canonical_xml_1_1_with_comments(signed_info_events)?
611 }
612 CANONICAL_EXCLUSIVE_1_0 => {
613 transform_exclusive_canonical_xml_1_0(signed_info_events)?
614 }
615 CANONICAL_EXCLUSIVE_1_0_COMMENTS => {
616 transform_exclusive_canonical_xml_1_0_with_comments(signed_info_events)?
617 }
618 u => return Err(format!("unsupported canonicalisation method: {}", u))
619 }.into_inner_data() {
620 InnerAlgorithmData::OctetStream(o) => o.to_string(),
621 _ => unreachable!()
622 };
623
624 let pkey = if let Some(ki) = &sig.signature.key_info {
625 decode_key(ki)?
626 } else {
627 return Err("key info not specified".to_string());
628 };
629
630 let sig_data = match base64::decode(
631 &sig.signature.signature_value.value.replace("\r", "").replace("\n", "")
632 ) {
633 Ok(s) => s,
634 Err(e) => {
635 return Err(format!("error decoding signature: {}", e));
636 }
637 };
638
639 if !verify_signature(
640 &sig.signature.signed_info.signature_method,
641 &pkey,
642 &sig_data,
643 signed_info_data.as_bytes(),
644 ) {
645 return Err("signature does not verify".to_string());
646 }
647
648 Ok(Output::Verified {
649 references: verified_outputs,
650 pkey,
651 })
652}
653
654pub fn sign_document(
655 events: &[xml::reader::XmlEvent],
656 pub_key: &openssl::x509::X509Ref,
657 priv_key: &openssl::pkey::PKeyRef<openssl::pkey::Private>,
658) -> Result<String, String> {
659 let pub_pkey = match pub_key.public_key() {
660 Ok(d) => d,
661 Err(e) => {
662 return Err(format!("openssl error: {}", e));
663 }
664 };
665 if !priv_key.public_eq(&pub_pkey) {
666 return Err("public and private key don't match".to_string());
667 }
668
669 let canonicalisied_events = match transform_exclusive_canonical_xml_1_0(AlgorithmData::NodeSet(events))?.into_inner_data() {
670 InnerAlgorithmData::OctetStream(s) => s.to_string(),
671 _ => unreachable!()
672 };
673
674 let digest = match openssl::hash::hash(openssl::hash::MessageDigest::sha256(), canonicalisied_events.as_bytes()) {
675 Ok(d) => d,
676 Err(e) => {
677 return Err(format!("openssl error: {}", e));
678 }
679 };
680
681 let reference = proto::ds::Reference {
682 transforms: Some(proto::ds::Transforms {
683 transforms: vec![proto::ds::Transform {
684 algorithm: TRANSFORM_ENVELOPED_SIGNATURE.to_string(),
685 }, proto::ds::Transform {
686 algorithm: CANONICAL_EXCLUSIVE_1_0.to_string(),
687 }]
688 }),
689 digest_method: proto::ds::DigestMethod {
690 algorithm: DIGEST_SHA256.to_string()
691 },
692 digest_value: base64::encode(digest),
693 id: None,
694 uri: Some("".to_string()),
695 ref_type: None,
696 };
697
698 let key_format = priv_key.id();
699 let (signature_method, digest_method) = match key_format {
700 openssl::pkey::Id::RSA => (SIGNATURE_RSA_SHA256, openssl::hash::MessageDigest::sha256()),
701 openssl::pkey::Id::DSA => (SIGNATURE_DSA_SHA256, openssl::hash::MessageDigest::sha256()),
702 openssl::pkey::Id::EC => (SIGNATURE_ECDSA_SHA512, openssl::hash::MessageDigest::sha512()),
703 f => return Err(format!("unsupported key format {:?}", f))
704 };
705
706 let signed_info = proto::ds::SignedInfo {
707 id: None,
708 canonicalization_method: proto::ds::CanonicalizationMethod {
709 algorithm: CANONICAL_EXCLUSIVE_1_0.to_string()
710 },
711 signature_method: proto::ds::SignatureMethod {
712 algorithm: signature_method.to_string()
713 },
714 reference: vec![reference],
715 };
716
717 let signed_info_events = xml_serde::to_events(&signed_info).unwrap();
718 let canonicalisied_signed_info_events = match transform_exclusive_canonical_xml_1_0(AlgorithmData::NodeSet(&signed_info_events))?.into_inner_data() {
719 InnerAlgorithmData::OctetStream(s) => s.to_string(),
720 _ => unreachable!()
721 };
722
723 let mut signer = match openssl::sign::Signer::new(digest_method, priv_key) {
724 Ok(d) => d,
725 Err(e) => {
726 return Err(format!("openssl error: {}", e));
727 }
728 };
729
730 if let Err(e) = signer.update(canonicalisied_signed_info_events.as_bytes()) {
731 return Err(format!("openssl error: {}", e));
732 }
733
734 let signature = match signer.sign_to_vec() {
735 Ok(d) => d,
736 Err(e) => {
737 return Err(format!("openssl error: {}", e));
738 }
739 };
740
741 let signature = proto::ds::OuterSignatre {
742 signature: proto::ds::Signature {
743 signed_info: signed_info,
744 signature_value: proto::ds::SignatureValue {
745 value: base64::encode(&signature),
746 id: None,
747 },
748 key_info: Some(proto::ds::KeyInfo {
749 keys_info: vec![proto::ds::KeyInfoType::X509Data(proto::ds::X509Data {
750 x509_data: vec![proto::ds::X509Datum::SubjectName(
751 x509_name_to_string(pub_key.subject_name())
752 ), proto::ds::X509Datum::Certificate(
753 base64::encode(pub_key.to_der().unwrap())
754 )]
755 })]
756 }),
757 }
758 };
759
760 let signature_events = xml_serde::to_events(&signature).unwrap();
761
762 let start_i = match events
763 .iter()
764 .enumerate()
765 .find_map(|(i, e)| if matches!(e, xml::reader::XmlEvent::StartElement { .. }) {
766 Some(i)
767 } else {
768 None
769 }) {
770 Some(i) => i + 1,
771 None => return Ok("".to_string())
772 };
773
774 let mut final_events = vec![];
775 final_events.extend_from_slice(&events[..start_i]);
776 final_events.extend(signature_events.into_iter());
777 final_events.extend_from_slice(&events[start_i..]);
778
779 Ok(events_to_string(&final_events))
780}
781
782#[cfg(test)]
783mod tests {
784 #[test]
785 fn sig_1() {
786 pretty_env_logger::init();
787
788 let source_xml = r##"<?xml version="1.0" encoding="UTF-8" standalone="no"?><saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://as207960-neptune.eu.ngrok.io/saml2/assertion_consumer" ID="_63e92115c9dbe3c22e06a6f3c311392b" InResponseTo="test" IssueInstant="2021-07-29T12:34:42.465Z" Version="2.0"><saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://accounts.google.com/o/saml2?idpid=C01n8o8t6</saml2:Issuer><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><ds:Reference URI="#_63e92115c9dbe3c22e06a6f3c311392b"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>HH+SiZfyXcyu7bSW7HzeR42JaHaAeACAkFIFK4X10LI=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>X7NJmvYyYpqqxdl2CUbI55a23BuekWiqJmLbLAzNR0IQfMZ2xCJf2Dcs3XWD0VEvtE1Mhrw905lK
789xoQ6IoUDo09bc5Om7ECE48V1MG90+Ds0fNKwGl+bXJp7/64H2qA1wucBfo1q4MrXpN15Z4tITLv7
790d1MI+4zeKtalCJflY0gmTrt1GjJ65mz2gUxLvNBnbzt6yfngqvQs1XcBL0Coot+YMJZeUmvPrYbT
791zWFYlDdxp79AjG0pM/IcDul0PxKwSctSaGaGxEmz1oJnrkw5EDvRBPdwhKm1e1sUXr/aCOzH1GYm
792fq2E4zhhTCjsvIW8zyH7ABk64+7w28rNmK/suw==</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509SubjectName>ST=California,C=US,OU=Google For Work,CN=Google,L=Mountain View,O=Google Inc.</ds:X509SubjectName><ds:X509Certificate>MIIDdDCCAlygAwIBAgIGAXI4fvJmMA0GCSqGSIb3DQEBCwUAMHsxFDASBgNVBAoTC0dvb2dsZSBJ
793bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MQ8wDQYDVQQDEwZHb29nbGUxGDAWBgNVBAsTD0dv
794b2dsZSBGb3IgV29yazELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEwHhcNMjAwNTIx
795MTgyOTAxWhcNMjUwNTIwMTgyOTAxWjB7MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEWMBQGA1UEBxMN
796TW91bnRhaW4gVmlldzEPMA0GA1UEAxMGR29vZ2xlMRgwFgYDVQQLEw9Hb29nbGUgRm9yIFdvcmsx
797CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
798MIIBCgKCAQEA399ymOjRthGk6J0whLP4GfxnkWXp8jL8ywpftyymO3k82zXSVZWXQEin1BbUiU1g
799iG+93pTu1s2cSFTPyGOpNqUPYwMfP2NPJPvN9AQKZq6tDWWP/KyvQumcmmK2nFezHqGvRLZDGOqM
800HjGq/XzwQSXX7eRGkexrKXvKOOhRUAgRmmZvrNqXmMpthj4S55uAP88814z96fMDnvP4U0qvN7zS
801MtA/aD/C8YcliSvBS1gA9EIYeklc8gL0btIxPHRY2tViU3TRq/Nl6OGee5n3oz1e9pG7Aj5klDYu
802uUaLXo9quzi8g8jcgnX5roPhnvtSkPbsRdGPq3YqDF8+Rq/wBwIDAQABMA0GCSqGSIb3DQEBCwUA
803A4IBAQBw42j9N6/1vEsxK4WTavsLuQzcuHomP4JiHIps31sThyKTolnu8v6J7ArznDUZz2k/PUqA
804Bi+gsU5C1/fibcbQ6xL8/TMlC3Rnwl33naWjph1pgfHU58zegSQB9nSvFtqIJqu5vdeLBbkX8+Ez
805PxqqTMVahAuXBHdDexvSk3tLpxbzhgfTYS4aGbGKTamnhkby66S9Ct1ugrWXg5xzNFDHMBkg6d+w
806kO9N4axmKDI4W6XWtxTRifLySfnklNqn20MEF1PstW18lwkKCAninmVorqil5MKoXKjuFrBJv06u
8073JTAEGYtBo4aAIrQFJlAIEUV4H0jbYAKo+drHEA86yqE</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature><saml2p:Status><saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></saml2p:Status><saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="_3381066a8dfd537274b2f43fea8bec4c" IssueInstant="2021-07-29T12:34:42.465Z" Version="2.0"><saml2:Issuer>https://accounts.google.com/o/saml2?idpid=C01n8o8t6</saml2:Issuer><saml2:Subject><saml2:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">q@as207960.net</saml2:NameID><saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml2:SubjectConfirmationData InResponseTo="test" NotOnOrAfter="2021-07-29T12:39:42.465Z" Recipient="https://as207960-neptune.eu.ngrok.io/saml2/assertion_consumer"/></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore="2021-07-29T12:29:42.465Z" NotOnOrAfter="2021-07-29T12:39:42.465Z"><saml2:AudienceRestriction><saml2:Audience>https://neptune.as207960.net/entity</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions><saml2:AuthnStatement AuthnInstant="2021-07-28T21:51:07.000Z" SessionIndex="_3381066a8dfd537274b2f43fea8bec4c"><saml2:AuthnContext><saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef></saml2:AuthnContext></saml2:AuthnStatement></saml2:Assertion></saml2p:Response>"##;
808
809 let verified = super::decode_and_verify_signed_document(source_xml).unwrap();
810 println!("{:#?}", verified);
811 assert_eq!(verified.references.len(), 1);
812 }
813}