├── .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 | [![Gem Version](https://badge.fury.io/rb/signer.svg)](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 | --------------------------------------------------------------------------------