SignCore.XML (sign_core v0.1.0)

Copy Markdown View Source

XML-DSig + XAdES B-B format adapter.

Sign

SignCore.XML.sign(xml_bytes,
  module: pkcs11_module,
  slot_id: slot_id,
  pin: "1234",
  key_label: "platform-signing-key",
  alg: :PS256,
  x5c: [leaf_der, intermediate_der]
)

Returns the original XML with an enveloped <ds:Signature> element spliced in before the root's closing tag. The output is a XAdES B-B signature with <xades:SigningCertificateV2>, <xades:SigningTime>, and the canonical Exclusive XML Canonicalization 1.0 C14N method on every <ds:Reference> and the <ds:SignedInfo> element itself.

Pipeline:

  1. Parse the input XML (xmerl).
  2. Generate fresh signature_id and signed_properties_id.
  3. Build the XAdES <xades:QualifyingProperties> block via SignCore.XML.XAdES. SHA-256 the leaf cert into <xades:CertDigest>; DER-encode the RFC 5035 IssuerSerial into <xades:IssuerSerialV2>.
  4. Canonicalise <xades:SignedProperties> (subtree only) with exc-c14n → SHA-256 → second <ds:Reference> digest.
  5. Canonicalise the input document with exc-c14n → SHA-256 → data <ds:Reference> digest. The enveloped-signature transform is conceptually applied; at sign time there is no Signature in the document yet so the transform is a no-op, leaving the canonical form unchanged.
  6. Build <ds:SignedInfo> with both references.
  7. Canonicalise <ds:SignedInfo> with exc-c14n. Those bytes are the to-be-signed input.
  8. Sign via Pkcs11ex.sign_bytes/2 (PKCS#11 → HSM). Software signing never enters the path.
  9. Build the full <ds:Signature> element and splice it into the source XML before the root's closing tag.

v1 limitations

  • Enveloped signatures only. Detached and enveloping XML-DSig modes are post-v1.
  • :PS256 and :RS256. PS256 emits the sha256-rsa-MGF1 signature method URI per RFC 4051; receivers should use SHA-256 / MGF1-SHA-256 / sLen=32 to match the HSM-produced signature.
  • Single <ds:Reference> URI is empty (whole-document). The :reference_uri opt is reserved for a future fragment-signing mode.
  • verify/2 lands in step 4b.1.6.

Architectural invariants

  • x5c is untrusted input until verify runs the configured SignCore.Policy (allowlist gate).
  • Signature math always runs in the HSM via Pkcs11ex.sign_bytes/2. Software signing is never invoked.

Summary

Functions

Sign an XML document with XML-DSig + XAdES B-B.

Verify a XAdES B-B-signed XML document.

Types

sign_result()

@type sign_result() :: {:ok, binary()} | {:error, term()}

verify_result()

@type verify_result() :: {:ok, subject_id :: term()} | {:error, term()}

Functions

sign(doc, opts)

@spec sign(
  binary() | iodata(),
  keyword()
) :: sign_result()

Sign an XML document with XML-DSig + XAdES B-B.

verify(doc, opts \\ [])

@spec verify(
  binary() | iodata(),
  keyword()
) :: verify_result()

Verify a XAdES B-B-signed XML document.

Returns {:ok, subject_id} where subject_id is whatever the configured SignCore.Policy.validate/3 returned. The verify pipeline runs in this order — every step is a checkpoint that can refuse the signature with the documented error class:

  1. Parse the signed XML. Locate the (single) <ds:Signature> element. v1 refuses multi-signature documents.
  2. Extract the embedded <ds:KeyInfo> x5c chain.
  3. Allowlist gate (architectural invariant). Synthesise a JOSE-style header and route it through the configured SignCore.Policyresolve/2 then validate/3. The chain is untrusted input until both succeed. No cryptographic check has happened yet.
  4. Verify the XAdES <xades:SigningCertificateV2> actually binds the leaf cert from <ds:KeyInfo>: SHA-256(leaf_der) must match <xades:CertDigest>, and <xades:IssuerSerialV2> must match the leaf's issuer + serial.
  5. Recompute the data <ds:Reference> digest: apply the enveloped-signature transform (remove <Signature> from the doc), exc-c14n the result, SHA-256.
  6. Recompute the SignedProperties <ds:Reference> digest: canonicalise the <xades:SignedProperties> subtree, SHA-256.
  7. Canonicalise <ds:SignedInfo> (subtree extraction with inherited-default-namespace clear) and verify the math against the leaf's SPKI.

Failures from step 3 short-circuit before any signature math, so callers cannot use verify/2 as a CPU-bound oracle on attacker-supplied certificates.