xmldsig1

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 30 Imported by: 0

README

xmldsig1

Scoped production support — The same-document XMLDSig 1.1 verification profile is supported when the application supplies an explicit trusted key or certificate source and checks which element the verified signature covers. External references and XSLT are opt-in advanced features with caller-owned transport, resource, and execution policy.

The xmldsig1 package implements W3C XML Digital Signatures 1.1 for helium documents.

Import path: github.com/lestrrat-go/helium/xmldsig1

package examples_test

import (
  "context"
  "crypto/rand"
  "crypto/rsa"
  "fmt"
  "strings"

  "github.com/lestrrat-go/helium"
  "github.com/lestrrat-go/helium/xmldsig1"
)

func Example_xmldsig1_sign_verify() {
  // Parse an XML document to sign. In SAML, this is typically an
  // Assertion or Response element.
  const src = `<root Id="doc1"><data>Hello, World!</data></root>`

  doc, err := helium.NewParser().Parse(context.Background(), []byte(src))
  if err != nil {
    fmt.Printf("parse error: %s\n", err)
    return
  }

  // Generate an RSA key pair. In production, load your private key
  // from a PEM file or key store.
  key, err := rsa.GenerateKey(rand.Reader, 2048)
  if err != nil {
    fmt.Printf("keygen error: %s\n", err)
    return
  }

  // Create a Signer configured for the most common SAML pattern:
  // RSA-SHA256 signature, enveloped signature transform + Exclusive
  // C14N, SHA-256 digest. NewEnvelopedReference() bundles these defaults.
  signer := xmldsig1.NewSigner().
    SignatureAlgorithm(xmldsig1.AlgRSASHA256).
    Reference(xmldsig1.NewEnvelopedReference())

  // SignEnveloped inserts a <ds:Signature> element as a child of the
  // given parent element. The signature covers the entire document
  // (URI=""), excluding the Signature element itself.
  err = signer.SignEnveloped(context.Background(), doc, doc.DocumentElement(), key)
  if err != nil {
    fmt.Printf("sign error: %s\n", err)
    return
  }

  out, _ := helium.WriteString(doc)
  fmt.Println(strings.Contains(out, "ds:Signature"))

  // To verify, create a Verifier with a KeySource that provides the
  // public key. StaticKey always returns the same key; for SAML you
  // would typically use X509CertKeySource with the IdP's certificate.
  //
  // Verify requires the document to contain exactly one ds:Signature
  // element; it returns ErrAmbiguousSignature when more than one is
  // present (use VerifyElement to disambiguate in that case). It
  // validates both the SignatureValue (cryptographic signature over the
  // canonical SignedInfo) and each Reference digest.
  _, err = xmldsig1.NewVerifier(xmldsig1.StaticKey(&key.PublicKey)).
    Verify(context.Background(), doc)
  if err != nil {
    fmt.Printf("verification failed: %s\n", err)
    return
  }

  fmt.Println("signature valid")
  // Output:
  // true
  // signature valid
}

source: examples/xmldsig1_sign_verify_example_test.go

Reference processing

Same-document URI forms

A Reference URI is dereferenced to a node-set fail-closed: only same-document forms are supported, and every other URI (an external reference, or an unrecognized XPointer scheme) is rejected with ErrReferenceNotFound. The supported forms and their comment-node semantics (XMLDSig core §4.3.3.2-3) are:

URI Node-set Comment nodes
"" whole document excluded
"#id" element with that id excluded
"#xpointer(/)" whole document included
"#xpointer(id('id'))" element with that id included

Comment membership is a property of the reference form, not of the canonicalization method. A C14N #WithComments method only emits comment nodes that are part of the node-set, so a bare "#id" or "" reference never emits comments even under a #WithComments canonicalization — the two #xpointer forms are the only ones that carry comments through. An "#id" that matches more than one element (across the document and any enveloping Object content) is rejected with ErrAmbiguousReference, defending against XML Signature Wrapping.

ds:RetrievalMethod requires its URI attribute. An absent attribute is ErrInvalidKeyInfo, including when LenientKeyInfo(true) is enabled; a present empty value remains the null same-document URI. External RetrievalMethod URIs are joined against the effective base of the RetrievalMethod element, including any inherited xml:base, before the configured resolver receives them.

Transforms

