NFe-shaped fixture

HankoXml is a general XML-DSig library. This page is only a test case: an XML document whose signature shape matches Brazilian NF-e (enveloped, RSA-SHA1, SHA-1 digest, C14N 1.0, Id on a child element). The suite keeps a fixture in that shape so the library can sign and verify it. It is not a product profile and not a reason to treat HankoXml as an NF-e toolkit.

That shape needs SHA-1 and explicit configuration that differs from the strict, modern defaults — a demonstration of overriding Policy and Signer deliberately, rather than reaching for permissive.

Signing

require "hanko_xml"

bundle = HankoXml::Key.from_pkcs12("certificado_a1.pfx", password: ENV.fetch("PFX_PASSWORD"))

# This fixture uses SHA-1, C14N 1.0, and a sibling <Signature> — the NF-e XML-DSig subset.
signer = HankoXml::Signer.new(
  key:                     bundle.key,
  certificates:            bundle.certificates,
  signature_method:        :rsa_sha1,
  digest_method:           :sha1,
  canonicalization_method: :c14n,
  id_attributes:           %w[Id],
  key_info:                [:x509_certificate],
  placement:               HankoXml::Placement.after("//ds:Signature"),
  policy:                  HankoXml::Policy.legacy_sha1
)

signed = signer.sign(nfe_shaped_doc)

HankoXml::Key.from_pkcs12 unwraps a PKCS#12 / PFX bundle (key + leaf certificate + CA chain) in one call — see the Key reference.

Policy.strict rejects SHA-1. Use HankoXml::Policy.legacy_sha1 (or an explicit Policy#with(...)) when a document genuinely uses that subset.

Verifying a fragment signature

The same APIs apply to any fragment signature (SAML assertions, a signed child element, this fixture’s inf* node):

v = HankoXml::Verifier.new(
  store:         idp_store,
  policy:        HankoXml::Policy.fragment,
  id_attributes: %w[ID],
  signature:     :only,
).verify(response_doc)

raise "invalid signature" unless v.valid?

assertion = response_doc.at_xpath("//saml:Assertion", "saml" => SAML_NS)

raise "assertion is not covered by the signature" unless v.signed?(assertion)

subject = assertion.at_xpath(".//saml:NameID", "saml" => SAML_NS).text

Policy.fragment drops require_signed_root because the signature covers a child, not the document element — precisely the case where #signed? is mandatory. See XSW Defense.