Trust Anchors & Verification Options

You can verify signatures against multiple certificates, strict schemas, or specific validation times.

HankoXml.verify!(
  doc,
  # Pass a single cert, an array of certs, an OpenSSL::X509::Store, or a mapping of <KeyName>s
  store: my_trusted_ca_store,

  # Ensure the certificate was valid at a specific time
  verification_time: Time.parse("2023-01-01"),

  # Pre-validate the <Signature> structure against the W3C XSD to prevent parser exploits
  schema: :default,

  # Enforce strict security policies (no SHA-1, no external references)
  policy: HankoXml::Policy.strict
)

Trust sources

Trust sources are mutually exclusive: passing more than one of certificate:, certificates:, store:, key:, keys: — or omitting all of them — raises HankoXml::ConfigurationError at construction.

Keyword Behaviour
store: Extracts the <X509Certificate> from KeyInfo, validates the chain with OpenSSL::X509::Store#verify, then hands libxmlsec1 only the validated leaf. Chain policy (CRLs, purpose, time) is OpenSSL’s and fully configurable.
certificates: The signature must verify against one of these exact certificates.
certificate: Pinned to exactly one certificate.
key: Bare public key; KeyInfo is ignored entirely.
keys: A KeyName => key-or-certificate map. The document’s <ds:KeyName> selects which one to verify against; an unrecognized name fails as UntrustedCertificate rather than falling back to trying every key.

Verifying against the operating system’s CA bundle is spelled out explicitly rather than hidden behind an empty array:

store = OpenSSL::X509::Store.new.tap(&:set_default_paths)
HankoXml::Verifier.new(store: store)

verification_time:

Certificate validity (notBefore/notAfter) is checked against this Time instead of Time.now. Without it, every archived document becomes unverifiable the day its signing certificate expires — which is wrong: the question is whether the certificate was valid when the document was signed. Long-lived archives (invoices, SAML assertions, contracts) whose signing certificate has since expired need this.

HankoXml::Verifier.new(store: store, verification_time: archived_at)

Setting policy: policy.with(check_certificate_validity: false) skips the window entirely.

schema:

An OpenSSL-independent structural check: the <ds:Signature> subtree is validated against the bundled xmldsig-core-schema.xsd (or a caller-supplied Nokogiri::XML::Schema) before any cryptography. A structurally malformed <SignedInfo> is rejected without reaching libxmlsec1’s parser at all. schema: :default uses the bundled XSD. When you don’t pass schema:, it defaults to the policy’s schema, which is :default under Policy.strict, so the check is on by default. Pass schema: nil explicitly to skip it.

referenced_documents:

A URI => document map for references that point outside the document — the only way a non-fragment reference can resolve. allow_external_references stays false, nothing is fetched, and a reference whose URI is not a key in this map fails verification: the Verification is invalid with a HankoXml::ReferenceError in #errors, and only verify! raises it.

Selecting which signature to verify

verifier = HankoXml::Verifier.new(certificate: cert, signature: :only)
Value Meaning
nil (default) as :only: verify the one <ds:Signature>, and raise MultipleSignatures if there is more than one
:first verify the first <ds:Signature> in document order, ignoring any others (unsafe opt-in)
:only same as the default: raise MultipleSignatures if more than one is present
Integer the nth signature, document order, 0-based
String an XPath selecting exactly one <ds:Signature>

See the Verifier reference and Policy reference for the complete configuration surface.