Class: Wssec::Signer

Inherits:
Object
  • Object
show all
Defined in:
lib/wssec/signer.rb

Overview

Signs a SOAP envelope with an OASIS WS-Security header carrying a detached XML-DSig signature (RSA-SHA256 / SHA256, exclusive-C14N) over the Body and a Timestamp, exactly as a WSS4J-based gateway expects.

doc = Nokogiri::XML(soap_envelope)
Wssec::Signer.new(
key: rsa_key, cert: x509_cert, key_identifier: "my-key",
inclusive_prefixes: %w[wsse wsu soapenv ds]
).sign!(doc, body_id: "Id-#{SecureRandom.hex(16)}")
doc.to_xml

The signer is vendor-agnostic: key/cert, the token identifier and the C14N inclusive prefix list are supplied per call, while namespaces, algorithm identifiers, token types and the timestamp TTL come from config (defaults to the global Wssec.configuration).

Instance Method Summary collapse

Constructor Details

#initialize(key:, cert:, key_identifier:, inclusive_prefixes:, timestamp_ttl: nil, config: Wssec.configuration) ⇒ Signer

key -- OpenSSL::PKey::RSA private key used to sign. cert -- OpenSSL::X509::Certificate embedded as the BinarySecurityToken. key_identifier -- names the token (BST wsu:Id becomes "X509-#key_identifier"). inclusive_prefixes -- namespace prefixes rendered into each signed subtree (the exclusive-C14N PrefixList); gateway-specific, so the caller supplies it. timestamp_ttl -- seconds until the wsu:Timestamp expires (defaults to config). config -- Wssec::Configuration for namespaces/algorithms/token types.



29
30
31
32
33
34
35
36
# File 'lib/wssec/signer.rb', line 29

def initialize(key:, cert:, key_identifier:, inclusive_prefixes:, timestamp_ttl: nil, config: Wssec.configuration)
  @key                = key
  @cert               = cert
  @key_identifier     = key_identifier
  @inclusive_prefixes = inclusive_prefixes
  @config             = config
  @timestamp_ttl      = timestamp_ttl || config.timestamp_ttl
end

Instance Method Details

#sign!(document, body_id:, now: Time.now.utc) ⇒ Object

Adds the signed wsse:Security header to document (a Nokogiri doc) in place.

Raises:



39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/wssec/signer.rb', line 39

def sign!(document, body_id:, now: Time.now.utc)
  root   = document.root
  header = root.at_xpath("soapenv:Header", "soapenv" => ns[:soapenv]) ||
           root.prepend_child(Nokogiri::XML::Node.new("soapenv:Header", document))
  body   = root.at_xpath("soapenv:Body", "soapenv" => ns[:soapenv])
  raise Wssec::Error, "envelope has no soapenv:Body" unless body

  tag_body(body, body_id)
  security = build_security_header(document, header, now)
  append_binary_security_token(document, security)
  append_signature(document, security, body, body_id)
  document
end