Skip to content

Commit ab2aef5

Browse files
use quick-xml
1 parent 9c9d089 commit ab2aef5

File tree

5 files changed

+94
-67
lines changed

5 files changed

+94
-67
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ rsa = "0.9.6"
2929
serial_test = "3.1.1"
3030
tokio = "1.39.2"
3131
libxml = "0.3.3"
32+
quick-xml = "0.37.2"
3233
x509-cert = "0.2.5"
3334
der = { version = "0.7.6" }
3435
openssl = "0.10"

xbuilder/tests/common/mod.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fs;
22

33
use chrono::NaiveDate;
4-
use libxml::tree::Document;
4+
use libxml::parser::Parser;
55
use rust_decimal_macros::dec;
66

77
use xbuilder::prelude::*;
@@ -101,7 +101,7 @@ pub fn cliente_base() -> Cliente {
101101
}
102102
}
103103

104-
fn sign_xml(xml: &str) -> Document {
104+
fn sign_xml(xml: &str) -> Vec<u8> {
105105
let private_key_from_file = fs::read_to_string("tests/resources/certificates/private.key")
106106
.expect("Could not read private.key");
107107
let certificate_from_file = fs::read_to_string("tests/resources/certificates/public.cer")
@@ -111,10 +111,10 @@ fn sign_xml(xml: &str) -> Document {
111111
RsaKeyPair::from_pkcs1_pem_and_certificate(&private_key_from_file, &certificate_from_file)
112112
.expect("Could not initialize RsaKeyPair");
113113

114-
let signer = XSigner::from_string(xml).expect("Could parse xml");
115-
signer.sign(&rsa_key_pair).expect("Could not sign document");
116-
117-
signer.xml_document
114+
let signer = XSigner {
115+
xml_document: xml.to_string(),
116+
};
117+
signer.sign(&rsa_key_pair).expect("Could not sign document")
118118
}
119119

120120
#[allow(dead_code)]
@@ -171,7 +171,7 @@ fn assert_snapshot(expected: &str, snapshot_filename: &str) {
171171
);
172172
}
173173

174-
fn assert_xsd(xml: &Document, schema: &str) {
174+
fn assert_xsd(xml: &Vec<u8>, schema: &str) {
175175
let mut xsdparser = SchemaParserContext::from_file(schema);
176176
let xsd = SchemaValidationContext::from_parser(&mut xsdparser);
177177

@@ -185,18 +185,20 @@ fn assert_xsd(xml: &Document, schema: &str) {
185185

186186
let mut xsd = xsd.unwrap();
187187

188-
if let Err(errors) = xsd.validate_document(xml) {
188+
let xml_document = Parser::default().parse_string(xml).unwrap();
189+
if let Err(errors) = xsd.validate_document(&xml_document) {
189190
for err in &errors {
190191
println!("{}", err.message.as_ref().unwrap());
191192
}
192193

193-
panic!("Invalid XML accoding to XSD schema");
194+
panic!("Invalid XML according to XSD schema");
194195
}
195196
}
196197

197-
async fn assert_sunat(xml: &Document) {
198+
async fn assert_sunat(xml: &Vec<u8>) {
199+
let file_content = String::from_utf8_lossy(xml).to_string();
198200
let xml_file = UblFile {
199-
file_content: xml.to_string(),
201+
file_content,
200202
};
201203

202204
let result = CLIENT

xsigner/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ description = "Sign your XML files"
99
thiserror = { workspace = true }
1010
anyhow = { workspace = true }
1111
libxml = { workspace = true }
12+
quick-xml = { workspace = true }
1213
rsa = { workspace = true, features = ["sha2"] }
1314
x509-cert = { workspace = true, features = ["builder"] }
1415
der = { workspace = true, features = ["alloc", "derive", "flagset", "oid"] }

xsigner/src/lib.rs

Lines changed: 69 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
use anyhow::anyhow;
21
use base64::engine::general_purpose;
32
use base64::Engine;
43
use der::{DecodePem, EncodePem};
5-
use libxml::parser::{Parser, XmlParseError};
6-
use libxml::tree::{Document, Node};
74
use openssl::hash::{hash, MessageDigest};
85
use openssl::pkey::PKey;
96
use openssl::rsa::Rsa;
107
use openssl::sign::Signer;
8+
use quick_xml::events::{BytesEnd, BytesStart, Event};
119
use rsa::pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey};
1210
use rsa::pkcs8::LineEnding;
1311
use rsa::RsaPrivateKey;
12+
use std::io::Cursor;
13+
use std::{fs, io};
1414
use x509_cert::Certificate;
1515
use xml_c14n::{canonicalize_xml, CanonicalizationMode, CanonicalizationOptions};
1616

@@ -88,36 +88,26 @@ impl From<Box<dyn std::error::Error + Send + Sync>> for SignErr {
8888
}
8989

9090
pub struct XSigner {
91-
pub xml_document: Document,
91+
pub xml_document: String,
9292
}
9393

9494
impl XSigner {
95-
pub fn from_file(filename: &str) -> Result<Self, XmlParseError> {
96-
let xml_parser = libxml::parser::Parser::default();
97-
let xml_document = xml_parser.parse_file(filename)?;
95+
pub fn from_file(filename: &str) -> Result<Self, io::Error> {
96+
let xml_document = fs::read_to_string(filename)?;
9897
Ok(Self { xml_document })
9998
}
10099

101-
pub fn from_string(xml: &str) -> Result<Self, XmlParseError> {
102-
let xml_parser = libxml::parser::Parser::default();
103-
let xml_document = xml_parser.parse_string(xml)?;
104-
Ok(Self { xml_document })
105-
}
106-
107-
pub fn sign(&self, key_pair: &RsaKeyPair) -> Result<(), SignErr> {
108-
let xml = &self.xml_document;
109-
let xml_string = xml.to_string();
110-
100+
pub fn sign(&self, key_pair: &RsaKeyPair) -> Result<Vec<u8>, SignErr> {
111101
let canonicalize_options = CanonicalizationOptions {
112102
mode: CanonicalizationMode::Canonical1_1,
113-
keep_comments: false,
103+
keep_comments: true,
114104
inclusive_ns_prefixes: vec![],
115105
};
116-
let xml_canonicalize = canonicalize_xml(&xml_string, canonicalize_options.clone())
106+
let xml_canonicalized = canonicalize_xml(&self.xml_document, canonicalize_options.clone())
117107
.expect("Could not canonicalize xml");
118108

119109
// Generate digest
120-
let digest = hash(MessageDigest::sha256(), xml_canonicalize.as_bytes())
110+
let digest = hash(MessageDigest::sha256(), xml_canonicalized.as_bytes())
121111
.expect("Digest generation error");
122112
let digest_base64 = general_purpose::STANDARD.encode(digest);
123113

@@ -159,31 +149,6 @@ impl XSigner {
159149
let signature = signer.sign_to_vec().expect("Error while signing");
160150
let signature_base64 = general_purpose::STANDARD.encode(&signature);
161151

162-
// Search Signature element
163-
fn find_extension_content_node(node: Node) -> Option<Node> {
164-
if let Some(ns) = node.get_namespace() {
165-
if ns.get_prefix() == "ext" && node.get_name() == "ExtensionContent" {
166-
return Some(node);
167-
}
168-
}
169-
170-
for child in node.get_child_nodes().into_iter() {
171-
let result = find_extension_content_node(child);
172-
if result.is_some() {
173-
return result;
174-
}
175-
}
176-
177-
None
178-
}
179-
180-
let xml_root_node = xml
181-
.get_root_element()
182-
.ok_or(SignErr::Any(anyhow!("Could not get the xml root element")))?;
183-
let mut extension_content_node = find_extension_content_node(xml_root_node).ok_or(
184-
SignErr::Any(anyhow!("Could not find the ext:ExtensionContent tag")),
185-
)?;
186-
187152
// Signature
188153
let signature_string = format!(
189154
"<ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" Id=\"PROJECT-OPENUBL\">
@@ -197,17 +162,65 @@ impl XSigner {
197162
</ds:Signature>"
198163
);
199164

200-
let parser = Parser::default();
201-
let signature_string_node = parser
202-
.parse_string(&signature_string)
203-
.expect("Could not parse Signature");
204-
let mut signed_info_node_root = signature_string_node
205-
.get_root_element()
206-
.expect("Could not get root element of Signature");
207-
signed_info_node_root.unlink();
208-
209-
extension_content_node.add_child(&mut signed_info_node_root)?;
210-
Ok(())
165+
let mut xml_reader = quick_xml::Reader::from_str(&xml_canonicalized);
166+
let mut xml_writer = quick_xml::Writer::new(Cursor::new(Vec::new()));
167+
168+
let mut inside_target_element = false;
169+
let mut requires_closing_extension_content_tag = false;
170+
171+
loop {
172+
match xml_reader.read_event() {
173+
Ok(Event::Empty(e)) => {
174+
if e.name().as_ref() == b"ext:ExtensionContent" {
175+
inside_target_element = true;
176+
requires_closing_extension_content_tag = true;
177+
178+
xml_writer
179+
.write_event(Event::Start(BytesStart::new("ext:ExtensionContent")))
180+
.unwrap();
181+
} else {
182+
assert!(xml_writer.write_event(Event::Start(e.clone())).is_ok());
183+
}
184+
}
185+
Ok(Event::Start(e)) => {
186+
assert!(xml_writer.write_event(Event::Start(e.clone())).is_ok());
187+
if e.name().as_ref() == b"ext:ExtensionContent" {
188+
inside_target_element = true;
189+
}
190+
}
191+
Ok(Event::End(e)) => {
192+
if inside_target_element {
193+
inside_target_element = false;
194+
195+
let mut xml_content_reader = quick_xml::Reader::from_str(&signature_string);
196+
loop {
197+
match xml_content_reader.read_event() {
198+
Ok(Event::Eof) => break,
199+
Ok(e) => assert!(xml_writer.write_event(e).is_ok()),
200+
Err(e) => panic!(
201+
"Error at position {}: {:?}",
202+
xml_reader.error_position(),
203+
e
204+
),
205+
}
206+
}
207+
208+
if requires_closing_extension_content_tag {
209+
xml_writer
210+
.write_event(Event::End(BytesEnd::new("ext:ExtensionContent")))
211+
.unwrap();
212+
}
213+
}
214+
assert!(xml_writer.write_event(Event::End(e.clone())).is_ok());
215+
}
216+
Ok(Event::Eof) => break,
217+
Ok(e) => assert!(xml_writer.write_event(e).is_ok()),
218+
Err(e) => panic!("Error at position {}: {:?}", xml_reader.error_position(), e),
219+
}
220+
}
221+
222+
let result = xml_writer.into_inner().into_inner();
223+
Ok(result)
211224
}
212225
}
213226

0 commit comments

Comments
 (0)