Wssec
Wssec::Signer adds an OASIS WS-Security header with a detached XML-DSig
signature to a SOAP envelope — signing the soapenv:Body and a wsu:Timestamp
with RSA-SHA256 over exclusive-C14N, exactly as WSS4J-based gateways verify.
It is deliberately vendor-agnostic: the signing key/cert, the token identifier, the exclusive-C14N inclusive prefix list, and the timestamp TTL are all supplied by the caller. The gem carries no keystore/ENV assumptions and no gateway-specific values.
Installation
Path/git dependency (this gem is not published to a public registry):
# Gemfile
gem "wssec", path: "../../gems/wssec"
# or
gem "wssec", git: "https://github.com/SELISEdigitalplatforms/wssec.git"
Usage
require "wssec"
key = OpenSSL::PKey::RSA.new(File.read("private_key.pem")) # your signing key
cert = OpenSSL::X509::Certificate.new(File.read("cert.pem")) # embedded as the BinarySecurityToken
signer = Wssec::Signer.new(
key: key,
cert: cert,
key_identifier: "my-key", # BST wsu:Id becomes "X509-my-key"
inclusive_prefixes: %w[wsse wsu soapenv ds], # exclusive-C14N PrefixList (gateway-specific)
timestamp_ttl: 300 # seconds until the Timestamp expires (optional)
)
doc = Nokogiri::XML(soap_envelope_string)
signer.sign!(doc, body_id: "Id-#{SecureRandom.hex(16)}")
signed_xml = doc.to_xml
sign! mutates the passed Nokogiri document in place and returns it. Pass now: to
pin the timestamp (useful for deterministic tests).
Notes
inclusive_prefixesis required — supply the exact prefix list your gateway expects rendered into each signed subtree. There is no default, to avoid baking in any single vendor's assumptions.- The digest references use exclusive-C14N with the prefix list;
SignedInfois canonicalized without one. This matches how WSS4J recomputes and verifies, so the signing internals are byte-sensitive — treat them as such.
Alternatives
Before reaching for this gem, check whether an existing library fits — it may save you from owning XML-DSig correctness yourself:
- akami (1.3.1) — the closest match: builds a
wsse:Securityheader with Timestamp and X.509 signature (Akami::WSSE::Signature/Akami::WSSE::VerifySignature), part of the Savon SOAP ecosystem. Choose it for a batteries-included WSSE layer when you don't need byte-level control over canonicalization or the exclusive-C14N inclusive-prefix list. - xmldsig (0.7.0) — a general XML-DSig
sign/verify implementation. Not WS-Security aware: you assemble the
wsse:Security/BinarySecurityToken/SecurityTokenReferencescaffolding yourself. Pairs with nokogiri-signatures for a Nokogiri-friendly interface. - xmlsec (libxmlsec1 native bindings) — the most standards-complete option, at the cost of a C system dependency.
Why this gem exists: it targets gateways (e.g. Apache WSS4J) that re-verify the exact
canonical bytes. It gives the caller precise control over the inclusive-prefix list and
the detached Body + Timestamp references, and deliberately canonicalizes SignedInfo
without a prefix list to match how those gateways verify. If you don't need that
byte-level fidelity, akami or xmldsig will likely serve you with less code to maintain.
Development
After checking out the repo, run bin/setup to install dependencies, then rake test
to run the tests (minitest) or rake for tests + RuboCop. bin/console gives an
interactive prompt.
License
Available as open source under the terms of the MIT License.