# Signer
[![Continuous Integration status](https://api.travis-ci.org/ebeigarts/signer.svg)](http://travis-ci.org/ebeigarts/signer)
[![Gem Version](https://badge.fury.io/rb/signer.svg)](http://badge.fury.io/rb/signer)
WS Security XML Certificate signing for Ruby
## Installation
```bash
gem install signer
```
## Usage
```ruby
require "signer"
signer = Signer.new(File.read("example.xml"))
signer.cert = OpenSSL::X509::Certificate.new(File.read("cert.pem"))
signer.private_key = OpenSSL::PKey::RSA.new(File.read("key.pem"), "password")
signer.document.xpath("//u:Timestamp", { "u" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" }).each do |node|
signer.digest!(node)
end
signer.document.xpath("//a:To", { "a" => "http://www.w3.org/2005/08/addressing" }).each do |node|
signer.digest!(node)
end
signer.sign!(:security_token => true)
signer.to_xml
```
## Usage with Savon
```ruby
client = Savon::Client.new do |wsdl, http|
wsdl.document = "..."
wsdl.endpoint = "..."
end
response = client.request(:search_documents) do
soap.version = 2
soap.xml do
builder = Nokogiri::XML::Builder.new do |xml|
xml.send("s:Envelope",
"xmlns:s" => "http://www.w3.org/2003/05/soap-envelope",
"xmlns:a" => "http://www.w3.org/2005/08/addressing",
"xmlns:u" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
) do
xml.send("s:Header") do
xml.send("a:Action", "s:mustUnderstand" => "1") do
xml.text "http://tempuri.org/IDocumentService/SearchDocuments"
end
xml.send("a:MessageID") do
xml.text "urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b"
end
xml.send("a:ReplyTo") do
xml.send("a:Address") do
xml.text "http://www.w3.org/2005/08/addressing/anonymous"
end
end
xml.send("a:To", "a:mustUnderstand" => "1") do
xml.text "..."
end
xml.send("o:Security",
"xmlns:o" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
"s:mustUnderstand" => "1"
) do
xml.send("u:Timestamp") do
time = Time.now.utc
xml.send("u:Created") do
xml.text(time.xmlschema)
end
xml.send("u:Expires") do
xml.text((time + 5.minutes).xmlschema)
end
end
end
end
xml.send("s:Body") do
xml.send("SearchDocuments", "xmlns" => "http://tempuri.org/") do
xml.send("searchCriteria",
"xmlns:b" => "http://schemas.datacontract.org/2004/07/ZMDVS.BusinessLogic.Data.Documents.Integration",
"xmlns:i" => "http://www.w3.org/2001/XMLSchema-instance"
) do
xml.send("b:RegistrationNo") do
xml.text "1"
end
end
end
end
end
end
signer = Signer.new(builder.to_xml)
signer.cert = OpenSSL::X509::Certificate.new(File.read("cert.pem"))
signer.private_key = OpenSSL::PKey::RSA.new(File.read("key.pem"), "test")
signer.document.xpath("//u:Timestamp", { "u" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" }).each do |node|
signer.digest!(node)
end
signer.document.xpath("//a:To", { "a" => "http://www.w3.org/2005/08/addressing" }).each do |node|
signer.digest!(node)
end
signer.sign!
signer.to_xml
end
end
```
## Example
Input:
```xml
http://tempuri.org/IDocumentService/SearchDocuments
urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
http://www.w3.org/2005/08/addressing/anonymous
http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
2012-05-02T18:17:14.467Z
2012-05-02T18:22:14.467Z
1
```
Output:
```xml
http://tempuri.org/IDocumentService/SearchDocuments
urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
http://www.w3.org/2005/08/addressing/anonymous
http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
2012-05-02T18:17:14.467Z
2012-05-02T18:22:14.467Z
MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
Oz29YgZk14+nchoqv9zGzhJcDUo=
leV/RNYhwuCuD7/DBzn3IgQzUxI=
en7YYAIn90ofH08aF917jNngMuse+vK6bihF0v6UsXFnGGMOflWfRTZ6mFmC2HwLmb2lSrhZ3eth3cs2fCBlEr/K2ZDMQfJo6CPxmbzfX/fxR/isCTDz+HIJd13J0HK4n+CzkndwplkCmT8SQlduUruUFUUmlQiiZQ7nryR+XyM=
1
```
## Different signature and digest algorithms support
You can change digest algorithms used for both node digesting and signing. Default for both is SHA1. Currently __SHA1__ `:sha1`, __SHA256__ `:sha256`, and __GOST R 34.11-94__ `:gostr3411` are supported out of the box.
```ruby
signer.digest_algorithm = :sha256 # Set algorithm for node digesting
signer.signature_digest_algorithm = :sha256 # Set algorithm for message digesting for signing
```
You can provide you own digest support by passing in these methods a `Hash` with `:id` and `:digester` keys. In `:id` should be a string for XML `//Reference/DigestMethod[Algorithm]`, in `:digester` should be a Ruby object, compatible by interface with `OpenSSL::Digest` class, at least it should respond to `digest` and `reset` methods.
Signature algorithm is dependent from keypair used for signing and can't be changed. Usually it's __RSA__. Currently gem recognizes __GOST R 34.10-2001__ certificates and sets up a XML identifier for it. If used signature algorithm and signature digest doesn't corresponds with XML identifier, you can change identifier with `signature_algorithm_id` method.
Please note, that these settings will be changed or reset on certificate assignment, please change them after setting certificate!
__NOTE__: To sign XMLs with __GOST R 34.10-2001__, you need to have Ruby compiled with patches from https://bugs.ruby-lang.org/issues/9830 and correctly configured OpenSSL (see https://github.com/openssl/openssl/blob/master/engines/ccgost/README.gost)
## Miscellaneous
Every new instance of signer has Nokogiri `noblanks` set as default in process of parsing xml file. If you need to disable it, pass optional argument `noblanks: false`.
```ruby
Signer.new(File.read("example.xml"), noblanks: false)
```
Available options for the `sign!` method:
* [:security_token] - Serializes certificate in DER format, encodes it with Base64 and inserts it within a `BinarySecurityToken` tag
If you need to digest a `BinarySecurityToken` tag, you need to construct it yourself **before** signing.
```ruby
signer.digest!(signer.binary_security_token_node) # Constructing tag and digesting it
signer.sign! # No need to pass a :security_token option, as we already constructed and inserted this node
```
* [:inclusive_namespaces] - Array of namespace prefixes which definitions should be added to signed info node during canonicalization
If you need `Signature` tags to be in explicit namespace (say, ``) instead of to be in implicit default namespace you can specify next option:
```ruby
signer.ds_namespace_prefix = 'ds'
```
If you need to use canonicalization with inclusive namespaces you can pass array of namespace prefixes in `:inclusive_namespaces` option in both `digest!` and `sign!` methods.
* [:issuer_serial] - flag to include a `X509Data` node to include information from a `X509Certificate`
* [:issuer_in_security_token] - flag to include the `X509Data` inside a `SecurityTokenReference` element