wssec 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +87 -0
- data/Rakefile +10 -0
- data/lib/generators/wssec/install/install_generator.rb +17 -0
- data/lib/generators/wssec/install/templates/wssec.rb +28 -0
- data/lib/wssec/configuration.rb +34 -0
- data/lib/wssec/signer.rb +162 -0
- data/lib/wssec/version.rb +3 -0
- data/lib/wssec.rb +28 -0
- data/sig/wssec.rbs +4 -0
- metadata +87 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6c4a7b1e50edb5cee54acd6da327061e37157d123eefb01e465df5922c7fb5ce
|
|
4
|
+
data.tar.gz: c7ba0c1a415432333ba763e695bc6cb050387b4a10c0442f0e14b94da6c1c20d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d443792ce61740d16058a7e0e75083f3743a54492a2ff4f11726fd138365f410a66671a70db673041ca5d4d8c86be8bba4b959292745cb29feaebc544020b79c
|
|
7
|
+
data.tar.gz: 35b2ba499928e04affbbf061a855989de044a754a5c132af1050f231f8efc1ab0d0144e3b85fae8cf5ebcba7d7708cfd8708e27a7af8059a66ee69e7e72e4b5a
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"wssec" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["bugloper@hey.com"](mailto:"bugloper@hey.com").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bugloper
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Wssec
|
|
2
|
+
|
|
3
|
+
`Wssec::Signer` adds an OASIS **WS-Security** header with a detached **XML-DSig**
|
|
4
|
+
signature to a SOAP envelope — signing the `soapenv:Body` and a `wsu:Timestamp`
|
|
5
|
+
with **RSA-SHA256** over **exclusive-C14N**, exactly as WSS4J-based gateways verify.
|
|
6
|
+
|
|
7
|
+
It is deliberately **vendor-agnostic**: the signing key/cert, the token identifier,
|
|
8
|
+
the exclusive-C14N inclusive prefix list, and the timestamp TTL are all supplied by
|
|
9
|
+
the caller. The gem carries no keystore/ENV assumptions and no gateway-specific values.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Path/git dependency (this gem is not published to a public registry):
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
# Gemfile
|
|
17
|
+
gem "wssec", path: "../../gems/wssec"
|
|
18
|
+
# or
|
|
19
|
+
gem "wssec", git: "https://github.com/SELISEdigitalplatforms/wssec.git"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require "wssec"
|
|
26
|
+
|
|
27
|
+
key = OpenSSL::PKey::RSA.new(File.read("private_key.pem")) # your signing key
|
|
28
|
+
cert = OpenSSL::X509::Certificate.new(File.read("cert.pem")) # embedded as the BinarySecurityToken
|
|
29
|
+
|
|
30
|
+
signer = Wssec::Signer.new(
|
|
31
|
+
key: key,
|
|
32
|
+
cert: cert,
|
|
33
|
+
key_identifier: "my-key", # BST wsu:Id becomes "X509-my-key"
|
|
34
|
+
inclusive_prefixes: %w[wsse wsu soapenv ds], # exclusive-C14N PrefixList (gateway-specific)
|
|
35
|
+
timestamp_ttl: 300 # seconds until the Timestamp expires (optional)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
doc = Nokogiri::XML(soap_envelope_string)
|
|
39
|
+
signer.sign!(doc, body_id: "Id-#{SecureRandom.hex(16)}")
|
|
40
|
+
signed_xml = doc.to_xml
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`sign!` mutates the passed Nokogiri document in place and returns it. Pass `now:` to
|
|
44
|
+
pin the timestamp (useful for deterministic tests).
|
|
45
|
+
|
|
46
|
+
### Notes
|
|
47
|
+
|
|
48
|
+
- `inclusive_prefixes` is **required** — supply the exact prefix list your gateway
|
|
49
|
+
expects rendered into each signed subtree. There is no default, to avoid baking in
|
|
50
|
+
any single vendor's assumptions.
|
|
51
|
+
- The digest references use exclusive-C14N **with** the prefix list; `SignedInfo` is
|
|
52
|
+
canonicalized **without** one. This matches how WSS4J recomputes and verifies, so
|
|
53
|
+
the signing internals are byte-sensitive — treat them as such.
|
|
54
|
+
|
|
55
|
+
## Alternatives
|
|
56
|
+
|
|
57
|
+
Before reaching for this gem, check whether an existing library fits — it may save
|
|
58
|
+
you from owning XML-DSig correctness yourself:
|
|
59
|
+
|
|
60
|
+
- **[akami](https://github.com/savonrb/akami)** (1.3.1) — the closest match: builds a
|
|
61
|
+
`wsse:Security` header with Timestamp and X.509 signature (`Akami::WSSE::Signature` /
|
|
62
|
+
`Akami::WSSE::VerifySignature`), part of the Savon SOAP ecosystem. Choose it for a
|
|
63
|
+
batteries-included WSSE layer when you don't need byte-level control over
|
|
64
|
+
canonicalization or the exclusive-C14N inclusive-prefix list.
|
|
65
|
+
- **[xmldsig](https://github.com/benoist/xmldsig)** (0.7.0) — a general XML-DSig
|
|
66
|
+
sign/verify implementation. Not WS-Security aware: you assemble the `wsse:Security` /
|
|
67
|
+
`BinarySecurityToken` / `SecurityTokenReference` scaffolding yourself. Pairs with
|
|
68
|
+
**[nokogiri-signatures](https://github.com/jch/nokogiri-signatures)** for a
|
|
69
|
+
Nokogiri-friendly interface.
|
|
70
|
+
- **xmlsec** (libxmlsec1 native bindings) — the most standards-complete option, at the
|
|
71
|
+
cost of a C system dependency.
|
|
72
|
+
|
|
73
|
+
**Why this gem exists:** it targets gateways (e.g. Apache WSS4J) that re-verify the exact
|
|
74
|
+
canonical bytes. It gives the caller precise control over the inclusive-prefix list and
|
|
75
|
+
the detached Body + Timestamp references, and deliberately canonicalizes `SignedInfo`
|
|
76
|
+
*without* a prefix list to match how those gateways verify. If you don't need that
|
|
77
|
+
byte-level fidelity, akami or xmldsig will likely serve you with less code to maintain.
|
|
78
|
+
|
|
79
|
+
## Development
|
|
80
|
+
|
|
81
|
+
After checking out the repo, run `bin/setup` to install dependencies, then `rake test`
|
|
82
|
+
to run the tests (minitest) or `rake` for tests + RuboCop. `bin/console` gives an
|
|
83
|
+
interactive prompt.
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
Available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require "rails/generators/base"
|
|
2
|
+
|
|
3
|
+
module Wssec
|
|
4
|
+
module Generators
|
|
5
|
+
# `rails g wssec:install` — writes config/initializers/wssec.rb pre-populated
|
|
6
|
+
# with every Wssec default so they can be reviewed and overridden in place.
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Creates a Wssec initializer (config/initializers/wssec.rb) with the default configuration."
|
|
11
|
+
|
|
12
|
+
def copy_initializer
|
|
13
|
+
template "wssec.rb", "config/initializers/wssec.rb"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Wssec configuration. Every value below is the built-in default — edit any of
|
|
2
|
+
# them to match your SOAP gateway's WS-Security expectations, or delete the ones
|
|
3
|
+
# you don't need to override (the gem falls back to the same defaults).
|
|
4
|
+
#
|
|
5
|
+
# Note: `inclusive_prefixes` (the exclusive-C14N PrefixList) and the signing
|
|
6
|
+
# key/cert are gateway-specific and passed per call to Wssec::Signer.new — they
|
|
7
|
+
# are intentionally not configured here.
|
|
8
|
+
Wssec.configure do |config|
|
|
9
|
+
# OASIS / SOAP / XML-DSig namespace URIs bound to the wsse/wsu/soapenv/ds prefixes.
|
|
10
|
+
config.namespaces = {
|
|
11
|
+
wsse: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
|
|
12
|
+
wsu: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
|
|
13
|
+
soapenv: "http://schemas.xmlsoap.org/soap/envelope/",
|
|
14
|
+
ds: "http://www.w3.org/2000/09/xmldsig#"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Canonicalization / signature / digest algorithm identifiers (declared in the XML).
|
|
18
|
+
config.exc_c14n = "http://www.w3.org/2001/10/xml-exc-c14n#"
|
|
19
|
+
config.signature_method = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
|
20
|
+
config.digest_method = "http://www.w3.org/2001/04/xmlenc#sha256"
|
|
21
|
+
|
|
22
|
+
# WS-Security token encoding / value types for the BinarySecurityToken.
|
|
23
|
+
config.encoding_type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
|
|
24
|
+
config.value_type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
|
|
25
|
+
|
|
26
|
+
# Seconds until the wsu:Timestamp expires.
|
|
27
|
+
config.timestamp_ttl = 300
|
|
28
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Wssec
|
|
2
|
+
# Holds the protocol-level defaults a WS-Security signature is built from:
|
|
3
|
+
# namespace URIs, algorithm identifiers, token types and the timestamp TTL.
|
|
4
|
+
# Every value ships with a standards-compliant default and can be overridden
|
|
5
|
+
# globally via `Wssec.configure` (see `rails g wssec:install`) or per-signer.
|
|
6
|
+
class Configuration
|
|
7
|
+
DEFAULT_NAMESPACES = {
|
|
8
|
+
wsse: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
|
|
9
|
+
wsu: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
|
|
10
|
+
soapenv: "http://schemas.xmlsoap.org/soap/envelope/",
|
|
11
|
+
ds: "http://www.w3.org/2000/09/xmldsig#"
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
DEFAULT_EXC_C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
|
|
15
|
+
DEFAULT_SIGNATURE_METHOD = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
|
|
16
|
+
DEFAULT_DIGEST_METHOD = "http://www.w3.org/2001/04/xmlenc#sha256"
|
|
17
|
+
DEFAULT_ENCODING_TYPE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
|
|
18
|
+
DEFAULT_VALUE_TYPE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
|
|
19
|
+
DEFAULT_TIMESTAMP_TTL = 300 # seconds
|
|
20
|
+
|
|
21
|
+
attr_accessor :namespaces, :exc_c14n, :signature_method, :digest_method,
|
|
22
|
+
:encoding_type, :value_type, :timestamp_ttl
|
|
23
|
+
|
|
24
|
+
def initialize
|
|
25
|
+
@namespaces = DEFAULT_NAMESPACES.dup
|
|
26
|
+
@exc_c14n = DEFAULT_EXC_C14N
|
|
27
|
+
@signature_method = DEFAULT_SIGNATURE_METHOD
|
|
28
|
+
@digest_method = DEFAULT_DIGEST_METHOD
|
|
29
|
+
@encoding_type = DEFAULT_ENCODING_TYPE
|
|
30
|
+
@value_type = DEFAULT_VALUE_TYPE
|
|
31
|
+
@timestamp_ttl = DEFAULT_TIMESTAMP_TTL
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/wssec/signer.rb
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require "nokogiri"
|
|
2
|
+
require "openssl"
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module Wssec
|
|
6
|
+
# Signs a SOAP envelope with an OASIS WS-Security header carrying a detached
|
|
7
|
+
# XML-DSig signature (RSA-SHA256 / SHA256, exclusive-C14N) over the Body and a
|
|
8
|
+
# Timestamp, exactly as a WSS4J-based gateway expects.
|
|
9
|
+
#
|
|
10
|
+
# doc = Nokogiri::XML(soap_envelope)
|
|
11
|
+
# Wssec::Signer.new(
|
|
12
|
+
# key: rsa_key, cert: x509_cert, key_identifier: "my-key",
|
|
13
|
+
# inclusive_prefixes: %w[wsse wsu soapenv ds]
|
|
14
|
+
# ).sign!(doc, body_id: "Id-#{SecureRandom.hex(16)}")
|
|
15
|
+
# doc.to_xml
|
|
16
|
+
#
|
|
17
|
+
# The signer is vendor-agnostic: key/cert, the token identifier and the C14N
|
|
18
|
+
# inclusive prefix list are supplied per call, while namespaces, algorithm
|
|
19
|
+
# identifiers, token types and the timestamp TTL come from +config+ (defaults
|
|
20
|
+
# to the global Wssec.configuration).
|
|
21
|
+
class Signer # rubocop:disable Metrics/ClassLength
|
|
22
|
+
# key -- OpenSSL::PKey::RSA private key used to sign.
|
|
23
|
+
# cert -- OpenSSL::X509::Certificate embedded as the BinarySecurityToken.
|
|
24
|
+
# key_identifier -- names the token (BST wsu:Id becomes "X509-#{key_identifier}").
|
|
25
|
+
# inclusive_prefixes -- namespace prefixes rendered into each signed subtree
|
|
26
|
+
# (the exclusive-C14N PrefixList); gateway-specific, so the caller supplies it.
|
|
27
|
+
# timestamp_ttl -- seconds until the wsu:Timestamp expires (defaults to config).
|
|
28
|
+
# config -- Wssec::Configuration for namespaces/algorithms/token types.
|
|
29
|
+
def initialize(key:, cert:, key_identifier:, inclusive_prefixes:, timestamp_ttl: nil, config: Wssec.configuration)
|
|
30
|
+
@key = key
|
|
31
|
+
@cert = cert
|
|
32
|
+
@key_identifier = key_identifier
|
|
33
|
+
@inclusive_prefixes = inclusive_prefixes
|
|
34
|
+
@config = config
|
|
35
|
+
@timestamp_ttl = timestamp_ttl || config.timestamp_ttl
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Adds the signed <wsse:Security> header to +document+ (a Nokogiri doc) in place.
|
|
39
|
+
def sign!(document, body_id:, now: Time.now.utc)
|
|
40
|
+
root = document.root
|
|
41
|
+
header = root.at_xpath("soapenv:Header", "soapenv" => ns[:soapenv]) ||
|
|
42
|
+
root.prepend_child(Nokogiri::XML::Node.new("soapenv:Header", document))
|
|
43
|
+
body = root.at_xpath("soapenv:Body", "soapenv" => ns[:soapenv])
|
|
44
|
+
raise Wssec::Error, "envelope has no soapenv:Body" unless body
|
|
45
|
+
|
|
46
|
+
tag_body(body, body_id)
|
|
47
|
+
security = build_security_header(document, header, now)
|
|
48
|
+
append_binary_security_token(document, security)
|
|
49
|
+
append_signature(document, security, body, body_id)
|
|
50
|
+
document
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def ns
|
|
56
|
+
@config.namespaces
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def tag_body(body, body_id)
|
|
60
|
+
ensure_ns(body, "wsu", ns[:wsu])
|
|
61
|
+
body["wsu:Id"] = body_id
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def build_security_header(document, header, now)
|
|
65
|
+
security = Nokogiri::XML::Node.new("wsse:Security", document)
|
|
66
|
+
security.add_namespace_definition("wsse", ns[:wsse])
|
|
67
|
+
security.add_namespace_definition("wsu", ns[:wsu])
|
|
68
|
+
security.add_namespace_definition("ds", ns[:ds])
|
|
69
|
+
security["soapenv:mustUnderstand"] = "1"
|
|
70
|
+
header.add_child(security)
|
|
71
|
+
|
|
72
|
+
timestamp = security.add_child(Nokogiri::XML::Node.new("wsu:Timestamp", document))
|
|
73
|
+
timestamp["wsu:Id"] = "TS-1"
|
|
74
|
+
timestamp.add_child(Nokogiri::XML::Node.new("wsu:Created", document)).content = format_time(now)
|
|
75
|
+
timestamp.add_child(Nokogiri::XML::Node.new("wsu:Expires", document)).content = format_time(now + @timestamp_ttl)
|
|
76
|
+
security
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def append_binary_security_token(document, security)
|
|
80
|
+
bst = security.add_child(Nokogiri::XML::Node.new("wsse:BinarySecurityToken", document))
|
|
81
|
+
bst["EncodingType"] = @config.encoding_type
|
|
82
|
+
bst["ValueType"] = @config.value_type
|
|
83
|
+
bst["wsu:Id"] = bst_id
|
|
84
|
+
bst.content = Base64.strict_encode64(@cert.to_der)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def append_signature(document, security, body, body_id)
|
|
88
|
+
timestamp = security.at_xpath("wsu:Timestamp", "wsu" => ns[:wsu])
|
|
89
|
+
|
|
90
|
+
signature = Nokogiri::XML::Node.new("ds:Signature", document)
|
|
91
|
+
signature.add_namespace_definition("ds", ns[:ds])
|
|
92
|
+
signed_info = signature.add_child(Nokogiri::XML::Node.new("ds:SignedInfo", document))
|
|
93
|
+
add_attr(signed_info.add_child(Nokogiri::XML::Node.new("ds:CanonicalizationMethod", document)), "Algorithm", @config.exc_c14n)
|
|
94
|
+
add_attr(signed_info.add_child(Nokogiri::XML::Node.new("ds:SignatureMethod", document)), "Algorithm", @config.signature_method)
|
|
95
|
+
add_reference(document, signed_info, "##{body_id}", digest(body))
|
|
96
|
+
add_reference(document, signed_info, "#TS-1", digest(timestamp))
|
|
97
|
+
|
|
98
|
+
signature_value = signature.add_child(Nokogiri::XML::Node.new("ds:SignatureValue", document))
|
|
99
|
+
append_key_info(document, signature)
|
|
100
|
+
|
|
101
|
+
# Attach the signature into its FINAL position BEFORE canonicalizing SignedInfo:
|
|
102
|
+
# exclusive-C14N must see the namespace context SignedInfo will have on the wire,
|
|
103
|
+
# otherwise the gateway recomputes different bytes and rejects with FAILED_AUTHENTICATION.
|
|
104
|
+
security.add_child(signature)
|
|
105
|
+
signed_info_c14n = canonicalize(signed_info, nil)
|
|
106
|
+
signature_value.content = Base64.strict_encode64(@key.sign(OpenSSL::Digest.new("SHA256"), signed_info_c14n))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def append_key_info(document, signature)
|
|
110
|
+
key_info = signature.add_child(Nokogiri::XML::Node.new("ds:KeyInfo", document))
|
|
111
|
+
str = key_info.add_child(Nokogiri::XML::Node.new("wsse:SecurityTokenReference", document))
|
|
112
|
+
str.add_namespace_definition("wsse", ns[:wsse])
|
|
113
|
+
reference = str.add_child(Nokogiri::XML::Node.new("wsse:Reference", document))
|
|
114
|
+
reference["URI"] = "##{bst_id}"
|
|
115
|
+
reference["ValueType"] = @config.value_type
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def add_reference(document, signed_info, uri, digest_value)
|
|
119
|
+
reference = signed_info.add_child(Nokogiri::XML::Node.new("ds:Reference", document))
|
|
120
|
+
reference["URI"] = uri
|
|
121
|
+
transform = reference
|
|
122
|
+
.add_child(Nokogiri::XML::Node.new("ds:Transforms", document))
|
|
123
|
+
.add_child(Nokogiri::XML::Node.new("ds:Transform", document))
|
|
124
|
+
transform["Algorithm"] = @config.exc_c14n
|
|
125
|
+
inclusive = transform.add_child(Nokogiri::XML::Node.new("ec:InclusiveNamespaces", document))
|
|
126
|
+
inclusive.add_namespace_definition("ec", @config.exc_c14n)
|
|
127
|
+
inclusive["PrefixList"] = @inclusive_prefixes.join(" ")
|
|
128
|
+
add_attr(reference.add_child(Nokogiri::XML::Node.new("ds:DigestMethod", document)), "Algorithm", @config.digest_method)
|
|
129
|
+
reference.add_child(Nokogiri::XML::Node.new("ds:DigestValue", document)).content = digest_value
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Reference digests use exclusive-C14N WITH the inclusive prefix list.
|
|
133
|
+
def digest(element)
|
|
134
|
+
Base64.strict_encode64(OpenSSL::Digest::SHA256.digest(canonicalize(element, @inclusive_prefixes)))
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# SignedInfo is canonicalized WITHOUT a prefix list. The old bridge documented this
|
|
138
|
+
# as a signxml 2.8 workaround; the gateway verifies the same way, so keep it.
|
|
139
|
+
def canonicalize(element, inclusive_prefixes)
|
|
140
|
+
element.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0, inclusive_prefixes)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def bst_id
|
|
144
|
+
"X509-#{@key_identifier}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def format_time(time)
|
|
148
|
+
time.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def add_attr(node, name, value)
|
|
152
|
+
node[name] = value
|
|
153
|
+
node
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def ensure_ns(node, prefix, href)
|
|
157
|
+
return if node.namespace_definitions.any? { |definition| definition.prefix == prefix }
|
|
158
|
+
|
|
159
|
+
node.add_namespace_definition(prefix, href)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
data/lib/wssec.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require_relative "wssec/version"
|
|
2
|
+
require_relative "wssec/configuration"
|
|
3
|
+
|
|
4
|
+
# OASIS WS-Security detached XML-DSig signing for SOAP envelopes. See Wssec::Signer
|
|
5
|
+
# for the signer and Wssec::Configuration for the tunable protocol defaults.
|
|
6
|
+
module Wssec
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
# Global configuration (namespaces, algorithms, token types, timestamp TTL).
|
|
11
|
+
# Signers read from this unless given their own +config:+.
|
|
12
|
+
def configuration
|
|
13
|
+
@configuration ||= Configuration.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Wssec.configure { |config| config.timestamp_ttl = 600 }
|
|
17
|
+
def configure
|
|
18
|
+
yield(configuration)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Restore all defaults — primarily for test isolation.
|
|
22
|
+
def reset_configuration!
|
|
23
|
+
@configuration = Configuration.new
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
require_relative "wssec/signer"
|
data/sig/wssec.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: wssec
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- bugloper
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: nokogiri
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.13'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.13'
|
|
40
|
+
description: 'Signs a SOAP envelope''s Body and Timestamp with a WS-Security header
|
|
41
|
+
carrying a detached XML-DSig signature (RSA-SHA256, exclusive-C14N), as expected
|
|
42
|
+
by WSS4J-based gateways. Vendor-agnostic: key, cert, token id, C14N prefix list
|
|
43
|
+
and timestamp TTL are all supplied by the caller.'
|
|
44
|
+
email:
|
|
45
|
+
- bugloper@hey.com
|
|
46
|
+
executables: []
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- CHANGELOG.md
|
|
51
|
+
- CODE_OF_CONDUCT.md
|
|
52
|
+
- LICENSE.txt
|
|
53
|
+
- README.md
|
|
54
|
+
- Rakefile
|
|
55
|
+
- lib/generators/wssec/install/install_generator.rb
|
|
56
|
+
- lib/generators/wssec/install/templates/wssec.rb
|
|
57
|
+
- lib/wssec.rb
|
|
58
|
+
- lib/wssec/configuration.rb
|
|
59
|
+
- lib/wssec/signer.rb
|
|
60
|
+
- lib/wssec/version.rb
|
|
61
|
+
- sig/wssec.rbs
|
|
62
|
+
homepage: https://github.com/SELISEdigitalplatforms/wssec
|
|
63
|
+
licenses:
|
|
64
|
+
- MIT
|
|
65
|
+
metadata:
|
|
66
|
+
allowed_push_host: https://rubygems.org
|
|
67
|
+
homepage_uri: https://github.com/SELISEdigitalplatforms/wssec
|
|
68
|
+
source_code_uri: https://github.com/SELISEdigitalplatforms/wssec
|
|
69
|
+
rubygems_mfa_required: 'true'
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 3.2.0
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubygems_version: 4.0.3
|
|
85
|
+
specification_version: 4
|
|
86
|
+
summary: OASIS WS-Security detached XML-DSig signer for SOAP envelopes.
|
|
87
|
+
test_files: []
|