├── .rspec
├── lib
├── signer
│ ├── version.rb
│ └── digester.rb
└── signer.rb
├── spec
├── spec_helper.rb
├── fixtures
│ ├── input_2.xml
│ ├── input_5.xml
│ ├── key.pem
│ ├── cert.pem
│ ├── input_3_c14n_comments.xml
│ ├── input_1.xml
│ ├── input_4_with_nested_signatures.xml
│ ├── output_2_legacy.xml
│ ├── output_3_c14n_comments.xml
│ ├── output_2.xml
│ ├── output_2_with_ds_prefix_legacy.xml
│ ├── output_2_with_ds_prefix.xml
│ ├── output_2_with_ds_prefix_and_wss_disabled_legacy.xml
│ ├── output_5_with_x509_data.xml
│ ├── output_5_with_security_token.xml
│ ├── output_2_with_ds_prefix_and_wss_disabled.xml
│ ├── output_1_sha256.xml
│ ├── output_1_inclusive_namespaces.xml
│ ├── output_4_with_nested_signatures_with_noblanks_disabled.xml
│ ├── output_1.xml
│ └── output_4_with_nested_signatures.xml
└── signer_spec.rb
├── Gemfile
├── .gitignore
├── Rakefile
├── .github
└── workflows
│ └── ruby.yml
├── signer.gemspec
├── LICENSE
├── CHANGELOG.md
└── README.md
/.rspec:
--------------------------------------------------------------------------------
1 | --colour
2 |
--------------------------------------------------------------------------------
/lib/signer/version.rb:
--------------------------------------------------------------------------------
1 | class Signer
2 | VERSION = '1.10.0'
3 | end
4 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $:.unshift File.dirname(__FILE__) + '/../lib'
2 |
3 | require "rubygems"
4 | require "bundler/setup"
5 | require "signer"
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in signer.gemspec
4 | gemspec
5 |
6 | gem "jruby-openssl", :platforms => :jruby
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | InstalledFiles
8 | _yardoc
9 | coverage
10 | doc/
11 | lib/bundler/man
12 | pkg
13 | rdoc
14 | spec/reports
15 | test/tmp
16 | test/version_tmp
17 | tmp
18 | tags
19 |
--------------------------------------------------------------------------------
/spec/fixtures/input_2.xml:
--------------------------------------------------------------------------------
1 |
2 | 679155330
3 | GetUserInfo
4 | 2010-05-10T13:22:19.847+03:00
5 | PRODUCTION
6 | Petri
7 |
8 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | require "bundler/gem_tasks"
3 |
4 | require "rspec/core/rake_task"
5 |
6 | Bundler::GemHelper.install_tasks
7 | RSpec::Core::RakeTask.new(:spec)
8 | task :default => :spec
9 |
10 | task :generate_cert_and_private_key do
11 | system "openssl req -new -x509 -keyout spec/fixtures/key.pem -out spec/fixtures/cert.pem -days 365"
12 | end
13 |
--------------------------------------------------------------------------------
/.github/workflows/ruby.yml:
--------------------------------------------------------------------------------
1 | name: Ruby
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | ruby-version: ['2.6', '2.7', '3.0']
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Set up Ruby
15 | uses: ruby/setup-ruby@v1
16 | with:
17 | ruby-version: ${{ matrix.ruby-version }}
18 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically
19 | - name: Run tests
20 | run: bundle exec rake
21 |
--------------------------------------------------------------------------------
/spec/fixtures/input_5.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 679155330
11 | GetUserInfo
12 | 2010-05-10T13:22:19.847+03:00
13 | PRODUCTION
14 | Petri
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/signer.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require File.expand_path('../lib/signer/version', __FILE__)
3 |
4 | Gem::Specification.new do |gem|
5 | gem.authors = ["Edgars Beigarts"]
6 | gem.email = ["edgars.beigarts@gmail.com"]
7 | gem.description = %q{WS Security XML signer}
8 | gem.summary = gem.description
9 | gem.homepage = ""
10 |
11 | gem.files = Dir.glob("lib/**/*") + %w(README.md CHANGELOG.md LICENSE)
12 | gem.test_files = Dir.glob("spec/**/*")
13 | gem.executables = []
14 |
15 | gem.name = "signer"
16 | gem.require_paths = ["lib"]
17 | gem.version = Signer::VERSION
18 |
19 | gem.required_ruby_version = '>= 2.1.0'
20 |
21 | gem.add_development_dependency 'rake'
22 | gem.add_development_dependency 'rspec'
23 |
24 | gem.add_runtime_dependency 'nokogiri', '>= 1.5.1', '!=1.12.3', '!=1.12.2', '!=1.12.1', '!=1.12.0'
25 | end
26 |
--------------------------------------------------------------------------------
/spec/fixtures/key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | Proc-Type: 4,ENCRYPTED
3 | DEK-Info: DES-EDE3-CBC,D53BEDA3FEA82258
4 |
5 | a9x4N5JZ6qwza28OAEBWyS4s4GysgbiJf11wX2e1Ol/rHt9/PGvZCK9PSyW/haRn
6 | l5iT95n8Ohyr16Ngx/p02Jq3TEbs/Jsxz7+z55R5E3LvGKGZIB8Yh8dvI4ArzPsK
7 | QiwoegJnSWdTQnfybbbpv+11RrKtb1/H0/i9fIHDJUhch9HwNn/yZGs9uYRxVBGe
8 | OFLAdnCB+JG5Q5p24aWeYEWQLbYB69uc9kiV60qgfp4ZxRgdeU55eLZe48d9clG7
9 | 8vFAjM3+Q5WcgAgthKgD2HbjgcQ11WIZyHsshRvdWukk7iir7ogbOR/20sIWc+mY
10 | hVdWfctYfWuheq7qwAfbvc8gSpgBRMziSI+5dFF4Ge80ahy7sxdlWJ4zNrqzTEIf
11 | 5CyN2cC6h8fbJ95bifJFUIKDi+rpkmFtnW4eh11BOL6vYlSFaL35Kw2teRmFCk6M
12 | iBmIStavcfVj61ZClla3Xso5gl8Ei8EGBSuSSLT1Uq3WJ6eLh5JWkpV3+mir1HWO
13 | vNIl6L4zm2kngwL73NFeG7ZFMTv2yAuif3Hpr3akZudp/+qDcIcnqnmY47HsOtOb
14 | ArLwBalIu4zsKF5IzqGiZ6tbMgRHDbLw/HAuuYHlJF5TeMDqnQFGi09LpO6IHPBh
15 | ZqVKuZ1LVLgCRpvkW12z0sxfByFHKK6RvgPgR9tPdVmCBJLwCbLa7AqNDVrJNe6q
16 | OEwp95+7OTx1b7jVXiZ3wO0XpPWKAMXWAEGDd9YeNVdfXPgypSe4p/wJgtoCiPtz
17 | Mm0NNYkrl2J/brj2jd5WRk6D2BWxf6pQKULzovTCX04pNxfLBBXBlQ==
18 | -----END RSA PRIVATE KEY-----
19 |
--------------------------------------------------------------------------------
/spec/fixtures/cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
4 | aWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBF
5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
7 | gQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuV
8 | wyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEb
9 | gDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0G
10 | A1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8
11 | Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
12 | U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4o
13 | ho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnN
14 | JJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpT
15 | BmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0
16 | jlyrGYJlLli1NxHiBz7FCEJaVI8=
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Edgars Beigarts
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/spec/fixtures/input_3_c14n_comments.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://tempuri.org/IDocumentService/SearchDocuments
5 | urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
6 | http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
7 |
8 |
9 | 2012-05-02T18:17:14.467Z
10 | 2012-05-02T18:22:14.467Z
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 1
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/spec/fixtures/input_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://tempuri.org/IDocumentService/SearchDocuments
5 | urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
6 |
7 | http://www.w3.org/2005/08/addressing/anonymous
8 |
9 | http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
10 |
11 |
12 | 2012-05-02T18:17:14.467Z
13 | 2012-05-02T18:22:14.467Z
14 |
15 |
16 |
17 |
18 |
19 |
20 | 1
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/spec/fixtures/input_4_with_nested_signatures.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://tempuri.org/IDocumentService/SearchDocuments
5 | urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
6 |
7 | http://www.w3.org/2005/08/addressing/anonymous
8 |
9 | http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
10 |
11 |
12 | 2012-05-02T18:17:14.467Z
13 | 2012-05-02T18:22:14.467Z
14 |
15 |
16 |
17 |
18 |
19 |
20 | 1
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.10.0 (2021-10-22)
2 |
3 | - Ensure compatibility with Nokogiri 1.12.4+ (#31, @flavorjones)
4 | - fix #26: add xml-exc-c14n Transform when :enveloped option is true. (#27, @kunxi)
5 |
6 | ## 1.9.0 (2019-04-16)
7 |
8 | - Refactor digest!() method for better extensibility, add GOST-R 34.10/11-2012 algorithms, fix digest node ID reference, cleanup (#22, @netcitylife)
9 |
10 | ## 1.8.0 (2018-11-14)
11 |
12 | - Add parameter to customize canonicalize algorithm (#19, @pistachiology)
13 | - Add references node type on digest (for xades-bes signing properties) (#19, @pistachiology)
14 | - change issuer x509 content to be more standard way (#19, @pistachiology)
15 |
16 | ## 1.7.0 (2018-11-06)
17 |
18 | - Add wss option for XML only signing (#18, @pistachiology)
19 | - Add support for SHA512 Digest
20 | - Rename id for SHA256 Digest
21 |
22 | ## 1.6.0 (2017-09-14)
23 |
24 | - X509 in SecurityTokenReference node (#17, @tiagocasanovapt)
25 |
26 | ## 1.5.1 (2017-03-23)
27 |
28 | - Allow to set up custom xmldsig namespace prefix for Signature node (#14, @Envek)
29 |
30 | ## 1.5.0 (2017-01-23)
31 |
32 | - Add posibility to disable noblanks method in Signer initialization (#16, @bpietraga)
33 | - Minimum ruby version is now 2.1
34 |
35 | ## 1.4.3 (2015-10-28)
36 |
37 | - Fixed Issuer Name node (#8, @tiagocasanovapt)
38 |
39 | ## 1.4.2 (2014-11-30)
40 |
41 | - Fixed behaviour on XMLs that already contains nested signatures somewhere
42 |
43 | ## 1.4.1 (2014-09-09)
44 |
45 | - Changed method of getting GOST R 34.11-94 digest algorithm to more short and generic (and working in Ubuntu 14.04 and other OS)
46 |
47 | ## 1.4.0 (2014-06-24)
48 |
49 | - Support signing and digesting with inclusive namespaces (#5, @Envek)
50 |
51 | ## 1.3.1 (2014-06-24)
52 |
53 | - Fix namespace issue for SecurityTokenReference tag (#4, #@Envek)
54 |
55 | ## 1.3.0 (2014-06-16)
56 |
57 | - Allow to sign with other digest algorithms - SHA1, SHA256, and GOST R 34.11-94 (#3, @Envek)
58 |
59 | ## 1.2.1 (2014-05-14)
60 |
61 | - Fix canonicalization: should be without comments (#2, @Envek)
62 |
63 | ## 1.2.0 (2014-05-06)
64 |
65 | - Id and attribute namespace preserving when digesting the nodes (#1, @Envek)
66 |
67 | ## 1.1.1 (2013-04-03)
68 |
69 | - Allow to sign using enveloped-signature
70 |
71 | ## 1.1.0 (2012-06-21)
72 |
73 | - Allow to sign XML documents without SOAP
74 |
75 | ## 1.0.0 (2012-05-03)
76 |
77 | - Allow to sign SOAP documents
78 |
--------------------------------------------------------------------------------
/spec/fixtures/output_2_legacy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 679155330
4 | GetUserInfo
5 | 2010-05-10T13:22:19.847+03:00
6 | PRODUCTION
7 | Petri
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | U9tsT4lrRMp8ZdKMnblgeMCGfvI=
18 |
19 |
20 | HpRIiW6/yGyAI0AwVaaGp3PltD3JOCFfxZLVt+kQD05u1tz9EA91/5CbvCNfn1ljoObMSGe3+W9gXFZewCXANu5VXMnt+FeZ42QYNuYj2oUCFaWlg3NcThWnehE1W/R+QPLJVgk4RxpSntNLK0WWtFy79JbAh0NO4CcD84/HEo8=
21 |
22 |
23 |
24 | O=Internet Widgits Pty Ltd,ST=Some-State,C=AU
25 | 16503368396260674861
26 |
27 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/spec/fixtures/output_3_c14n_comments.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | http://tempuri.org/IDocumentService/SearchDocuments
6 | urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
7 | http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
8 |
10 |
11 | 2012-05-02T18:17:14.467Z
12 | 2012-05-02T18:22:14.467Z
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | iqiXdxdsix9HMz4rEBEo/sYazDU=
24 |
25 |
26 | XOMmCzcg7Un+BpWIP5WpAAeT1Sq2B+WZ8eM4MiDR1bhIFV8aPScAXX/cB3Esa88JcBltsiBlZTdq1hCQ8GKrLEvWTFMhkSCQrkAR+3eCUR894UzPrWTr0jYA7RZaVaw+XODf7ICbYIhLs7n50cPyFrslKVOjh6EKlCq1ZV5XFYE=
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 1
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/spec/fixtures/output_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 679155330
4 | GetUserInfo
5 | 2010-05-10T13:22:19.847+03:00
6 | PRODUCTION
7 | Petri
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | U9tsT4lrRMp8ZdKMnblgeMCGfvI=
19 |
20 |
21 | pjz9q0RI02SGuFs3ok+qQjKKyibAG+dScZBIxmWebD4JmfjIMOCTvk7RR1S5ZqJqkDp2kMV4DOBg+AqJAEu9ZO6gBBceCfYHYgmdvKWz3Ex42fyRYjfZlnR/7Vxk94VJ806J/H+7n2TBJlSndkMGJ2X8agKq1Zto0ip/k2qDfm4=
22 |
23 |
24 |
25 | O=Internet Widgits Pty Ltd,ST=Some-State,C=AU
26 | 16503368396260674861
27 |
28 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/spec/fixtures/output_2_with_ds_prefix_legacy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 679155330
4 | GetUserInfo
5 | 2010-05-10T13:22:19.847+03:00
6 | PRODUCTION
7 | Petri
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | U9tsT4lrRMp8ZdKMnblgeMCGfvI=
18 |
19 |
20 | rOCe8McbIFa4Ul3pnzd/dBjFWoT4JtSghJgzZGLrz17K/j0W1JyaopcZeMD+8M5/GplAlQrJg3ZSkQvY9Sf7WpqZeLYHW17J0ZJpwas+/OOXUEdyUiec7q9OgWsFLH9DBNuJdLKE3CO6w/8tTKQ/kidYnPBXT6FKioNlSJVZsuI=
21 |
22 |
23 |
24 | O=Internet Widgits Pty Ltd,ST=Some-State,C=AU
25 | 16503368396260674861
26 |
27 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/lib/signer/digester.rb:
--------------------------------------------------------------------------------
1 | require 'openssl'
2 |
3 | class Signer
4 |
5 | # Digest algorithms supported "out of the box"
6 | DIGEST_ALGORITHMS = {
7 | # SHA 1
8 | sha1: {
9 | name: 'SHA1',
10 | id: 'http://www.w3.org/2000/09/xmldsig#sha1',
11 | digester: lambda { OpenSSL::Digest::SHA1.new },
12 | },
13 | # SHA 256
14 | sha256: {
15 | name: 'SHA256',
16 | id: 'http://www.w3.org/2001/04/xmlenc#sha256',
17 | digester: lambda { OpenSSL::Digest::SHA256.new },
18 | },
19 | # SHA512
20 | sha512: {
21 | name: 'SHA512',
22 | id: 'http://www.w3.org/2001/04/xmlenc#sha512',
23 | digester: lambda { OpenSSL::Digest::SHA512.new },
24 | },
25 | # GOST R 34-11 94
26 | gostr3411: {
27 | name: 'GOST R 34.11-94',
28 | id: 'http://www.w3.org/2001/04/xmldsig-more#gostr3411',
29 | digester: lambda { OpenSSL::Digest.new('md_gost94') },
30 | },
31 | # GOST R 34-11 2012 256 bit
32 | gostr34112012_256: {
33 | name: 'GOST R 34.11-2012 256',
34 | id: 'urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256',
35 | digester: lambda { begin OpenSSL::Digest.new('streebog256') rescue OpenSSL::Digest.new('md_gost12_256') end },
36 | },
37 | }.freeze
38 |
39 | # Class that holds +OpenSSL::Digest+ instance with some meta information for digesting in XML.
40 | class Digester
41 |
42 | # You may pass either a one of +:sha1+, +:sha256+ or +:gostr3411+ symbols
43 | # or +Hash+ with keys +:id+ with a string, which will denote algorithm in XML Reference tag
44 | # and +:digester+ with instance of class with interface compatible with +OpenSSL::Digest+ class.
45 | def initialize(algorithm)
46 | if algorithm.kind_of? Symbol
47 | @digest_info = DIGEST_ALGORITHMS[algorithm].dup
48 | @digest_info[:digester] = @digest_info[:digester].call
49 | @symbol = algorithm
50 | else
51 | @digest_info = algorithm
52 | end
53 | end
54 |
55 | attr_reader :symbol
56 |
57 | # Digest
58 | def digest(message)
59 | self.digester.digest(message)
60 | end
61 |
62 | alias call digest
63 |
64 | # Returns +OpenSSL::Digest+ (or derived class) instance
65 | def digester
66 | @digest_info[:digester].reset
67 | end
68 |
69 | # Human-friendly name
70 | def digest_name
71 | @digest_info[:name]
72 | end
73 |
74 | # XML-friendly name (for specifying in XML +DigestMethod+ node +Algorithm+ attribute)
75 | def digest_id
76 | @digest_info[:id]
77 | end
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/spec/fixtures/output_2_with_ds_prefix.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 679155330
4 | GetUserInfo
5 | 2010-05-10T13:22:19.847+03:00
6 | PRODUCTION
7 | Petri
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | U9tsT4lrRMp8ZdKMnblgeMCGfvI=
19 |
20 |
21 | oh0PAqWsOY+QROz2ks9rJ6wqD8756qC+Gg2uj9lfR75khHS9LBY0jidThh18iynkflluqD1/gA98Hze8raYjmXdw09X7z+kYkxRB/QBY6YkqsWdxSDMhuW63XynrI372bv5p4fC0YjS1lix195qFbk2i0h5LcTByimquzkwEMUk=
22 |
23 |
24 |
25 | O=Internet Widgits Pty Ltd,ST=Some-State,C=AU
26 | 16503368396260674861
27 |
28 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/spec/fixtures/output_2_with_ds_prefix_and_wss_disabled_legacy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 679155330
4 | GetUserInfo
5 | 2010-05-10T13:22:19.847+03:00
6 | PRODUCTION
7 | Petri
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | AttQv5nkiNZFLKlFfVfX5+JYmSA=
18 |
19 |
20 |
21 |
22 |
23 |
24 | 9Z9YtwWWlyGnFB36gxXj+mGcv14=
25 |
26 |
27 | YwPuF4il34qUeAhIfzsLy/oKr4gxB9hlCYqEhVo8nYsrnDJKtBMznvkmi89TuKJ4FIibWnjsMqDDC74rpkcoUVs9O4pE/zLQxdRnQeRWPZjZnwEsmbBirFK+uk+Q7aVMUTRxxQwjZQRfBain4YdatqKDYCq/VkX4muAzxtHBYN4=
28 |
29 |
30 |
31 | O=Internet Widgits Pty Ltd,ST=Some-State,C=AU
32 | 16503368396260674861
33 |
34 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/spec/fixtures/output_5_with_x509_data.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | gi4IR3SwMg7auRoEe4//3J5nIw8=
17 |
18 |
19 | P1eVMjbEbxD131Q5F8syj0ky8dZ1lbl57K6JM3tAk8HE8pFHD50k6jf0FoI/njuqRESq+EKLvNx8akjiQxlLazV/H2uzdZCNJwqtc+78xGrGgWaMlicYwIZKtaiL0qV0eEfHTkYr9l5SrXelZjEzHil/vy1V98Y05lRcuByMI7o=
20 |
21 |
22 |
23 | O=Internet Widgits Pty Ltd,ST=Some-State,C=AU
24 | 16503368396260674861
25 |
26 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 679155330
35 | GetUserInfo
36 | 2010-05-10T13:22:19.847+03:00
37 | PRODUCTION
38 | Petri
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/spec/fixtures/output_5_with_security_token.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | gi4IR3SwMg7auRoEe4//3J5nIw8=
17 |
18 |
19 | P1eVMjbEbxD131Q5F8syj0ky8dZ1lbl57K6JM3tAk8HE8pFHD50k6jf0FoI/njuqRESq+EKLvNx8akjiQxlLazV/H2uzdZCNJwqtc+78xGrGgWaMlicYwIZKtaiL0qV0eEfHTkYr9l5SrXelZjEzHil/vy1V98Y05lRcuByMI7o=
20 |
21 |
22 |
23 |
24 | O=Internet Widgits Pty Ltd,ST=Some-State,C=AU
25 | 16503368396260674861
26 |
27 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | 679155330
37 | GetUserInfo
38 | 2010-05-10T13:22:19.847+03:00
39 | PRODUCTION
40 | Petri
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/spec/fixtures/output_2_with_ds_prefix_and_wss_disabled.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 679155330
4 | GetUserInfo
5 | 2010-05-10T13:22:19.847+03:00
6 | PRODUCTION
7 | Petri
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | AttQv5nkiNZFLKlFfVfX5+JYmSA=
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | gZjyHqoTlsz5D1JQJEFNvSmtwjk=
27 |
28 |
29 | Vhsr3WaCPA0dDB6THouzG9/EA0xfhzHzfbyCn1PY8+Y9MMsLpiW0KHOWtAiWLULDN2mFvTFDr90kCZR6YzgdaztbQewiZHeeu7M0WEC5f8VCgfO0N8J7mzOCWHBELHtDzoN+9phTbqDqbX06TH0mszIpZhnsGa4d+Ko3Y+AA3cs=
30 |
31 |
32 |
33 | O=Internet Widgits Pty Ltd,ST=Some-State,C=AU
34 | 16503368396260674861
35 |
36 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/spec/fixtures/output_1_sha256.xml:
--------------------------------------------------------------------------------
1 |
2 | http://tempuri.org/IDocumentService/SearchDocumentsurn:uuid:30db5d4f-ab84-46be-907c-be690a92979bhttp://www.w3.org/2005/08/addressing/anonymoushttp://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc2012-05-02T18:17:14.467Z2012-05-02T18:22:14.467ZMIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=2ca0eR2o1+y/CovNwnle3yEK1wI+ztlKQfCqcGvoSAA=PoUuYfxElOzG8Dw8/zdDrgPXxbFpj+Gxz4Fi7KDJ0XUgUNcQ6/Tk871cwdFA641Pkqo2DvyD2RIylXEuaY57abDQ4JTB86KCqrdt1cgAecn/lqfoojdTflrq+ugc1JGm6UZFQRcHrW4m2wjQgWFFAPFwNnRVdNGTRf5SHtmbMvc=1
3 |
--------------------------------------------------------------------------------
/spec/fixtures/output_1_inclusive_namespaces.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://tempuri.org/IDocumentService/SearchDocuments
5 | urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
6 |
7 | http://www.w3.org/2005/08/addressing/anonymous
8 |
9 | http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
10 |
11 |
12 | 2012-05-02T18:17:14.467Z
13 | 2012-05-02T18:22:14.467Z
14 |
15 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | lXzKYCXgcLpnf/2Q8ieQqj3Er7I=
30 |
31 |
32 | Bs6O0JuOix9vzDX7gTUMWJ+xzB4IfIWI7l/HjMnE4MnfnFlDQeU1a+0OuqiWiesdzImDLZvqjAeSBUPQOnP4eil2O9qTEd4FvTAUm6DldBV+4ECSXozDiLyPaHMSrm4JZyuePF6d3IOroDYn7ZREWBYgBGHiga7F7+h4s9ZW2XQ=
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 1
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/spec/fixtures/output_4_with_nested_signatures_with_noblanks_disabled.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://tempuri.org/IDocumentService/SearchDocuments
5 | urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
6 |
7 | http://www.w3.org/2005/08/addressing/anonymous
8 |
9 | http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
10 |
11 |
12 | 2012-05-02T18:17:14.467Z
13 | 2012-05-02T18:22:14.467Z
14 |
15 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=YDcfIJMN1Ef14aySyIDif+0XeUE=/rAVEm0SjaC0ckFViZd+A0hYe+U=QfyyHQKBXN21QNXRHMDBkxZeTMI=AKYUCGtsWq4HzQ5NgATA/K6mpEGyoQ7S1imImIChmIcSGVCWfdr01KVpjQPxqnhJSiMZo3gLy4buY5ZUvckFIahIYxbG0ZNCrLACAjtAhXpvUNKjGHgUsW5UV1d5+wrEkUDiMnaym+FM37fyJMfCmmuJAc623LnBSLX349DETlI=
16 |
17 |
18 |
19 |
20 | 1
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/spec/fixtures/output_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://tempuri.org/IDocumentService/SearchDocuments
5 | urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
6 |
7 | http://www.w3.org/2005/08/addressing/anonymous
8 |
9 | http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
10 |
11 |
12 | 2012-05-02T18:17:14.467Z
13 | 2012-05-02T18:22:14.467Z
14 |
15 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | hUP34KxVar1UE5I87U1kH8MzV+o=
26 |
27 |
28 |
29 |
30 |
31 |
32 | /rAVEm0SjaC0ckFViZd+A0hYe+U=
33 |
34 |
35 |
36 |
37 |
38 |
39 | QfyyHQKBXN21QNXRHMDBkxZeTMI=
40 |
41 |
42 | c1YkLvip/5njmwGakJ1Er6PMDUO2zC1HpinFkEEQkL+Ay1XJzGFehQdflIQjb6oRkT3c5DY3c+tcvE+G9/Wzy1m89RKkLvUAdPeI+ZMZw+90Rf5mvJku3QJ/G1bDlBOL54zYIC76El+XmHy1YM71x9+Y56z2mtCxZWAFOP7C3rA=
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 1
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/spec/fixtures/output_4_with_nested_signatures.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | http://tempuri.org/IDocumentService/SearchDocuments
5 | urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
6 |
7 | http://www.w3.org/2005/08/addressing/anonymous
8 |
9 | http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
10 |
11 |
12 | 2012-05-02T18:17:14.467Z
13 | 2012-05-02T18:22:14.467Z
14 |
15 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | hUP34KxVar1UE5I87U1kH8MzV+o=
26 |
27 |
28 |
29 |
30 |
31 |
32 | /rAVEm0SjaC0ckFViZd+A0hYe+U=
33 |
34 |
35 |
36 |
37 |
38 |
39 | QfyyHQKBXN21QNXRHMDBkxZeTMI=
40 |
41 |
42 | c1YkLvip/5njmwGakJ1Er6PMDUO2zC1HpinFkEEQkL+Ay1XJzGFehQdflIQjb6oRkT3c5DY3c+tcvE+G9/Wzy1m89RKkLvUAdPeI+ZMZw+90Rf5mvJku3QJ/G1bDlBOL54zYIC76El+XmHy1YM71x9+Y56z2mtCxZWAFOP7C3rA=
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 1
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Signer
2 |
3 | [](http://badge.fury.io/rb/signer)
4 |
5 | WS Security XML Certificate signing for Ruby
6 |
7 | ## Installation
8 |
9 | ```bash
10 | gem install signer
11 | ```
12 |
13 | ## Usage
14 |
15 | ```ruby
16 | require "signer"
17 |
18 | signer = Signer.new(File.read("example.xml"))
19 | signer.cert = OpenSSL::X509::Certificate.new(File.read("cert.pem"))
20 | signer.private_key = OpenSSL::PKey::RSA.new(File.read("key.pem"), "password")
21 |
22 | 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|
23 | signer.digest!(node)
24 | end
25 |
26 | signer.document.xpath("//a:To", { "a" => "http://www.w3.org/2005/08/addressing" }).each do |node|
27 | signer.digest!(node)
28 | end
29 |
30 | signer.sign!(:security_token => true)
31 |
32 | signer.to_xml
33 | ```
34 |
35 | ## Usage with Savon
36 |
37 | ```ruby
38 | client = Savon::Client.new do |wsdl, http|
39 | wsdl.document = "..."
40 | wsdl.endpoint = "..."
41 | end
42 |
43 | response = client.request(:search_documents) do
44 | soap.version = 2
45 | soap.xml do
46 | builder = Nokogiri::XML::Builder.new do |xml|
47 | xml.send("s:Envelope",
48 | "xmlns:s" => "http://www.w3.org/2003/05/soap-envelope",
49 | "xmlns:a" => "http://www.w3.org/2005/08/addressing",
50 | "xmlns:u" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
51 | ) do
52 | xml.send("s:Header") do
53 | xml.send("a:Action", "s:mustUnderstand" => "1") do
54 | xml.text "http://tempuri.org/IDocumentService/SearchDocuments"
55 | end
56 | xml.send("a:MessageID") do
57 | xml.text "urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b"
58 | end
59 | xml.send("a:ReplyTo") do
60 | xml.send("a:Address") do
61 | xml.text "http://www.w3.org/2005/08/addressing/anonymous"
62 | end
63 | end
64 | xml.send("a:To", "a:mustUnderstand" => "1") do
65 | xml.text "..."
66 | end
67 | xml.send("o:Security",
68 | "xmlns:o" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
69 | "s:mustUnderstand" => "1"
70 | ) do
71 | xml.send("u:Timestamp") do
72 | time = Time.now.utc
73 | xml.send("u:Created") do
74 | xml.text(time.xmlschema)
75 | end
76 | xml.send("u:Expires") do
77 | xml.text((time + 5.minutes).xmlschema)
78 | end
79 | end
80 | end
81 | end
82 | xml.send("s:Body") do
83 | xml.send("SearchDocuments", "xmlns" => "http://tempuri.org/") do
84 | xml.send("searchCriteria",
85 | "xmlns:b" => "http://schemas.datacontract.org/2004/07/ZMDVS.BusinessLogic.Data.Documents.Integration",
86 | "xmlns:i" => "http://www.w3.org/2001/XMLSchema-instance"
87 | ) do
88 | xml.send("b:RegistrationNo") do
89 | xml.text "1"
90 | end
91 | end
92 | end
93 | end
94 | end
95 | end
96 |
97 | signer = Signer.new(builder.to_xml)
98 | signer.cert = OpenSSL::X509::Certificate.new(File.read("cert.pem"))
99 | signer.private_key = OpenSSL::PKey::RSA.new(File.read("key.pem"), "test")
100 |
101 | 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|
102 | signer.digest!(node)
103 | end
104 |
105 | signer.document.xpath("//a:To", { "a" => "http://www.w3.org/2005/08/addressing" }).each do |node|
106 | signer.digest!(node)
107 | end
108 |
109 | signer.sign!
110 |
111 | signer.to_xml
112 | end
113 | end
114 | ```
115 |
116 | ## Example
117 |
118 | Input:
119 |
120 | ```xml
121 |
122 |
123 |
124 | http://tempuri.org/IDocumentService/SearchDocuments
125 | urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
126 |
127 | http://www.w3.org/2005/08/addressing/anonymous
128 |
129 | http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
130 |
131 |
132 | 2012-05-02T18:17:14.467Z
133 | 2012-05-02T18:22:14.467Z
134 |
135 |
136 |
137 |
138 |
139 |
140 | 1
141 |
142 |
143 |
144 |
145 | ```
146 |
147 | Output:
148 |
149 | ```xml
150 |
151 |
152 |
153 | http://tempuri.org/IDocumentService/SearchDocuments
154 | urn:uuid:30db5d4f-ab84-46be-907c-be690a92979b
155 |
156 | http://www.w3.org/2005/08/addressing/anonymous
157 |
158 | http://tempuri.org/PublicServices/Test/1.0.12/PublicServices/DocumentService.svc
159 |
160 |
161 | 2012-05-02T18:17:14.467Z
162 | 2012-05-02T18:22:14.467Z
163 |
164 | MIICsDCCAhmgAwIBAgIJAOUHvh4oho0tMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTIwNTAzMTMxODIyWhcNMTMwNTAzMTMxODIyWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvK5hMPv/R5IFmwWyJOyEaFUrF/ZsmN+Gip8hvR6rLP3YPNx9iFYvPcZllFmuVwyaz7YT2N5BsqTwLdyi5v4HY4fUtuz0p8jIPoSd6dfDvcnSpf4QLTOgOaL3ciPEbgDHH2tnIksukoWzqCYva+qFZ74NFl19swXotW9fA4Jzs4QIDAQABo4GnMIGkMB0GA1UdDgQWBBRU1WEHDnP8Hr7ZulxrSzEwOcYpMzB1BgNVHSMEbjBsgBRU1WEHDnP8Hr7ZulxrSzEwOcYpM6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAOUHvh4oho0tMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEASY/9SAOK57q9mGnNJJeyDbmyGrAHSJTod646xTHYkMvhUqwHyk9PTr5bdfmswpmyVn+AQ43U2tU5vnpTBmKpHWD2+HSHgGa92mMLrfBOd8EBZ329NL3N2HDPIaHr4NPGyhNrSK3QVOnAq2D0jlyrGYJlLli1NxHiBz7FCEJaVI8=
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | Oz29YgZk14+nchoqv9zGzhJcDUo=
175 |
176 |
177 |
178 |
179 |
180 |
181 | leV/RNYhwuCuD7/DBzn3IgQzUxI=
182 |
183 |
184 | en7YYAIn90ofH08aF917jNngMuse+vK6bihF0v6UsXFnGGMOflWfRTZ6mFmC2HwLmb2lSrhZ3eth3cs2fCBlEr/K2ZDMQfJo6CPxmbzfX/fxR/isCTDz+HIJd13J0HK4n+CzkndwplkCmT8SQlduUruUFUUmlQiiZQ7nryR+XyM=
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | 1
197 |
198 |
199 |
200 |
201 | ```
202 |
203 | ## Different signature and digest algorithms support
204 |
205 | 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.
206 |
207 | ```ruby
208 | signer.digest_algorithm = :sha256 # Set algorithm for node digesting
209 | signer.signature_digest_algorithm = :sha256 # Set algorithm for message digesting for signing
210 | ```
211 |
212 | 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.
213 |
214 | 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.
215 |
216 | Please note, that these settings will be changed or reset on certificate assignment, please change them after setting certificate!
217 |
218 | __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)
219 |
220 | ## Miscellaneous
221 |
222 | 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`.
223 |
224 | ```ruby
225 | Signer.new(File.read("example.xml"), noblanks: false)
226 | ```
227 |
228 | Available options for the `sign!` method:
229 | * [:security_token] - Serializes certificate in DER format, encodes it with Base64 and inserts it within a `BinarySecurityToken` tag
230 |
231 | If you need to digest a `BinarySecurityToken` tag, you need to construct it yourself **before** signing.
232 |
233 | ```ruby
234 | signer.digest!(signer.binary_security_token_node) # Constructing tag and digesting it
235 | signer.sign! # No need to pass a :security_token option, as we already constructed and inserted this node
236 | ```
237 |
238 | * [:inclusive_namespaces] - Array of namespace prefixes which definitions should be added to signed info node during canonicalization
239 |
240 | If you need `Signature` tags to be in explicit namespace (say, ``) instead of to be in implicit default namespace you can specify next option:
241 |
242 | ```ruby
243 | signer.ds_namespace_prefix = 'ds'
244 | ```
245 |
246 | 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.
247 |
248 | * [:issuer_serial] - flag to include a `X509Data` node to include information from a `X509Certificate`
249 | * [:issuer_in_security_token] - flag to include the `X509Data` inside a `SecurityTokenReference` element
250 |
--------------------------------------------------------------------------------
/spec/signer_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe Signer do
4 | it "should digest and sign SOAP XML with security node and digested binary token" do
5 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_1.xml')
6 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
7 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
8 |
9 | signer = Signer.new(File.read(input_xml_file))
10 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
11 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
12 |
13 | 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|
14 | signer.digest!(node)
15 | end
16 |
17 | signer.document.xpath("//a:To", { "a" => "http://www.w3.org/2005/08/addressing" }).each do |node|
18 | signer.digest!(node)
19 | end
20 |
21 | signer.digest!(signer.binary_security_token_node)
22 |
23 | signer.sign!
24 |
25 | # File.open(File.join(File.dirname(__FILE__), 'fixtures', 'output_1.xml'), "w") do |f|
26 | # f.write signer.document.to_s
27 | # end
28 | output_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'output_1.xml')
29 |
30 | signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(save_with: 0)
31 | end
32 |
33 | it "should correctly canonicalize digested nodes (shouldn't account comments)" do
34 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_3_c14n_comments.xml')
35 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
36 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
37 |
38 | signer = Signer.new(File.read(input_xml_file))
39 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
40 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
41 |
42 | signer.digest!(signer.document.at_xpath('//soap:Body', { 'soap' => 'http://www.w3.org/2003/05/soap-envelope'}))
43 | signer.sign!
44 |
45 | output_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'output_3_c14n_comments.xml')
46 |
47 | signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(save_with: 0)
48 | end
49 |
50 | it "should digest and sign SOAP XML with SHA256" do
51 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_1.xml')
52 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
53 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
54 |
55 | signer = Signer.new(File.read(input_xml_file))
56 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
57 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
58 | signer.digest_algorithm = :sha256
59 | signer.signature_digest_algorithm = :sha256
60 | signer.signature_algorithm_id = 'http://www.w3.org/2001/04/xmlenc#sha256'
61 |
62 | signer.digest!(signer.binary_security_token_node)
63 |
64 | signer.sign!
65 |
66 | output_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'output_1_sha256.xml')
67 |
68 | signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(save_with: 0)
69 | end
70 |
71 | it "should digest and sign SOAP XML with inclusive namespaces" do
72 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_1.xml')
73 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
74 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
75 |
76 | signer = Signer.new(File.read(input_xml_file))
77 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
78 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
79 |
80 | signer.document.xpath("//soap:Body", { "soap" => "http://www.w3.org/2003/05/soap-envelope" }).each do |node|
81 | signer.digest!(node, inclusive_namespaces: ['s'])
82 | end
83 |
84 | signer.sign!(security_token: true, inclusive_namespaces: ['s'])
85 |
86 | output_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'output_1_inclusive_namespaces.xml')
87 |
88 | signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(save_with: 0)
89 | end
90 |
91 | [
92 | [{ enveloped: true, enveloped_legacy: true }, 'output_2_legacy.xml'],
93 | [{ enveloped: true, enveloped_legacy: false }, 'output_2.xml'],
94 | [{ enveloped: true }, 'output_2.xml']
95 | ].each do |options, output_xml|
96 | it "should sign simple XML with options=#{options}" do
97 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_2.xml')
98 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
99 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
100 |
101 | signer = Signer.new(File.read(input_xml_file))
102 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
103 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
104 | signer.security_node = signer.document.root
105 | signer.security_token_id = ""
106 | signer.digest!(signer.document.root, id: "", **options)
107 | signer.sign!(:issuer_serial => true)
108 |
109 | # File.open(File.join(File.dirname(__FILE__), 'fixtures', 'output_2.xml'), "w") do |f|
110 | # f.write signer.document.to_s
111 | # end
112 | output_xml_file = File.join(File.dirname(__FILE__), 'fixtures', output_xml)
113 |
114 | signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(save_with: 0)
115 | end
116 | end
117 |
118 |
119 | it "should digest and sign SOAP XML with security node and digested binary token" do
120 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_4_with_nested_signatures.xml')
121 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
122 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
123 |
124 | signer = Signer.new(File.read(input_xml_file))
125 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
126 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
127 | signer.security_node = signer.document.at_xpath('//soap:Header/wsse:Security', soap: 'http://www.w3.org/2003/05/soap-envelope', wsse: Signer::WSSE_NAMESPACE)
128 |
129 | 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|
130 | signer.digest!(node)
131 | end
132 |
133 | signer.document.xpath("//a:To", { "a" => "http://www.w3.org/2005/08/addressing" }).each do |node|
134 | signer.digest!(node)
135 | end
136 |
137 | signer.digest!(signer.binary_security_token_node)
138 |
139 | signer.sign!
140 |
141 | # File.open(File.join(File.dirname(__FILE__), 'fixtures', 'output_4_with_nested_signatures.xml'), "w") do |f|
142 | # f.write signer.document.to_s
143 | # end
144 | output_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'output_4_with_nested_signatures.xml')
145 |
146 | signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(save_with: 0)
147 | end
148 |
149 | [
150 | [{ enveloped: true, enveloped_legacy: true }, 'output_2_with_ds_prefix_legacy.xml'],
151 | [{ enveloped: true, enveloped_legacy: false }, 'output_2_with_ds_prefix.xml'],
152 | [{ enveloped: true }, 'output_2_with_ds_prefix.xml']
153 | ].each do |options, output_xml|
154 | it "should sign simple XML with custom DS namespace prefix with options=#{options}" do
155 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_2.xml')
156 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
157 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
158 |
159 | signer = Signer.new(File.read(input_xml_file))
160 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
161 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
162 | signer.security_node = signer.document.root
163 | signer.security_token_id = ""
164 | signer.ds_namespace_prefix = 'ds'
165 |
166 | signer.digest!(signer.document.root, id: "", **options)
167 | signer.sign!(issuer_serial: true)
168 |
169 | # File.open(File.join(File.dirname(__FILE__), 'fixtures', 'output_2_with_ds_prefix.xml'), "w") do |f|
170 | # f.write signer.document.to_s
171 | # end
172 | output_xml_file = File.join(File.dirname(__FILE__), 'fixtures', output_xml)
173 |
174 | signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(save_with: 0)
175 | end
176 | end
177 |
178 | it "should digest simple XML without transforms node" do
179 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_2.xml')
180 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
181 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
182 |
183 | signer = Signer.new(File.read(input_xml_file))
184 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
185 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
186 | signer.security_node = signer.document.root
187 | signer.security_token_id = ""
188 | signer.ds_namespace_prefix = 'ds'
189 |
190 | signer.digest!(signer.document.root, id: "", no_transform: true)
191 | signer.sign!(issuer_serial: true)
192 |
193 | expect(signer.document.at_xpath('//ds:Transforms', ds: Signer::DS_NAMESPACE)).to be_nil
194 | end
195 |
196 | [
197 | [{ enveloped: true, enveloped_legacy: true }, 'output_2_with_ds_prefix_and_wss_disabled_legacy.xml'],
198 | [{ enveloped: true, enveloped_legacy: false }, 'output_2_with_ds_prefix_and_wss_disabled.xml'],
199 | [{ enveloped: true }, 'output_2_with_ds_prefix_and_wss_disabled.xml']
200 | ].each do |options, output_xml|
201 | it "should partially sign element and simple XML with custom DS namespace prefix when wss is false with options=#{options}" do
202 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_2.xml')
203 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
204 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
205 |
206 | signer = Signer.new(File.read(input_xml_file), wss: false)
207 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
208 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
209 | signer.security_node = signer.document.root
210 | signer.security_token_id = ""
211 | signer.ds_namespace_prefix = 'ds'
212 |
213 | # partially sign element
214 | signer.digest!(signer.document.root.children.first, **options)
215 |
216 | signer.digest!(signer.document.root, id: "", **options)
217 | signer.sign!(issuer_serial: true)
218 |
219 | # File.open(File.join(File.dirname(__FILE__), 'fixtures', 'output_2_with_ds_prefix_and_wss_disabled.xml'), "w") do |f|
220 | # f.write signer.document.to_s
221 | # end
222 | output_xml_file = File.join(File.dirname(__FILE__), 'fixtures', output_xml)
223 |
224 | signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(:save_with => 0)
225 | end
226 | end
227 |
228 | it "should digest and sign SOAP XML with security node and digested binary token with noblanks disabled" do
229 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_4_with_nested_signatures.xml')
230 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
231 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
232 |
233 | signer = Signer.new(File.read(input_xml_file), noblanks: false)
234 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
235 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
236 | signer.security_node = signer.document.at_xpath('//soap:Header/wsse:Security', soap: 'http://www.w3.org/2003/05/soap-envelope', wsse: Signer::WSSE_NAMESPACE)
237 |
238 | 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|
239 | signer.digest!(node)
240 | end
241 |
242 | signer.document.xpath("//a:To", { "a" => "http://www.w3.org/2005/08/addressing" }).each do |node|
243 | signer.digest!(node)
244 | end
245 |
246 | signer.digest!(signer.binary_security_token_node)
247 |
248 | signer.sign!
249 |
250 | output_xml_file = File.join(File.dirname(__FILE__),
251 | 'fixtures',
252 | 'output_4_with_nested_signatures_with_noblanks_disabled.xml')
253 |
254 | signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file)).to_xml(save_with: 0)
255 | end
256 |
257 | it "should digest and sign SOAP XML with X509Data inside SecurityTokenReference node" do
258 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_5.xml')
259 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
260 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
261 |
262 | signer = Signer.new(File.read(input_xml_file))
263 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
264 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
265 |
266 | # digest Body element from XML
267 | signer.digest!(signer.document.at_xpath('//soapenv:Body'), id: 'Body', inclusive_namespaces: ['soapenv'])
268 |
269 | # sign data from this request
270 | signer.sign!(issuer_serial: true, issuer_in_security_token: true)
271 |
272 | output_xml_file = File.join(File.dirname(__FILE__),
273 | 'fixtures',
274 | 'output_5_with_security_token.xml')
275 |
276 | signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(save_with: 0)
277 | end
278 |
279 | it "should digest and sign SOAP XML with X509Data" do
280 | input_xml_file = File.join(File.dirname(__FILE__), 'fixtures', 'input_5.xml')
281 | cert_file = File.join(File.dirname(__FILE__), 'fixtures', 'cert.pem')
282 | private_key_file = File.join(File.dirname(__FILE__), 'fixtures', 'key.pem')
283 |
284 | signer = Signer.new(File.read(input_xml_file))
285 | signer.cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
286 | signer.private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file), "test")
287 |
288 | # digest Body element from XML
289 | signer.digest!(signer.document.at_xpath('//soapenv:Body'), id: 'Body', inclusive_namespaces: ['soapenv'])
290 |
291 | # sign data from this request
292 | signer.sign!(issuer_serial: true)
293 |
294 | output_xml_file = File.join(File.dirname(__FILE__),
295 | 'fixtures',
296 | 'output_5_with_x509_data.xml')
297 |
298 | signer.to_xml.should == Nokogiri::XML(File.read(output_xml_file), &:noblanks).to_xml(save_with: 0)
299 | end
300 | end
301 |
--------------------------------------------------------------------------------
/lib/signer.rb:
--------------------------------------------------------------------------------
1 | require "nokogiri"
2 | require "base64"
3 | require "digest/sha1"
4 | require "openssl"
5 |
6 | require "signer/digester"
7 | require "signer/version"
8 |
9 | class Signer
10 | attr_accessor :document, :private_key, :signature_algorithm_id, :ds_namespace_prefix, :wss
11 | attr_reader :cert
12 | attr_writer :security_node, :signature_node, :security_token_id
13 |
14 | WSU_NAMESPACE = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'.freeze
15 | WSSE_NAMESPACE = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'.freeze
16 | DS_NAMESPACE = 'http://www.w3.org/2000/09/xmldsig#'.freeze
17 |
18 | SIGNATURE_ALGORITHM = {
19 | # SHA 1
20 | sha1: {
21 | id: 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
22 | name: 'SHA1'
23 | },
24 | # SHA 256
25 | sha256: {
26 | id: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
27 | name: 'SHA256'
28 | },
29 | # SHA512
30 | sha512: {
31 | id: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512',
32 | name: 'SHA512'
33 | },
34 | # GOST R 34-11 94
35 | gostr3411: {
36 | id: 'http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411',
37 | name: 'GOST R 34.11-94'
38 | },
39 | # GOST R 34-11 2012 256 bit
40 | gostr34112012_256: {
41 | id: 'urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102012-gostr34112012-256',
42 | name: 'GOST R 34.11-2012 256',
43 | },
44 | }.freeze
45 |
46 | CANONICALIZE_ALGORITHM = {
47 | c14n_exec_1_0: {
48 | name: 'c14n execlusive 1.0',
49 | value: Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0,
50 | id: 'http://www.w3.org/2001/10/xml-exc-c14n#'
51 | },
52 | c14n_1_0: {
53 | name: 'c14n 1.0',
54 | value: Nokogiri::XML::XML_C14N_1_0,
55 | id: 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
56 | },
57 | c14n_1_1: {
58 | name: 'c14n 1.1',
59 | value: Nokogiri::XML::XML_C14N_1_1,
60 | id: 'https://www.w3.org/TR/2008/REC-xml-c14n11-20080502/'
61 | }
62 | }.freeze
63 |
64 | def initialize(document, noblanks: true, wss: true, canonicalize_algorithm: :c14n_exec_1_0)
65 | self.document = Nokogiri::XML(document.to_s) do |config|
66 | config.noblanks if noblanks
67 | end
68 | self.document.namespace_inheritance = true if self.document.respond_to?(:namespace_inheritance)
69 | self.digest_algorithm = :sha1
70 | self.wss = wss
71 | self.canonicalize_algorithm = canonicalize_algorithm
72 | self.signature_digest_algorithm = :sha1
73 | end
74 |
75 | def to_xml
76 | document.to_xml(save_with: 0)
77 | end
78 |
79 | def canonicalize_name
80 | @canonicalize_algorithm[:name]
81 | end
82 |
83 | def canonicalize_id
84 | @canonicalize_algorithm[:id]
85 | end
86 |
87 | def canonicalize_algorithm
88 | @canonicalize_algorithm[:value]
89 | end
90 |
91 | def canonicalize_algorithm=(algorithm)
92 | @canonicalize_algorithm = CANONICALIZE_ALGORITHM[algorithm]
93 | end
94 |
95 | # Return symbol name for supported digest algorithms and string name for custom ones.
96 | def digest_algorithm
97 | @digester.symbol || @digester.digest_name
98 | end
99 |
100 | # Allows to change algorithm for node digesting (default is SHA1).
101 | #
102 | # You may pass either a one of +:sha1+, +:sha256+ or +:gostr3411+ symbols
103 | # or +Hash+ with keys +:id+ with a string, which will denote algorithm in XML Reference tag
104 | # and +:digester+ with instance of class with interface compatible with +OpenSSL::Digest+ class.
105 | def digest_algorithm=(algorithm)
106 | @digester = Signer::Digester.new(algorithm)
107 | end
108 |
109 | # Return symbol name for supported digest algorithms and string name for custom ones.
110 | def signature_digest_algorithm
111 | @sign_digester.symbol || @sign_digester.digest_name
112 | end
113 |
114 | # Allows to change digesting algorithm for signature creation. Same as +digest_algorithm=+
115 | def signature_digest_algorithm=(algorithm)
116 | @sign_digester = Signer::Digester.new(algorithm)
117 | self.signature_algorithm_id = SIGNATURE_ALGORITHM[algorithm][:id]
118 | end
119 |
120 | # Receives certificate for signing and tries to guess a digest algorithm for signature creation.
121 | #
122 | # Will change +signature_digest_algorithm+ and +signature_algorithm_id+ for known certificate types and reset to defaults for others.
123 | def cert=(certificate)
124 | @cert = certificate
125 | # Try to guess a digest algorithm for signature creation
126 | case @cert.signature_algorithm
127 | when 'GOST R 34.11-94 with GOST R 34.10-2001'
128 | self.signature_digest_algorithm = :gostr3411
129 | end
130 | end
131 |
132 | def security_token_id
133 | @security_token_id ||= wss? ? "uuid-639b8970-7644-4f9e-9bc4-9c2e367808fc-1" : ""
134 | end
135 |
136 | def security_node
137 | @security_node ||= wss? ? document.xpath('//wsse:Security', wsse: WSSE_NAMESPACE).first : ''
138 | end
139 |
140 | def canonicalize(node = document, inclusive_namespaces=nil, algorithm: canonicalize_algorithm)
141 | node.canonicalize(algorithm, inclusive_namespaces, nil)
142 | end
143 |
144 | #
145 | def signature_node
146 | @signature_node ||= begin
147 | @signature_node = security_node.at_xpath('ds:Signature', ds: DS_NAMESPACE)
148 | unless @signature_node
149 | @signature_node = Nokogiri::XML::Node.new('Signature', document)
150 | set_namespace_for_node(@signature_node, DS_NAMESPACE, ds_namespace_prefix)
151 | security_node.add_child(@signature_node)
152 | end
153 | @signature_node
154 | end
155 | end
156 |
157 | #
158 | #
159 | #
160 | # ...
161 | #
162 | def signed_info_node
163 | node = signature_node.at_xpath('ds:SignedInfo', ds: DS_NAMESPACE)
164 | unless node
165 | node = Nokogiri::XML::Node.new('SignedInfo', document)
166 | signature_node.add_child(node)
167 | set_namespace_for_node(node, DS_NAMESPACE, ds_namespace_prefix)
168 | canonicalization_method_node = Nokogiri::XML::Node.new('CanonicalizationMethod', document)
169 | canonicalization_method_node['Algorithm'] = canonicalize_id
170 | node.add_child(canonicalization_method_node)
171 | set_namespace_for_node(canonicalization_method_node, DS_NAMESPACE, ds_namespace_prefix)
172 | signature_method_node = Nokogiri::XML::Node.new('SignatureMethod', document)
173 | signature_method_node['Algorithm'] = self.signature_algorithm_id
174 | node.add_child(signature_method_node)
175 | set_namespace_for_node(signature_method_node, DS_NAMESPACE, ds_namespace_prefix)
176 | end
177 | node
178 | end
179 |
180 | #
181 | # ...
182 | #
183 | #
184 | # ...
185 | #
186 | #
187 | #
188 | #
189 | #
190 | #
191 | def binary_security_token_node
192 | return unless wss?
193 | node = document.at_xpath('wsse:BinarySecurityToken', wsse: WSSE_NAMESPACE)
194 | unless node
195 | node = Nokogiri::XML::Node.new('BinarySecurityToken', document)
196 | node['ValueType'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3'
197 | node['EncodingType'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary'
198 | node.content = Base64.encode64(cert.to_der).gsub("\n", '')
199 | signature_node.add_previous_sibling(node)
200 | wsse_ns = namespace_prefix(node, WSSE_NAMESPACE, 'wsse')
201 | wsu_ns = namespace_prefix(node, WSU_NAMESPACE, 'wsu')
202 | node["#{wsu_ns}:Id"] = security_token_id
203 | key_info_node = Nokogiri::XML::Node.new('KeyInfo', document)
204 | security_token_reference_node = Nokogiri::XML::Node.new("#{wsse_ns}:SecurityTokenReference", document)
205 | key_info_node.add_child(security_token_reference_node)
206 | set_namespace_for_node(key_info_node, DS_NAMESPACE, ds_namespace_prefix)
207 | reference_node = Nokogiri::XML::Node.new("#{wsse_ns}:Reference", document)
208 | reference_node['ValueType'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3'
209 | reference_node['URI'] = "##{security_token_id}"
210 | security_token_reference_node.add_child(reference_node)
211 | signed_info_node.add_next_sibling(key_info_node)
212 | end
213 | node
214 | end
215 |
216 | #
217 | # (optional)
218 | #
219 | #
220 | # System.Security.Cryptography.X509Certificates.X500DistinguishedName
221 | # 13070789
222 | #
223 | # MIID+jCCAuKgAwIBAgIEAMdxxTANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQGEwJTRTEeMBwGA1UEChMVTm9yZGVhIEJhbmsgQUIgKHB1YmwpMScwJQYDVQQDEx5Ob3JkZWEgcm9sZS1jZXJ0aWZpY2F0ZXMgQ0EgMDExFDASBgNVBAUTCzUxNjQwNi0wMTIwMB4XDTA5MDYxMTEyNTAxOVoXDTExMDYxMTEyNTAxOVowcjELMAkGA1UEBhMCU0UxIDAeBgNVBAMMF05vcmRlYSBEZW1vIENlcnRpZmljYXRlMRQwEgYDVQQEDAtDZXJ0aWZpY2F0ZTEUMBIGA1UEKgwLTm9yZGVhIERlbW8xFTATBgNVBAUTDDAwOTU1NzI0Mzc3MjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwcgz5AzbxTbsCE51No7fPnSqmQBIMW9OiPkiHotwYQTl+H9qwDvQRyBqHN26tnw7hNvEShd1ZRGUg4drMEXDV5CmKqsAevs9lauWDaHnGKPNHZJ1hNNYXHwymksEz5zMnG8eqRdhb4vOV2FzreJeYpsgx31Bv0aTofHcHVz4uGcCAwEAAaOCASAwggEcMAkGA1UdEwQCMAAwEQYDVR0OBAoECEj6Y9/vU03WMBMGA1UdIAQMMAowCAYGKoVwRwEDMBMGA1UdIwQMMAqACEIFjfLBeTpRMDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovL29jc3Aubm9yZGVhLnNlL1JDQTAxMA4GA1UdDwEB/wQEAwIGQDCBiAYDVR0fBIGAMH4wfKB6oHiGdmxkYXA6Ly9sZGFwLm5iLnNlL2NuPU5vcmRlYSUyMHJvbGUtY2VydGlmaWNhdGVzJTIwQ0ElMjAwMSxvPU5vcmRlYSUyMEJhbmslMjBBQiUyMChwdWJsKSxjPVNFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwDQYJKoZIhvcNAQEFBQADggEBAEXUv87VpHk51y3TqkMb1MYDqeKvQRE1cNcvhEJhIzdDpXMA9fG0KqvSTT1e0ZI2r78mXDvtTZnpic44jX2XMSmKO6n+1taAXq940tJUhF4arYMUxwDKOso0Doanogug496gipqMlpLgvIhGt06sWjNrvHzp2eGydUFdCsLr2ULqbDcut7g6eMcmrsnrOntjEU/J3hO8gyCeldJ+fI81qarrK/I0MZLR5LWCyVG/SKduoxHLX7JohsbIGyK1qAh9fi8l6X1Rcu80v5inpu71E/DnjbkAZBo7vsj78zzdk7KNliBIqBcIszdJ3dEHRWSI7FspRxyiR0NDm4lpyLwFtfw=
224 | #
225 | # (optional)
226 | #
227 | def x509_data_node(issuer_in_security_token = false)
228 | issuer_name_node = Nokogiri::XML::Node.new('X509IssuerName', document)
229 | issuer_name_node.content = cert.issuer.to_s(OpenSSL::X509::Name::RFC2253)
230 |
231 | issuer_number_node = Nokogiri::XML::Node.new('X509SerialNumber', document)
232 | issuer_number_node.content = cert.serial
233 |
234 | issuer_serial_node = Nokogiri::XML::Node.new('X509IssuerSerial', document)
235 | issuer_serial_node.add_child(issuer_name_node)
236 | issuer_serial_node.add_child(issuer_number_node)
237 |
238 | cetificate_node = Nokogiri::XML::Node.new('X509Certificate', document)
239 | cetificate_node.content = Base64.encode64(cert.to_der).delete("\n")
240 |
241 | data_node = Nokogiri::XML::Node.new('X509Data', document)
242 | data_node.add_child(issuer_serial_node)
243 | data_node.add_child(cetificate_node)
244 |
245 | if issuer_in_security_token
246 | security_token_reference_node = Nokogiri::XML::Node.new("wsse:SecurityTokenReference", document)
247 | security_token_reference_node.add_child(data_node)
248 | end
249 |
250 | key_info_node = Nokogiri::XML::Node.new('KeyInfo', document)
251 | key_info_node.add_child(issuer_in_security_token ? security_token_reference_node : data_node)
252 |
253 | signed_info_node.add_next_sibling(key_info_node)
254 |
255 | set_namespace_for_node(key_info_node, DS_NAMESPACE, ds_namespace_prefix)
256 | set_namespace_for_node(security_token_reference_node, WSSE_NAMESPACE, ds_namespace_prefix) if issuer_in_security_token
257 | set_namespace_for_node(data_node, DS_NAMESPACE, ds_namespace_prefix)
258 | set_namespace_for_node(issuer_serial_node, DS_NAMESPACE, ds_namespace_prefix)
259 | set_namespace_for_node(cetificate_node, DS_NAMESPACE, ds_namespace_prefix)
260 | set_namespace_for_node(issuer_name_node, DS_NAMESPACE, ds_namespace_prefix)
261 | set_namespace_for_node(issuer_number_node, DS_NAMESPACE, ds_namespace_prefix)
262 |
263 | data_node
264 | end
265 |
266 | ##
267 | # Digests some +target_node+, which integrity you wish to track. Any changes in digested node will invalidate signed message.
268 | # All digest should be calculated **before** signing.
269 | #
270 | # Available options:
271 | # * [+:id+] Id for the node, if you don't want to use automatically calculated one
272 | # * [+:inclusive_namespaces+] Array of namespace prefixes which definitions should be added to node during canonicalization
273 | # * [+:enveloped+]
274 | # * [+:enveloped_legacy+] add solely `enveloped-signature` in `Transforms` with :enveloped:.
275 | # * [+:ref_type+] add `Type` attribute to Reference node, if ref_type is not nil
276 | #
277 | # Example of XML that will be inserted in message for call like digest!(node, inclusive_namespaces: ['soap']):
278 | #
279 | #
280 | #
281 | #
282 | #
283 | #
284 | #
285 | #
286 | # aeqXriJuUCk4tPNPAGDXGqHj6ao=
287 | #
288 |
289 | def digest!(target_node, options = {})
290 | if wss?
291 | wsu_ns = namespace_prefix(target_node, WSU_NAMESPACE)
292 | current_id = target_node["#{wsu_ns}:Id"] if wsu_ns
293 | id = options[:id] || current_id || "_#{Digest::SHA1.hexdigest(target_node.to_s)}"
294 | unless id.to_s.empty?
295 | wsu_ns ||= namespace_prefix(target_node, WSU_NAMESPACE, 'wsu')
296 | target_node["#{wsu_ns}:Id"] = id.to_s
297 | end
298 | elsif target_node['Id'].nil?
299 | id = options[:id] || "_#{Digest::SHA1.hexdigest(target_node.to_s)}"
300 | target_node['Id'] = id.to_s unless id.empty?
301 | else
302 | id = options[:id] || target_node['Id']
303 | end
304 |
305 | target_canon = canonicalize(target_node, options[:inclusive_namespaces])
306 | target_digest = Base64.encode64(@digester.digest(target_canon)).strip
307 |
308 | reference_node = Nokogiri::XML::Node.new('Reference', document)
309 | reference_node['URI'] = id.to_s.size > 0 ? "##{id}" : ""
310 | reference_node['Type'] = options[:ref_type] if options[:ref_type]
311 |
312 | signed_info_node.add_child(reference_node)
313 | set_namespace_for_node(reference_node, DS_NAMESPACE, ds_namespace_prefix)
314 |
315 | transforms_node = Nokogiri::XML::Node.new('Transforms', document)
316 | reference_node.add_child(transforms_node) unless options[:no_transform]
317 | set_namespace_for_node(transforms_node, DS_NAMESPACE, ds_namespace_prefix)
318 |
319 | # create reference + transforms node
320 | transform!(transforms_node, options)
321 |
322 | digest_method_node = Nokogiri::XML::Node.new('DigestMethod', document)
323 | digest_method_node['Algorithm'] = @digester.digest_id
324 |
325 | reference_node.add_child(digest_method_node)
326 | set_namespace_for_node(digest_method_node, DS_NAMESPACE, ds_namespace_prefix)
327 |
328 | digest_value_node = Nokogiri::XML::Node.new('DigestValue', document)
329 | digest_value_node.content = target_digest
330 | reference_node.add_child(digest_value_node)
331 | set_namespace_for_node(digest_value_node, DS_NAMESPACE, ds_namespace_prefix)
332 | self
333 | end
334 |
335 | ##
336 | # Sign document with provided certificate, private key and other options
337 | #
338 | # This should be very last action before calling +to_xml+, all the required nodes should be digested with +digest!+ **before** signing.
339 | #
340 | # Available options:
341 | # * [+:security_token+] Serializes certificate in DER format, encodes it with Base64 and inserts it within ++ tag
342 | # * [+:issuer_serial+]
343 | # * [+:issuer_in_security_token+]
344 | # * [+:inclusive_namespaces+] Array of namespace prefixes which definitions should be added to signed info node during canonicalization
345 |
346 | def sign!(options = {})
347 | if options[:security_token]
348 | binary_security_token_node
349 | end
350 |
351 | if options[:issuer_serial]
352 | x509_data_node(options[:issuer_in_security_token])
353 | end
354 |
355 | if options[:inclusive_namespaces]
356 | c14n_method_node = signed_info_node.at_xpath('ds:CanonicalizationMethod', ds: DS_NAMESPACE)
357 | inclusive_namespaces_node = Nokogiri::XML::Node.new('ec:InclusiveNamespaces', document)
358 | inclusive_namespaces_node.add_namespace_definition('ec', c14n_method_node['Algorithm'])
359 | inclusive_namespaces_node['PrefixList'] = options[:inclusive_namespaces].join(' ')
360 | c14n_method_node.add_child(inclusive_namespaces_node)
361 | end
362 |
363 | signed_info_canon = canonicalize(signed_info_node, options[:inclusive_namespaces])
364 |
365 | signature = private_key.sign(@sign_digester.digester, signed_info_canon)
366 | signature_value_digest = Base64.encode64(signature).delete("\n")
367 |
368 | signature_value_node = Nokogiri::XML::Node.new('SignatureValue', document)
369 | signature_value_node.content = signature_value_digest
370 | signed_info_node.add_next_sibling(signature_value_node)
371 | set_namespace_for_node(signature_value_node, DS_NAMESPACE, ds_namespace_prefix)
372 | self
373 | end
374 |
375 | protected
376 |
377 | # Create transform nodes
378 | def transform_node(algorithm, options)
379 | transform_node = Nokogiri::XML::Node.new('Transform', document)
380 | set_namespace_for_node(transform_node, DS_NAMESPACE, ds_namespace_prefix)
381 | transform_node['Algorithm'] = algorithm
382 |
383 | if options[:inclusive_namespaces]
384 | inclusive_namespaces_node = Nokogiri::XML::Node.new('ec:InclusiveNamespaces', document)
385 | inclusive_namespaces_node.add_namespace_definition('ec', transform_node['Algorithm'])
386 | inclusive_namespaces_node['PrefixList'] = options[:inclusive_namespaces].join(' ')
387 | transform_node.add_child(inclusive_namespaces_node)
388 | end
389 |
390 | transform_node
391 | end
392 |
393 | def transform!(transforms_node, options)
394 | # With PR-26, a new flag :enveloped_legacy is introduced for backward compatibility, the logics are:
395 | # - :enveloped is false, include xml-exc-c14n
396 | # - :enveloped is true, include xml-exc-c14n and enveloped-signature
397 | # - :enveloped is true and :enveloped_legacy is true, include enveloped-signature.
398 |
399 | if options[:enveloped] && options[:enveloped_legacy]
400 | transforms_node.add_child(transform_node('http://www.w3.org/2000/09/xmldsig#enveloped-signature', options))
401 | return
402 | end
403 |
404 | transforms_node.add_child(transform_node('http://www.w3.org/2001/10/xml-exc-c14n#', options))
405 | transforms_node.add_child(transform_node('http://www.w3.org/2000/09/xmldsig#enveloped-signature', options)) if options[:enveloped]
406 | end
407 |
408 | # Check are we using ws security?
409 | def wss?
410 | wss
411 | end
412 |
413 | ##
414 | # Searches in namespaces, defined on +target_node+ or its ancestors,
415 | # for the +namespace+ with given URI and returns its prefix.
416 | #
417 | # If there is no such namespace and +desired_prefix+ is specified,
418 | # adds such a namespace to +target_node+ with +desired_prefix+
419 |
420 | def namespace_prefix(target_node, namespace, desired_prefix = nil)
421 | ns = target_node.namespaces.key(namespace)
422 | if ns
423 | ns.match(/(?:xmlns:)?(.*)/) && $1
424 | elsif desired_prefix
425 | target_node.add_namespace_definition(desired_prefix, namespace)
426 | desired_prefix
427 | end
428 | end
429 |
430 | ##
431 | # Searches for namespace with given +href+ within +node+ ancestors and assigns it to this node.
432 | # If there is no such namespace, it will create it with +desired_prefix+ if present or as default namespace otherwise.
433 | # In most cases you should insert +node+ in the document tree before calling this method to avoid duplicating namespace definitions
434 | def set_namespace_for_node(node, href, desired_prefix = nil)
435 | return node.namespace if node.namespace && node.namespace.href == href # This node already in target namespace, done
436 | namespace = node.namespace_scopes.find { |ns| ns.href == href }
437 | node.namespace = namespace || node.add_namespace_definition(desired_prefix, href)
438 | end
439 | end
440 |
--------------------------------------------------------------------------------