Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.

Commit 6505929

Browse files
committed
Fix some mismatches with the original implementation
1 parent 958bbf0 commit 6505929

3 files changed

Lines changed: 29 additions & 38 deletions

File tree

hlkx-sign/src/opc.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ pub const MIME_RELS: &str =
4444
"application/vnd.openxmlformats-package.relationships+xml";
4545
pub const MIME_OCTET: &str = "application/octet-stream";
4646

47+
/// Extension token used with `[Content_Types].xml` lookup, mirroring .NET
48+
/// `Path.GetExtension(partPath)?.TrimStart('.')` on `OpcPart` paths.
49+
///
50+
/// Rust's [`std::path::Path::extension`] returns `None` for file names like
51+
/// `.rels` (a leading dot before the extension), which would incorrectly fall
52+
/// back to `application/octet-stream` for `/_rels/.rels`.
53+
pub fn extension_for_opc_content_type(part_path: &str) -> &str {
54+
let file = part_path.rsplit('/').next().unwrap_or(part_path);
55+
file.rfind('.').map(|i| &file[i + 1..]).unwrap_or("")
56+
}
57+
4758
// ─────────────────────────────────────────────────────────────────────────────
4859
// OpcRelationship
4960
// ─────────────────────────────────────────────────────────────────────────────

hlkx-sign/src/signing.rs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
1212
use crate::c14n::c14n;
1313
use crate::opc::{
14-
entry_to_uri, rels_path_for_part, serialize_rels, OpcPackage, OpcRelationship,
15-
MIME_DS_CERTIFICATE, MIME_DS_ORIGIN, MIME_DS_SIGNATURE, MIME_RELS, REL_DS_CERTIFICATE,
16-
REL_DS_ORIGIN, REL_DS_SIGNATURE,
14+
entry_to_uri, extension_for_opc_content_type, rels_path_for_part, serialize_rels, OpcPackage,
15+
OpcRelationship, MIME_DS_CERTIFICATE, MIME_DS_ORIGIN, MIME_DS_SIGNATURE, MIME_RELS,
16+
REL_DS_CERTIFICATE, REL_DS_ORIGIN, REL_DS_SIGNATURE,
1717
};
1818
use crate::timestamp;
1919
use crate::xml_sig::{build_signature_xml, DigestAlgorithm, PartDigest, TransformInfo};
@@ -152,12 +152,8 @@ pub fn sign(
152152

153153
for part_path in &parts_to_sign {
154154
let data = pkg.entries.get(part_path.as_str()).cloned().unwrap_or_default();
155-
let mime = pkg.content_type_for_extension(
156-
std::path::Path::new(part_path)
157-
.extension()
158-
.and_then(|e| e.to_str())
159-
.unwrap_or(""),
160-
).to_string();
155+
let ext_token = extension_for_opc_content_type(part_path);
156+
let mime = pkg.content_type_for_extension(ext_token).to_string();
161157

162158
if part_path == crate::opc::GLOBAL_RELS {
163159
// Two digest entries for _rels/.rels (mirrors OpcSignatureManifest.Build).
@@ -277,13 +273,9 @@ fn digest_rels_part(
277273
let c14n_filtered = c14n(filtered_xml.as_bytes())?;
278274
let hash2 = digest_alg.hash(&c14n_filtered);
279275

280-
// Collect the relationship types for the RelationshipsGroupReference elements.
281-
let source_types: Vec<String> = {
282-
let mut types: Vec<String> =
283-
filtered.iter().map(|r| r.rel_type.clone()).collect();
284-
types.dedup();
285-
types
286-
};
276+
// One RelationshipsGroupReference per filtered relationship (same order as C#
277+
// `XmlSignatureBuilder` iterating `nodes` from the filtered relationships doc).
278+
let source_types: Vec<String> = filtered.iter().map(|r| r.rel_type.clone()).collect();
287279

288280
Ok(vec![
289281
// Entry 1 – C14N transform only.
@@ -324,15 +316,14 @@ fn build_filtered_rels_xml(rels: &[&OpcRelationship]) -> String {
324316
s
325317
}
326318

327-
/// Compute the certificate file name: serial number bytes reversed, hex-encoded.
328-
/// Matches the C# `ByteArrayToReverseString(certificate.GetSerialNumber())`.
319+
/// Compute the certificate file name: serial number bytes, hex-encoded.
329320
fn cert_der_filename(cert_der: &[u8]) -> Result<String> {
330321
// Parse the DER certificate to extract the serial number.
331322
let cert = Certificate::from_der(cert_der)
332323
.context("Failed to parse certificate DER")?;
333-
// serial_number().as_bytes() returns the big-endian integer content bytes.
334324
let bytes = cert.tbs_certificate.serial_number.as_bytes();
335-
let reversed: Vec<u8> = bytes.iter().rev().copied().collect();
336-
let hex_str = hex::encode_upper(&reversed);
325+
// Don't reverse the hex. C# had some broken code that appeared to reverse
326+
// it, but actually doesn't.
327+
let hex_str = hex::encode_upper(bytes);
337328
Ok(format!("{}.cer", hex_str))
338329
}

hlkx-sign/src/xml_sig.rs

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -200,27 +200,16 @@ pub fn build_signature_xml(
200200
// canonicalised – it just embeds the same content).
201201
doc.extend_from_slice(&object_xml);
202202

203-
// Optional timestamp <Object>, matching the C# OpcPackageTimestampBuilder output:
204-
//
205-
// <Object xmlns="http://www.w3.org/2000/09/xmldsig#">
206-
// <mdssi:TimeStamp Id="idSignatureTimestamp"
207-
// xmlns:mdssi="http://schemas.openxmlformats.org/package/2006/digital-signature">
208-
// <mdssi:Comment>Timestamp got from the time stamp server</mdssi:Comment>
209-
// <mdssi:EncodedTime>BASE64</mdssi:EncodedTime>
210-
// </mdssi:TimeStamp>
211-
// </Object>
203+
// Optional timestamp <Object>, matching `OpcPackageTimestampBuilder.ApplyTimestamp`.
212204
if let Some(token) = timestamp_token {
213205
let token_b64 = B64.encode(token);
214-
doc.extend_from_slice(b"<Object>");
215-
doc.extend_from_slice(b"<mdssi:TimeStamp Id=\"idSignatureTimestamp\" xmlns:mdssi=\"");
206+
// Match `OpcPackageTimestampBuilder.ApplyTimestamp` (default namespace on
207+
// `TimeStamp`, unprefixed children — not `mdssi:` prefixes).
208+
doc.extend_from_slice(b"<Object><TimeStamp Id=\"idSignatureTimestamp\" xmlns=\"");
216209
doc.extend_from_slice(NS_OPC_DSIG.as_bytes());
217-
doc.extend_from_slice(b"\">");
218-
doc.extend_from_slice(b"<mdssi:Comment>Timestamp got from the time stamp server</mdssi:Comment>");
219-
doc.extend_from_slice(b"<mdssi:EncodedTime>");
210+
doc.extend_from_slice(b"\"><Comment>Timestamp got from the time stamp server</Comment><EncodedTime>");
220211
doc.extend_from_slice(token_b64.as_bytes());
221-
doc.extend_from_slice(b"</mdssi:EncodedTime>");
222-
doc.extend_from_slice(b"</mdssi:TimeStamp>");
223-
doc.extend_from_slice(b"</Object>");
212+
doc.extend_from_slice(b"</EncodedTime></TimeStamp></Object>");
224213
}
225214

226215
doc.extend_from_slice(b"</Signature>");

0 commit comments

Comments
 (0)