rust_xmlsec/
lib.rs

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}