The supported transforms are the enveloped-signature transform, the canonicalization transforms (Canonical XML 1.0 / 1.1 and Exclusive C14N 1.0, each with an optional #WithComments variant), the XPath filter transform (http://www.w3.org/TR/1999/REC-xpath-19991116), and the base64 decode transform (http://www.w3.org/2000/09/xmldsig#base64). The XPath filter evaluates its ds:Transform/XPath expression once per input node — with that node as the context node, under the XPath element's in-scope namespace bindings — and keeps each node whose result converts to boolean true (XPath 1.0 semantics: no default element namespace). During Verify, every XPath filter across all top-level Reference chains is compiled and statically validated before any Reference resolver or transformer runs, and the prepared state is reused during digest execution. An invalid expression therefore fails even when an earlier filter produces an empty node-set or an earlier Reference carries XSLT. The XMLDSig here() function (core §6.6.3.1) is available inside an XPath filter expression: it returns the ds:XPath element that bears the expression, which is what the standard "enveloped signature via here()" filter uses to omit the enclosing ds:Signature. Evaluation runs on a bounded XPath 1.0 evaluator (an operation-count cap on top of the recursion and node-set caps), so an attacker-supplied expression cannot stall verification. The Base64 transform decodes octet input directly. For node-set input it concatenates the remaining text-node string-values, with element markup, comments, and processing instructions stripped, before decoding. Signing supports Base64 through the Transform interface, although no typed constructor is provided.

Transforms run in declared order over either a node-set or octets. The executor parses octets when the next transform requires a node-set and applies inclusive Canonical XML 1.0 when the next transform requires octets. A final node-set gets the same default canonicalization. This permits repeated transitions such as XPath → C14N 1.1 → XSLT → XPath → C14N 1.1, multiple XSLT steps, and a second canonicalization. Base64's node-set text conversion is its one algorithm-specific exception to the generic C14N conversion. Unknown algorithms and unusable parameters fail with ErrUnsupportedTransform before any injected transformer runs. An enveloped transform is limited to the original same-document node-set; it fails after an octet boundary because the containing Signature's node identity cannot be reconstructed from serialized markup. XPath and XSLT remain verify-only because the signing API cannot emit their required child content. RetrievalMethod pipelines are all statically validated, including reachable transform-free same-document chains, before any resolver or transformer callback runs. They also have a pre-authentication step cap; see Verification resource limits.

XSLT transform (opt-in, verify-only)

The XSLT transform is off by default and verify-only. XSLT is a powerful language (document(), unbounded recursion and compute), and both the stylesheet and its input are attacker-controlled on verification, so helium never runs XSLT on its own: an XSLT transform fails closed with ErrUnsupportedTransform unless a transformer is injected, mirroring the "no HTTP resolver shipped" stance for external references.

To verify a signature whose Reference carries an XSLT transform, supply an XSLTTransformer:

type XSLTTransformer interface {
    TransformXSLT(ctx context.Context, stylesheet []byte, input []byte) ([]byte, error)
}

Verifier.XSLTTransformer(t) opts in. The single ds:Transform/xsl:stylesheet (or xsl:transform) child is captured and serialized, and passed to t together with the current pipeline octets. Its output feeds the next transform or the digest, and one Reference may invoke t multiple times. The implementer owns all resource and XXE policy — compute/time/memory limits and disabling document()/external access — because both inputs are attacker-controlled. The core package runs no XSLT automatically; the separate xmldsig1/transform.XSLT adapter is an explicit opt-in. The xslt3 direct XML serializer disables helium.Writer's per-document-child terminators. The adapter retains explicit top-level text content as result content, including non-indented newline cases and non-UTF-8 XML output, except when serializer indentation intentionally discards whitespace-only text because indentation is enabled and the result has an element child. It does not promise byte-for-byte preservation of serializer-added document-child terminators.

The shipped adapter returns []byte and does not expose an output-size cap or transform-step budget. It uses ctx during parsing, compilation, and invocation, but final serialization into its in-memory buffer remains unbounded and does not consult ctx. Use it only for interoperability testing or a controlled profile. A production boundary that accepts attacker-controlled stylesheets must inject a caller-supplied transformer with cancellation-aware, bounded serialization and explicit CPU, memory, output, URI, and step limits.

General XPointer references (opt-in)

By default a Reference URI is resolved fail-closed to the four same-document forms above. Verifier.AllowXPointer(true) additionally resolves a general XPointer framework URI — zero or more xmlns(prefix=uri) scheme parts followed by one xpointer(<expr>) part, for example #xmlns(a=urn:x)xpointer(//a:Target). It stays fail-closed by default: with AllowXPointer off, a general XPointer URI is treated as an external reference and, without a ReferenceResolver, rejected with ErrReferenceNotFound, so default verification is unchanged.

When enabled, every top-level xpointer() expression joins the verification-wide static preflight before any Reference resolver or transformer runs, and its prepared evaluator is reused during digest execution. It uses the same bounded XPath 1.0 evaluator (the document element's in-scope namespaces overlaid with the xmlns() bindings). An unresolved variable, function, or prefix fails with ErrReferenceNotFound before evaluation. The result must identify a single element — the XML Signature Wrapping defense. An empty node-set is ErrReferenceNotFound; a node-set selecting more than one element, or a non-element node, is ErrAmbiguousReference. A literal xpointer(id('X')) keeps the same duplicate-detecting id resolution the #id form uses (never a last-one-wins id table). The here() function is not available inside a URI-borne XPointer.

External references (opt-in)

By default a Reference URI that is not one of the four same-document forms — an absolute URL, or a relative path pointing outside the document — is rejected with ErrReferenceNotFound. This fail-closed default is unchanged: helium never dereferences external content on its own.

To verify a detached signature whose References point outside the document, supply a ReferenceResolver:

type ReferenceResolver interface {
    ResolveReference(ctx context.Context, uri string) ([]byte, error)
}

Verifier.ReferenceResolver(r) opts in. An external Reference URI is joined against the document's base URI (the BaseURI the document was parsed with, via the same libxml2 URI-resolution helium uses elsewhere) and passed to the resolver; the resolved octets are then run through the Reference's transform pipeline before digesting:

  • an empty transform chain digests the resolved octets directly;
  • any transform that requires a node-set parses the current octets through Verifier.ReferenceParser, including octets produced by an earlier Base64, canonicalization, or XSLT step. The parser is locked down by default (helium.NewParser(): XXE blocked, no filesystem, no network);
  • an enveloped-signature transform on an external reference is rejected fail-closed (ErrUnsupportedTransform): removing the Signature's own subtree is meaningless on a resource that does not contain the Signature;
  • an XSLT transform on an external reference applies the same off-by-default, verify-only rule as a same-document reference: the current octets are handed to the injected XSLTTransformer, and with no (or a typed-nil) transformer it fails closed with ErrUnsupportedTransform.

A Reference satisfied through the resolver is marked External in the result. An external reference covers bytes outside the document, not an element, so VerifyResult.Covers and VerifyResult.SignedElement never attribute in-document coverage to it — confirming a specific *Element was signed still requires a same-document reference.

helium ships one resolver, FSReferenceResolver(fsys fs.FS), which serves the (base-joined) URI as a slash path inside fsys with no network access. It is fail-closed on anything that is not a plain in-tree path: a URI carrying a scheme (http:, https:, file:, urn:, any scheme: per RFC 3986, or a Windows drive letter) is refused; a path escaping the root (absolute, or .. past the root) is refused; a leftover fragment is refused. Reads are bounded — a resource larger than 64 MiB fails with ErrReferenceTooLarge before it can be buffered in full.

No HTTP resolver is provided. The interface is public so callers can dereference over any transport, but anyone implementing network dereferencing owns the resulting SSRF and availability risk (an attacker who controls a Reference URI could otherwise steer requests at internal hosts or stall verification), so that decision is left explicitly to the caller.

For detached-signature services, compose a smaller per-resource cap around the filesystem resolver, set a low Verifier.MaxReferences value, and pass a deadline-bearing context:

resolver := xmldsig1.LimitReferenceResolver(
    xmldsig1.FSReferenceResolver(fsys),
    1<<20, // 1 MiB per resource
)

The built-in filesystem resolver applies this cap while reading. A custom resolver receives a post-return size check, so it must enforce its own aggregate byte, connection, and transport budget.

Signer.ReferenceResolver / Signer.ReferenceParser are the symmetric signing side, letting a detached signature cover external content. The sign and verify paths funnel through the same octet-to-digest logic, so the signed digest is byte-identical to what verification recomputes for the same input.

Manifest inner-reference validation (opt-in)

A ds:Reference whose Type is http://www.w3.org/2000/09/xmldsig#Manifest points at a ds:Manifest, which holds its own list of ds:Reference elements (XMLDSig core §5.1). The signature commits to the Manifest's own bytes — the top-level Manifest reference's digest over the ds:Manifest subtree is checked exactly like any other reference — but by design it says nothing about whether the Manifest's inner references still match their targets. Per §5.1 that is left to the application.

Verifier.ValidateManifests(true) opts in to walking those inner references. When enabled, after a top-level Manifest-typed reference has itself verified, every direct inner ds:Reference is parsed and statically prepared before any is executed. If preparation succeeds for all of them, each is resolved, run through its transform pipeline, and digested through the same fail-closed path as a top-level reference. If one fails preparation, resolver and transformer callbacks do not run for that Manifest; the failing result reports the error, and each otherwise prepared peer reports an advisory error wrapping the same cause. Per-reference outcomes are reported in VerifyResult.Manifests:

type ManifestResult struct {
    Reference  *VerifiedReference // the top-level Manifest reference
    Element    *helium.Element    // the ds:Manifest element
    References []ManifestReference
}
type ManifestReference struct {
    URI, DigestAlgorithm string
    Element              *helium.Element
    Valid                bool
    Err                  error
}

Inner-reference results are advisory. A failed inner digest, an unsupported inner transform, or an unresolved external inner reference is recorded as that ManifestReference's Valid:false / Err — it does not fail Verify, and it never contributes to VerifyResult.Covers or SignedElement. Coverage is never attributed through a Manifest, preserving the XML Signature Wrapping guarantee: confirming a specific *Element was signed still requires a top-level same-document reference. Only one level is walked — a Manifest nested inside a Manifest is digested but not recursively expanded, which bounds the work.

The toggle defaults to false: VerifyResult.Manifests is nil and no inner references are walked, byte-identical to a Verifier without it. It is opt-in because inner references may pull in transforms or external URIs the top-level policy did not intend. The top-level VerifiedReference.Type is reported in the result regardless of the toggle.

Security: SHA-1 rejected by default

SHA-1-based algorithms (rsa-sha1, ecdsa-sha1, hmac-sha1, and the sha1 digest) are rejected by default for both signing and verification. SHA-1 is cryptographically weak; accepting it silently exposes callers to algorithm downgrade and collision attacks. When a SHA-1 algorithm is encountered without an explicit opt-in, the operation fails with ErrWeakAlgorithm.

If you must interoperate with a legacy system that cannot be upgraded, opt in explicitly by calling AllowSHA1(true) on both the Signer and the Verifier, as shown in the example below:

package examples_test

import (
  "context"
  "crypto/rand"
  "crypto/rsa"
  "fmt"

  "github.com/lestrrat-go/helium"
  "github.com/lestrrat-go/helium/xmldsig1"
)

// Example_xmldsig1_sha1_optin demonstrates the explicit opt-in required to
// produce and verify legacy SHA-1 signatures. SHA-1 (rsa-sha1, hmac-sha1, and
// the sha1 digest) is rejected by default with ErrWeakAlgorithm; call
// AllowSHA1(true) on both the Signer and the Verifier only when you must
// interoperate with a legacy system that cannot be upgraded.
func Example_xmldsig1_sha1_optin() {
  const src = `<root Id="doc1"><data>Hello, World!</data></root>`

  doc, err := helium.NewParser().Parse(context.Background(), []byte(src))
  if err != nil {
    fmt.Printf("parse error: %s\n", err)
    return
  }

  key, err := rsa.GenerateKey(rand.Reader, 2048)
  if err != nil {
    fmt.Printf("keygen error: %s\n", err)
    return
  }

  // Produce a legacy SHA-1 signature (discouraged). AllowSHA1(true) is
  // required; without it SignEnveloped returns ErrWeakAlgorithm.
  signer := xmldsig1.NewSigner().
    AllowSHA1(true).
    SignatureAlgorithm(xmldsig1.AlgRSASHA1).
    Reference(xmldsig1.ReferenceConfig{
      URI:             "",
      DigestAlgorithm: xmldsig1.DigestSHA1,
      Transforms:      []xmldsig1.Transform{xmldsig1.Enveloped(), xmldsig1.ExcC14NTransform()},
    })

  if err := signer.SignEnveloped(context.Background(), doc, doc.DocumentElement(), key); err != nil {
    fmt.Printf("sign error: %s\n", err)
    return
  }

  // Verify the legacy SHA-1 signature. The default verifier rejects SHA-1,
  // so AllowSHA1(true) is required here as well.
  _, err = xmldsig1.NewVerifier(xmldsig1.StaticKey(&key.PublicKey)).
    AllowSHA1(true).
    Verify(context.Background(), doc)
  if err != nil {
    fmt.Printf("verification failed: %s\n", err)
    return
  }

  fmt.Println("legacy SHA-1 signature valid")
  // Output:
  // legacy SHA-1 signature valid
}

source: examples/xmldsig1_sha1_optin_example_test.go

Note (breaking default change): earlier versions accepted SHA-1 signatures and digests without any opt-in. Code that relied on verifying SHA-1 signatures must now call Verifier.AllowSHA1(true); code that produced SHA-1 signatures must call Signer.AllowSHA1(true). SHA-256 and stronger algorithms are unaffected.

Verification resource limits

An attacker-controlled, unsigned document can force verification to do substantial decode/parse work before the SignatureValue is ever checked: many or large DigestValue/SignatureValue/X509Certificate values to base64-decode, and one x509.ParseCertificate per embedded certificate. To bound that work the Verifier enforces three parse-time caps, each with a conservative default that sits well above any legitimate signature so existing documents verify unchanged:

Builder Bounds Default
Verifier.MaxReferences(n) number of ds:Reference elements 1024
Verifier.MaxKeyInfoEntries(n) KeyInfo children + X509Data children 256
Verifier.MaxDecodedBytes(n) running total of certificate and signature octets 10 MiB

Exceeding a cap fails with ErrResourceLimitExceeded before any Reference is digested or the signature is checked. For each builder, n == 0 selects the default and a negative n disables that cap.

MaxDecodedBytes charges five sites. Four are base64 values decoded straight off the document — the Signature's own DigestValue, SignatureValue, and X509Certificate content, plus the rawX509Certificate a same-document ds:RetrievalMethod points at. That last one is not confined to the Signature: a RetrievalMethod URI names any element in the document by ID, of any local name and any namespace, inside ds:Signature or outside it.

Each of those four is charged before the value is materialized, so for them the cap bounds what verification builds, and not merely what it keeps. xs:base64Binary permits XML whitespace between characters and a value may be spread over any number of text and CDATA children, so the lexical text wrapped around a value is unbounded and unrelated to the bytes it decodes to; counting it first is what keeps the memory under the cap both for a value the cap refuses and for every value it accepts. Text, CDATA, and entity-reference children carry a value's characters — a comment or processing instruction contributes none, and an element child, which xs:base64Binary does not admit at all, is rejected unread. An entity reference is read as the declared replacement text of the entity it names, taken in one step and not expanded further, so a document that writes a value as an entity reference verifies while the read stays bounded; a replacement that is not base64 fails the decode just as the same characters written inline would. A reference to an entity nothing declares contributes no characters. The parser keeps such a reference rather than refusing the document — with an external subset it did not read, or a parameter-entity reference, an undeclared general entity is a validity error and not a well-formedness one — and canonicalization renders it as nothing, so reading it as nothing is what keeps the two agreeing about the same document.

The fifth site is the exception to both halves of that. An external ds:RetrievalMethod is dereferenced through the configured ReferenceResolver, which materializes the whole resource under its own size cap (FSReferenceResolver bounds one resource at 64 MiB) and runs it through the RetrievalMethod's transforms; only the result is charged. Those octets are therefore charged after they are materialized, and they are raw certificate bytes that were never base64-decoded.

Verification also polls the context inside the KeyInfo and Reference parse loops, and inside every stage that grows or narrows a node set: the subtree and whole-document collections, the comment-excluding and enveloped-signature filters, and the base64 node-set-to-text conversion. A cancelled context or a passed deadline stops that work where it stands, without waiting for a stage boundary. Growing a node set is a single operation that charges what it added, so a stage added later inherits the poll instead of having to remember it.

What a deadline does NOT bound is the canonicalization those stages feed. Canonical XML is written by the c14n package, and c14n.Canonicalizer.CanonicalizeTo takes no context; neither does the helium.CopyDoc an enveloped canonicalization clones the document with. Once a node set is handed over, that stage runs to completion however large it is, and giving it a deadline would be a public API change in two other packages. So a deadline bounds the node-set stages and the gaps between pipeline steps, NOT the whole verify path.

What bounds the canonicalization of a subtree is its SIZE. The node set built for it carries one namespace node per declaration actually written, plus at most one per element, so it is linear in the document. It is deliberately not the complete XPath in-scope namespace axis, which would be one namespace node per (element × ancestor declaration) — quadratic work an attacker gets from a small well-formed document, before any signature is checked, since SignedInfo is canonicalized before the SignatureValue and a ds:RetrievalMethod can name a subtree anywhere in the document. The reduced set renders byte-identical canonical octets in every supported method, Exclusive C14N and its InclusiveNamespaces PrefixList included.

The one node set that does carry the complete axis is the input to an XPath filter transform. That transform is evaluated once per node — namespace nodes included — and may keep an element whose parent it drops, so every element there needs its own axis; the node set, and the one evaluation per member, are quadratic in the document. That is the transform's own data model, not a choice this package makes, and it is where a deadline earns its keep: nothing caps that node set's size, and both the walk that builds it and the per-node evaluation poll the context, so a ctx deadline bounds the work at roughly the rate times the deadline instead of running to completion. The walk charges its poll per collected node-set MEMBER — every element, attribute, and namespace node — and not per tree node walked, so an element that repeats the whole axis cannot carry a document's worth of members past a poll: what a passed deadline may still cost is one poll interval of members, whatever the document's shape.

Pass one when verifying documents from untrusted sources. A Reference's transforms run only after the SignatureValue has verified, but a ds:RetrievalMethod's transforms run before it, so a RetrievalMethod carrying an XPath filter transform reaches that quadratic node set without a key or a valid signature.

RetrievalMethod transforms have a separate fixed maxRetrievalTransformSteps cap because they execute before the SignatureValue check. It is not affected by the builder limits above.

The two KeyInfo values written as decimal text, where the rest carry base64, have their own fixed digit ceilings, also unaffected by the builder limits above: 1024 digits for a ds:X509SerialNumber, and 1024 digits for each of the RFC 4050 ECDSAKeyValue PublicKey X and Y Value attributes. Both are read before the SignatureValue is checked, and both are converted to a big.Int — a conversion that is quadratic in the number of digits, so a megabyte of digits costs about a second and gigabytes of scratch, hundreds of times what parsing the document carrying them cost. Each value is refused past its ceiling before it is converted, so that cost is never paid.

Both are fixed internal constants with no builder knob, and deliberately not folded into MaxDecodedBytes: a byte budget generous enough for real certificates and keys still admits a conversion that runs for minutes, so a byte budget is the wrong shape for a quadratic cost. Nothing conforming comes close to either ceiling — RFC 5280 §4.1.2.2 caps a certificate serial at 20 octets, which is at most 49 decimal digits, and a P-521 field element needs at most 157 — so there is no legitimate value for a knob to admit.

An XPath filter expression has a fixed 8 KiB length ceiling for the same reason: every ds:Transform/XPath expression is compiled during Reference preflight, before the SignatureValue is checked, and compiling one costs far more than its own length. The expression is refused with ErrResourceLimitExceeded where it is read off the document, so no over-length expression is compiled. Real filter expressions are tens to hundreds of bytes — the W3C defCan-1 interop vector is 75 characters — so the ceiling sits orders of magnitude above anything interoperable. It is a policy limit, and no conformance boundary requires it; like the RetrievalMethod cap it is not affected by the builder limits above.

Detached signature placement (inclusive C14N)

SignDetached and SignEnveloping return a detached ds:Signature for the caller to place. SignedInfo (and any in-Object Reference for SignEnveloping) is canonicalized under a proxy carrying the signing document element's inherited canonicalization context. If SignedInfo's CanonicalizationMethod — or an in-Object Reference — uses inclusive Canonical XML (C14N10 / C14N11), the caller MUST place the returned Signature directly under the document element, or under an element with the same in-scope namespaces and inherited xml:* attributes. Placing it under an element that contributes extra in-scope namespace declarations or xml:* attributes changes the bytes inclusive C14N canonicalizes, so verification recomputes a different canonical form and fails. Exclusive Canonical XML (the NewSigner default, ExcC14NTransform) inherits no namespaces or xml:* and is unaffected by placement.

Legacy and interop KeyInfo (verification)

For interoperating with older producers, verification-side KeyInfo parsing recognizes several legacy constructs and surfaces them through KeyInfoData so a KeySource can build the verification key. Parsing is namespace-strict and fails closed (ErrInvalidKeyInfo) on unknown or partial key material.

Security: KeyInfoData is untrusted. A KeySource receives the parsed KeyInfoData before the signature is verified, so every value in it — embedded X509Certificates, RSAKeyValue/ECKeyValue/DSAKeyValue, issuer/serial and subject-name selectors — is attacker-controlled and NOT authenticated by the signature. A KeySource.ResolveKey implementation MUST decide trust itself: match the KeyInfoData against a trust store, a pinned key, or a validated certificate chain, and return a key the caller already trusts. It MUST NOT blindly return an embedded certificate's public key or a KeyValue as the verification key — that lets an attacker sign with their own key and have it verify. KeyInfoData is a selector into trusted key material, never the key material itself. StaticKey and X509CertKeySource ignore KeyInfoData entirely and return a pre-trusted key, which is the safe default; a custom KeySource that consults KeyInfoData owns the trust decision.

  • RFC 4050 ECDSAKeyValue (namespace http://www.w3.org/2001/04/xmldsig-more#): DomainParameters/NamedCurve@URN selects the curve (P-256/P-384/P-521) and PublicKey/X,/Y carry the point as decimal integer Value attributes. It is surfaced through the same KeyInfoData.ECKeyValue as a 1.1 ECKeyValue, so a KeySource builds an *ecdsa.PublicKey from ECKeyValue.Curve/X/Y. Emitting RFC 4050 on the signing side is not supported.
  • X509IssuerSerial and X509SubjectName inside X509Data: the issuer DN + serial number (KeyInfoData.X509IssuerSerials) and subject DN (KeyInfoData.X509SubjectNames) are extracted verbatim — the library does no DName canonicalization or matching — so a KeySource can select the right certificate out of band.
  • DSAKeyValue: P/Q/G/Y are parsed into KeyInfoData.DSAKeyValue; a KeySource builds a *dsa.PublicKey from them.
DSA-SHA1 (verify-only)

DSA-SHA1 (xmldsig#dsa-sha1) is supported for verification only, as legacy interop. It sits behind the same SHA-1 weak gate as rsa-sha1: verification requires Verifier.AllowSHA1(true), otherwise it fails with ErrWeakAlgorithm. The SignatureValue is the XML-DSig fixed-width r||s concatenation. A DSA key may come from a parsed DSAKeyValue or from an X.509 certificate (which crypto/x509 parses into a *dsa.PublicKey). Signing with DSA is not supported — a signing attempt with the DSA URI fails with a clear ErrUnsupportedAlgorithm ("DSA signing is not supported").

W3C interop conformance

The package is measured against three W3C XML Signature interop suites through the helium-w3c-tests harness (merlinxmldsig, xmldsig2ed, and xmldsig11 suites). Committed point-in-time evidence:

The merlin, xmldsig2ed, and xmldsig11 suites pass in full. The xmldsig2ed defCan-2/3 cases exercise a multi-phase XPath → c14n → XSLT → XPath → c14n chain. The ordered transform pipeline executes its repeated node-set/octet transitions when the Verifier has an XSLTTransformer configured, such as the ready xslt3-backed xmldsig1/transform.XSLT adapter.

Documentation

Overview

Package xmldsig1 implements W3C XML Digital Signatures 1.1.

Index

Constants

View Source
const (
	// NamespaceDSig is the XML Digital Signatures namespace.
	NamespaceDSig = "http://www.w3.org/2000/09/xmldsig#"

	// NamespaceDSig11 is the XML Digital Signatures 1.1 namespace.
	NamespaceDSig11 = "http://www.w3.org/2009/xmldsig11#"

	// NamespaceDSigMore is the xmldsig-more namespace. RFC 4050 places its
	// legacy ECDSAKeyValue (and its DomainParameters/NamedCurve/PublicKey
	// children) in this namespace, distinct from both the core xmldsig#
	// namespace and the XML-Signature 1.1 xmldsig11# namespace.
	NamespaceDSigMore = "http://www.w3.org/2001/04/xmldsig-more#"
)
View Source
const (
	AlgRSASHA1     = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
	AlgRSASHA224   = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224"
	AlgRSASHA256   = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
	AlgRSASHA384   = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
	AlgRSASHA512   = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
	AlgECDSASHA1   = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha1"
	AlgECDSASHA224 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha224"
	AlgECDSASHA256 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"
	AlgECDSASHA384 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384"
	AlgECDSASHA512 = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512"
	AlgHMACSHA1    = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
	AlgHMACSHA224  = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha224"
	AlgHMACSHA256  = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256"
	AlgHMACSHA384  = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384"
	AlgHMACSHA512  = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512"
	AlgEd25519     = "http://www.w3.org/2021/04/xmldsig-more#eddsa-ed25519"
	// AlgDSASHA1 is DSA-SHA1. It is verify-only (signing is not supported) and
	// SHA-1-weak, so it is rejected on verify unless Verifier.AllowSHA1(true).
	AlgDSASHA1 = "http://www.w3.org/2000/09/xmldsig#dsa-sha1"
)

Signature algorithm URIs.

View Source
const (
	DigestSHA1   = "http://www.w3.org/2000/09/xmldsig#sha1"
	DigestSHA224 = "http://www.w3.org/2001/04/xmldsig-more#sha224"
	DigestSHA256 = "http://www.w3.org/2001/04/xmlenc#sha256"
	DigestSHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
	DigestSHA512 = "http://www.w3.org/2001/04/xmlenc#sha512"
)

Digest algorithm URIs.

View Source
const (
	C14N10            = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
	C14N10Comments    = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
	ExcC14N10         = "http://www.w3.org/2001/10/xml-exc-c14n#"
	ExcC14N10Comments = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
	C14N11URI         = "http://www.w3.org/2006/12/xml-c14n11"
	C14N11Comments    = "http://www.w3.org/2006/12/xml-c14n11#WithComments"
)

Canonicalization method URIs.

View Source
const (
	TransformEnvelopedSignature = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
	// TransformXPath is the XPath filter transform (XMLDSig core §6.6.3). It is
	// verify-only: verification evaluates the ds:Transform/XPath expression to
	// filter the reference node-set, but signing has no typed Transform for it and
	// the sign preflight rejects it fail-closed (there is no way to author the
	// required <XPath> child from the signing API).
	TransformXPath = "http://www.w3.org/TR/1999/REC-xpath-19991116"
	// TransformXSLT is the XSLT transform (XMLDSig core §6.6.5). Its
	// ds:Transform/xsl:stylesheet child is applied to the current pipeline octets;
	// the result feeds the next transform or digest. XSLT is powerful
	// (document(), unbounded compute), so
	// it is verify-only and OFF by default: it runs only through an injected
	// [XSLTTransformer] and fails closed with [ErrUnsupportedTransform] when none is
	// configured. Signing has no typed Transform for it and the sign preflight
	// rejects it fail-closed.
	TransformXSLT = "http://www.w3.org/TR/1999/REC-xslt-19991116"
	// TransformBase64 is the base64 decode transform (XMLDSig core §6.6.2). Its
	// node-set input is converted from the remaining text-node string-values, while
	// an octet input is decoded directly. The decoded octets feed the next declared
	// transform or the digest. Signing supports it through the Transform interface;
	// there is no typed constructor.
	TransformBase64 = "http://www.w3.org/2000/09/xmldsig#base64"
)

Transform URIs.

View Source
const (
	TypeObject    = "http://www.w3.org/2000/09/xmldsig#Object"
	TypeManifest  = "http://www.w3.org/2000/09/xmldsig#Manifest"
	TypeSignProps = "http://www.w3.org/2000/09/xmldsig#SignatureProperties"
)

Type URIs for Reference elements.

View Source
const (
	// TypeRawX509Certificate identifies a resource that is a single raw (DER)
	// X.509 certificate.
	TypeRawX509Certificate = "http://www.w3.org/2000/09/xmldsig#rawX509Certificate"
	// TypeX509Data identifies a resource that is a ds:X509Data element.
	TypeX509Data = "http://www.w3.org/2000/09/xmldsig#X509Data"
)

Type URIs for the ds:RetrievalMethod Type attribute, identifying the kind of key material the referenced resource holds.

Variables

View Source
var (
	// ErrVerificationFailed is returned when signature verification fails.
	ErrVerificationFailed = errors.New("xmldsig1: verification failed")

	// ErrDigestMismatch is returned when a Reference digest does not match.
	ErrDigestMismatch = errors.New("xmldsig1: digest mismatch")

	// ErrSignatureNotFound is returned when no Signature element is found.
	ErrSignatureNotFound = errors.New("xmldsig1: signature element not found")

	// ErrUnsupportedAlgorithm is returned for unrecognized algorithm URIs.
	ErrUnsupportedAlgorithm = errors.New("xmldsig1: unsupported algorithm")

	// ErrWeakAlgorithm is returned when a SHA-1-based signature or digest
	// algorithm is encountered while SHA-1 is not allowed. SHA-1 is rejected
	// by default; opt in with Verifier.AllowSHA1(true) (for verification) or
	// Signer.AllowSHA1(true) (for signing) to accept it for legacy interop.
	ErrWeakAlgorithm = errors.New("xmldsig1: weak algorithm SHA-1 not allowed")

	// ErrUnsupportedTransform is returned for unrecognized transform URIs.
	ErrUnsupportedTransform = errors.New("xmldsig1: unsupported transform")

	// ErrKeyMismatch is returned when the key type does not match the algorithm.
	ErrKeyMismatch = errors.New("xmldsig1: key type does not match algorithm")

	// ErrNoReferences is returned when signing is attempted with no references.
	ErrNoReferences = errors.New("xmldsig1: no references configured")

	// ErrReferenceNotFound is returned when a Reference URI cannot be resolved.
	// It also covers an external Reference that no ReferenceResolver is
	// configured to dereference (the fail-closed default), and every
	// FSReferenceResolver rejection short of the size cap (scheme URI, escaping
	// path, leftover fragment, missing file).
	ErrReferenceNotFound = errors.New("xmldsig1: reference URI not resolved")

	// ErrReferenceTooLarge is returned when an external Reference resource
	// exceeds the resolver's size cap (FSReferenceResolver bounds a single
	// resource at 64 MiB) so a large or attacker-supplied file cannot exhaust
	// memory during verification.
	ErrReferenceTooLarge = errors.New("xmldsig1: external reference exceeds size cap")

	// ErrResourceLimitExceeded is returned when an attacker-controlled Signature
	// element exceeds one of the Verifier's parse-time resource caps before the
	// SignatureValue is checked: too many ds:Reference elements
	// ([Verifier.MaxReferences]), too many KeyInfo entries
	// ([Verifier.MaxKeyInfoEntries]), or too many total certificate and signature
	// octets — the DigestValue/SignatureValue/X509Certificate values plus
	// whatever a ds:RetrievalMethod in KeyInfo pulls in
	// ([Verifier.MaxDecodedBytes]). It also covers two fixed pre-verification
	// caps: a ds:RetrievalMethod transform list past its step cap, and a
	// ds:Transform/XPath filter expression past its length ceiling. The caps have
	// conservative defaults and bound the decode/parse/transform work an unsigned
	// document can force before verification rejects it. A base64 value decoded
	// off the document is charged against [Verifier.MaxDecodedBytes] before it is
	// decoded, so a value that is both over the cap and invalid base64 reports
	// this error, and never the base64 one.
	ErrResourceLimitExceeded = errors.New("xmldsig1: verification resource limit exceeded")

	// ErrAmbiguousReference is returned when a Reference URI resolves to more
	// than one element. This is the primary defense against XML Signature
	// Wrapping (XSW) attacks where an attacker injects a duplicate-ID element
	// containing malicious content alongside the legitimately signed element.
	ErrAmbiguousReference = errors.New("xmldsig1: reference URI matches multiple elements")

	// ErrAmbiguousSignature is returned when the document contains more than
	// one Signature element and Verify cannot decide which one to verify.
	// Callers must use VerifyElement to disambiguate.
	ErrAmbiguousSignature = errors.New("xmldsig1: document contains multiple Signature elements")

	// ErrInvalidKeyInfo is returned when KeyInfo content cannot be parsed.
	ErrInvalidKeyInfo = errors.New("xmldsig1: invalid KeyInfo")

	// ErrRetrievalMethodLoop is returned when a ds:RetrievalMethod chain is
	// cyclic or exceeds the maximum follow depth. A RetrievalMethod whose target
	// is itself a RetrievalMethod is followed, so an unbounded or self-referential
	// chain is rejected fail-closed, and never dereferenced without limit.
	ErrRetrievalMethodLoop = errors.New("xmldsig1: RetrievalMethod chain is cyclic or too deep")

	// ErrInvalidSignature is returned when the Signature element is malformed.
	ErrInvalidSignature = errors.New("xmldsig1: invalid signature structure")

	// ErrNoKeySource is returned when a Verifier was created with a nil
	// KeySource and verification is attempted. Without a KeySource there is no
	// way to resolve a verification key, so this is rejected before any key
	// resolution, and never panics on a nil dereference.
	ErrNoKeySource = errors.New("xmldsig1: no key source configured")

	// ErrHereUnavailable is returned when the XPath here() function is invoked in
	// a context that has no bearing node. here() (XMLDSig core §6.6.3.1) resolves
	// to the element carrying the XPath expression — the ds:XPath element of an
	// XPath filter transform. It is threaded through only on the XPath filter
	// transform path; the signing path and a URI-borne XPointer carry no bearing
	// element, so here() fails closed there, resolving to no wrong node.
	ErrHereUnavailable = errors.New("xmldsig1: here() has no bearing node in this context")
)

Functions

This section is empty.

Types

type DSAKeyValueData added in v0.7.0

type DSAKeyValueData struct {
	P, Q, G, Y *big.Int
}

DSAKeyValueData holds parsed DSAKeyValue content (the P, Q, G, Y CryptoBinary parameters). A KeySource builds a *dsa.PublicKey from these.

type ECKeyValueData

type ECKeyValueData struct {
	Curve elliptic.Curve
	X, Y  *big.Int
}

ECKeyValueData holds parsed ECKeyValue content.

type KeyInfoBuilder

type KeyInfoBuilder interface {
	BuildKeyInfo(ctx context.Context, doc *helium.Document, key any) (*helium.Element, error)
}

KeyInfoBuilder configures how the KeyInfo element is constructed during signing.

func RSAKeyValueKeyInfo

func RSAKeyValueKeyInfo() KeyInfoBuilder

RSAKeyValueKeyInfo returns a KeyInfoBuilder that includes RSAKeyValue derived from the signing key.

func X509DataKeyInfo

func X509DataKeyInfo(certs ...*x509.Certificate) KeyInfoBuilder

X509DataKeyInfo returns a KeyInfoBuilder that includes X509Data containing the given certificates.

type KeyInfoData

type KeyInfoData struct {
	KeyNames          []string
	X509Certificates  []*x509.Certificate
	X509SKIs          [][]byte
	X509IssuerSerials []*X509IssuerSerial
	X509SubjectNames  []string
	RSAKeyValue       *RSAKeyValueData
	ECKeyValue        *ECKeyValueData
	DSAKeyValue       *DSAKeyValueData
}

KeyInfoData holds parsed KeyInfo content for verification.

SECURITY: every field is parsed from the document's ds:KeyInfo, which is attacker-controlled and NOT authenticated by the signature — KeyInfo is resolved before the signature is checked. Treat these values as untrusted hints for selecting a key from trusted material (a trust store, a pinned key, a validated chain), never as the key material to verify with. In particular an embedded X509Certificate is not proof of anything on its own: an attacker can embed a certificate for a key they control. See the KeySource contract.

type KeySource

type KeySource interface {
	// ResolveKey returns the verification key for a signature. keyInfo is the
	// document's parsed, UNTRUSTED KeyInfo (nil when the Signature carries no
	// KeyInfo); alg is the SignatureMethod algorithm URI. See the [KeySource]
	// contract: match keyInfo against trusted material, and never trust it.
	ResolveKey(ctx context.Context, keyInfo *KeyInfoData, alg string) (any, error)
}

KeySource provides keys for signature verification.

SECURITY: the keyInfo passed to ResolveKey is parsed from the document's ds:KeyInfo BEFORE the signature is verified, so it is entirely attacker-controlled (see KeyInfoData). A KeySource MUST decide trust itself: select the verification key by matching keyInfo against a trust store, a pinned key, or a validated certificate chain. It MUST NOT blindly return an embedded X509Certificate's public key or a KeyValue as the verification key — doing so lets an attacker present a signature made with their own key and have it verify. keyInfo is a selector into trusted key material, never the key material itself. StaticKey and X509CertKeySource ignore keyInfo and return a key the caller already trusts, which is the safe pattern; a custom KeySource that consults keyInfo carries the trust decision.

func KeyByNameSource added in v0.7.0

func KeyByNameSource(keys map[string]any) KeySource

KeyByNameSource returns a KeySource that maps a ds:KeyName to a key. The KeyInfo's KeyNames are tried in document order and the first name present in keys wins; a KeyInfo with no matching KeyName (including one that carries no KeyName at all) fails closed with ErrNoKeySource. A ds:KeyName is an opaque, producer-chosen label, so the caller owns the name→key mapping and the trust decision that a named key is acceptable.

func StaticKey

func StaticKey(key any) KeySource

StaticKey returns a KeySource that always returns the given key.

func X509CertKeySource

func X509CertKeySource(cert *x509.Certificate) KeySource

X509CertKeySource returns a KeySource that extracts the public key from a trusted X.509 certificate. This is the common SAML pattern.

func X509CertPoolKeySource added in v0.7.0

func X509CertPoolKeySource(certs ...*x509.Certificate) KeySource

X509CertPoolKeySource returns a KeySource that selects a certificate from the given set by matching the verification-side KeyInfo against it, and returns the matched certificate's PublicKey. Selector strength is applied across the WHOLE pool, strongest first, so a strong match on a later certificate is never masked by a weak match on an earlier one:

  • first, an exact raw-DER match against a ds:X509Certificate in the KeyInfo, over every certificate in the pool;
  • then a ds:X509SKI match against the certificate's SubjectKeyId;
  • then a ds:X509IssuerSerial match against the certificate's Issuer and SerialNumber;
  • finally a ds:X509SubjectName match against the certificate's Subject.

Pool order is preserved only within a single selector class. The raw-DER and SubjectKeyId paths are exact and reliable. The IssuerSerial and SubjectName paths compare Go's pkix.Name.String() rendering, which is NOT RFC 2253 canonical, so DName matching is best-effort — prefer supplying the certificate whose SKI or raw bytes the signature carries. Selecting a certificate never establishes trust: the returned public key is still subject to the same out-of-band trust decision as any other verification key.

type KeySourceFunc

type KeySourceFunc func(ctx context.Context, keyInfo *KeyInfoData, alg string) (any, error)

KeySourceFunc adapts a function to the KeySource interface.

func (KeySourceFunc) ResolveKey

func (f KeySourceFunc) ResolveKey(ctx context.Context, keyInfo *KeyInfoData, alg string) (any, error)

type ManifestReference added in v0.7.0

type ManifestReference struct {
	// URI is the value of the inner Reference URI attribute.
	URI string

	// DigestAlgorithm is the algorithm URI declared in the inner Reference's
	// DigestMethod element. It is "" when the inner Reference could not be
	// parsed.
	DigestAlgorithm string

	// Element is the element the inner Reference URI resolved to, or nil for an
	// external reference or when resolution failed.
	Element *helium.Element

	// Valid reports whether the inner Reference's recomputed digest matched its
	// declared DigestValue.
	Valid bool

	// Err is the reason the inner Reference could not be validated, including a
	// sibling static-preparation failure that prevented execution. It is nil
	// only when Valid is true.
	Err error
}

ManifestReference reports the outcome of digesting a single inner ds:Reference child of a ds:Manifest. Valid reports whether the inner reference's recomputed digest matched its DigestValue; Err carries the reason it could not be validated (a resolution, transform, digest, or mismatch error). Both are advisory — see ManifestResult.

type ManifestResult added in v0.7.0

type ManifestResult struct {
	// Reference is the top-level VerifiedReference whose Type is TypeManifest —
	// the reference the signature actually commits to. It points into
	// VerifyResult.References.
	Reference *VerifiedReference

	// Element is the ds:Manifest element that Reference resolved to.
	Element *helium.Element

	// References lists the validation result for each inner ds:Reference child
	// of the Manifest, in document order. If one child fails static preparation,
	// none are executed and otherwise prepared peers carry an error wrapping the
	// same cause. Only one level is walked: a Manifest Reference nested inside
	// this Manifest is digested but not recursively expanded.
	References []ManifestReference
}

ManifestResult reports the outcome of validating the inner ds:Reference children of a ds:Manifest element. It is populated only when Verifier.ValidateManifests(true) is set and the top-level Manifest-typed Reference's own digest verified. Inner-reference results are ADVISORY: per XMLDSig core §5.1 the application decides Manifest policy, so a failed inner reference does not fail Verify — the top-level Manifest Reference's digest is what the signature commits to. Coverage attribution (VerifyResult.Covers / SignedElement) is never made through a Manifest.

type RSAKeyValueData

type RSAKeyValueData struct {
	Modulus  *big.Int
	Exponent int
}

RSAKeyValueData holds parsed RSAKeyValue content.

type ReferenceConfig

type ReferenceConfig struct {
	URI             string
	DigestAlgorithm string
	Transforms      []Transform
	ID              string
	Type            string
}

ReferenceConfig describes a single Reference element in a signature.

func NewEnvelopedReference

func NewEnvelopedReference() ReferenceConfig

NewEnvelopedReference returns a ReferenceConfig for a WHOLE-DOCUMENT enveloped signature: an empty URI, an enveloped-signature transform + Exclusive C14N, and a SHA-256 digest. The empty URI always resolves to the document element, so the reference covers the entire document regardless of which parent element the Signature is inserted into by Signer.SignEnveloped.

To envelope-sign a specific nested element by its id (for example a SAML Assertion inside a Response), use NewEnvelopedReferenceByID instead — an empty URI does NOT scope coverage to the SignEnveloped parent.

func NewEnvelopedReferenceByID added in v0.7.0

func NewEnvelopedReferenceByID(id string) ReferenceConfig

NewEnvelopedReferenceByID returns a ReferenceConfig for an enveloped signature that covers the single element carrying the given id (URI="#id"), with an enveloped-signature transform + Exclusive C14N and a SHA-256 digest. This is the correct choice for signing a specific nested element — for example a SAML Assertion by its AssertionID/ID — where NewEnvelopedReference (empty URI) would cover the whole document instead.

The id must be recognized as an ID attribute per the package's ID rules: a DTD/schema-declared ID-typed attribute, xml:id, or the "id" token in the casings "Id", "ID", or "id" (see Verifier.Verify). More than one element matching the id makes the reference ambiguous (ErrAmbiguousReference).

type ReferenceError added in v0.7.0

type ReferenceError struct {
	// Op is the operation during which the failure occurred ("sign").
	Op string
	// Reference is the 0-based index of the failing Reference.
	Reference int
	// URI is the Reference URI that failed.
	URI string
	// Err is the underlying cause.
	Err error
}

ReferenceError identifies which Reference failed during a signing operation. A per-reference failure carries the reference's 0-based index and URI so a caller signing over a multi-reference configuration can pinpoint the offending Reference, symmetric with how VerificationError reports a verification-side per-reference failure. The underlying cause stays reachable via errors.Is and errors.As (Unwrap), so a bare sentinel such as ErrReferenceNotFound or ErrUnsupportedTransform remains matchable through the wrapper.

func (*ReferenceError) Error added in v0.7.0

func (e *ReferenceError) Error() string

func (*ReferenceError) Unwrap added in v0.7.0

func (e *ReferenceError) Unwrap() error

type ReferenceResolver added in v0.7.0

type ReferenceResolver interface {
	ResolveReference(ctx context.Context, uri string) ([]byte, error)
}

ReferenceResolver supplies the octet stream for a Reference whose URI is NOT one of the four supported same-document forms (see Verifier.Verify for those forms). It is the opt-in seam for verifying detached signatures that reference content outside the signed document.

A resolver is consulted ONLY for a non-same-document (external) Reference URI, after that URI has been joined against the document's base URI. Same-document references never reach it. When no resolver is configured an external reference stays fail-closed with ErrReferenceNotFound, the default.

The interface is public so callers can dereference references over any transport. helium ships only FSReferenceResolver, a filesystem resolver with no network access. No HTTP resolver is provided: anyone implementing network dereferencing owns the resulting SSRF and availability risk (an attacker who controls a Reference URI could otherwise steer requests at internal hosts or stall verification), so that decision is left explicitly to the caller.

ResolveReference must be safe to call from multiple goroutines, and should honor ctx cancellation. The returned octets are the resource's raw bytes; the package then applies every declared transform in order, parsing or canonicalizing only when the next transform requires the other value kind.

func FSReferenceResolver added in v0.7.0

func FSReferenceResolver(fsys fs.FS) ReferenceResolver

FSReferenceResolver returns a ReferenceResolver that serves external references from fsys, treating the (already base-joined) Reference URI as a slash-separated path inside fsys. It performs NO network access.

It is fail-closed on anything that is not a plain in-tree path:

  • a URI carrying a scheme (http:, https:, file:, urn:, or any "scheme:" per RFC 3986, including a Windows drive letter) is refused — the resolver never interprets a scheme, so it cannot be steered into a fetch;
  • a path escaping the root (an absolute path, or one with ".." segments that leave the root after cleaning) is refused via an fs.ValidPath containment check;
  • a leftover fragment ("#...") is refused.

Reads are bounded: a resource larger than 64 MiB fails with ErrReferenceTooLarge before it can be buffered in full. Every rejection wraps ErrReferenceNotFound (or ErrReferenceTooLarge) so callers can match it with errors.Is.

func LimitReferenceResolver added in v0.8.0

func LimitReferenceResolver(resolver ReferenceResolver, maxBytes int) ReferenceResolver

LimitReferenceResolver composes a per-resource byte limit around a resolver. A non-positive value or a value above the package maximum selects the 64 MiB default; the cap cannot be disabled through this helper. The built-in FSReferenceResolver applies the limit while reading. Other resolvers are checked after they return their octets, so those implementations must enforce their own transport and aggregate-work limits.

type Signer

type Signer struct {
	// contains filtered or unexported fields
}

Signer creates XML Digital Signatures. It uses clone-on-write semantics: each builder method returns a new Signer and the original is never mutated.

func NewSigner

func NewSigner() Signer

NewSigner creates a new Signer with default settings. Defaults: Exclusive C14N for SignedInfo canonicalization.

func (Signer) AllowSHA1 added in v0.3.0

func (s Signer) AllowSHA1(allow bool) Signer

AllowSHA1 controls whether SHA-1-based signature and digest algorithms (rsa-sha1, hmac-sha1, sha1) may be used when signing. SHA-1 is rejected by default; pass true to opt in for legacy interoperability. SHA-1 is cryptographically weak and should not be used for new signatures.

func (Signer) CanonicalizationMethod

func (s Signer) CanonicalizationMethod(method string) Signer

CanonicalizationMethod sets the canonicalization algorithm for SignedInfo.

func (Signer) KeyInfo

func (s Signer) KeyInfo(builder KeyInfoBuilder) Signer

KeyInfo configures KeyInfo element construction.

func (Signer) Reference

func (s Signer) Reference(ref ReferenceConfig) Signer

Reference adds a Reference to be signed. The Reference's Transforms slice is copied at ingress, so a later mutation of the slice the caller passed cannot alter this Signer or race with an in-flight signing operation.

func (Signer) ReferenceParser added in v0.7.0

func (s Signer) ReferenceParser(p helium.Parser) Signer

ReferenceParser configures the helium.Parser used whenever a Reference transform converts octets to a node-set. This includes external resolver bytes and intermediate Base64 or canonicalization output. It is symmetric with Verifier.ReferenceParser and defaults to the same locked-down parser.

func (Signer) ReferenceResolver added in v0.7.0

func (s Signer) ReferenceResolver(r ReferenceResolver) Signer

ReferenceResolver configures a ReferenceResolver that dereferences external Reference URIs during signing, so a detached signature can cover content outside the document. It is opt-in and symmetric with Verifier.ReferenceResolver: the default is nil, leaving an external Reference URI fail-closed with ErrReferenceNotFound. When set, an external Reference URI is joined against the document's base URI, passed to r, and the resolved octets are run through the same transform pipeline the verifier applies, so the signed digest is byte-identical to what verification recomputes for the same input.

func (Signer) SignDetached

func (s Signer) SignDetached(ctx context.Context, doc *helium.Document, key any) (*helium.Element, error)

SignDetached creates a detached Signature element referencing URIs specified in the configured References. Returns the Signature element.

Placement (inclusive C14N only): SignedInfo is canonicalized under a proxy carrying the signing document element's inherited canonicalization context. If SignedInfo is canonicalized with inclusive Canonical XML (C14N10 / C14N11) — the SignedInfo CanonicalizationMethod, not the Reference transforms — the caller MUST place the returned Signature directly under the document element, or under an element with the same in-scope namespaces and inherited xml:* attributes. Placing it under an element that contributes extra in-scope namespace declarations or xml:* attributes changes the bytes inclusive C14N canonicalizes for SignedInfo, so verification recomputes a different canonical form and fails. Exclusive Canonical XML (the NewSigner default, ExcC14NTransform) inherits no such context and is unaffected by placement.

Lifetime: the returned Signature is allocated from doc's slab storage (its nodes are created via doc.CreateElement) and is owned by doc, but a successful sign leaves it safe to keep after doc.Free(). Canonicalizing SignedInfo grafts the live Signature into a throwaway document, a cross-document move that marks doc's slab as escaped; doc.Free() then becomes a no-op and never recycles the chunks backing the Signature. So the returned Signature stays valid after doc.Free() — the caller does NOT need to move it into another document first to keep it.

func (Signer) SignEnveloped

func (s Signer) SignEnveloped(ctx context.Context, doc *helium.Document, parent *helium.Element, key any) error

SignEnveloped creates an enveloped signature inside the given parent element of the document. The key is a concrete *rsa.PrivateKey, *ecdsa.PrivateKey, or ed25519.PrivateKey, any crypto.Signer whose public key is one of those types (for example an HSM/KMS/PKCS#11-backed key), or []byte for HMAC.

func (Signer) SignEnveloping

func (s Signer) SignEnveloping(ctx context.Context, doc *helium.Document, content []helium.Node, key any) (*helium.Element, error)

SignEnveloping creates an enveloping signature wrapping the given content nodes in a <ds:Object>. Returns the (detached) Signature element for the caller to place. A configured Reference may point at an element inside the content by its Id (URI="#id") — for example a <ds:Manifest> or <ds:SignatureProperties> — and it is resolved and digested during signing without ever inserting the Signature into the caller's document: an in-Object target is canonicalized on its own, and a target in the document (URI="#root", even the document element) is digested over its unchanged subtree, byte-identical to a signature with no such internal reference. An id that matches in both the document and the Signature's own Object content is rejected as an ambiguous reference (ErrAmbiguousReference).

An in-Object target is canonicalized under a proxy that reproduces the full inherited canonicalization context the target will have once the caller places the Signature under the document element — every in-scope namespace declaration plus the inherited xml:* attributes, copied per the C14N version to match exactly what helium's own canonicalizer inherits to a node-set apex (Canonical XML 1.0 inherits every xml:* attribute including xml:id; Canonical XML 1.1 inherits only xml:lang/xml:space and lexically joins xml:base) — so a reference into the Object verifies under inclusive Canonical XML 1.0 or 1.1. Exclusive Canonical XML inherits no xml:*, so its digests are unaffected.

Placement (inclusive C14N only): the same proxy canonicalizes SignedInfo, and an in-Object Reference is digested under the context of the signing document element. When SignedInfo's CanonicalizationMethod or an in-Object Reference uses inclusive Canonical XML (C14N10 / C14N11), the caller MUST place the returned Signature directly under the document element, or under an element with the same in-scope namespaces and inherited xml:* attributes. Placing it under an element that contributes extra in-scope namespace declarations or xml:* attributes changes the inclusively-canonicalized bytes, so verification recomputes a different canonical form and fails. Exclusive Canonical XML (the NewSigner default, ExcC14NTransform) inherits no such context and is unaffected by placement.

Every content entry must be a movable node (helium.MutableNode); an ordinary DOM element qualifies. A nil, typed-nil, or read-only content entry (e.g. a namespace-node wrapper) is rejected with an indexed error wrapping ErrInvalidSignature before any node is moved, and is never silently dropped from the Object. Moving the content into the Object detaches it from the caller's tree; if signing then fails at any later step, every moved node is restored to its exact original position (parent, siblings, and order), leaving the caller's document byte-identical to before the call.

Lifetime: the returned Signature is allocated from doc's slab storage (its nodes are created via doc.CreateElement) and is owned by doc, but a successful sign leaves it safe to keep after doc.Free(). Canonicalizing SignedInfo grafts the live Signature into a throwaway document, a cross-document move that marks doc's slab as escaped; doc.Free() then becomes a no-op and never recycles the chunks backing the Signature. So the returned Signature stays valid after doc.Free() — the caller does NOT need to move it into another document first to keep it.

func (Signer) SignatureAlgorithm

func (s Signer) SignatureAlgorithm(alg string) Signer

SignatureAlgorithm sets the signature algorithm URI.

func (Signer) SignatureID

func (s Signer) SignatureID(id string) Signer

SignatureID sets the Id attribute on the Signature element.

type Transform

type Transform interface {
	URI() string
}

Transform represents a single step in a reference transform pipeline.

func C14NTransform

func C14NTransform(method string) Transform

C14NTransform returns a canonicalization transform for the given method URI.

func Enveloped

func Enveloped() Transform

Enveloped returns the enveloped-signature transform. When applied during signing or verification, the ds:Signature element and its descendants are omitted from the canonical input. This is done on a deep copy of the document, so the caller's live DOM is never mutated.

func ExcC14NTransform

func ExcC14NTransform(prefixes ...string) Transform

ExcC14NTransform returns an Exclusive C14N transform with optional inclusive namespace prefixes. The prefixes are copied, so a later mutation of the caller's slice cannot alter the returned transform.

type VerificationError

type VerificationError struct {
	// Reference is the 0-based index of the failing Reference, or -1 for
	// a SignatureValue failure.
	Reference int
	// URI is the Reference URI that failed (empty for SignatureValue).
	URI string
	// Err is the underlying cause.
	Err error
}

VerificationError provides details about which step of verification failed.

func (*VerificationError) Error

func (e *VerificationError) Error() string

func (*VerificationError) Unwrap

func (e *VerificationError) Unwrap() error

type VerifiedReference

type VerifiedReference struct {
	// URI is the value of the Reference URI attribute as it appeared in the
	// signed document.
	URI string

	// Element is the element that the URI resolved to at verification time.
	// For the enveloped pattern (URI=""), this is the document element.
	// For fragment references (URI="#id"), this is the unique element with
	// that Id/ID attribute. If duplicate matches existed, verification fails
	// with ErrAmbiguousReference before this field is populated.
	//
	// Element is nil for an External reference: an external resource is a byte
	// stream outside the document, not an in-document element.
	Element *helium.Element

	// External reports whether this Reference was satisfied via a configured
	// ReferenceResolver (its URI is not a same-document form). An external
	// reference covers content outside the document, so Element is nil and
	// neither Covers nor SignedElement ever attributes in-document coverage to
	// it — a caller confirming a specific *Element was signed must rely on a
	// same-document reference, never an external one.
	External bool

	// DigestAlgorithm is the algorithm URI declared in the DigestMethod
	// element (e.g. DigestSHA256).
	DigestAlgorithm string

	// Type is the value of the Reference Type attribute as it appeared in the
	// signed document, or "" when the attribute was absent. A Reference whose
	// Type is TypeManifest points at a ds:Manifest element; when
	// Verifier.ValidateManifests(true) is set, that Manifest's inner references
	// are reported in VerifyResult.Manifests.
	Type string
}

VerifiedReference describes a single Reference that was successfully verified. Callers should use this to confirm the *Element they are about to consume from the document is actually covered by the signature — guarding against XML Signature Wrapping (XSW) attacks.

type Verifier

type Verifier struct {
	// contains filtered or unexported fields
}

Verifier verifies XML Digital Signatures. It uses clone-on-write semantics: each builder method returns a new Verifier and the original is never mutated.

func NewVerifier

func NewVerifier(ks KeySource) Verifier

NewVerifier creates a new Verifier with the given key source.

func (Verifier) AllowSHA1 added in v0.3.0

func (v Verifier) AllowSHA1(allow bool) Verifier

AllowSHA1 controls whether SHA-1-based signature and digest algorithms (rsa-sha1, hmac-sha1, sha1) are accepted during verification. SHA-1 is rejected by default; pass true to opt in for verifying legacy signatures. SHA-1 is cryptographically weak and accepting it exposes callers to downgrade and collision risks, so only enable it when interoperating with systems that cannot be upgraded.

func (Verifier) AllowXPointer added in v0.7.0

func (v Verifier) AllowXPointer(allow bool) Verifier

AllowXPointer opts into resolving a general XPointer Reference URI — an XPointer framework URI of the form "#xmlns(prefix=uri)...xpointer(<expr>)" (zero or more xmlns() namespace-binding parts followed by one xpointer() XPath expression) — in addition to the four safe same-document forms (see Verifier.Verify). It is opt-in and fail-closed by default: with AllowXPointer off (the default) a general XPointer URI is treated as an external reference and, without a ReferenceResolver, rejected with ErrReferenceNotFound, so default verification is byte-identical.

When enabled, every top-level xpointer() expression is compiled and statically validated during the all-Reference preflight before any Reference resolver or transformer runs. Its prepared bounded XPath 1.0 evaluator is reused during digest execution under the document element's in-scope namespaces overlaid with the xmlns() bindings. An unresolved variable, function, or prefix fails with ErrReferenceNotFound before evaluation. The result MUST identify a single element apex — the XML Signature Wrapping defense: an empty node-set is ErrReferenceNotFound, and a multi-element or non-element node-set is ErrAmbiguousReference. A literal xpointer(id('X')) keeps the duplicate-detecting id resolution (never a last-one-wins id table). The XMLDSig here() function is not available inside a URI-borne XPointer.

func (Verifier) LenientKeyInfo added in v0.7.0

func (v Verifier) LenientKeyInfo(lenient bool) Verifier

LenientKeyInfo controls how an UNRESOLVABLE ds:RetrievalMethod in the ds:KeyInfo is treated. A RetrievalMethod is an optional hint pointing at key material held elsewhere; ds:KeyInfo may carry several such hints alongside inline key material. By default (lenient off), a RetrievalMethod that cannot be dereferenced at all — there is no ReferenceResolver configured, or the target cannot be found — fails the whole verification with ErrReferenceNotFound, even when other, usable key material is present. With LenientKeyInfo(true) such an unresolvable RetrievalMethod is skipped instead, so a KeySource still sees the inline certificates/keys and any RetrievalMethod that DID resolve.

Leniency covers ONLY the "could not dereference" case. A RetrievalMethod that resolves but is invalid — an unsupported or absent Type, an unsupported or mis-ordered transform, a cyclic or over-deep chain, an over-cap resource, or an ambiguous same-document target (the XML Signature Wrapping guard) — still fails closed regardless of this setting: a corrupt hint is an error, not a missing one. Neither mode ever trusts retrieved material by itself; the KeySource's out-of-band trust decision still governs, exactly as for an inline certificate.

func (Verifier) MaxDecodedBytes added in v0.7.0

func (v Verifier) MaxDecodedBytes(n int) Verifier

MaxDecodedBytes caps the running total of certificate and signature octets the verifier produces before the SignatureValue check. Exceeding the cap is rejected with ErrResourceLimitExceeded, bounding the decode allocation an attacker-controlled document can force. n <= 0 has special meaning: 0 (the default) selects the conservative built-in cap, and a negative n disables the cap.

Four of the charged sites are base64 values decoded straight off the document: the Signature's own DigestValue, SignatureValue, and X509Certificate content, plus the rawX509Certificate a same-document ds:RetrievalMethod points at. That last one is not confined to the Signature. A RetrievalMethod URI names any element in the document by ID — any local name, any namespace, inside ds:Signature or outside it — and both the rawX509Certificate branch and the ds:X509Data branch charge whatever element it resolves to.

Each of those four is charged BEFORE it is materialized, so for them the cap bounds what verification BUILDS and not merely what it keeps. That matters because xs:base64Binary permits XML whitespace between characters and a value may be spread over any number of text and CDATA children: the lexical text an attacker wraps around a value is unbounded and unrelated to the bytes it decodes to, so counting it first is what keeps the memory under the cap for a value the cap refuses AND for every value it accepts.

The fifth site is the exception to both halves of that. An EXTERNAL ds:RetrievalMethod is dereferenced through the configured Verifier.ReferenceResolver, which materializes the whole resource under its OWN size cap (FSReferenceResolver bounds one resource at 64 MiB) and runs it through the RetrievalMethod's transforms; only then is the result charged. So those octets are charged AFTER they are materialized, they are bounded by the resolver's cap, and by no cap of this one's on the way in, and they are raw certificate bytes that were never base64-decoded at all.

func (Verifier) MaxKeyInfoEntries added in v0.7.0

func (v Verifier) MaxKeyInfoEntries(n int) Verifier

MaxKeyInfoEntries caps the number of KeyInfo entries the verifier parses: the KeyInfo element's own children plus every X509Data child (each X509Certificate is parsed with x509.ParseCertificate, which is not free). A KeyInfo carrying more entries than the cap is rejected with ErrResourceLimitExceeded. n <= 0 has special meaning: 0 (the default) selects the conservative built-in cap, and a negative n disables the cap.

func (Verifier) MaxReferences added in v0.7.0

func (v Verifier) MaxReferences(n int) Verifier

MaxReferences caps the number of ds:Reference elements the verifier parses out of SignedInfo. A document whose Signature declares more References than the cap is rejected with ErrResourceLimitExceeded before any Reference is digested, bounding the per-Reference canonicalization work an unsigned, attacker-controlled document can force. n <= 0 has special meaning: 0 (the default) selects the conservative built-in cap, and a negative n disables the cap entirely. The default sits well above any legitimate signature.

func (Verifier) ReferenceParser added in v0.7.0

func (v Verifier) ReferenceParser(p helium.Parser) Verifier

ReferenceParser configures the helium.Parser used whenever the ordered transform pipeline converts octets to a node-set. It applies to external resolver bytes and to intermediate Base64, canonicalization, or XSLT output consumed by a later node-set transform. The default is a locked-down parser (helium.NewParser(): XXE blocked, no filesystem access, no network). Override it only to relax those defaults deliberately.

func (Verifier) ReferenceResolver added in v0.7.0

func (v Verifier) ReferenceResolver(r ReferenceResolver) Verifier

ReferenceResolver configures a ReferenceResolver that dereferences external Reference URIs (those that are not one of the four supported same-document forms). It is opt-in: the default is nil, which keeps external references fail-closed with ErrReferenceNotFound, byte-identical to a Verifier without a resolver. When set, an external Reference URI is joined against the document's base URI and passed to r; the resolved octets are then run through the Reference's transform pipeline before digesting.

A Reference satisfied via the resolver is marked External in the result (see VerifiedReference); VerifyResult.Covers and VerifyResult.SignedElement never report an external reference as covering in-document content, since it resolves to bytes outside the document, and to no element.

func (Verifier) ValidateManifests added in v0.7.0

func (v Verifier) ValidateManifests(validate bool) Verifier

ValidateManifests controls whether the inner ds:Reference children of a Manifest-typed Reference are digested and reported (XMLDSig core §5.1). It is opt-in: the default is false, which leaves VerifyResult.Manifests nil and walks no inner references, byte-identical to a Verifier without it.

When enabled, after a top-level Reference whose Type is TypeManifest has itself verified (its own digest over the ds:Manifest subtree is checked exactly as any other Reference), that Manifest's inner references are each resolved, transformed, and digested through the same fail-closed pipeline, with the per-reference outcome recorded in ManifestResult.

Every direct inner Reference is parsed and statically prepared before any resolver or transformer callback runs. If one fails preparation, none are executed: the failing ManifestReference reports that error, and each otherwise prepared peer reports an advisory error wrapping the same cause.

Inner-reference results are ADVISORY: per §5.1 the application decides how to treat a Manifest, so an inner-reference digest mismatch or an unresolved or unsupported inner reference does NOT fail Verify — the top-level Manifest Reference's own digest is what the signature commits to. Inner references never contribute to VerifyResult.Covers or VerifyResult.SignedElement, so coverage is never attributed through a Manifest. Only one level is walked: a Manifest nested inside a Manifest is digested but not recursively expanded.

It is off by default because inner references may pull in transforms or external URIs the top-level policy did not intend, so evaluating them is left to callers who want the report.

func (Verifier) Verify

func (v Verifier) Verify(ctx context.Context, doc *helium.Document) (*VerifyResult, error)

Verify verifies the Signature element in the document. The document must contain exactly one ds:Signature element; if it contains more than one the function returns ErrAmbiguousSignature and the caller must use VerifyElement to disambiguate.

On success the returned VerifyResult exposes the set of elements actually covered by the signature so callers can confirm — by pointer identity — that the element they intend to consume was signed. This is the primary defense against XML Signature Wrapping (XSW) attacks at the application layer.

Same-document reference resolution (ds:Reference URI="#id") locates the target element by its ID attribute. An attribute is recognized as an ID when it is any of:

  • declared ID-typed by a DTD or schema the document was parsed with;
  • xml:id (ID-typed by the W3C xml:id Recommendation);
  • the "id" attribute token in the casings "Id", "ID", or "id".

This name set is deliberately limited to the "id" token. Other conventions (for example "wsu:Id" or SAML "AssertionID") are ID-typed only by their own schemas, so a document relying on them must carry that typing — via its DTD/schema, or by marking the attribute's type as an ID before verifying. The name alone never infers it. If more than one element matches the referenced ID the reference is refused (ErrAmbiguousReference).

Verification honors ctx: an already-cancelled or already-expired context short-circuits before any work, and cancellation is rechecked between References. Because a SignedInfo may carry arbitrarily many References and each empty-URI enveloped Reference canonicalizes a copy of the whole document, the per-Reference work scales with the number of References; bound it by passing a ctx with a deadline. On cancellation the context error (ctx.Err()) is returned.

func (Verifier) VerifyElement

func (v Verifier) VerifyElement(ctx context.Context, doc *helium.Document, sig *helium.Element) (*VerifyResult, error)

VerifyElement verifies a specific Signature element. Use this when the document contains more than one Signature, or when the caller wants explicit control over which Signature is targeted.

Same-document reference resolution recognizes the same ID attributes as Verifier.Verify.

Verification honors ctx the same way as Verifier.Verify: an already-cancelled or already-expired context short-circuits before any work, cancellation is rechecked between References, and a ctx deadline is the lever for bounding the per-Reference work of a SignedInfo that carries many References.

func (Verifier) XSLTTransformer added in v0.7.0

func (v Verifier) XSLTTransformer(t XSLTTransformer) Verifier

XSLTTransformer configures the XSLTTransformer that applies the XSLT transform (http://www.w3.org/TR/1999/REC-xslt-19991116) to a Reference that carries one. It is opt-in: the default is nil, which keeps the XSLT transform fail-closed with ErrUnsupportedTransform, byte-identical to a Verifier without one. When set, a Reference's ds:Transform/xsl:stylesheet subtree is serialized and passed to t together with the current pipeline octets. One Reference may invoke t multiple times, and a later node-set transform reparses its output through Verifier.ReferenceParser.

XSLT is a powerful language and both the stylesheet and its input are attacker-controlled on verify, so the transformer owns all resource and XXE policy (see XSLTTransformer). The core package runs no XSLT automatically; the separate xmldsig1/transform package provides an explicit opt-in adapter.

type VerifyResult

type VerifyResult struct {
	// Signature is the Signature element that was verified.
	Signature *helium.Element

	// References lists every Reference inside SignedInfo that was
	// successfully verified, in document order.
	References []VerifiedReference

	// Manifests lists, for each top-level Reference whose Type is TypeManifest,
	// the result of digesting that Manifest's inner references. It is populated
	// only when Verifier.ValidateManifests(true) is set; otherwise it is nil.
	// Inner-reference results are ADVISORY (see ManifestResult): they never
	// affect whether Verify succeeds, nor do they contribute to Covers or
	// SignedElement coverage attribution.
	Manifests []ManifestResult
}

VerifyResult is returned by Verifier.Verify and Verifier.VerifyElement on success. It exposes the set of elements that were actually covered by the signature so callers can correlate signed content with the element they intend to consume.

func (*VerifyResult) Covers

func (r *VerifyResult) Covers(elem *helium.Element) bool

Covers reports whether elem was covered by any verified Reference. An External reference never counts: it covers content outside the document, so it cannot attest that an in-document *Element was signed.

func (*VerifyResult) SignedElement

func (r *VerifyResult) SignedElement(uri string) *helium.Element

SignedElement returns the resolved element for the Reference with the given URI, or nil if no such Reference was verified. This is the preferred way to confirm an element was covered by the signature before consuming it. An External reference is skipped: it resolves to bytes outside the document, not an element, so it can never satisfy an in-document element lookup.

type X509IssuerSerial added in v0.7.0

type X509IssuerSerial struct {
	IssuerName   string
	SerialNumber *big.Int
}

X509IssuerSerial holds a parsed ds:X509IssuerSerial: the issuer distinguished name and certificate serial number. The library performs no DName canonicalization or matching; it extracts the values verbatim so a KeySource can select the corresponding certificate out of band.

type XSLTTransformer added in v0.7.0

type XSLTTransformer interface {
	TransformXSLT(ctx context.Context, stylesheet []byte, input []byte) ([]byte, error)
}

XSLTTransformer applies the XMLDSig XSLT transform (XMLDSig core §6.6.5): stylesheet is the serialized xsl:stylesheet/xsl:transform subtree taken from the ds:Transform element, and input is the current pipeline octet stream. It may be raw resolver bytes or output from canonicalization, Base64, or an earlier XSLT step. The result feeds the next declared transform or the DigestValue.

It is the opt-in seam for the XSLT transform, mirroring ReferenceResolver: the XSLT transform is OFF by default and runs only when a transformer is configured via Verifier.XSLTTransformer; without one an XSLT transform fails closed with ErrUnsupportedTransform. XSLT is verify-only — signing rejects it fail-closed and never invokes a transformer.

SECURITY: both stylesheet and input are attacker-controlled on verify (an attacker who controls the signature controls the ds:Transform/xsl:stylesheet subtree, and input derives from the signed document). XSLT is a powerful language — document(), unbounded recursion, and other unbounded computation — so the implementer owns ALL resource and XXE policy (compute/time/memory limits, disabling document()/external access). The core package runs no XSLT automatically; callers may explicitly import the separate xmldsig1/transform adapter.

One Reference may invoke TransformXSLT multiple times. TransformXSLT must be safe to call from multiple goroutines and should honor ctx cancellation.

Directories

Path Synopsis
Package transform provides plug-in transform implementations for the xmldsig1 verifier's injected seams.
Package transform provides plug-in transform implementations for the xmldsig1 verifier's injected seams.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL