diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..ac0a9f6c4 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [SAML-Toolkits] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..abf4b1511 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,92 @@ +name: ruby-saml CI + +on: [push, pull_request] + +jobs: + test: + name: Unit test + strategy: + fail-fast: false + matrix: + os: + - ubuntu-22.04 + - macos-latest + - windows-latest + ruby-version: + - 2.1 + - 2.2 + - 2.3 + - 2.4 + - 2.5 + - 2.6 + - 2.7 + - 3.0 + - 3.1 + - 3.2 + - 3.3 + - 3.4 + - jruby-9.1 + - jruby-9.2 + - jruby-9.3 + - jruby-9.4 + - truffleruby + exclude: + - os: macos-latest + ruby-version: 2.1 + - os: macos-latest + ruby-version: 2.2 + - os: macos-latest + ruby-version: 2.3 + - os: macos-latest + ruby-version: 2.4 + - os: macos-latest + ruby-version: 2.5 + - os: macos-latest + ruby-version: jruby-9.1 + - os: macos-latest + ruby-version: jruby-9.2 + - os: windows-latest + ruby-version: 2.1 + - os: windows-latest + ruby-version: jruby-9.1 + - os: windows-latest + ruby-version: jruby-9.2 + - os: windows-latest + ruby-version: jruby-9.3 + - os: windows-latest + ruby-version: jruby-9.4 + - os: windows-latest + ruby-version: truffleruby + - os: ubuntu-22.04 + ruby-version: 2.2 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby ${{ matrix.ruby-version }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + + - name: Install dependencies + run: bundle install + + - name: Run tests + run: bundle exec rake + + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.github_token }} + parallel: true + flag-name: run-${{ matrix.ruby-version }} + + finish: + needs: test + runs-on: ubuntu-latest + steps: + - name: Coveralls Finished + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.github_token }} + flag-name: run-${{ matrix.ruby-version }} + parallel-finished: true diff --git a/.gitignore b/.gitignore index f799a67e3..f7c0e3efc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ coverage rdoc pkg Gemfile.lock +gemfiles/*.lock .idea/* lib/Lib.iml test/Test.iml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5c7a02fdb..000000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: ruby -rvm: - - 1.8.7 - - 1.9.3 - - 2.0.0 - - 2.1.5 - - 2.2.0 - - ree -gemfile: - - Gemfile - - gemfiles/nokogiri-1.5.gemfile -matrix: - exclude: - - rvm: 1.8.7 - gemfile: Gemfile - - rvm: ree - gemfile: Gemfile diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..a0c8d9e54 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,314 @@ +# Ruby SAML Changelog + +### 1.18.1 (Jul 29, 2025) +* Fix vulnerability CVE-2025-54572 Prevent DOS due large SAML Message +* Adapt tests to be able to execute signature validation sooner +* CI Improvements. Support Ruby 3.4 + +### 1.18.0 (Mar 12, 2025) +* [#750](https://github.com/SAML-Toolkits/ruby-saml/pull/750) Fix vulnerabilities: CVE-2025-25291, CVE-2025-25292: SAML authentication bypass via Signature Wrapping attack allowed due parser differential. Fix vulnerability: CVE-2025-25293: Potential DOS abusing of compressed messages. +* [#718](https://github.com/SAML-Toolkits/ruby-saml/pull/718/) Add support to retrieve from SAMLResponse the AuthnInstant and AuthnContextClassRef values +* [#720](https://github.com/SAML-Toolkits/ruby-saml/pull/720) Fix ambiguous regex warnings +* [#715](https://github.com/SAML-Toolkits/ruby-saml/pull/715) Fix typo in SPNameQualifier error text + +### 1.17.0 (Sep 10, 2024) +* Fix for critical vulnerability CVE-2024-45409: SAML authentication bypass via Incorrect XPath selector +* [#687](https://github.com/SAML-Toolkits/ruby-saml/pull/687) Add CI coverage for Ruby 3.3 and Windows. +* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) Add `Settings#sp_cert_multi` paramter to facilitate SP certificate and key rotation. +* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) Support multiple simultaneous SP decryption keys via `Settings#sp_cert_multi` parameter. +* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) Deprecate `Settings#certificate_new` parameter. +* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) `:check_sp_cert_expiration` will use the first non-expired certificate/key when signing/decrypting. It will raise an error only if there are no valid certificates/keys. +* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) `:check_sp_cert_expiration` now validates the certificate `not_before` condition; previously it was only validating `not_after`. +* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) `:check_sp_cert_expiration` now causes the generated SP metadata to exclude any inactive/expired certificates. + +### 1.16.0 (Oct 09, 2023) +* [#671](https://github.com/SAML-Toolkits/ruby-saml/pull/671) Add support on LogoutRequest with Encrypted NameID + +### 1.15.0 (Jan 04, 2023) +* [#650](https://github.com/SAML-Toolkits/ruby-saml/pull/650) Replace strip! by strip on compute_digest method +* [#638](https://github.com/SAML-Toolkits/ruby-saml/pull/638) Fix dateTime format for the validUntil attribute of the generated metadata +* [#576](https://github.com/SAML-Toolkits/ruby-saml/pull/576) Support `Settings#idp_cert_multi` with string keys +* [#567](https://github.com/SAML-Toolkits/ruby-saml/pull/567) Improve Code quality +* Add info about new repo, new maintainer, new security contact +* Fix tests, Adjust dependencies, Add ruby 3.2 and new jruby versions tests to the CI. Add coveralls support + +### 1.14.0 (Feb 01, 2022) +* [#627](https://github.com/onelogin/ruby-saml/pull/627) Support escape downcasing for validating SLO Signatures of ADFS/Azure +* [#633](https://github.com/onelogin/ruby-saml/pull/633) Support ability to change ID prefix +* Make the uuid editable on the SAML Messages generated by the toolkit +* [#622](https://github.com/onelogin/ruby-saml/pull/622) Add security setting to more strictly enforce audience validation + +### 1.13.0 (Sept 06, 2021) +* [#611](https://github.com/onelogin/ruby-saml/pull/601) Replace MAX_BYTE_SIZE constant with setting: message_max_bytesize +* [#605](https://github.com/onelogin/ruby-saml/pull/605) :allowed_clock_drift is now bidrectional +* [#614](https://github.com/onelogin/ruby-saml/pull/614) Support :name_id_format option for IdpMetadataParser +* [#611](https://github.com/onelogin/ruby-saml/pull/611) IdpMetadataParser should always set idp_cert_multi, even when there is only one cert +* [#610](https://github.com/onelogin/ruby-saml/pull/610) New IDP sso/slo binding params which deprecate :embed_sign +* [#602](https://github.com/onelogin/ruby-saml/pull/602) Refactor the OneLogin::RubySaml::Metadata class +* [#586](https://github.com/onelogin/ruby-saml/pull/586) Support milliseconds in cacheDuration parsing +* [#585](https://github.com/onelogin/ruby-saml/pull/585) Do not append " | " to StatusCode unnecessarily +* [#607](https://github.com/onelogin/ruby-saml/pull/607) Clean up +* Add warning about the use of IdpMetadataParser class and SSRF +* CI: Migrate from Travis to Github Actions + +### 1.12.4 (Mar 12, 2025) +* [#750](https://github.com/SAML-Toolkits/ruby-saml/pull/750) Fix vulnerabilities: CVE-2025-25291, CVE-2025-25292: SAML authentication bypass via Signature Wrapping attack allowed due parser differential. Fix vulnerability: CVE-2025-25293: Potential DOS abusing of compressed messages. + +### 1.12.3 (Sep 10, 2024) +* Fix for critical vulnerability CVE-2024-45409: SAML authentication bypass via Incorrect XPath selector + +### 1.12.2 (Apr 08, 2021) +* [#575](https://github.com/onelogin/ruby-saml/pull/575) Fix SloLogoutresponse bug on LogoutRequest + +### 1.12.1 (Apr 05, 2021) +* Fix XPath typo incompatible with Rexml 3.2.5 +* Refactor GCM support + +### 1.12.0 (Feb 18, 2021) +* Support AES-128-GCM, AES-192-GCM, and AES-256-GCM encryptions +* Parse & return SLO ResponseLocation in IDPMetadataParser & Settings +* Adding idp_sso_service_url and idp_slo_service_url settings +* [#536](https://github.com/onelogin/ruby-saml/pull/536) Adding feth method to be able retrieve attributes based on regex +* Reduce size of built gem by excluding the test folder +* Improve protection on Zlib deflate decompression bomb attack. +* Add ValidUntil and cacheDuration support on Metadata generator +* Add support for cacheDuration at the IdpMetadataParser +* Support customizable statusCode on generated LogoutResponse +* [#545](https://github.com/onelogin/ruby-saml/pull/545) More specific error messages for signature validation +* Support Process Transform +* Raise SettingError if invoking an action with no endpoint defined on the settings +* Made IdpMetadataParser more extensible for subclasses +* [#548](https://github.com/onelogin/ruby-saml/pull/548) Add :skip_audience option +* [#555](https://github.com/onelogin/ruby-saml/pull/555) Define 'soft' variable to prevent exception when doc cert is invalid +* Improve documentation + +### 1.11.0 (Jul 24, 2019) + +* Deprecate settings.issuer in favor of settings.sp_entity_id +* Add support for certification expiration + +### 1.10.2 (Apr 29, 2019) + +* Add valid until, accessor +* Fix Rubygem metadata that requested nokogiri <= 1.5.11 + +### 1.10.1 (Apr 08, 2019) + +* Fix ruby 1.8.7 incompatibilities + +### 1.10.0 (Mar 21, 2019) +* Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user to be authenticated +* Improves IdpMetadataParser to allow parse multiple IDPSSODescriptors +* Improves format_cert method to accept certs with /\x0d/ +* Forces nokogiri >= 1.8.2 when possible + +### 1.9.0 (Sept 03, 2018) +* [#458](https://github.com/onelogin/ruby-saml/pull/458) Remove ruby 2.4+ warnings +* Improve JRuby support +* [#465](https://github.com/onelogin/ruby-saml/pull/465) Extend Settings initialization with the new keep_security_attributes parameter +* Fix wrong message when SessionNotOnOrAfter expired +* [#471](https://github.com/onelogin/ruby-saml/pull/471) Allow for `allowed_clock_drift` to be set as a string + +### 1.8.0 (April 23, 2018) +* [#437](https://github.com/onelogin/ruby-saml/issues/437) Creating AuthRequests/LogoutRequests/LogoutResponses with nil RelayState should not send empty RelayState URL param +* [#454](https://github.com/onelogin/ruby-saml/pull/454) Added Response available options +* [#453](https://github.com/onelogin/ruby-saml/pull/453) Raise a more descriptive exception if idp_sso_target_url is missing +* [#452](https://github.com/onelogin/ruby-saml/pull/452) Fix behavior of skip_conditions flag on Response +* [#449](https://github.com/onelogin/ruby-saml/pull/449) Add ability to skip authnstatement validation +* Clear cached values to be able to use IdpMetadataParser more than once +* Updated invalid audience error message + +### 1.7.2 (Feb 28, 2018) +* [#446](https://github.com/onelogin/ruby-saml/pull/446) Normalize text returned by OneLogin::RubySaml::Utils.element_text + +### 1.7.1 (Feb 28, 2018) +* [#444](https://github.com/onelogin/ruby-saml/pull/444) Fix audience validation for empty audience restriction + +### 1.7.0 (Feb 27, 2018) +* Fix vulnerability CVE-2017-11428. Process text of nodes properly, ignoring comments + +### 1.6.1 (January 15, 2018) +* [#428](https://github.com/onelogin/ruby-saml/issues/428) Fix a bug on IdPMetadataParser when parsing certificates +* [#426](https://github.com/onelogin/ruby-saml/pull/426) Ensure `Rails` responds to `logger` + +### 1.6.0 (November 27, 2017) +* [#418](https://github.com/onelogin/ruby-saml/pull/418) Improve SAML message signature validation using original encoded parameters instead decoded in order to avoid conflicts (URL-encoding is not canonical, reported issues with ADFS) +* [#420](https://github.com/onelogin/ruby-saml/pull/420) Expose NameID Format on SloLogoutrequest +* [#423](https://github.com/onelogin/ruby-saml/pull/423) Allow format_cert to work with chained certificates +* [#422](https://github.com/onelogin/ruby-saml/pull/422) Use to_s for requested attribute value + + +### 1.5.0 (August 31, 2017) +* [#400](https://github.com/onelogin/ruby-saml/pull/400) When validating Signature use stored IdP certficate if Signature contains no info about Certificate +* [#402](https://github.com/onelogin/ruby-saml/pull/402) Fix validate_response_state method that rejected SAMLResponses when using idp_cert_multi and idp_cert and idp_cert_fingerprint were not provided. +* [#411](https://github.com/onelogin/ruby-saml/pull/411) Allow space in Base64 string +* [#407](https://github.com/onelogin/ruby-saml/issues/407) Improve IdpMetadataParser raising an ArgumentError when parser method receive a metadata string with no IDPSSODescriptor element. +* [#374](https://github.com/onelogin/ruby-saml/issues/374) Support more than one level of StatusCode +* [#405](https://github.com/onelogin/ruby-saml/pull/405) Support ADFS encrypted key (Accept KeyInfo nodes with no ds namespace) + + +### 1.4.3 (May 18, 2017) +* Added SubjectConfirmation Recipient validation +* [#393](https://github.com/onelogin/ruby-saml/pull/393) Implement IdpMetadataParser#parse_to_hash +* Adapt IdP XML metadata parser to take care of multiple IdP certificates and be able to inject the data obtained on the settings. +* Improve binding detection on idp metadata parser +* [#373](https://github.com/onelogin/ruby-saml/pull/373) Allow metadata to be retrieved from source containing data for multiple entities +* Be able to register future SP x509cert on the settings and publish it on SP metadata +* Be able to register more than 1 Identity Provider x509cert, linked with an specific use (signing or encryption. +* Improve regex to detect base64 encoded messages +* Fix binding configuration example in README.md +* Add Fix SLO request. Correct NameQualifier/SPNameQualifier values. +* Validate serial number as string to work around libxml2 limitation +* Propagate isRequired on md:RequestedAttribute when generating SP metadata + +### 1.4.2 (January 11, 2017) +* Improve tests format +* Fix nokogiri requirements based on ruby version +* Only publish `KeyDescriptor[use="encryption"]` at SP metadata if `security[:want_assertions_encrypted]` is true +* Be able to skip destination validation +* Improved inResponse validation on SAMLResponses and LogoutResponses +* [#354](https://github.com/onelogin/ruby-saml/pull/354) Allow scheme and domain to match ignoring case +* [#363](https://github.com/onelogin/ruby-saml/pull/363) Add support for multiple requested attributes + +### 1.4.1 (October 19, 2016) +* [#357](https://github.com/onelogin/ruby-saml/pull/357) Add EncryptedAttribute support. Improve decrypt method +* Allow multiple authn_context_decl_ref in settings +* Allow options[:settings] to be an hash for Settings overrides in IdpMetadataParser#parse +* Recover issuers method + +### 1.4.0 (October 13, 2016) +* Several security improvements: + * Conditions element required and unique. + * AuthnStatement element required and unique. + * SPNameQualifier must math the SP EntityID + * Reject saml:Attribute element with same “Name” attribute + * Reject empty nameID + * Require Issuer element. (Must match IdP EntityID). + * Destination value can't be blank (if present must match ACS URL). + * Check that the EncryptedAssertion element only contains 1 Assertion element. + +* [#335](https://github.com/onelogin/ruby-saml/pull/335) Explicitly parse as XML and fix setting of Nokogiri options. +* [#345](https://github.com/onelogin/ruby-saml/pull/345)Support multiple settings.auth_context +* More tests to prevent XML Signature Wrapping +* [#342](https://github.com/onelogin/ruby-saml/pull/342) Correct the usage of Mutex +* [352](https://github.com/onelogin/ruby-saml/pull/352) Support multiple AttributeStatement tags + + +### 1.3.1 (July 10, 2016) +* Fix response_test.rb of gem 1.3.0 +* Add reference to Security Guidelines +* Update License +* [#334](https://github.com/onelogin/ruby-saml/pull/334) Keep API backward-compatibility on IdpMetadataParser fingerprint method. + +### 1.3.0 (June 24, 2016) +* [Security Fix](https://github.com/onelogin/ruby-saml/commit/a571f52171e6bfd87db59822d1d9e8c38fb3b995) Add extra validations to prevent Signature wrapping attacks +* Fix XMLSecurity SHA256 and SHA512 uris +* [#326](https://github.com/onelogin/ruby-saml/pull/326) Fix Destination validation + +### 1.2.0 (April 29, 2016) +* [#269](https://github.com/onelogin/ruby-saml/pull/269) Refactor error handling; allow collect error messages when soft=true (normal validation stop after find first error) +* [#289](https://github.com/onelogin/ruby-saml/pull/289) Remove uuid gem in favor of SecureRandom +* [#297](https://github.com/onelogin/ruby-saml/pull/297) Implement EncryptedKey RetrievalMethod support +* [#298](https://github.com/onelogin/ruby-saml/pull/298) IDP metadata parsing improved: binding parsing, fingerprint_algorithm support) +* [#299](https://github.com/onelogin/ruby-saml/pull/299) Make 'signing' at KeyDescriptor optional +* [#308](https://github.com/onelogin/ruby-saml/pull/308) Support name_id_format on SAMLResponse +* [#315](https://github.com/onelogin/ruby-saml/pull/315) Support for canonicalization with comments +* [#316](https://github.com/onelogin/ruby-saml/pull/316) Fix Misspelling of transation_id to transaction_id +* [#321](https://github.com/onelogin/ruby-saml/pull/321) Support Attribute Names on IDPSSODescriptor parser +* Changes on empty URI of Signature reference management +* [#320](https://github.com/onelogin/ruby-saml/pull/320) Dont mutate document to fix lack of reference URI +* [#306](https://github.com/onelogin/ruby-saml/pull/306) Support WantAssertionsSigned + +### 1.1.2 (February 15, 2016) +* Improve signature validation. Add tests. + [#302](https://github.com/onelogin/ruby-saml/pull/302) Add Destination validation. +* [#292](https://github.com/onelogin/ruby-saml/pull/292) Improve the error message when validating the audience. +* [#287](https://github.com/onelogin/ruby-saml/pull/287) Keep the extracted certificate when parsing IdP metadata. + +### 1.1.1 (November 10, 2015) +* [#275](https://github.com/onelogin/ruby-saml/pull/275) Fix a bug on signature validations that invalidates valid SAML messages. + +### 1.1.0 (October 27, 2015) +* [#273](https://github.com/onelogin/ruby-saml/pull/273) Support SAMLResponse without ds:x509certificate +* [#270](https://github.com/onelogin/ruby-saml/pull/270) Allow SAML elements to come from any namespace (at decryption process) +* [#261](https://github.com/onelogin/ruby-saml/pull/261) Allow validate_subject_confirmation Response validation to be skipped +* [#258](https://github.com/onelogin/ruby-saml/pull/258) Fix allowed_clock_drift on the validate_session_expiration test +* [#256](https://github.com/onelogin/ruby-saml/pull/256) Separate the create_authentication_xml_doc in two methods. +* [#255](https://github.com/onelogin/ruby-saml/pull/255) Refactor validate signature. +* [#254](https://github.com/onelogin/ruby-saml/pull/254) Handle empty URI references +* [#251](https://github.com/onelogin/ruby-saml/pull/251) Support qualified and unqualified NameID in attributes +* [#234](https://github.com/onelogin/ruby-saml/pull/234) Add explicit support for JRuby + +### 1.0.0 (June 30, 2015) +* [#247](https://github.com/onelogin/ruby-saml/pull/247) Avoid entity expansion (XEE attacks) +* [#246](https://github.com/onelogin/ruby-saml/pull/246) Fix bug generating Logout Response (issuer was at wrong order) +* [#243](https://github.com/onelogin/ruby-saml/issues/243) and [#244](https://github.com/onelogin/ruby-saml/issues/244) Fix metadata builder errors. Fix metadata xsd. +* [#241](https://github.com/onelogin/ruby-saml/pull/241) Add decrypt support (EncryptID and EncryptedAssertion). Improve compatibility with namespaces. +* [#240](https://github.com/onelogin/ruby-saml/pull/240) and [#238](https://github.com/onelogin/ruby-saml/pull/238) Improve test coverage and refactor. +* [#239](https://github.com/onelogin/ruby-saml/pull/239) Improve security: Add more validations to SAMLResponse, LogoutRequest and LogoutResponse. Refactor code and improve tests coverage. +* [#237](https://github.com/onelogin/ruby-saml/pull/237) Don't pretty print metadata by default. +* [#235](https://github.com/onelogin/ruby-saml/pull/235) Remove the soft parameter from validation methods. Now can be configured on the settings and each class read it and store as an attribute of the class. Adding some validations and refactor old ones. +* [#232](https://github.com/onelogin/ruby-saml/pull/232) Improve validations: Store the causes in the errors array, code refactor +* [#231](https://github.com/onelogin/ruby-saml/pull/231) Refactor HTTP-Redirect Sign method, Move test data to right folder +* [#226](https://github.com/onelogin/ruby-saml/pull/226) Ensure IdP certificate is formatted properly +* [#225](https://github.com/onelogin/ruby-saml/pull/225) Add documentation to several methods. Fix xpath injection on xml_security.rb +* [#223](https://github.com/onelogin/ruby-saml/pull/223) Allow logging to be delegated to an arbitrary Logger +* [#222](https://github.com/onelogin/ruby-saml/pull/222) No more silent failure fetching idp metadata (OneLogin::RubySaml::HttpError raised). + +### 0.9.2 (Apr 28, 2015) +* [#216](https://github.com/onelogin/ruby-saml/pull/216) Add fingerprint algorithm support +* [#218](https://github.com/onelogin/ruby-saml/pull/218) Update README.md +* [#214](https://github.com/onelogin/ruby-saml/pull/214) Cleanup `SamlMessage` class +* [#213](https://github.com/onelogin/ruby-saml/pull/213) Add ability to sign metadata. (Improved) +* [#212](https://github.com/onelogin/ruby-saml/pull/212) Rename library entry point +* [#210](https://github.com/onelogin/ruby-saml/pull/210) Call assert in tests +* [#208](https://github.com/onelogin/ruby-saml/pull/208) Update tests and CI for Ruby 2.2.0 +* [#205](https://github.com/onelogin/ruby-saml/pull/205) Allow requirement of single files +* [#204](https://github.com/onelogin/ruby-saml/pull/204) Require ‘net/http’ library +* [#201](https://github.com/onelogin/ruby-saml/pull/201) Freeze and duplicate default security settings hash so that it doesn't get modified. +* [#200](https://github.com/onelogin/ruby-saml/pull/200) Set default SSL certificate store in Ruby 1.8. +* [#199](https://github.com/onelogin/ruby-saml/pull/199) Change Nokogiri's runtime dependency to fix support for Ruby 1.8.7. +* [#179](https://github.com/onelogin/ruby-saml/pull/179) Add support for setting the entity ID and name ID format when parsing metadata +* [#175](https://github.com/onelogin/ruby-saml/pull/175) Introduce thread safety to SAML schema validation +* [#171](https://github.com/onelogin/ruby-saml/pull/171) Fix inconsistent results with using regex matches in decode_raw_saml + +### 0.9.1 (Feb 10, 2015) +* [#194](https://github.com/onelogin/ruby-saml/pull/194) Relax nokogiri gem requirements +* [#191](https://github.com/onelogin/ruby-saml/pull/191) Use Minitest instead of Test::Unit + +### 0.9 (Jan 26, 2015) +* [#169](https://github.com/onelogin/ruby-saml/pull/169) WantAssertionSigned should be either true or false +* [#167](https://github.com/onelogin/ruby-saml/pull/167) (doc update) make unit of clock drift obvious +* [#160](https://github.com/onelogin/ruby-saml/pull/160) Extended solution for Attributes method [] can raise NoMethodError +* [#158](https://github.com/onelogin/ruby-saml/pull/1) Added ability to specify attribute services in metadata +* [#154](https://github.com/onelogin/ruby-saml/pull/154) Fix incorrect gem declaration statement +* [#152](https://github.com/onelogin/ruby-saml/pull/152) Fix the PR #99 +* [#150](https://github.com/onelogin/ruby-saml/pull/150) Nokogiri already in gemspec +* [#147](https://github.com/onelogin/ruby-saml/pull/147) Fix LogoutResponse issuer validation and implement SAML Response issuer validation. +* [#144](https://github.com/onelogin/ruby-saml/pull/144) Fix DigestMethod lookup bug +* [#139](https://github.com/onelogin/ruby-saml/pull/139) Fixes handling of some soft and hard validation failures +* [#138](https://github.com/onelogin/ruby-saml/pull/138) Change logoutrequest.rb to UTC time +* [#136](https://github.com/onelogin/ruby-saml/pull/136) Remote idp metadata +* [#135](https://github.com/onelogin/ruby-saml/pull/135) Restored support for NIL as well as empty AttributeValues +* [#134](https://github.com/onelogin/ruby-saml/pull/134) explicitly require "onelogin/ruby-saml/logging" +* [#133](https://github.com/onelogin/ruby-saml/pull/133) Added license to gemspec +* [#132](https://github.com/onelogin/ruby-saml/pull/132) Support AttributeConsumingServiceIndex in AuthnRequest +* [#131](https://github.com/onelogin/ruby-saml/pull/131) Add ruby 2.1.1 to .travis.yml +* [#122](https://github.com/onelogin/ruby-saml/pull/122) Fixes #112 and #117 in a backwards compatible manner +* [#119](https://github.com/onelogin/ruby-saml/pull/119) Add support for extracting IdP details from metadata xml + +### 0.8.2 (Jan 26, 2015) +* [#183](https://github.com/onelogin/ruby-saml/pull/183) Resolved a security vulnerability where string interpolation in a `REXML::XPath.first()` method call allowed for arbitrary code execution. + +### 0.8.0 (Feb 21, 2014) +**IMPORTANT**: This release changed namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly. + +* [#111](https://github.com/onelogin/ruby-saml/pull/111) `Onelogin::` is `OneLogin::` +* [#108](https://github.com/onelogin/ruby-saml/pull/108) Change namespacing from `Onelogin::Saml` to `Onelogin::Rubysaml` + + +### 0.7.3 (Feb 20, 2014) +Updated gem dependencies to be compatible with Ruby 1.8.7-p374 and 1.9.3-p448. Removed unnecessary `canonix` gem dependency. + +* [#107](https://github.com/onelogin/ruby-saml/pull/107) Relax nokogiri version requirement to >= 1.5.0 +* [#105](https://github.com/onelogin/ruby-saml/pull/105) Lock Gem versions, fix to resolve possible namespace collision diff --git a/LICENSE b/LICENSE index b55ffa04a..c141165ed 100644 --- a/LICENSE +++ b/LICENSE @@ -1,19 +1,24 @@ -Copyright (c) 2010 OneLogin, LLC +Copyright (c) 2010-2022 OneLogin, Inc. +Copyright (c) 2023 IAM Digital Services, SL. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index f1cc4ab75..36b5adaaf 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,109 @@ -# Ruby SAML [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.png)](http://travis-ci.org/onelogin/ruby-saml) +# Ruby SAML +[![ruby-saml CI](https://github.com/SAML-Toolkits/ruby-saml/actions/workflows/test.yml/badge.svg)](https://github.com/SAML-Toolkits/ruby-saml/actions/workflows/test.yml) +[![Coverage Status](https://coveralls.io/repos/github/SAML-Toolkits/ruby-saml/badge.svg?branch=master)](https://coveralls.io/github/SAML-Toolkits/ruby-saml?branch=master) +[![Rubygem Version](https://badge.fury.io/rb/ruby-saml.svg)](https://badge.fury.io/rb/ruby-saml) +[![GitHub version](https://badge.fury.io/gh/SAML-Toolkits%2Fruby-saml.svg)](https://badge.fury.io/gh/SAML-Toolkits%2Fruby-saml) ![GitHub](https://img.shields.io/github/license/SAML-Toolkits/ruby-saml) ![Gem](https://img.shields.io/gem/dtv/ruby-saml?label=gem%20downloads%20latest) ![Gem](https://img.shields.io/gem/dt/ruby-saml?label=gem%20total%20downloads) -## Updating from 0.8.x to 0.9.x -Version `0.9` adds many new features and improvements. It is a recommended update for all Ruby SAML users. For more details, please review [the changelog](changelog.md) +Minor and patch versions of Ruby SAML may introduce breaking changes. Please read +[UPGRADING.md](UPGRADING.md) for guidance on upgrading to new Ruby SAML versions. -## Updating from 0.7.x to 0.8.x -Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly. +### Pay it Forward: Support RubySAML and Strengthen Open-Source Security + +RubySAML is a trusted authentication library used by startups and enterprises alike. + +But security doesn't happen in a vacuum. Vulnerabilities in authentication libraries can +have widespread consequences. Maintaining open-source security requires continuous +effort, expertise, and funding. By supporting RubySAML, you’re not just securing your +own systems—you’re strengthening auth security globally. + +#### How you can help + +* Sponsor RubySAML: [GitHub Sponsors](https://github.com/sponsors/SAML-Toolkits) +* Contribute to secure-by-design improvements +* Responsibly report vulnerabilities (see "Vulnerability Reporting" above) + +Security is a shared responsibility. If RubySAML has helped your organization, please +consider giving back. Together, we can keep authentication secure. + +### Sponsors + +Thanks to the following sponsors for securing the open source ecosystem, + +[84codes](https://www.84codes.com) + + +## Vulnerabilities + +CVE-2025-54572 affects version ruby-saml < 1.18.1 + + +There are critical vulnerabilities affecting ruby-saml < 1.18.0, two of them allows SAML authentication bypass (CVE-2025-25291, CVE-2025-25292, CVE-2025-25293). Please upgrade to a fixed version (1.18.0) ## Overview -The Ruby SAML library is for implementing the client side of a SAML authorization, i.e. it provides a means for managing authorization initialization and confirmation requests from identity providers. +The Ruby SAML library is for implementing the client side of a SAML authorization, +i.e. it provides a means for managing authorization initialization and confirmation +requests from identity providers. + +SAML authorization is a two-step process and you are expected to implement support for both. -SAML authorization is a two step process and you are expected to implement support for both. +We created a demo project for Rails 4 that uses the latest version of this library: +[ruby-saml-example](https://github.com/saml-toolkits/ruby-saml-example) -We created a demo project for Rails4 that uses the latest version of this library: [ruby-saml-example](https://github.com/onelogin/ruby-saml-example) +### Supported Ruby Versions -### Supported versions of Ruby -* 1.8.7 -* 1.9.x -* 2.1.x -* 2.2.0 +The following Ruby versions are covered by CI testing: + +* Ruby (MRI) 2.1 to 3.4 +* JRuby 9.1 to 9.4 +* TruffleRuby (latest) ## Adding Features, Pull Requests + * Fork the repository * Make your feature addition or bug fix * Add tests for your new features. This is important so we don't break any features in a future version unintentionally. -* Ensure all tests pass. -* Do not change rakefile, version, or history. +* Ensure all tests pass by running `bundle exec rake test`. +* Do not change Rakefile, version, or history. * Open a pull request, following [this template](https://gist.github.com/Lordnibbler/11002759). +## Security Guidelines + +If you believe you have discovered a security vulnerability in this gem, please report it +by mail to the maintainer: sixto.martin.garcia+security@gmail.com + +### Security Warning + +Some tools may incorrectly report ruby-saml is a potential security vulnerability. +ruby-saml depends on Nokogiri, and it is possible to use Nokogiri in a dangerous way +(by enabling its DTDLOAD option and disabling its NONET option). +This dangerous Nokogiri configuration, which is sometimes used by other components, +can create an XML External Entity (XXE) vulnerability if the XML data is not trusted. +However, ruby-saml never enables this dangerous Nokogiri configuration; +ruby-saml never enables DTDLOAD, and it never disables NONET. + +The OneLogin::RubySaml::IdpMetadataParser class does not validate the provided URL before parsing. + +Usually, the same administrator who handles the Service Provider also sets the URL to +the IdP, which should be a trusted resource. + +But there are other scenarios, like a SaaS app where the administrator of the app +delegates this functionality to other users. In this case, extra precautions should +be taken in order to validate such URL inputs and avoid attacks like SSRF. + ## Getting Started -In order to use the toolkit you will need to install the gem (either manually or using Bundler), and require the library in your Ruby application: + +In order to use Ruby SAML you will need to install the gem (either manually or using Bundler), +and require the library in your Ruby application: Using `Gemfile` ```ruby # latest stable -gem 'ruby-saml', '~> 0.9.1' +gem 'ruby-saml', '~> 1.18.0' # or track master for bleeding-edge -gem 'ruby-saml', :github => 'onelogin/ruby-saml' +gem 'ruby-saml', :github => 'saml-toolkit/ruby-saml' ``` Using RubyGems @@ -47,7 +112,8 @@ Using RubyGems gem install ruby-saml ``` -When requiring the gem, you can add the whole toolkit +You may require the entire Ruby SAML gem: + ```ruby require 'onelogin/ruby-saml' ``` @@ -60,7 +126,9 @@ require 'onelogin/ruby-saml/authrequest' ### Installation on Ruby 1.8.7 -This gem has a dependency on Nokogiri, which dropped support for Ruby 1.8.x in Nokogiri 1.6. When installing this gem on Ruby 1.8.7, you will need to make sure a version of Nokogiri prior to 1.6 is installed or specified if it hasn't been already. +This gem uses Nokogiri as a dependency, which dropped support for Ruby 1.8.x in Nokogiri 1.6. +When installing this gem on Ruby 1.8.7, you will need to make sure a version of Nokogiri +prior to 1.6 is installed or specified if it hasn't been already. Using `Gemfile` @@ -74,62 +142,127 @@ Using RubyGems gem install nokogiri --version '~> 1.5.10' ```` +### Configuring Logging + +When troubleshooting SAML integration issues, you will find it extremely helpful to examine the +output of this gem's business logic. By default, log messages are emitted to `RAILS_DEFAULT_LOGGER` +when the gem is used in a Rails context, and to `STDOUT` when the gem is used outside of Rails. + +To override the default behavior and control the destination of log messages, provide +a ruby Logger object to the gem's logging singleton: + +```ruby +OneLogin::RubySaml::Logging.logger = Logger.new('/var/log/ruby-saml.log') +``` + ## The Initialization Phase -This is the first request you will get from the identity provider. It will hit your application at a specific URL (that you've announced as being your SAML initialization point). The response to this initialization, is a redirect back to the identity provider, which can look something like this (ignore the saml_settings method call for now): +This is the first request you will get from the identity provider. It will hit your application +at a specific URL that you've announced as your SAML initialization point. The response to +this initialization is a redirect back to the identity provider, which can look something +like this (ignore the saml_settings method call for now): + +```ruby +def init + request = OneLogin::RubySaml::Authrequest.new + redirect_to(request.create(saml_settings)) +end +``` + +If the SP knows who should be authenticated in the IdP, it can provide that info as follows: ```ruby def init request = OneLogin::RubySaml::Authrequest.new + saml_settings.name_identifier_value_requested = "testuser@example.com" + saml_settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" redirect_to(request.create(saml_settings)) end ``` -Once you've redirected back to the identity provider, it will ensure that the user has been authorized and redirect back to your application for final consumption, this is can look something like this (the authorize_success and authorize_failure methods are specific to your application): +Once you've redirected back to the identity provider, it will ensure that the user has been +authorized and redirect back to your application for final consumption. +This can look something like this (the `authorize_success` and `authorize_failure` +methods are specific to your application): ```ruby def consume - response = OneLogin::RubySaml::Response.new(params[:SAMLResponse]) - response.settings = saml_settings + response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :settings => saml_settings) # We validate the SAML Response and check if the user already exists in the system if response.is_valid? # authorize_success, log the user - session[:userid] = response.name_id + session[:userid] = response.nameid session[:attributes] = response.attributes else authorize_failure # This method shows an error message + # List of errors is available in response.errors array end end ``` -In the above there are a few assumptions in place, one being that the response.name_id is an email address. This is all handled with how you specify the settings that are in play via the saml_settings method. That could be implemented along the lines of this: +In the above there are a few assumptions, one being that `response.nameid` is an email address. +This is all handled with how you specify the settings that are in play via the `saml_settings` method. +That could be implemented along the lines of this: + +``` +response = OneLogin::RubySaml::Response.new(params[:SAMLResponse]) +response.settings = saml_settings +``` + +If the assertion of the SAMLResponse is not encrypted, you can initialize the Response +without the `:settings` parameter and set it later. If the SAMLResponse contains an encrypted +assertion, you need to provide the settings in the initialize method in order to obtain the +decrypted assertion, using the service provider private key in order to decrypt. +If you don't know what expect, always use the former (set the settings on initialize). ```ruby def saml_settings settings = OneLogin::RubySaml::Settings.new - settings.assertion_consumer_service_url = "http://#{request.host}/saml/finalize" - settings.issuer = request.host - settings.idp_sso_target_url = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}" + settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume" + settings.sp_entity_id = "http://#{request.host}/saml/metadata" settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}" - settings.idp_sso_target_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}" - settings.idp_slo_target_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}" + settings.idp_sso_service_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}" + settings.idp_sso_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect + settings.idp_slo_service_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}" + settings.idp_slo_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint + settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1" settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" # Optional for most SAML IdPs settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" + # or as an array + settings.authn_context = [ + "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", + "urn:oasis:names:tc:SAML:2.0:ac:classes:Password" + ] - # Optional bindings (defaults to Redirect for logout POST for acs) - settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" - settings.single_logout_service_url_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + # Optional bindings (defaults to Redirect for logout POST for ACS) + settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect + settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect settings end ``` -What's left at this point, is to wrap it all up in a controller and point the initialization and consumption URLs in OneLogin at that. A full controller example could look like this: +The use of `settings.issuer` is deprecated in favor of `settings.sp_entity_id` since version 1.11.0 + +Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`. +For example, you can skip the `AuthnStatement`, `Conditions`, `Recipient`, or the `SubjectConfirmation` +validations by initializing the response with different options: + +```ruby +response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_authnstatement: true}) # skips AuthnStatement +response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions +response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation +response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_recipient_check: true}) # doesn't skip subject confirmation, but skips the recipient check which is a sub check of the subject_confirmation check +response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_audience: true}) # skips audience check +``` + +All that's left is to wrap everything in a controller and reference it in the initialization and +consumption URLs in OneLogin. A full controller example could look like this: ```ruby # This controller expects you to use the URLs /saml/init and /saml/consume in your OneLogin application. @@ -146,10 +279,11 @@ class SamlController < ApplicationController # We validate the SAML Response and check if the user already exists in the system if response.is_valid? # authorize_success, log the user - session[:userid] = response.name_id + session[:userid] = response.nameid session[:attributes] = response.attributes else authorize_failure # This method shows an error message + # List of errors is available in response.errors array end end @@ -159,8 +293,8 @@ class SamlController < ApplicationController settings = OneLogin::RubySaml::Settings.new settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume" - settings.issuer = request.host - settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}" + settings.sp_entity_id = "http://#{request.host}/saml/metadata" + settings.idp_sso_service_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}" settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" @@ -180,22 +314,61 @@ class SamlController < ApplicationController end end ``` + +## Signature Validation + +Ruby SAML allows different ways to validate the signature of the SAMLResponse: +- You can provide the IdP X.509 public certificate at the `idp_cert` setting. +- You can provide the IdP X.509 public certificate in fingerprint format using the + `idp_cert_fingerprint` setting parameter and additionally the `idp_cert_fingerprint_algorithm` parameter. + +When validating the signature of redirect binding, the fingerprint is useless and the certificate +of the IdP is required in order to execute the validation. You can pass the option +`:relax_signature_validation` to `SloLogoutrequest` and `Logoutresponse` if want to avoid signature +validation if no certificate of the IdP is provided. + +In production also we highly recommend to register on the settings the IdP certificate instead +of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision +attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism, +we maintain it for compatibility and also to be used on test environment. + +## Handling Multiple IdP Certificates + +If the IdP metadata XML includes multiple certificates, you may specify the `idp_cert_multi` +parameter. When used, the `idp_cert` and `idp_cert_fingerprint` parameters are ignored. +This is useful in the following scenarios: + +* The IdP uses different certificates for signing versus encryption. +* The IdP is undergoing a key rollover and is publishing the old and new certificates in parallel. + +The `idp_cert_multi` must be a `Hash` as follows. The `:signing` and `:encryption` arrays below, +add the IdP X.509 public certificates which were published in the IdP metadata. + +```ruby +{ + :signing => [], + :encryption => [] +} +``` + ## Metadata Based Configuration -The method above requires a little extra work to manually specify attributes about the IdP. (And your SP application) There's an easier method -- use a metadata exchange. Metadata is just an XML file that defines the capabilities of both the IdP and the SP application. It also contains the X.509 public -key certificates which add to the trusted relationship. The IdP administrator can also configure custom settings for an SP based on the metadata. +The method above requires a little extra work to manually specify attributes about both the IdP and your SP application. +There's an easier method: use a metadata exchange. Metadata is an XML file that defines the capabilities of both the IdP +and the SP application. It also contains the X.509 public key certificates which add to the trusted relationship. +The IdP administrator can also configure custom settings for an SP based on the metadata. -Using ```idp_metadata_parser.parse_remote``` IdP metadata will be added to the settings withouth further ado. +Using `IdpMetadataParser#parse_remote`, the IdP metadata will be added to the settings. ```ruby def saml_settings idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new - # Returns OneLogin::RubySaml::Settings prepopulated with idp metadata + # Returns OneLogin::RubySaml::Settings pre-populated with IdP metadata settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata") settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume" - settings.issuer = request.host + settings.sp_entity_id = "http://#{request.host}/saml/metadata" settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" # Optional for most SAML IdPs settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" @@ -203,22 +376,117 @@ def saml_settings settings end ``` + The following attributes are set: - * id_sso_target_url - * idp_slo_target_url - * id_cert_fingerpint + * idp_entity_id + * name_identifier_format + * idp_sso_service_url + * idp_slo_service_url + * idp_attribute_names + * idp_cert + * idp_cert_fingerprint + * idp_cert_multi -If are using saml:AttributeStatement to transfer metadata, like the user name, you can access all the attributes through response.attributes. It contains all the saml:AttributeStatement with its 'Name' as a indifferent key the one/more saml:AttributeValue as value. The value returned depends on the value of the -`single_value_compatibility` (when activate, only one value returned, the first one) +### Retrieve one Entity Descriptor when many exist in Metadata + +If the Metadata contains several entities, the relevant Entity +Descriptor can be specified when retrieving the settings from the +IdpMetadataParser by its Entity Id value: ```ruby -response = OneLogin::RubySaml::Response.new(params[:SAMLResponse]) + validate_cert = true + settings = idp_metadata_parser.parse_remote( + "https://example.com/auth/saml2/idp/metadata", + validate_cert, + entity_id: "http//example.com/target/entity" + ) +``` + +### Retrieve one Entity Descriptor with a specific binding and nameid format when several are available + +If the metadata contains multiple bindings and NameID formats, the relevant ones +can be specified when retrieving the settings from the IdpMetadataParser +by the values of binding and NameID: + +```ruby + validate_cert = true + options = { + entity_id: "http//example.com/target/entity", + name_id_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + sso_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + slo_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + } + settings = idp_metadata_parser.parse_remote( + "https://example.com/auth/saml2/idp/metadata", + validate_cert, + options + ) +``` + +### Parsing Metadata into an Hash + +The `OneLogin::RubySaml::IdpMetadataParser` also provides the methods `#parse_to_hash` and `#parse_remote_to_hash`. +Those return an Hash instead of a `Settings` object, which may be useful for configuring +[omniauth-saml](https://github.com/omniauth/omniauth-saml), for instance. + + +### Validating Signature of Metadata and retrieve settings + +Right now there is no method at ruby_saml to validate the signature of the metadata that gonna be parsed, +but it can be done as follows: +* Download the XML. +* Validate the Signature, providing the cert. +* Provide the XML to the parse method if the signature was validated + +```ruby +require "xml_security" +require "onelogin/ruby-saml/utils" +require "onelogin/ruby-saml/idp_metadata_parser" + +url = "" +idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + +uri = URI.parse(url) +raise ArgumentError.new("url must begin with http or https") unless /^https?/ =~ uri.scheme +http = Net::HTTP.new(uri.host, uri.port) +if uri.scheme == "https" + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_PEER +end + +get = Net::HTTP::Get.new(uri.request_uri) +get.basic_auth uri.user, uri.password if uri.user +response = http.request(get) +xml = response.body +errors = [] +doc = XMLSecurity::SignedDocument.new(xml, errors) +cert_str = "" +cert = OneLogin::RubySaml::Utils.format_cert("cert_str") +metadata_sign_cert = OpenSSL::X509::Certificate.new(cert) +valid = doc.validate_document_with_cert(metadata_sign_cert, true) +if valid + settings = idp_metadata_parser.parse( + xml, + entity_id: "" + ) +else + print "Metadata Signature failed to be verified with the cert provided" +end +``` + +## Retrieving Attributes + +If you are using `saml:AttributeStatement` to transfer data, such as the username, you can access all the attributes through `response.attributes`. It contains all the `saml:AttributeStatement`s with its 'Name' as an indifferent key and one or more `saml:AttributeValue`s as values. The value returned depends on the value of the +`single_value_compatibility` (when activated, only the first value is returned) + +```ruby +response = OneLogin::RubySaml::Response.new(params[:SAMLResponse]) response.settings = saml_settings response.attributes[:username] ``` -Imagine this saml:AttributeStatement +Imagine this `saml:AttributeStatement` ```xml @@ -245,6 +513,9 @@ Imagine this saml:AttributeStatement + + usersName + ``` @@ -255,7 +526,8 @@ pp(response.attributes) # is an OneLogin::RubySaml::Attributes object "another_value"=>["value1", "value2"], "role"=>["role1", "role2", "role3"], "attribute_with_nil_value"=>[nil], - "attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]}> + "attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil] + "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"=>["usersName"]}> # Active single_value_compatibility OneLogin::RubySaml::Attributes.single_value_compatibility = true @@ -272,6 +544,9 @@ pp(response.attributes.single(:role)) pp(response.attributes.multi(:role)) # => ["role1", "role2", "role3"] +pp(response.attributes.fetch(:role)) +# => "role1" + pp(response.attributes[:attribute_with_nil_value]) # => nil @@ -287,7 +562,10 @@ pp(response.attributes.single(:not_exists)) pp(response.attributes.multi(:not_exists)) # => nil -# Deactive single_value_compatibility +pp(response.attributes.fetch(/givenname/)) +# => "usersName" + +# Deprecated single_value_compatibility OneLogin::RubySaml::Attributes.single_value_compatibility = false pp(response.attributes[:uid]) @@ -302,6 +580,9 @@ pp(response.attributes.single(:role)) pp(response.attributes.multi(:role)) # => ["role1", "role2", "role3"] +pp(response.attributes.fetch(:role)) +# => ["role1", "role2", "role3"] + pp(response.attributes[:attribute_with_nil_value]) # => [nil] @@ -316,49 +597,225 @@ pp(response.attributes.single(:not_exists)) pp(response.attributes.multi(:not_exists)) # => nil + +pp(response.attributes.fetch(/givenname/)) +# => ["usersName"] ``` -The saml:AuthnContextClassRef of the AuthNRequest can be provided by `settings.authn_context` , possible values are described at [SAMLAuthnCxt]. The comparison method can be set using the parameter `settings.authn_context_comparison` (the possible values are: 'exact', 'better', 'maximum' and 'minimum'), 'exact' is the default value. -If we want to add a saml:AuthnContextDeclRef, define a `settings.authn_context_decl_ref`. +The `saml:AuthnContextClassRef` of the AuthNRequest can be provided by `settings.authn_context`; possible values are described at [SAMLAuthnCxt]. The comparison method can be set using `settings.authn_context_comparison` parameter. Possible values include: 'exact', 'better', 'maximum' and 'minimum' (default value is 'exact'). +To add a `saml:AuthnContextDeclRef`, define `settings.authn_context_decl_ref`. + +In a SP-initiated flow, the SP can indicate to the IdP the subject that should be authenticated. This is done by defining the `settings.name_identifier_value_requested` before +building the authrequest object. +## Service Provider Metadata -## Signing +To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML +to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc) -The Ruby Toolkit supports 2 different kinds of signature: Embeded and as GET parameter +The class `OneLogin::RubySaml::Metadata` takes care of this by reading the Settings and returning XML. All you have to do is add a controller to return the data, then give this URL to the IdP administrator. -In order to be able to sign we need first to define the private key and the public cert of the service provider +The metadata will be polled by the IdP every few minutes, so updating your settings should propagate +to the IdP settings. ```ruby - settings.certificate = "CERTIFICATE TEXT WITH HEADS" - settings.private_key = "PRIVATE KEY TEXT WITH HEADS" +class SamlController < ApplicationController + # ... the rest of your controller definitions ... + def metadata + settings = Account.get_saml_settings + meta = OneLogin::RubySaml::Metadata.new + render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml" + end +end ``` -The settings related to sign are stored in the `security` attribute of the settings: +You can add `ValidUntil` and `CacheDuration` to the SP Metadata XML using instead: ```ruby - settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest - settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request - settings.security[:logout_responses_signed] = true # Enable or not signature on Logout Response - settings.security[:metadata_signed] = true # Enable or not signature on Metadata + # Valid until => 2 days from now + # Cache duration = 604800s = 1 week + valid_until = Time.now + 172800 + cache_duration = 604800 + meta.generate(settings, false, valid_until, cache_duration) +``` + +## Signing and Decryption +Ruby SAML supports the following functionality: + +1. Signing your SP Metadata XML +2. Signing your SP SAML messages +3. Decrypting IdP Assertion messages upon receipt (EncryptedAssertion) +4. Verifying signatures on SAML messages and IdP Assertions + +In order to use functions 1-3 above, you must first define your SP public certificate and private key: + +```ruby + settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER" + settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER" +``` + +Note that the same certificate (and its associated private key) are used to perform +all decryption and signing-related functions (1-4) above. Ruby SAML does not currently allow +to specify different certificates for each function. + +You may also globally set the SP signature and digest method, to be used in SP signing (functions 1 and 2 above): + +```ruby settings.security[:digest_method] = XMLSecurity::Document::SHA1 settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 +``` + +#### Signing SP Metadata + +You may add a `` digital signature element to your SP Metadata XML using the following setting: + +```ruby + settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER" + settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER" + + settings.security[:metadata_signed] = true # Enable signature on Metadata +``` + +#### Signing SP SAML Messages + +Ruby SAML supports SAML request signing. The Service Provider will sign the +request/responses with its private key. The Identity Provider will then validate the signature +of the received request/responses with the public X.509 cert of the Service Provider. + +To enable, please first set your certificate and private key. This will add `` +to your SP Metadata XML, to be read by the IdP. + +```ruby + settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER" + settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER" +``` + +Next, you may specify the specific SP SAML messages you would like to sign: + +```ruby + settings.security[:authn_requests_signed] = true # Enable signature on AuthNRequest + settings.security[:logout_requests_signed] = true # Enable signature on Logout Request + settings.security[:logout_responses_signed] = true # Enable signature on Logout Response +``` + +Signatures will be handled automatically for both `HTTP-Redirect` and `HTTP-POST` Binding. +Note that the RelayState parameter is used when creating the Signature on the `HTTP-Redirect` Binding. +Remember to provide it to the Signature builder if you are sending a `GET RelayState` parameter or the +signature validation process will fail at the Identity Provider. + +#### Decrypting IdP SAML Assertions + +Ruby SAML supports EncryptedAssertion. The Identity Provider will encrypt the Assertion with the +public cert of the Service Provider. The Service Provider will decrypt the EncryptedAssertion with its private key. + +You may enable EncryptedAssertion as follows. This will add `` to your +SP Metadata XML, to be read by the IdP. + +```ruby + settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER" + settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER" + + settings.security[:want_assertions_encrypted] = true # Invalidate SAML messages without an EncryptedAssertion +``` + +#### Verifying Signature on IdP Assertions + +You may require the IdP to sign its SAML Assertions using the following setting. +With will add `` to your SP Metadata XML. +The signature will be checked against the `` element +present in the IdP's metadata. + +```ruby + settings.security[:want_assertions_signed] = true # Require the IdP to sign its SAML Assertions +``` + +#### Certificate and Signature Validation + +You may require SP and IdP certificates to be non-expired using the following settings: + +```ruby + settings.security[:check_idp_cert_expiration] = true # Raise error if IdP X.509 cert is expired + settings.security[:check_sp_cert_expiration] = true # Raise error SP X.509 cert is expired +``` + +By default, Ruby SAML will raise a `OneLogin::RubySaml::ValidationError` if a signature or certificate +validation fails. You may disable such exceptions using the `settings.security[:soft]` parameter. + +```ruby + settings.security[:soft] = true # Do not raise error on failed signature/certificate validations +``` + +#### Advanced SP Certificate Usage & Key Rollover + +Ruby SAML provides the `settings.sp_cert_multi` parameter to enable the following +advanced usage scenarios: +- Rotating SP certificates and private keys without disruption of service. +- Specifying separate SP certificates for signing and encryption. + +The `sp_cert_multi` parameter replaces `certificate` and `private_key` +(you may not specify both pparameters at the same time.) `sp_cert_multi` has the following shape: + +```ruby +settings.sp_cert_multi = { + signing: [ + { certificate: cert1, private_key: private_key1 }, + { certificate: cert2, private_key: private_key2 } + ], + encryption: [ + { certificate: cert1, private_key: private_key1 }, + { certificate: cert3, private_key: private_key1 } + ], +} +``` + +Certificate rotation is acheived by inserting new certificates at the bottom of each list, +and then removing the old certificates from the top of the list once your IdPs have migrated. +A common practice is for apps to publish the current SP metadata at a URL endpoint and have +the IdP regularly poll for updates. + +Note the following: +- You may re-use the same certificate and/or private key in multiple places, including for both signing and encryption. +- The IdP should attempt to verify signatures with *all* `:signing` certificates, + and permit if *any one* succeeds. When signing, Ruby SAML will use the first SP certificate + in the `sp_cert_multi[:signing]` array. This will be the first active/non-expired certificate + in the array if `settings.security[:check_sp_cert_expiration]` is true. +- The IdP may encrypt with any of the SP certificates in the `sp_cert_multi[:encryption]` + array. When decrypting, Ruby SAML attempt to decrypt with each SP private key in + `sp_cert_multi[:encryption]` until the decryption is successful. This will skip private + keys for inactive/expired certificates if `:check_sp_cert_expiration` is true. +- If `:check_sp_cert_expiration` is true, the generated SP metadata XML will not include + inactive/expired certificates. This avoids validation errors when the IdP reads the SP + metadata. + +#### Audience Validation + +A service provider should only consider a SAML response valid if the IdP includes an +element containing an element that uniquely identifies the service provider. Unless you specify +the `skip_audience` option, Ruby SAML will validate that each SAML response includes an element +whose contents matches `settings.sp_entity_id`. + +By default, Ruby SAML considers an element containing only empty elements +to be valid. That means an otherwise valid SAML response with a condition like this would be valid: - # Embeded signature or HTTP GET parameter signature - # Note that metadata signature is always embedded regardless of this value. - settings.security[:embed_sign] = false +```xml + + + ``` -Notice that the RelayState parameter is used when creating the Signature on the HTTP-Redirect Binding, -remember to provide it to the Signature builder if you are sending a GET RelayState parameter or -Signature validation process will fail at the Identity Provider. +You may enforce that an element containing only empty elements +is invalid using the `settings.security[:strict_audience_validation]` parameter. +```ruby +settings.security[:strict_audience_validation] = true +``` ## Single Log Out -The Ruby Toolkit supports SP-initiated Single Logout and IdP-Initiated Single Logout. +Ruby SAML supports SP-initiated Single Logout and IdP-Initiated Single Logout. -Here is an example that we could add to our previous controller to generate and send a SAML Logout Request to the IdP +Here is an example that we could add to our previous controller to generate and send a SAML Logout Request to the IdP: ```ruby # Create a SP initiated SLO @@ -366,28 +823,34 @@ def sp_logout_request # LogoutRequest accepts plain browser requests w/o paramters settings = saml_settings - if settings.idp_slo_target_url.nil? + if settings.idp_slo_service_url.nil? logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'" delete_session else - # Since we created a new SAML request, save the transaction_id - # to compare it with the response we get back - logout_request = OneLogin::RubySaml::Logoutrequest.new() - session[:transaction_id] = logout_request.uuid - logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{session[:transaction_id]}'" + logout_request = OneLogin::RubySaml::Logoutrequest.new + logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{logout_request.uuid}'" if settings.name_identifier_value.nil? settings.name_identifier_value = session[:userid] end - relayState = url_for controller: 'saml', action: 'index' + # Ensure user is logged out before redirect to IdP, in case anything goes wrong during single logout process (as recommended by saml2int [SDP-SP34]) + logged_user = session[:userid] + logger.info "Delete session for '#{session[:userid]}'" + delete_session + + # Save the transaction_id to compare it with the response we get back + session[:transaction_id] = logout_request.uuid + session[:logged_out_user] = logged_user + + relayState = url_for(controller: 'saml', action: 'index') redirect_to(logout_request.create(settings, :RelayState => relayState)) end end ``` -and this method process the SAML Logout Response sent by the IdP as reply of the SAML Logout Request +This method processes the SAML Logout Response sent by the IdP as the reply of the SAML Logout Request: ```ruby # After sending an SP initiated LogoutRequest to the IdP, we need to accept @@ -395,8 +858,8 @@ and this method process the SAML Logout Response sent by the IdP as reply of the def process_logout_response settings = Account.get_saml_settings - if session.has_key? :transation_id - logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transation_id]) + if session.has_key? :transaction_id + logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transaction_id]) else logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings) end @@ -408,10 +871,8 @@ def process_logout_response logger.error "The SAML Logout Response is invalid" else # Actually log out this session - if logout_response.success? - logger.info "Delete session for '#{session[:userid]}'" - delete_session - end + logger.info "SLO completed for '#{session[:logged_out_user]}'" + delete_session end end @@ -419,19 +880,27 @@ end def delete_session session[:userid] = nil session[:attributes] = nil + session[:transaction_id] = nil + session[:logged_out_user] = nil end ``` -Here is an example that we could add to our previous controller to process a SAML Logout Request from the IdP and reply a SAML Logout Response to the IdP +Here is an example that we could add to our previous controller to process a SAML Logout Request from the IdP and reply with a SAML Logout Response to the IdP: ```ruby # Method to handle IdP initiated logouts def idp_logout_request settings = Account.get_saml_settings - logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest]) + # ADFS URL-Encodes SAML data as lowercase, and the toolkit by default uses + # uppercase. Turn it True for ADFS compatibility on signature verification + settings.security[:lowercase_url_encoding] = true + + logout_request = OneLogin::RubySaml::SloLogoutrequest.new( + params[:SAMLRequest], settings: settings + ) if !logout_request.is_valid? logger.error "IdP initiated LogoutRequest was not valid!" - render :inline => logger.error + return render :inline => logger.error end logger.info "IdP initiated Logout for #{logout_request.name_id}" @@ -463,50 +932,47 @@ def logout end ``` +## Clock Drift +Server clocks tend to drift naturally. If during validation of the response you get the error "Current time is earlier than NotBefore condition", this may be due to clock differences between your system and that of the Identity Provider. -## Service Provider Metadata - -To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML -to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc) - -The class `OneLogin::RubySaml::Metadata` takes care of this by reading the Settings and returning XML. All you have to do is add a controller to return the data, then give this URL to the IdP administrator. +First, ensure that both systems synchronize their clocks, using for example the industry standard [Network Time Protocol (NTP)](http://en.wikipedia.org/wiki/Network_Time_Protocol). -The metdata will be polled by the IdP every few minutes, so updating your settings should propagate -to the IdP settings. +Even then you may experience intermittent issues, as the clock of the Identity Provider may drift slightly ahead of your system clocks. To allow for a small amount of clock drift, you can initialize the response by passing in an option named `:allowed_clock_drift`. Its value must be given in a number (and/or fraction) of seconds. The value given is added to the current time at which the response is validated before it's tested against the `NotBefore` assertion. For example: ```ruby -class SamlController < ApplicationController - # ... the rest of your controller definitions ... - def metadata - settings = Account.get_saml_settings - meta = OneLogin::RubySaml::Metadata.new - render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml" - end -end +response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_clock_drift => 1.second) ``` -## Clock Drift - -Server clocks tend to drift naturally. If during validation of the response you get the error "Current time is earlier than NotBefore condition" then this may be due to clock differences between your system and that of the Identity Provider. +Make sure to keep the value as comfortably small as possible to keep security risks to a minimum. -First, ensure that both systems synchronize their clocks, using for example the industry standard [Network Time Protocol (NTP)](http://en.wikipedia.org/wiki/Network_Time_Protocol). +## Deflation Limit -Even then you may experience intermittent issues though, because the clock of the Identity Provider may drift slightly ahead of your system clocks. To allow for a small amount of clock drift you can initialize the response passing in an option named `:allowed_clock_drift`. Its value must be given in a number (and/or fraction) of seconds. The value given is added to the current time at which the response is validated before it's tested against the `NotBefore` assertion. For example: +To protect against decompression bombs (a form of DoS attack), SAML messages are limited to 250,000 bytes by default. +Sometimes legitimate SAML messages will exceed this limit, +for example due to custom claims like including groups a user is a member of. +If you want to customize this limit, you need to provide a different setting when initializing the response object. +Example: ```ruby -response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_clock_drift => 1.second) -``` +def consume + response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], { settings: saml_settings }) + ... +end -Make sure to keep the value as comfortably small as possible to keep security risks to a minimum. +private + +def saml_settings + OneLogin::RubySaml::Settings.new(message_max_bytesize: 500_000) +end +``` ## Attribute Service -To request attributes from the IdP the SP needs to provide an attribute service within it's metadata and reference the index in the assertion. +To request attributes from the IdP the SP must provide an attribute service within its metadata and reference the index in the assertion. ```ruby settings = OneLogin::RubySaml::Settings.new - settings.attributes_index = 5 settings.attribute_consuming_service.configure do service_name "Service" @@ -515,3 +981,29 @@ settings.attribute_consuming_service.configure do add_attribute :name => "Another Attribute", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value" end ``` + +The `attribute_value` option additionally accepts an array of possible values. + +## Custom Metadata Fields + +Some IdPs may require SPs to add additional fields (Organization, ContactPerson, etc.) +into the SP metadata. This can be achieved by extending the `OneLogin::RubySaml::Metadata` +class and overriding the `#add_extras` method as per the following example: + +```ruby +class MyMetadata < OneLogin::RubySaml::Metadata + def add_extras(root, _settings) + org = root.add_element("md:Organization") + org.add_element("md:OrganizationName", 'xml:lang' => "en-US").text = 'ACME Inc.' + org.add_element("md:OrganizationDisplayName", 'xml:lang' => "en-US").text = 'ACME' + org.add_element("md:OrganizationURL", 'xml:lang' => "en-US").text = 'https://www.acme.com' + + cp = root.add_element("md:ContactPerson", 'contactType' => 'technical') + cp.add_element("md:GivenName").text = 'ACME SAML Team' + cp.add_element("md:EmailAddress").text = 'saml@acme.com' + end +end + +# Output XML with custom metadata +MyMetadata.new.generate(settings) +``` diff --git a/Rakefile b/Rakefile index 7c2e2c4e9..40fa25605 100644 --- a/Rakefile +++ b/Rakefile @@ -25,17 +25,3 @@ end task :test task :default => :test - -# require 'rake/rdoctask' -# Rake::RDocTask.new do |rdoc| -# if File.exist?('VERSION') -# version = File.read('VERSION') -# else -# version = "" -# end - -# rdoc.rdoc_dir = 'rdoc' -# rdoc.title = "ruby-saml #{version}" -# rdoc.rdoc_files.include('README*') -# rdoc.rdoc_files.include('lib/**/*.rb') -#end diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 000000000..d4ec15fb7 --- /dev/null +++ b/UPGRADING.md @@ -0,0 +1,158 @@ +# Ruby SAML Migration Guide + +## Updating from 1.17.x to 1.18.0 + +Version `1.18.0` changes the way the toolkit validates SAML signatures. There is a new order +how validation happens in the toolkit and also the toolkit by default will check malformed doc +when parsing a SAML Message (`settings.check_malformed_doc`). + +The SignedDocument class defined at xml_security.rb experienced several changes. +We don't expect compatibilty issues if you use the main methods offered by ruby-saml, but if you use a fork or customized usage, is possible that you need to adapt your code. + +## Updating from 1.12.x to 1.13.0 + +Version `1.13.0` adds `settings.idp_sso_service_binding` and `settings.idp_slo_service_binding`, and +deprecates `settings.security[:embed_sign]`. If specified, new binding parameters will be used in place of `:embed_sign` +to determine how to handle SAML message signing (`HTTP-POST` embeds signature and `HTTP-Redirect` does not.) + +In addition, the `IdpMetadataParser#parse`, `#parse_to_hash` and `#parse_to_array` methods now retrieve +`idp_sso_service_binding` and `idp_slo_service_binding`. + +Lastly, for convenience you may now use the Symbol aliases `:post` and `:redirect` for any `settings.*_binding` parameter. + +## Upgrading from 1.11.x to 1.12.0 + +Version `1.12.0` adds support for gcm algorithm and +change/adds specific error messages for signature validations + +`idp_sso_target_url` and `idp_slo_target_url` attributes of the Settings class deprecated +in favor of `idp_sso_service_url` and `idp_slo_service_url`. The `IdpMetadataParser#parse`, +`#parse_to_hash` and `#parse_to_array` methods now retrieve SSO URL and SLO URL endpoints with +`idp_sso_service_url` and `idp_slo_service_url` (previously `idp_sso_target_url` and +`idp_slo_target_url` respectively). + +## Upgrading from 1.10.x to 1.11.0 + +Version `1.11.0` deprecates the use of `settings.issuer` in favour of `settings.sp_entity_id`. +There are two new security settings: `settings.security[:check_idp_cert_expiration]` and +`settings.security[:check_sp_cert_expiration]` (both false by default) that check if the +IdP or SP X.509 certificate has expired, respectively. + +Version `1.10.2` includes the `valid_until` attribute in parsed IdP metadata. + +Version `1.10.1` improves Ruby 1.8.7 support. + +## Upgrading from 1.9.0 to 1.10.0 + +Version `1.10.0` improves IdpMetadataParser to allow parse multiple IDPSSODescriptor, +Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user +to be authenticated and updates the format_cert method to accept certs with /\x0d/ + +## Upgrading from 1.8.0 to 1.9.0 + +Version `1.9.0` better supports Ruby 2.4+ and JRuby 9.2.0.0. `Settings` initialization +now has a second parameter, `keep_security_settings` (default: false), which saves security +settings attributes that are not explicitly overridden, if set to true. + +## Upgrading from 1.7.x to 1.8.0 + +On Version `1.8.0`, creating AuthRequests/LogoutRequests/LogoutResponses with nil RelayState +param will not generate a URL with an empty RelayState parameter anymore. It also changes +the invalid audience error message. + +## Upgrading from 1.6.0 to 1.7.0 + +Version `1.7.0` is a recommended update for all Ruby SAML users as it includes a fix for +the [CVE-2017-11428](https://www.cvedetails.com/cve/CVE-2017-11428/) vulnerability. + +## Upgrading from 1.5.0 to 1.6.0 + +Version `1.6.0` changes the preferred way to construct instances of `Logoutresponse` and +`SloLogoutrequest`. Previously the _SAMLResponse_, _RelayState_, and _SigAlg_ parameters +of these message types were provided via the constructor's `options[:get_params]` parameter. +Unfortunately this can result in incompatibility with other SAML implementations; signatures +are specified to be computed based on the _sender's_ URI-encoding of the message, which can +differ from that of Ruby SAML. In particular, Ruby SAML's URI-encoding does not match that +of Microsoft ADFS, so messages from ADFS can fail signature validation. + +The new preferred way to provide _SAMLResponse_, _RelayState_, and _SigAlg_ is via the +`options[:raw_get_params]` parameter. For example: + +```ruby +# In this example `query_params` is assumed to contain decoded query parameters, +# and `raw_query_params` is assumed to contain encoded query parameters as sent by the IDP. +settings = { + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.soft = false +} +options = { + get_params: { + "Signature" => query_params["Signature"], + }, + raw_get_params: { + "SAMLRequest" => raw_query_params["SAMLRequest"], + "SigAlg" => raw_query_params["SigAlg"], + "RelayState" => raw_query_params["RelayState"], + }, +} +slo_logout_request = OneLogin::RubySaml::SloLogoutrequest.new(query_params["SAMLRequest"], settings, options) +raise "Invalid Logout Request" unless slo_logout_request.is_valid? +``` + +The old form is still supported for backward compatibility, but all Ruby SAML users +should prefer `options[:raw_get_params]` where possible to ensure compatibility with +other SAML implementations. + +## Upgrading from 1.4.2 to 1.4.3 + +Version `1.4.3` introduces Recipient validation of SubjectConfirmation elements. +The 'Recipient' value is compared with the settings.assertion_consumer_service_url +value. + +If you want to skip that validation, add the :skip_recipient_check option to the +initialize method of the Response object. + +Parsing metadata that contains more than one certificate will propagate the +idp_cert_multi property rather than idp_cert. See [signature validation +section](#signature-validation) for details. + +## Upgrading from 1.3.x to 1.4.x + +Version `1.4.0` is a recommended update for all Ruby SAML users as it includes security improvements. + +## Upgrading from 1.2.x to 1.3.x + +Version `1.3.0` is a recommended update for all Ruby SAML users as it includes security fixes. +It adds security improvements in order to prevent Signature wrapping attacks. +[CVE-2016-5697](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5697) + +## Upgrading from 1.1.x to 1.2.x + +Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom, +refactor error handling and some minor improvements. + +There is no compatibility issue detected. + +For more details, please review [CHANGELOG.md](CHANGELOG.md). + +## Upgrading from 1.0.x to 1.1.x + +Version `1.1` adds some improvements on signature validation and solves some namespace conflicts. + +## Upgrading from 0.9.x to 1.0.x + +Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes. + +Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support. + +### Important Changes + +Please note the `get_idp_metadata` method raises an exception when it is not able to fetch the idp metadata, so review your integration if you are using this functionality. + +## Upgrading from 0.8.x to 0.9.x + +Version `0.9` adds many new features and improvements. + +## Upgrading from 0.7.x to 0.8.x + +Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly. diff --git a/changelog.md b/changelog.md deleted file mode 100644 index 6dcc0c6c5..000000000 --- a/changelog.md +++ /dev/null @@ -1,41 +0,0 @@ -# RubySaml Changelog -### 0.9.1 (Feb 10, 2015) -* [#194](https://github.com/onelogin/ruby-saml/pull/194) Relax nokogiri gem requirements -* [#191](https://github.com/onelogin/ruby-saml/pull/191) Use Minitest instead of Test::Unit - -### 0.9 (Jan 26, 2015) -* [#169](https://github.com/onelogin/ruby-saml/pull/169) WantAssertionSigned should be either true or false -* [#167](https://github.com/onelogin/ruby-saml/pull/167) (doc update) make unit of clock drift obvious -* [#160](https://github.com/onelogin/ruby-saml/pull/160) Extended solution for Attributes method [] can raise NoMethodError -* [#158](https://github.com/onelogin/ruby-saml/pull/1) Added ability to specify attribute services in metadata -* [#154](https://github.com/onelogin/ruby-saml/pull/154) Fix incorrect gem declaration statement -* [#152](https://github.com/onelogin/ruby-saml/pull/152) Fix the PR #99 -* [#150](https://github.com/onelogin/ruby-saml/pull/150) Nokogiri already in gemspec -* [#147](https://github.com/onelogin/ruby-saml/pull/147) Fix LogoutResponse issuer validation and implement SAML Response issuer validation. -* [#144](https://github.com/onelogin/ruby-saml/pull/144) Fix DigestMethod lookup bug -* [#139](https://github.com/onelogin/ruby-saml/pull/139) Fixes handling of some soft and hard validation failures -* [#138](https://github.com/onelogin/ruby-saml/pull/138) Change logoutrequest.rb to UTC time -* [#136](https://github.com/onelogin/ruby-saml/pull/136) Remote idp metadata -* [#135](https://github.com/onelogin/ruby-saml/pull/135) Restored support for NIL as well as empty AttributeValues -* [#134](https://github.com/onelogin/ruby-saml/pull/134) explicitly require "onelogin/ruby-saml/logging" -* [#133](https://github.com/onelogin/ruby-saml/pull/133) Added license to gemspec -* [#132](https://github.com/onelogin/ruby-saml/pull/132) Support AttributeConsumingServiceIndex in AuthnRequest -* [#131](https://github.com/onelogin/ruby-saml/pull/131) Add ruby 2.1.1 to .travis.yml -* [#122](https://github.com/onelogin/ruby-saml/pull/122) Fixes #112 and #117 in a backwards compatible manner -* [#119](https://github.com/onelogin/ruby-saml/pull/119) Add support for extracting IdP details from metadata xml - -### 0.8.2 (Jan 26, 2015) -* [#183](https://github.com/onelogin/ruby-saml/pull/183) Resolved a security vulnerability where string interpolation in a `REXML::XPath.first()` method call allowed for arbitrary code execution. - -### 0.8.0 (Feb 21, 2014) -**IMPORTANT**: This release changed namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly. - -* [#111](https://github.com/onelogin/ruby-saml/pull/111) `Onelogin::` is `OneLogin::` -* [#108](https://github.com/onelogin/ruby-saml/pull/108) Change namespacing from `Onelogin::Saml` to `Onelogin::Rubysaml` - - -### 0.7.3 (Feb 20, 2014) -Updated gem dependencies to be compatible with Ruby 1.8.7-p374 and 1.9.3-p448. Removed unnecessary `canonix` gem dependency. - -* [#107](https://github.com/onelogin/ruby-saml/pull/107) Relax nokogiri version requirement to >= 1.5.0 -* [#105](https://github.com/onelogin/ruby-saml/pull/105) Lock Gem versions, fix to resolve possible namespace collision diff --git a/lib/onelogin/ruby-saml.rb b/lib/onelogin/ruby-saml.rb index 135f7ec09..61b79b640 100644 --- a/lib/onelogin/ruby-saml.rb +++ b/lib/onelogin/ruby-saml.rb @@ -9,6 +9,7 @@ require 'onelogin/ruby-saml/response' require 'onelogin/ruby-saml/settings' require 'onelogin/ruby-saml/attribute_service' +require 'onelogin/ruby-saml/http_error' require 'onelogin/ruby-saml/validation_error' require 'onelogin/ruby-saml/metadata' require 'onelogin/ruby-saml/idp_metadata_parser' diff --git a/lib/onelogin/ruby-saml/attribute_service.rb b/lib/onelogin/ruby-saml/attribute_service.rb index e3aa5f2ff..1571e7f54 100644 --- a/lib/onelogin/ruby-saml/attribute_service.rb +++ b/lib/onelogin/ruby-saml/attribute_service.rb @@ -1,33 +1,56 @@ module OneLogin module RubySaml + + # SAML2 AttributeService. Auxiliary class to build the AttributeService of the SP Metadata + # class AttributeService attr_reader :attributes attr_reader :name attr_reader :index + # Initializes the AttributeService, set the index value as 1 and an empty array as attributes + # def initialize @index = "1" @attributes = [] end def configure(&block) - instance_eval &block + instance_eval(&block) end + # @return [Boolean] True if the AttributeService object has been initialized and set with the required values + # (has attributes and a name) def configured? @attributes.length > 0 && !@name.nil? end + # Set a name to the service + # @param name [String] The service name + # def service_name(name) @name = name end + # Set an index to the service + # @param index [Integer] An index + # def service_index(index) @index = index end - + + # Add an AttributeService + # @param options [Hash] AttributeService option values + # add_attribute( + # :name => "Name", + # :name_format => "Name Format", + # :index => 1, + # :friendly_name => "Friendly Name", + # :attribute_value => "Attribute Value" + # ) + # def add_attribute(options={}) - attributes << options + attributes << options end end end diff --git a/lib/onelogin/ruby-saml/attributes.rb b/lib/onelogin/ruby-saml/attributes.rb index edaedf155..cb4ad9648 100644 --- a/lib/onelogin/ruby-saml/attributes.rb +++ b/lib/onelogin/ruby-saml/attributes.rb @@ -1,91 +1,110 @@ module OneLogin module RubySaml - # Wraps all attributes and provides means to query them for single or multiple values. - # - # For backwards compatibility Attributes#[] returns *first* value for the attribute. - # Turn off compatibility to make it return all values as an array: - # Attributes.single_value_compatibility = false + + # SAML2 Attributes. Parse the Attributes from the AttributeStatement of the SAML Response. + # class Attributes include Enumerable + attr_reader :attributes + # By default Attributes#[] is backwards compatible and # returns only the first value for the attribute # Setting this to `false` returns all values for an attribute @@single_value_compatibility = true - # Get current status of backwards compatibility mode. + # @return [Boolean] Get current status of backwards compatibility mode. + # def self.single_value_compatibility @@single_value_compatibility end # Sets the backwards compatibility mode on/off. + # @param value [Boolean] + # def self.single_value_compatibility=(value) @@single_value_compatibility = value end - # Initialize Attributes collection, optionally taking a Hash of attribute names and values. - # - # The +attrs+ must be a Hash with attribute names as keys and **arrays** as values: + # @param attrs [Hash] The +attrs+ must be a Hash with attribute names as keys and **arrays** as values: # Attributes.new({ # 'name' => ['value1', 'value2'], # 'mail' => ['value1'], # }) + # def initialize(attrs = {}) @attributes = attrs end # Iterate over all attributes + # def each attributes.each{|name, values| yield name, values} end + # Test attribute presence by name + # @param name [String] The attribute name to be checked + # def include?(name) attributes.has_key?(canonize_name(name)) end # Return first value for an attribute + # @param name [String] The attribute name + # @return [String] The value (First occurrence) + # def single(name) attributes[canonize_name(name)].first if include?(name) end # Return all values for an attribute + # @param name [String] The attribute name + # @return [Array] Values of the attribute + # def multi(name) attributes[canonize_name(name)] end - # By default returns first value for an attribute. + # Retrieve attribute value(s) + # @param name [String] The attribute name + # @return [String|Array] Depending on the single value compatibility status this returns: + # - First value if single_value_compatibility = true + # response.attributes['mail'] # => 'user@example.com' + # - All values if single_value_compatibility = false + # response.attributes['mail'] # => ['user@example.com','user@example.net'] # - # Depending on the single value compatibility status this returns first value - # Attributes.single_value_compatibility = true # Default - # response.attributes['mail'] # => 'user@example.com' - # - # Or all values: - # Attributes.single_value_compatibility = false - # response.attributes['mail'] # => ['user@example.com','user@example.net'] def [](name) self.class.single_value_compatibility ? single(canonize_name(name)) : multi(canonize_name(name)) end - # Return all attributes as an array + # @return [Hash] Return all attributes as a hash + # def all attributes end - # Set values for an attribute, overwriting all existing values + # @param name [String] The attribute name + # @param values [Array] The values + # def set(name, values) attributes[canonize_name(name)] = values end alias_method :[]=, :set - # Add new attribute or new value(s) to an existing attribute + # @param name [String] The attribute name + # @param values [Array] The values + # def add(name, values = []) attributes[canonize_name(name)] ||= [] attributes[canonize_name(name)] += Array(values) end # Make comparable to another Attributes collection based on attributes + # @param other [Attributes] An Attributes object to compare with + # @return [Boolean] True if are contains the same attributes and values + # def ==(other) if other.is_a?(Attributes) all == other.all @@ -94,16 +113,39 @@ def ==(other) end end + # Fetch attribute value using name or regex + # @param name [String|Regexp] The attribute name + # @return [String|Array] Depending on the single value compatibility status this returns: + # - First value if single_value_compatibility = true + # response.attributes['mail'] # => 'user@example.com' + # - All values if single_value_compatibility = false + # response.attributes['mail'] # => ['user@example.com','user@example.net'] + # + def fetch(name) + attributes.each_key do |attribute_key| + if name.is_a?(Regexp) + if name.respond_to? :match? + return self[attribute_key] if name.match?(attribute_key) + else + return self[attribute_key] if name.match(attribute_key) + end + elsif canonize_name(name) == canonize_name(attribute_key) + return self[attribute_key] + end + end + nil + end + protected # stringifies all names so both 'email' and :email return the same result + # @param name [String] The attribute name + # @return [String] stringified name + # def canonize_name(name) name.to_s end - def attributes - @attributes - end end end end diff --git a/lib/onelogin/ruby-saml/authrequest.rb b/lib/onelogin/ruby-saml/authrequest.rb index f12e797ae..78a8f385d 100644 --- a/lib/onelogin/ruby-saml/authrequest.rb +++ b/lib/onelogin/ruby-saml/authrequest.rb @@ -1,41 +1,70 @@ -require "uuid" require "rexml/document" require "onelogin/ruby-saml/logging" require "onelogin/ruby-saml/saml_message" +require "onelogin/ruby-saml/utils" +require "onelogin/ruby-saml/setting_error" +# Only supports SAML 2.0 module OneLogin module RubySaml include REXML + + # SAML2 Authentication. AuthNRequest (SSO SP initiated, Builder) + # class Authrequest < SamlMessage - attr_reader :uuid # Can be obtained if neccessary + # AuthNRequest ID + attr_accessor :uuid + # Initializes the AuthNRequest. An Authrequest Object that is an extension of the SamlMessage class. + # Asigns an ID, a random uuid. + # def initialize - @uuid = "_" + UUID.new.generate + @uuid = OneLogin::RubySaml::Utils.uuid + end + + def request_id + @uuid end + # Creates the AuthNRequest string. + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState + # @return [String] AuthNRequest string that includes the SAMLRequest + # def create(settings, params = {}) params = create_params(settings, params) - params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?' + params_prefix = (settings.idp_sso_service_url =~ /\?/) ? '&' : '?' saml_request = CGI.escape(params.delete("SAMLRequest")) request_params = "#{params_prefix}SAMLRequest=#{saml_request}" params.each_pair do |key, value| - request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}" + request_params << "&#{key}=#{CGI.escape(value.to_s)}" end - @login_url = settings.idp_sso_target_url + request_params + raise SettingError.new "Invalid settings, idp_sso_service_url is not set!" if settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty? + @login_url = settings.idp_sso_service_url + request_params end + # Creates the Get parameters for the request. + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState + # @return [Hash] Parameters + # def create_params(settings, params={}) # The method expects :RelayState but sometimes we get 'RelayState' instead. # Based on the HashWithIndifferentAccess value in Rails we could experience # conflicts so this line will solve them. relay_state = params[:RelayState] || params['RelayState'] + if relay_state.nil? + params.delete(:RelayState) + params.delete('RelayState') + end + request_doc = create_authentication_xml_doc(settings) request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values - request = "" + request = "".dup request_doc.write(request) Logging.debug "Created AuthnRequest: #{request}" @@ -43,14 +72,18 @@ def create_params(settings, params={}) request = deflate(request) if settings.compress_request base64_request = encode(request) request_params = {"SAMLRequest" => base64_request} - - if settings.security[:authn_requests_signed] && !settings.security[:embed_sign] && settings.private_key - params['SigAlg'] = settings.security[:signature_method] - url_string = "SAMLRequest=#{CGI.escape(base64_request)}" - url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state - url_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - private_key = settings.get_sp_key() - signature = private_key.sign(XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]).new, url_string) + sp_signing_key = settings.get_sp_signing_key + + if settings.idp_sso_service_binding == Utils::BINDINGS[:redirect] && settings.security[:authn_requests_signed] && sp_signing_key + params['SigAlg'] = settings.security[:signature_method] + url_string = OneLogin::RubySaml::Utils.build_query( + :type => 'SAMLRequest', + :data => base64_request, + :relay_state => relay_state, + :sig_alg => params['SigAlg'] + ) + sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + signature = sp_signing_key.sign(sign_algorithm.new, url_string) params['Signature'] = encode(signature) end @@ -61,7 +94,16 @@ def create_params(settings, params={}) request_params end + # Creates the SAMLRequest String. + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @return [String] The SAMLRequest String. + # def create_authentication_xml_doc(settings) + document = create_xml_document(settings) + sign_document(document, settings) + end + + def create_xml_document(settings) time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") request_doc = XMLSecurity::Document.new @@ -71,7 +113,7 @@ def create_authentication_xml_doc(settings) root.attributes['ID'] = uuid root.attributes['IssueInstant'] = time root.attributes['Version'] = "2.0" - root.attributes['Destination'] = settings.idp_sso_target_url unless settings.idp_sso_target_url.nil? + root.attributes['Destination'] = settings.idp_sso_service_url unless settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty? root.attributes['IsPassive'] = settings.passive unless settings.passive.nil? root.attributes['ProtocolBinding'] = settings.protocol_binding unless settings.protocol_binding.nil? root.attributes["AttributeConsumingServiceIndex"] = settings.attributes_index unless settings.attributes_index.nil? @@ -81,10 +123,22 @@ def create_authentication_xml_doc(settings) if settings.assertion_consumer_service_url != nil root.attributes["AssertionConsumerServiceURL"] = settings.assertion_consumer_service_url end - if settings.issuer != nil + if settings.sp_entity_id != nil issuer = root.add_element "saml:Issuer" - issuer.text = settings.issuer + issuer.text = settings.sp_entity_id end + + if settings.name_identifier_value_requested != nil + subject = root.add_element "saml:Subject" + + nameid = subject.add_element "saml:NameID" + nameid.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format + nameid.text = settings.name_identifier_value_requested + + subject_confirmation = subject.add_element "saml:SubjectConfirmation" + subject_confirmation.attributes['Method'] = "urn:oasis:names:tc:SAML:2.0:cm:bearer" + end + if settings.name_identifier_format != nil root.add_element "samlp:NameIDPolicy", { # Might want to make AllowCreate a setting? @@ -106,26 +160,33 @@ def create_authentication_xml_doc(settings) } if settings.authn_context != nil - class_ref = requested_context.add_element "saml:AuthnContextClassRef" - class_ref.text = settings.authn_context + authn_contexts_class_ref = settings.authn_context.is_a?(Array) ? settings.authn_context : [settings.authn_context] + authn_contexts_class_ref.each do |authn_context_class_ref| + class_ref = requested_context.add_element "saml:AuthnContextClassRef" + class_ref.text = authn_context_class_ref + end end - # add saml:AuthnContextDeclRef element + if settings.authn_context_decl_ref != nil - class_ref = requested_context.add_element "saml:AuthnContextDeclRef" - class_ref.text = settings.authn_context_decl_ref + authn_contexts_decl_refs = settings.authn_context_decl_ref.is_a?(Array) ? settings.authn_context_decl_ref : [settings.authn_context_decl_ref] + authn_contexts_decl_refs.each do |authn_context_decl_ref| + decl_ref = requested_context.add_element "saml:AuthnContextDeclRef" + decl_ref.text = authn_context_decl_ref + end end end - # embed signature - if settings.security[:authn_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign] - private_key = settings.get_sp_key() - cert = settings.get_sp_cert() - request_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) - end - request_doc end + def sign_document(document, settings) + cert, private_key = settings.get_sp_signing_pair + if settings.idp_sso_service_binding == Utils::BINDINGS[:post] && settings.security[:authn_requests_signed] && private_key && cert + document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) + end + + document + end end end end diff --git a/lib/onelogin/ruby-saml/error_handling.rb b/lib/onelogin/ruby-saml/error_handling.rb new file mode 100644 index 000000000..02f0c62f7 --- /dev/null +++ b/lib/onelogin/ruby-saml/error_handling.rb @@ -0,0 +1,27 @@ +require "onelogin/ruby-saml/validation_error" + +module OneLogin + module RubySaml + module ErrorHandling + attr_accessor :errors + + # Append the cause to the errors array, and based on the value of soft, return false or raise + # an exception. soft_override is provided as a means of overriding the object's notion of + # soft for just this invocation. + def append_error(error_msg, soft_override = nil) + @errors << error_msg + + unless soft_override.nil? ? soft : soft_override + raise ValidationError.new(error_msg) + end + + false + end + + # Reset the errors array + def reset_errors! + @errors = [] + end + end + end +end diff --git a/lib/onelogin/ruby-saml/http_error.rb b/lib/onelogin/ruby-saml/http_error.rb new file mode 100644 index 000000000..7f77c56ac --- /dev/null +++ b/lib/onelogin/ruby-saml/http_error.rb @@ -0,0 +1,7 @@ +module OneLogin + module RubySaml + class HttpError < StandardError + end + end +end + diff --git a/lib/onelogin/ruby-saml/idp_metadata_parser.rb b/lib/onelogin/ruby-saml/idp_metadata_parser.rb index 89be03eb6..08b030ce7 100644 --- a/lib/onelogin/ruby-saml/idp_metadata_parser.rb +++ b/lib/onelogin/ruby-saml/idp_metadata_parser.rb @@ -1,105 +1,470 @@ require "base64" -require "uuid" -require "zlib" -require "cgi" require "net/http" require "net/https" require "rexml/document" require "rexml/xpath" +# Only supports SAML 2.0 module OneLogin module RubySaml include REXML + # Auxiliary class to retrieve and parse the Identity Provider Metadata + # + # This class does not validate in any way the URL that is introduced, + # make sure to validate it properly before use it in a parse_remote method. + # Read the `Security warning` section of the README.md file to get more info + # class IdpMetadataParser - METADATA = "urn:oasis:names:tc:SAML:2.0:metadata" - DSIG = "http://www.w3.org/2000/09/xmldsig#" + module SamlMetadata + module Vocabulary + METADATA = "urn:oasis:names:tc:SAML:2.0:metadata".freeze + DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze + NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*".freeze + SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze + end + + NAMESPACE = { + "md" => Vocabulary::METADATA, + "NameFormat" => Vocabulary::NAME_FORMAT, + "saml" => Vocabulary::SAML_ASSERTION, + "ds" => Vocabulary::DSIG + }.freeze + end + include SamlMetadata::Vocabulary attr_reader :document + attr_reader :response + attr_reader :options - def parse_remote(url, validate_cert = true) + # fetch IdP descriptors from a metadata document + def self.get_idps(metadata_document, only_entity_id=nil) + path = "//md:EntityDescriptor#{only_entity_id && '[@entityID="' + only_entity_id + '"]'}/md:IDPSSODescriptor" + REXML::XPath.match( + metadata_document, + path, + SamlMetadata::NAMESPACE + ) + end + + # Parse the Identity Provider metadata and update the settings with the + # IdP values + # + # @param url [String] Url where the XML of the Identity Provider Metadata is published. + # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked. + # + # @param options [Hash] options used for parsing the metadata and the returned Settings instance + # @option options [OneLogin::RubySaml::Settings, Hash] :settings the OneLogin::RubySaml::Settings object which gets the parsed metadata merged into or an hash for Settings overrides. + # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used. + # @option options [String, Array, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used. + # + # @return [OneLogin::RubySaml::Settings] + # + # @raise [HttpError] Failure to fetch remote IdP metadata + def parse_remote(url, validate_cert = true, options = {}) idp_metadata = get_idp_metadata(url, validate_cert) - parse(idp_metadata) + parse(idp_metadata, options) end - def parse(idp_metadata) - @document = REXML::Document.new(idp_metadata) + # Parse the Identity Provider metadata and return the results as Hash + # + # @param url [String] Url where the XML of the Identity Provider Metadata is published. + # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked. + # + # @param options [Hash] options used for parsing the metadata + # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used. + # @option options [String, Array, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used. + # + # @return [Hash] + # + # @raise [HttpError] Failure to fetch remote IdP metadata + def parse_remote_to_hash(url, validate_cert = true, options = {}) + parse_remote_to_array(url, validate_cert, options)[0] + end + + # Parse all Identity Provider metadata and return the results as Array + # + # @param url [String] Url where the XML of the Identity Provider Metadata is published. + # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked. + # + # @param options [Hash] options used for parsing the metadata + # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, all found IdPs are returned. + # @option options [String, Array, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used. + # + # @return [Array] + # + # @raise [HttpError] Failure to fetch remote IdP metadata + def parse_remote_to_array(url, validate_cert = true, options = {}) + idp_metadata = get_idp_metadata(url, validate_cert) + parse_to_array(idp_metadata, options) + end + + # Parse the Identity Provider metadata and update the settings with the IdP values + # + # @param idp_metadata [String] + # + # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides + # @option options [OneLogin::RubySaml::Settings, Hash] :settings the OneLogin::RubySaml::Settings object which gets the parsed metadata merged into or an hash for Settings overrides. + # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used. + # @option options [String, Array, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used. + # + # @return [OneLogin::RubySaml::Settings] + def parse(idp_metadata, options = {}) + parsed_metadata = parse_to_hash(idp_metadata, options) - OneLogin::RubySaml::Settings.new.tap do |settings| - settings.idp_entity_id = idp_entity_id - settings.name_identifier_format = idp_name_id_format - settings.idp_sso_target_url = single_signon_service_url - settings.idp_slo_target_url = single_logout_service_url - settings.idp_cert_fingerprint = fingerprint + unless parsed_metadata[:cache_duration].nil? + cache_valid_until_timestamp = OneLogin::RubySaml::Utils.parse_duration(parsed_metadata[:cache_duration]) + unless cache_valid_until_timestamp.nil? + if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until], Time.now.utc).to_i + parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).utc.strftime("%Y-%m-%dT%H:%M:%SZ") + end + end + end + # Remove the cache_duration because on the settings + # we only gonna suppot valid_until + parsed_metadata.delete(:cache_duration) + + settings = options[:settings] + + if settings.nil? + OneLogin::RubySaml::Settings.new(parsed_metadata) + elsif settings.is_a?(Hash) + OneLogin::RubySaml::Settings.new(settings.merge(parsed_metadata)) + else + merge_parsed_metadata_into(settings, parsed_metadata) end end - private + # Parse the Identity Provider metadata and return the results as Hash + # + # @param idp_metadata [String] + # + # @param options [Hash] options used for parsing the metadata and the returned Settings instance + # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used. + # @option options [String, Array, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used. + # + # @return [Hash] + def parse_to_hash(idp_metadata, options = {}) + parse_to_array(idp_metadata, options)[0] + end + + # Parse all Identity Provider metadata and return the results as Array + # + # @param idp_metadata [String] + # + # @param options [Hash] options used for parsing the metadata and the returned Settings instance + # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, all found IdPs are returned. + # @option options [String, Array, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used. + # @option options [String, Array, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used. + # + # @return [Array] + def parse_to_array(idp_metadata, options = {}) + parse_to_idp_metadata_array(idp_metadata, options).map { |idp_md| idp_md.to_hash(options) } + end + + def parse_to_idp_metadata_array(idp_metadata, options = {}) + @document = REXML::Document.new(idp_metadata) + @options = options + + idpsso_descriptors = self.class.get_idps(@document, options[:entity_id]) + if !idpsso_descriptors.any? + raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element") + end + + idpsso_descriptors.map {|id| IdpMetadata.new(id, id.parent.attributes["entityID"])} + end - # Retrieve the remote IdP metadata from the URL or a cached copy - # # returns a REXML document of the metadata + # Retrieve the remote IdP metadata from the URL or a cached copy. + # @param url [String] Url where the XML of the Identity Provider Metadata is published. + # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked. + # @return [REXML::document] Parsed XML IdP metadata + # @raise [HttpError] Failure to fetch remote IdP metadata def get_idp_metadata(url, validate_cert) uri = URI.parse(url) - if uri.scheme == "http" - response = Net::HTTP.get_response(uri) - meta_text = response.body - elsif uri.scheme == "https" - http = Net::HTTP.new(uri.host, uri.port) + raise ArgumentError.new("url must begin with http or https") unless /^https?/ =~ uri.scheme + http = Net::HTTP.new(uri.host, uri.port) + + if uri.scheme == "https" http.use_ssl = true # Most IdPs will probably use self signed certs - if validate_cert - http.verify_mode = OpenSSL::SSL::VERIFY_PEER + http.verify_mode = validate_cert ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE - # Net::HTTP in Ruby 1.8 did not set the default certificate store - # automatically when VERIFY_PEER was specified. - if RUBY_VERSION < '1.9' && !http.ca_file && !http.ca_path && !http.cert_store - http.cert_store = OpenSSL::SSL::SSLContext::DEFAULT_CERT_STORE - end - else - http.verify_mode = OpenSSL::SSL::VERIFY_NONE + # Net::HTTP in Ruby 1.8 did not set the default certificate store + # automatically when VERIFY_PEER was specified. + if RUBY_VERSION < '1.9' && !http.ca_file && !http.ca_path && !http.cert_store + http.cert_store = OpenSSL::SSL::SSLContext::DEFAULT_CERT_STORE end - get = Net::HTTP::Get.new(uri.request_uri) - response = http.request(get) - meta_text = response.body end - meta_text - end - def idp_entity_id - node = REXML::XPath.first(document, "/md:EntityDescriptor/@entityID", { "md" => METADATA }) - node.value if node - end + get = Net::HTTP::Get.new(uri.request_uri) + get.basic_auth uri.user, uri.password if uri.user + @response = http.request(get) + return response.body if response.is_a? Net::HTTPSuccess - def idp_name_id_format - node = REXML::XPath.first(document, "/md:EntityDescriptor/md:IDPSSODescriptor/md:NameIDFormat", { "md" => METADATA }) - node.text if node + raise OneLogin::RubySaml::HttpError.new( + "Failed to fetch idp metadata: #{response.code}: #{response.message}" + ) end - def single_signon_service_url - node = REXML::XPath.first(document, "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Location", { "md" => METADATA }) - node.value if node - end + private - def single_logout_service_url - node = REXML::XPath.first(document, "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService/@Location", { "md" => METADATA }) - node.value if node - end + class IdpMetadata + attr_reader :idpsso_descriptor, :entity_id - def certificate - @certificate ||= begin - node = REXML::XPath.first(document, "/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate", { "md" => METADATA, "ds" => DSIG }) - Base64.decode64(node.text) if node + def initialize(idpsso_descriptor, entity_id) + @idpsso_descriptor = idpsso_descriptor + @entity_id = entity_id + end + + def to_hash(options = {}) + sso_binding = options[:sso_binding] + slo_binding = options[:slo_binding] + { + :idp_entity_id => @entity_id, + :name_identifier_format => idp_name_id_format(options[:name_id_format]), + :idp_sso_service_url => single_signon_service_url(sso_binding), + :idp_sso_service_binding => single_signon_service_binding(sso_binding), + :idp_slo_service_url => single_logout_service_url(slo_binding), + :idp_slo_service_binding => single_logout_service_binding(slo_binding), + :idp_slo_response_service_url => single_logout_response_service_url(slo_binding), + :idp_attribute_names => attribute_names, + :idp_cert => nil, + :idp_cert_fingerprint => nil, + :idp_cert_multi => nil, + :valid_until => valid_until, + :cache_duration => cache_duration, + }.tap do |response_hash| + merge_certificates_into(response_hash) unless certificates.nil? + end + end + + # @return [String|nil] 'validUntil' attribute of metadata + # + def valid_until + root = @idpsso_descriptor.root + root.attributes['validUntil'] if root && root.attributes + end + + # @return [String|nil] 'cacheDuration' attribute of metadata + # + def cache_duration + root = @idpsso_descriptor.root + root.attributes['cacheDuration'] if root && root.attributes + end + + # @param name_id_priority [String|Array] The prioritized list of NameIDFormat values to select. Will select first value if nil. + # @return [String|nil] IdP NameIDFormat value if exists + # + def idp_name_id_format(name_id_priority = nil) + nodes = REXML::XPath.match( + @idpsso_descriptor, + "md:NameIDFormat", + SamlMetadata::NAMESPACE + ) + first_ranked_text(nodes, name_id_priority) + end + + # @param binding_priority [String|Array] The prioritized list of Binding values to select. Will select first value if nil. + # @return [String|nil] SingleSignOnService binding if exists + # + def single_signon_service_binding(binding_priority = nil) + nodes = REXML::XPath.match( + @idpsso_descriptor, + "md:SingleSignOnService/@Binding", + SamlMetadata::NAMESPACE + ) + first_ranked_value(nodes, binding_priority) end - end - def fingerprint - @fingerprint ||= begin - if certificate - cert = OpenSSL::X509::Certificate.new(certificate) - Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":") + # @param binding_priority [String|Array] The prioritized list of Binding values to select. Will select first value if nil. + # @return [String|nil] SingleLogoutService binding if exists + # + def single_logout_service_binding(binding_priority = nil) + nodes = REXML::XPath.match( + @idpsso_descriptor, + "md:SingleLogoutService/@Binding", + SamlMetadata::NAMESPACE + ) + first_ranked_value(nodes, binding_priority) + end + + # @param binding_priority [String|Array] The prioritized list of Binding values to select. Will select first value if nil. + # @return [String|nil] SingleSignOnService endpoint if exists + # + def single_signon_service_url(binding_priority = nil) + binding = single_signon_service_binding(binding_priority) + return if binding.nil? + + node = REXML::XPath.first( + @idpsso_descriptor, + "md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location", + SamlMetadata::NAMESPACE + ) + node.value if node + end + + # @param binding_priority [String|Array] The prioritized list of Binding values to select. Will select first value if nil. + # @return [String|nil] SingleLogoutService endpoint if exists + # + def single_logout_service_url(binding_priority = nil) + binding = single_logout_service_binding(binding_priority) + return if binding.nil? + + node = REXML::XPath.first( + @idpsso_descriptor, + "md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location", + SamlMetadata::NAMESPACE + ) + node.value if node + end + + # @param binding_priority [String|Array] The prioritized list of Binding values to select. Will select first value if nil. + # @return [String|nil] SingleLogoutService response url if exists + # + def single_logout_response_service_url(binding_priority = nil) + binding = single_logout_service_binding(binding_priority) + return if binding.nil? + + node = REXML::XPath.first( + @idpsso_descriptor, + "md:SingleLogoutService[@Binding=\"#{binding}\"]/@ResponseLocation", + SamlMetadata::NAMESPACE + ) + node.value if node + end + + # @return [String|nil] Unformatted Certificate if exists + # + def certificates + @certificates ||= begin + signing_nodes = REXML::XPath.match( + @idpsso_descriptor, + "md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate", + SamlMetadata::NAMESPACE + ) + + encryption_nodes = REXML::XPath.match( + @idpsso_descriptor, + "md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate", + SamlMetadata::NAMESPACE + ) + + return nil if signing_nodes.empty? && encryption_nodes.empty? + + certs = {} + unless signing_nodes.empty? + certs['signing'] = [] + signing_nodes.each do |cert_node| + certs['signing'] << Utils.element_text(cert_node) + end + end + + unless encryption_nodes.empty? + certs['encryption'] = [] + encryption_nodes.each do |cert_node| + certs['encryption'] << Utils.element_text(cert_node) + end + end + certs + end + end + + # @return [String|nil] the fingerpint of the X509Certificate if it exists + # + def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1) + return unless certificate + + cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate)) + + fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new + fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":") + end + + # @return [Array] the names of all SAML attributes if any exist + # + def attribute_names + nodes = REXML::XPath.match( + @idpsso_descriptor , + "saml:Attribute/@Name", + SamlMetadata::NAMESPACE + ) + nodes.map(&:value) + end + + def merge_certificates_into(parsed_metadata) + if (certificates.size == 1 && + (certificates_has_one('signing') || certificates_has_one('encryption'))) || + (certificates_has_one('signing') && certificates_has_one('encryption') && + certificates["signing"][0] == certificates["encryption"][0]) + + if certificates.key?("signing") + parsed_metadata[:idp_cert] = certificates["signing"][0] + parsed_metadata[:idp_cert_fingerprint] = fingerprint( + parsed_metadata[:idp_cert], + parsed_metadata[:idp_cert_fingerprint_algorithm] + ) + else + parsed_metadata[:idp_cert] = certificates["encryption"][0] + parsed_metadata[:idp_cert_fingerprint] = fingerprint( + parsed_metadata[:idp_cert], + parsed_metadata[:idp_cert_fingerprint_algorithm] + ) + end end + + # symbolize keys of certificates and pass it on + parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }] end + + def certificates_has_one(key) + certificates.key?(key) && certificates[key].size == 1 + end + + private + + def first_ranked_text(nodes, priority = nil) + return unless nodes.any? + + priority = Array(priority) + if priority.any? + values = nodes.map(&:text) + priority.detect { |candidate| values.include?(candidate) } + else + nodes.first.text + end + end + + def first_ranked_value(nodes, priority = nil) + return unless nodes.any? + + priority = Array(priority) + if priority.any? + values = nodes.map(&:value) + priority.detect { |candidate| values.include?(candidate) } + else + nodes.first.value + end + end + end + + def merge_parsed_metadata_into(settings, parsed_metadata) + parsed_metadata.each do |key, value| + settings.send("#{key}=".to_sym, value) + end + + settings end end end diff --git a/lib/onelogin/ruby-saml/logging.rb b/lib/onelogin/ruby-saml/logging.rb index a6e45ad3f..862fcefec 100644 --- a/lib/onelogin/ruby-saml/logging.rb +++ b/lib/onelogin/ruby-saml/logging.rb @@ -1,25 +1,32 @@ +require 'logger' + # Simplistic log class when we're running in Rails module OneLogin module RubySaml class Logging + DEFAULT_LOGGER = ::Logger.new(STDOUT) + + def self.logger + @logger ||= begin + (defined?(::Rails) && Rails.respond_to?(:logger) && Rails.logger) || + DEFAULT_LOGGER + end + end + + def self.logger=(logger) + @logger = logger + end + def self.debug(message) return if !!ENV["ruby-saml/testing"] - if defined? Rails - Rails.logger.debug message - else - puts message - end + logger.debug message end def self.info(message) return if !!ENV["ruby-saml/testing"] - if defined? Rails - Rails.logger.info message - else - puts message - end + logger.info message end end end diff --git a/lib/onelogin/ruby-saml/logoutrequest.rb b/lib/onelogin/ruby-saml/logoutrequest.rb index c7c2104c4..7ed0766d8 100644 --- a/lib/onelogin/ruby-saml/logoutrequest.rb +++ b/lib/onelogin/ruby-saml/logoutrequest.rb @@ -1,39 +1,67 @@ -require "uuid" - require "onelogin/ruby-saml/logging" require "onelogin/ruby-saml/saml_message" +require "onelogin/ruby-saml/utils" +require "onelogin/ruby-saml/setting_error" +# Only supports SAML 2.0 module OneLogin module RubySaml + + # SAML2 Logout Request (SLO SP initiated, Builder) + # class Logoutrequest < SamlMessage - attr_reader :uuid # Can be obtained if neccessary + # Logout Request ID + attr_accessor :uuid + # Initializes the Logout Request. A Logoutrequest Object that is an extension of the SamlMessage class. + # Asigns an ID, a random uuid. + # def initialize - @uuid = "_" + UUID.new.generate + @uuid = OneLogin::RubySaml::Utils.uuid + end + + def request_id + @uuid end + # Creates the Logout Request string. + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState + # @return [String] Logout Request string that includes the SAMLRequest + # def create(settings, params={}) params = create_params(settings, params) - params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?' + params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?' saml_request = CGI.escape(params.delete("SAMLRequest")) request_params = "#{params_prefix}SAMLRequest=#{saml_request}" params.each_pair do |key, value| - request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}" + request_params << "&#{key}=#{CGI.escape(value.to_s)}" end - @logout_url = settings.idp_slo_target_url + request_params + raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty? + @logout_url = settings.idp_slo_service_url + request_params end + # Creates the Get parameters for the logout request. + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState + # @return [Hash] Parameters + # def create_params(settings, params={}) # The method expects :RelayState but sometimes we get 'RelayState' instead. # Based on the HashWithIndifferentAccess value in Rails we could experience # conflicts so this line will solve them. relay_state = params[:RelayState] || params['RelayState'] + if relay_state.nil? + params.delete(:RelayState) + params.delete('RelayState') + end + request_doc = create_logout_request_xml_doc(settings) request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values - request = "" + request = "".dup request_doc.write(request) Logging.debug "Created SLO Logout Request: #{request}" @@ -41,14 +69,18 @@ def create_params(settings, params={}) request = deflate(request) if settings.compress_request base64_request = encode(request) request_params = {"SAMLRequest" => base64_request} - - if settings.security[:logout_requests_signed] && !settings.security[:embed_sign] && settings.private_key - params['SigAlg'] = settings.security[:signature_method] - url_string = "SAMLRequest=#{CGI.escape(base64_request)}" - url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state - url_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - private_key = settings.get_sp_key() - signature = private_key.sign(XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]).new, url_string) + sp_signing_key = settings.get_sp_signing_key + + if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_requests_signed] && sp_signing_key + params['SigAlg'] = settings.security[:signature_method] + url_string = OneLogin::RubySaml::Utils.build_query( + :type => 'SAMLRequest', + :data => base64_request, + :relay_state => relay_state, + :sig_alg => params['SigAlg'] + ) + sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + signature = settings.get_sp_signing_key.sign(sign_algorithm.new, url_string) params['Signature'] = encode(signature) end @@ -59,7 +91,16 @@ def create_params(settings, params={}) request_params end + # Creates the SAMLRequest String. + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @return [String] The SAMLRequest String. + # def create_logout_request_xml_doc(settings) + document = create_xml_document(settings) + sign_document(document, settings) + end + + def create_xml_document(settings) time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") request_doc = XMLSecurity::Document.new @@ -69,22 +110,23 @@ def create_logout_request_xml_doc(settings) root.attributes['ID'] = uuid root.attributes['IssueInstant'] = time root.attributes['Version'] = "2.0" - root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil? + root.attributes['Destination'] = settings.idp_slo_service_url unless settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty? - if settings.issuer + if settings.sp_entity_id issuer = root.add_element "saml:Issuer" - issuer.text = settings.issuer + issuer.text = settings.sp_entity_id end - name_id = root.add_element "saml:NameID" + nameid = root.add_element "saml:NameID" if settings.name_identifier_value - name_id.attributes['NameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier - name_id.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format - name_id.text = settings.name_identifier_value + nameid.attributes['NameQualifier'] = settings.idp_name_qualifier if settings.idp_name_qualifier + nameid.attributes['SPNameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier + nameid.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format + nameid.text = settings.name_identifier_value else # If no NameID is present in the settings we generate one - name_id.text = "_" + UUID.new.generate - name_id.attributes['Format'] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' + nameid.text = OneLogin::RubySaml::Utils.uuid + nameid.attributes['Format'] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' end if settings.sessionindex @@ -92,14 +134,17 @@ def create_logout_request_xml_doc(settings) sessionindex.text = settings.sessionindex end + request_doc + end + + def sign_document(document, settings) # embed signature - if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign] - private_key = settings.get_sp_key() - cert = settings.get_sp_cert() - request_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) + cert, private_key = settings.get_sp_signing_pair + if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.security[:logout_requests_signed] && private_key && cert + document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) end - request_doc + document end end end diff --git a/lib/onelogin/ruby-saml/logoutresponse.rb b/lib/onelogin/ruby-saml/logoutresponse.rb index 0c5ddf337..3fb99f65b 100644 --- a/lib/onelogin/ruby-saml/logoutresponse.rb +++ b/lib/onelogin/ruby-saml/logoutresponse.rb @@ -3,112 +3,280 @@ require "time" +# Only supports SAML 2.0 module OneLogin module RubySaml + + # SAML2 Logout Response (SLO IdP initiated, Parser) + # class Logoutresponse < SamlMessage - # For API compability, this is mutable. + include ErrorHandling + + # OneLogin::RubySaml::Settings Toolkit settings attr_accessor :settings attr_reader :document attr_reader :response attr_reader :options + attr_accessor :soft + + # Constructs the Logout Response. A Logout Response Object that is an extension of the SamlMessage class. + # @param response [String] A UUEncoded logout response from the IdP. + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @param options [Hash] Extra parameters. + # :matches_request_id It will validate that the logout response matches the ID of the request. + # :get_params GET Parameters, including the SAMLResponse + # :relax_signature_validation to accept signatures if no idp certificate registered on settings # - # In order to validate that the response matches a given request, append - # the option: - # :matches_request_id => REQUEST_ID - # - # It will validate that the logout response matches the ID of the request. - # You can also do this yourself through the in_response_to accessor. + # @raise [ArgumentError] if response is nil # def initialize(response, settings = nil, options = {}) + @errors = [] raise ArgumentError.new("Logoutresponse cannot be nil") if response.nil? - self.settings = settings + @settings = settings + + if settings.nil? || settings.soft.nil? + @soft = true + else + @soft = settings.soft + end @options = options - @response = decode_raw_saml(response) + @response = decode_raw_saml(response, settings) @document = XMLSecurity::SignedDocument.new(@response) end - def validate! - validate(false) - end - - def validate(soft = true) - return false unless valid_saml?(document, soft) && valid_state?(soft) - - valid_in_response_to?(soft) && valid_issuer?(soft) && success?(soft) + def response_id + id(document) end - def success?(soft = true) - unless status_code == "urn:oasis:names:tc:SAML:2.0:status:Success" - return soft ? false : validation_error("Bad status code. Expected , but was: <#@status_code> ") - end - true + # Checks if the Status has the "Success" code + # @return [Boolean] True if the StatusCode is Sucess + # @raise [ValidationError] if soft == false and validation fails + # + def success? + return status_code == "urn:oasis:names:tc:SAML:2.0:status:Success" end + # @return [String|nil] Gets the InResponseTo attribute from the Logout Response if exists. + # def in_response_to @in_response_to ||= begin - node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL, "a" => ASSERTION }) + node = REXML::XPath.first( + document, + "/p:LogoutResponse", + { "p" => PROTOCOL } + ) node.nil? ? nil : node.attributes['InResponseTo'] end end + # @return [String] Gets the Issuer from the Logout Response. + # def issuer @issuer ||= begin - node = REXML::XPath.first(document, "/p:LogoutResponse/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION }) - node ||= REXML::XPath.first(document, "/p:LogoutResponse/a:Assertion/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION }) - node.nil? ? nil : node.text + node = REXML::XPath.first( + document, + "/p:LogoutResponse/a:Issuer", + { "p" => PROTOCOL, "a" => ASSERTION } + ) + Utils.element_text(node) end end + # @return [String] Gets the StatusCode from a Logout Response. + # def status_code @status_code ||= begin - node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION }) + node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL }) node.nil? ? nil : node.attributes["Value"] end end - private - - def valid_state?(soft = true) - if response.empty? - return soft ? false : validation_error("Blank response") + def status_message + @status_message ||= begin + node = REXML::XPath.first( + document, + "/p:LogoutResponse/p:Status/p:StatusMessage", + { "p" => PROTOCOL } + ) + Utils.element_text(node) end + end - if settings.nil? - return soft ? false : validation_error("No settings on response") - end + # Aux function to validate the Logout Response + # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true) + # @return [Boolean] TRUE if the SAML Response is valid + # @raise [ValidationError] if soft == false and validation fails + # + def validate(collect_errors = false) + reset_errors! + + validations = [ + :valid_state?, + :validate_success_status, + :validate_structure, + :valid_in_response_to?, + :valid_issuer?, + :validate_signature + ] - if settings.issuer.nil? - return soft ? false : validation_error("No issuer in settings") + if collect_errors + validations.each { |validation| send(validation) } + @errors.empty? + else + validations.all? { |validation| send(validation) } end + end + + private - if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? - return soft ? false : validation_error("No fingerprint or certificate on settings") + # Validates the Status of the Logout Response + # If fails, the error is added to the errors array, including the StatusCode returned and the Status Message. + # @return [Boolean] True if the Logout Response contains a Success code, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_success_status + return true if success? + + error_msg = 'The status code of the Logout Response was not Success' + status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code, status_message) + append_error(status_error_msg) + end + + # Validates the Logout Response against the specified schema. + # @return [Boolean] True if the XML is valid, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_structure + check_malformed_doc = check_malformed_doc?(settings) + unless valid_saml?(document, soft, check_malformed_doc) + return append_error("Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd") end true end - def valid_in_response_to?(soft = true) - return true unless self.options.has_key? :matches_request_id + # Validates that the Logout Response provided in the initialization is not empty, + # also check that the setting and the IdP cert were also provided + # @return [Boolean] True if the required info is found, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def valid_state? + return append_error("Blank logout response") if response.empty? + + return append_error("No settings on logout response") if settings.nil? + + return append_error("No sp_entity_id in settings of the logout response") if settings.sp_entity_id.nil? - unless self.options[:matches_request_id] == in_response_to - return soft ? false : validation_error("Response does not match the request ID, expected: <#{self.options[:matches_request_id]}>, but was: <#{in_response_to}>") + if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? && settings.idp_cert_multi.nil? + return append_error("No fingerprint or certificate on settings of the logout response") end true end - def valid_issuer?(soft = true) - return true if self.settings.idp_entity_id.nil? or self.issuer.nil? + # Validates if a provided :matches_request_id matchs the inResponseTo value. + # @param soft [String|nil] request_id The ID of the Logout Request sent by this SP to the IdP (if was sent any) + # @return [Boolean] True if there is no request_id or it match, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def valid_in_response_to? + return true unless options.has_key? :matches_request_id + return true if options[:matches_request_id].nil? + return true unless options[:matches_request_id] != in_response_to + + error_msg = "The InResponseTo of the Logout Response: #{in_response_to}, does not match the ID of the Logout Request sent by the SP: #{options[:matches_request_id]}" + append_error(error_msg) + end + + # Validates the Issuer of the Logout Response + # @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def valid_issuer? + return true if settings.idp_entity_id.nil? || issuer.nil? + + unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id) + return append_error("Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>") + end + true + end + + # Validates the Signature if it exists and the GET parameters are provided + # @return [Boolean] True if not contains a Signature or if the Signature is valid, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_signature + return true unless !options.nil? + return true unless options.has_key? :get_params + return true unless options[:get_params].has_key? 'Signature' + + options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params], settings.security[:lowercase_url_encoding]) - unless URI.parse(self.issuer) == URI.parse(self.settings.idp_entity_id) - return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.issuer}>, but was: <#{issuer}>") + if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil? + options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg']) + end + + idp_cert = settings.get_idp_cert + idp_certs = settings.get_idp_cert_multi + + if idp_cert.nil? && (idp_certs.nil? || idp_certs[:signing].empty?) + return options.has_key? :relax_signature_validation + end + + query_string = OneLogin::RubySaml::Utils.build_query_from_raw_parts( + :type => 'SAMLResponse', + :raw_data => options[:raw_get_params]['SAMLResponse'], + :raw_relay_state => options[:raw_get_params]['RelayState'], + :raw_sig_alg => options[:raw_get_params]['SigAlg'] + ) + + expired = false + if idp_certs.nil? || idp_certs[:signing].empty? + valid = OneLogin::RubySaml::Utils.verify_signature( + :cert => idp_cert, + :sig_alg => options[:get_params]['SigAlg'], + :signature => options[:get_params]['Signature'], + :query_string => query_string + ) + if valid && settings.security[:check_idp_cert_expiration] + if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert) + expired = true + end + end + else + valid = false + idp_certs[:signing].each do |signing_idp_cert| + valid = OneLogin::RubySaml::Utils.verify_signature( + :cert => signing_idp_cert, + :sig_alg => options[:get_params]['SigAlg'], + :signature => options[:get_params]['Signature'], + :query_string => query_string + ) + if valid + if settings.security[:check_idp_cert_expiration] + if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert) + expired = true + end + end + break + end + end + end + + if expired + error_msg = "IdP x509 certificate expired" + return append_error(error_msg) + end + unless valid + error_msg = "Invalid Signature on Logout Response" + return append_error(error_msg) end true end + end end end diff --git a/lib/onelogin/ruby-saml/metadata.rb b/lib/onelogin/ruby-saml/metadata.rb index ac1821221..a50e9e66b 100644 --- a/lib/onelogin/ruby-saml/metadata.rb +++ b/lib/onelogin/ruby-saml/metadata.rb @@ -1,43 +1,94 @@ require "uri" -require "uuid" require "onelogin/ruby-saml/logging" +require "onelogin/ruby-saml/utils" -# Class to return SP metadata based on the settings requested. -# Return this XML in a controller, then give that URL to the the -# IdP administrator. The IdP will poll the URL and your settings -# will be updated automatically +# Only supports SAML 2.0 module OneLogin module RubySaml + + # SAML2 Metadata. XML Metadata Builder + # class Metadata - def generate(settings, pretty_print=true) + + # Return SP metadata based on the settings. + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @param pretty_print [Boolean] Pretty print or not the response + # (No pretty print if you gonna validate the signature) + # @param valid_until [DateTime] Metadata's valid time + # @param cache_duration [Integer] Duration of the cache in seconds + # @return [String] XML Metadata of the Service Provider + # + def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil) meta_doc = XMLSecurity::Document.new - root = meta_doc.add_element "md:EntityDescriptor", { + add_xml_declaration(meta_doc) + root = add_root_element(meta_doc, settings, valid_until, cache_duration) + sp_sso = add_sp_sso_element(root, settings) + add_sp_certificates(sp_sso, settings) + add_sp_service_elements(sp_sso, settings) + add_extras(root, settings) + embed_signature(meta_doc, settings) + output_xml(meta_doc, pretty_print) + end + + protected + + def add_xml_declaration(meta_doc) + meta_doc << REXML::XMLDecl.new('1.0', 'UTF-8') + end + + def add_root_element(meta_doc, settings, valid_until, cache_duration) + namespaces = { "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata" } - sp_sso = root.add_element "md:SPSSODescriptor", { + + if settings.attribute_consuming_service.configured? + namespaces["xmlns:saml"] = "urn:oasis:names:tc:SAML:2.0:assertion" + end + + root = meta_doc.add_element("md:EntityDescriptor", namespaces) + root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid + root.attributes["entityID"] = settings.sp_entity_id if settings.sp_entity_id + root.attributes["validUntil"] = valid_until.utc.strftime('%Y-%m-%dT%H:%M:%SZ') if valid_until + root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S" if cache_duration + root + end + + def add_sp_sso_element(root, settings) + root.add_element "md:SPSSODescriptor", { "protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol", "AuthnRequestsSigned" => settings.security[:authn_requests_signed], - # However we would like assertions signed if idp_cert_fingerprint or idp_cert is set - "WantAssertionsSigned" => !!(settings.idp_cert_fingerprint || settings.idp_cert) + "WantAssertionsSigned" => settings.security[:want_assertions_signed], } - root.attributes["ID"] = "_" + UUID.new.generate - if settings.issuer - root.attributes["entityID"] = settings.issuer + end + + # Add KeyDescriptor elements for SP certificates. + def add_sp_certificates(sp_sso, settings) + certs = settings.get_sp_certs + + certs[:signing].each { |cert, _| add_sp_cert_element(sp_sso, cert, :signing) } + + if settings.security[:want_assertions_encrypted] + certs[:encryption].each { |cert, _| add_sp_cert_element(sp_sso, cert, :encryption) } end + + sp_sso + end + + def add_sp_service_elements(sp_sso, settings) if settings.single_logout_service_url sp_sso.add_element "md:SingleLogoutService", { "Binding" => settings.single_logout_service_binding, "Location" => settings.single_logout_service_url, - "ResponseLocation" => settings.single_logout_service_url, - "isDefault" => true, - "index" => 0 + "ResponseLocation" => settings.single_logout_service_url } end + if settings.name_identifier_format - name_id = sp_sso.add_element "md:NameIDFormat" - name_id.text = settings.name_identifier_format + nameid = sp_sso.add_element "md:NameIDFormat" + nameid.text = settings.name_identifier_format end + if settings.assertion_consumer_service_url sp_sso.add_element "md:AssertionConsumerService", { "Binding" => settings.assertion_consumer_service_binding, @@ -47,20 +98,10 @@ def generate(settings, pretty_print=true) } end - # Add KeyDescriptor if messages will be signed - cert = settings.get_sp_cert() - if cert - kd = sp_sso.add_element "md:KeyDescriptor", { "use" => "signing" } - ki = kd.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"} - xd = ki.add_element "ds:X509Data" - xc = xd.add_element "ds:X509Certificate" - xc.text = Base64.encode64(cert.to_der).gsub("\n", '') - end - if settings.attribute_consuming_service.configured? sp_acs = sp_sso.add_element "md:AttributeConsumingService", { "isDefault" => "true", - "index" => settings.attribute_consuming_service.index + "index" => settings.attribute_consuming_service.index } srv_name = sp_acs.add_element "md:ServiceName", { "xml:lang" => "en" @@ -69,12 +110,15 @@ def generate(settings, pretty_print=true) settings.attribute_consuming_service.attributes.each do |attribute| sp_req_attr = sp_acs.add_element "md:RequestedAttribute", { "NameFormat" => attribute[:name_format], - "Name" => attribute[:name], - "FriendlyName" => attribute[:friendly_name] + "Name" => attribute[:name], + "FriendlyName" => attribute[:friendly_name], + "isRequired" => attribute[:is_required] || false } unless attribute[:attribute_value].nil? - sp_attr_val = sp_req_attr.add_element "md:AttributeValue" - sp_attr_val.text = attribute[:attribute_value] + Array(attribute[:attribute_value]).each do |value| + sp_attr_val = sp_req_attr.add_element "saml:AttributeValue" + sp_attr_val.text = value.to_s + end end end end @@ -83,23 +127,46 @@ def generate(settings, pretty_print=true) # # - meta_doc << REXML::XMLDecl.new("1.0", "UTF-8") + sp_sso + end - # embed signature - if settings.security[:metadata_signed] && settings.private_key && settings.certificate - private_key = settings.get_sp_key() - meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) - end + # can be overridden in subclass + def add_extras(root, _settings) + root + end + + def embed_signature(meta_doc, settings) + return unless settings.security[:metadata_signed] + + cert, private_key = settings.get_sp_signing_pair + return unless private_key && cert + + meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) + end + + def output_xml(meta_doc, pretty_print) + ret = ''.dup - ret = "" # pretty print the XML so IdP administrators can easily see what the SP supports if pretty_print meta_doc.write(ret, 1) - else + else ret = meta_doc.to_s end - return ret + ret + end + + private + + def add_sp_cert_element(sp_sso, cert, use) + return unless cert + cert_text = Base64.encode64(cert.to_der).gsub("\n", '') + kd = sp_sso.add_element "md:KeyDescriptor", { "use" => use.to_s } + ki = kd.add_element "ds:KeyInfo", { "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#" } + xd = ki.add_element "ds:X509Data" + xc = xd.add_element "ds:X509Certificate" + xc.text = cert_text end end end diff --git a/lib/onelogin/ruby-saml/response.rb b/lib/onelogin/ruby-saml/response.rb index 4a5babd79..48ab366d7 100644 --- a/lib/onelogin/ruby-saml/response.rb +++ b/lib/onelogin/ruby-saml/response.rb @@ -8,47 +8,124 @@ module OneLogin module RubySaml + # SAML2 Authentication Response. SAML Response + # class Response < SamlMessage + include ErrorHandling + ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion" PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol" DSIG = "http://www.w3.org/2000/09/xmldsig#" + XENC = "http://www.w3.org/2001/04/xmlenc#" + SAML_NAMESPACES = { + "p" => PROTOCOL, + "a" => ASSERTION + }.freeze + + # TODO: Settings should probably be initialized too... WDYT? - # TODO: This should probably be ctor initialized too... WDYT? + # OneLogin::RubySaml::Settings Toolkit settings attr_accessor :settings - attr_accessor :errors - attr_reader :options - attr_reader :response attr_reader :document + attr_reader :decrypted_document + attr_reader :response + attr_reader :options + attr_accessor :soft + + # Response available options + # This is not a whitelist to allow people extending OneLogin::RubySaml:Response + # and pass custom options + AVAILABLE_OPTIONS = [ + :allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_audience, :skip_authnstatement, :skip_conditions, + :skip_destination, :skip_recipient_check, :skip_subject_confirmation + ] + # TODO: Update the comment on initialize to describe every option + + # Constructs the SAML Response. A Response Object that is an extension of the SamlMessage class. + # @param response [String] A UUEncoded SAML response from the IdP. + # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object + # Or some options for the response validation process like skip the conditions validation + # with the :skip_conditions, or allow a clock_drift when checking dates with :allowed_clock_drift + # or :matches_request_id that will validate that the response matches the ID of the request, + # or skip the subject confirmation validation with the :skip_subject_confirmation option + # or skip the recipient validation of the subject confirmation element with :skip_recipient_check option + # or skip the audience validation with :skip_audience option + # def initialize(response, options = {}) - @errors = [] raise ArgumentError.new("Response cannot be nil") if response.nil? - @options = options - @response = decode_raw_saml(response) + + @errors = [] + + @options = options + @soft = true + unless options[:settings].nil? + @settings = options[:settings] + unless @settings.soft.nil? + @soft = @settings.soft + end + end + + @response = decode_raw_saml(response, settings) @document = XMLSecurity::SignedDocument.new(@response, @errors) + + if assertion_encrypted? + @decrypted_document = generate_decrypted_document + end + end + + # Validates the SAML Response with the default values (soft = true) + # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true) + # @return [Boolean] TRUE if the SAML Response is valid + # + def is_valid?(collect_errors = false) + validate(collect_errors) end - def is_valid? - validate + # @return [String] the NameID provided by the SAML response from the IdP. + # + def name_id + @name_id ||= Utils.element_text(name_id_node) end - def validate! - validate(false) + alias_method :nameid, :name_id + + # @return [String] the NameID Format provided by the SAML response from the IdP. + # + def name_id_format + @name_id_format ||= + if name_id_node && name_id_node.attribute("Format") + name_id_node.attribute("Format").value + end end - def errors - @errors + alias_method :nameid_format, :name_id_format + + # @return [String] the NameID SPNameQualifier provided by the SAML response from the IdP. + # + def name_id_spnamequalifier + @name_id_spnamequalifier ||= + if name_id_node && name_id_node.attribute("SPNameQualifier") + name_id_node.attribute("SPNameQualifier").value + end end - # The value of the user identifier as designated by the initialization request response - def name_id - @name_id ||= begin - node = xpath_first_from_signed_assertion('/a:Subject/a:NameID') - node.nil? ? nil : node.text - end + # @return [String] the NameID NameQualifier provided by the SAML response from the IdP. + # + def name_id_namequalifier + @name_id_namequalifier ||= + if name_id_node && name_id_node.attribute("NameQualifier") + name_id_node.attribute("NameQualifier").value + end end + # Gets the SessionIndex from the AuthnStatement. + # Could be used to be stored in the local session in order + # to be used in a future Logout Request that the SP could + # send to the IdP, to set what specific session must be deleted + # @return [String] SessionIndex Value + # def sessionindex @sessionindex ||= begin node = xpath_first_from_signed_assertion('/a:AuthnStatement') @@ -56,9 +133,9 @@ def sessionindex end end - # Returns OneLogin::RubySaml::Attributes enumerable collection. - # All attributes can be iterated over +attributes.each+ or returned as array by +attributes.all+ + # Gets the Attributes from the AttributeStatement element. # + # All attributes can be iterated over +attributes.each+ or returned as array by +attributes.all+ # For backwards compatibility ruby-saml returns by default only the first value for a given attribute with # attributes['name'] # To get all of the attributes, use: @@ -67,181 +144,992 @@ def sessionindex # OneLogin::RubySaml::Attributes.single_value_compatibility = false # Now this will return an array: # attributes['name'] + # + # @return [Attributes] OneLogin::RubySaml::Attributes enumerable collection. + # @raise [ValidationError] if there are 2+ Attribute with the same Name + # def attributes @attr_statements ||= begin attributes = Attributes.new - stmt_element = xpath_first_from_signed_assertion('/a:AttributeStatement') - return attributes if stmt_element.nil? + stmt_elements = xpath_from_signed_assertion('/a:AttributeStatement') + stmt_elements.each do |stmt_element| + stmt_element.elements.each do |attr_element| + if attr_element.name == "EncryptedAttribute" + node = decrypt_attribute(attr_element.dup) + else + node = attr_element + end - stmt_element.elements.each do |attr_element| - name = attr_element.attributes["Name"] - values = attr_element.elements.collect{|e| - # SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1" - # otherwise the value is to be regarded as empty. - ["true", "1"].include?(e.attributes['xsi:nil']) ? nil : e.text.to_s - } + name = node.attributes["Name"] - attributes.add(name, values) - end + if options[:check_duplicated_attributes] && attributes.include?(name) + raise ValidationError.new("Found an Attribute element with duplicated Name") + end + + values = node.elements.collect{|e| + if (e.elements.nil? || e.elements.size == 0) + # SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1" + # otherwise the value is to be regarded as empty. + ["true", "1"].include?(e.attributes['xsi:nil']) ? nil : Utils.element_text(e) + # explicitly support saml2:NameID with saml2:NameQualifier if supplied in attributes + # this is useful for allowing eduPersonTargetedId to be passed as an opaque identifier to use to + # identify the subject in an SP rather than email or other less opaque attributes + # NameQualifier, if present is prefixed with a "/" to the value + else + REXML::XPath.match(e,'a:NameID', { "a" => ASSERTION }).collect do |n| + base_path = n.attributes['NameQualifier'] ? "#{n.attributes['NameQualifier']}/" : '' + "#{base_path}#{Utils.element_text(n)}" + end + end + } + attributes.add(name, values.flatten) + end + end attributes end end - # When this user session should expire at latest + # Gets the SessionNotOnOrAfter from the AuthnStatement. + # Could be used to set the local session expiration (expire at latest) + # @return [String] The SessionNotOnOrAfter value + # def session_expires_at @expires_at ||= begin node = xpath_first_from_signed_assertion('/a:AuthnStatement') - parse_time(node, "SessionNotOnOrAfter") + node.nil? ? nil : parse_time(node, "SessionNotOnOrAfter") end end - # Checks the status of the response for a "Success" code + # Gets the AuthnInstant from the AuthnStatement. + # Could be used to require re-authentication if a long time has passed + # since the last user authentication. + # @return [String] AuthnInstant value + # + def authn_instant + @authn_instant ||= begin + node = xpath_first_from_signed_assertion('/a:AuthnStatement') + node.nil? ? nil : node.attributes['AuthnInstant'] + end + end + + # Gets the AuthnContextClassRef from the AuthnStatement + # Could be used to require re-authentication if the assertion + # did not met the requested authentication context class. + # @return [String] AuthnContextClassRef value + # + def authn_context_class_ref + @authn_context_class_ref ||= Utils.element_text(xpath_first_from_signed_assertion('/a:AuthnStatement/a:AuthnContext/a:AuthnContextClassRef')) + end + + # Checks if the Status has the "Success" code + # @return [Boolean] True if the StatusCode is Sucess + # def success? + status_code == "urn:oasis:names:tc:SAML:2.0:status:Success" + end + + # @return [String] StatusCode value from a SAML Response. + # + def status_code @status_code ||= begin - node = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION }) - node.attributes["Value"] == "urn:oasis:names:tc:SAML:2.0:status:Success" + nodes = REXML::XPath.match( + document, + "/p:Response/p:Status/p:StatusCode", + { "p" => PROTOCOL } + ) + if nodes.size == 1 + node = nodes[0] + code = node.attributes["Value"] if node && node.attributes + + unless code == "urn:oasis:names:tc:SAML:2.0:status:Success" + nodes = REXML::XPath.match( + document, + "/p:Response/p:Status/p:StatusCode/p:StatusCode", + { "p" => PROTOCOL } + ) + statuses = nodes.collect do |inner_node| + inner_node.attributes["Value"] + end + + code = [code, statuses].flatten.join(" | ") + end + + code + end end end + # @return [String] the StatusMessage value from a SAML Response. + # def status_message @status_message ||= begin - node = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusMessage", { "p" => PROTOCOL, "a" => ASSERTION }) - node.text if node + nodes = REXML::XPath.match( + document, + "/p:Response/p:Status/p:StatusMessage", + { "p" => PROTOCOL } + ) + if nodes.size == 1 + Utils.element_text(nodes.first) + end end end - # Conditions (if any) for the assertion to run + # Gets the Condition Element of the SAML Response if exists. + # (returns the first node that matches the supplied xpath) + # @return [REXML::Element] Conditions Element if exists + # def conditions @conditions ||= xpath_first_from_signed_assertion('/a:Conditions') end + # Gets the NotBefore Condition Element value. + # @return [Time] The NotBefore value in Time format + # def not_before @not_before ||= parse_time(conditions, "NotBefore") end + # Gets the NotOnOrAfter Condition Element value. + # @return [Time] The NotOnOrAfter value in Time format + # def not_on_or_after @not_on_or_after ||= parse_time(conditions, "NotOnOrAfter") end - def issuer - @issuer ||= begin - node = REXML::XPath.first(document, "/p:Response/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION }) - node ||= xpath_first_from_signed_assertion('/a:Issuer') - node.nil? ? nil : node.text + # Gets the Issuers (from Response and Assertion). + # (returns the first node that matches the supplied xpath from the Response and from the Assertion) + # @return [Array] Array with the Issuers (REXML::Element) + # + def issuers + @issuers ||= begin + issuer_response_nodes = REXML::XPath.match( + document, + "/p:Response/a:Issuer", + SAML_NAMESPACES + ) + + unless issuer_response_nodes.size == 1 + error_msg = "Issuer of the Response not found or multiple." + raise ValidationError.new(error_msg) + end + + issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer") + unless issuer_assertion_nodes.size == 1 + error_msg = "Issuer of the Assertion not found or multiple." + raise ValidationError.new(error_msg) + end + + nodes = issuer_response_nodes + issuer_assertion_nodes + nodes.map { |node| Utils.element_text(node) }.compact.uniq end end - private + # @return [String|nil] The InResponseTo attribute from the SAML Response. + # + def in_response_to + @in_response_to ||= begin + node = REXML::XPath.first( + document, + "/p:Response", + { "p" => PROTOCOL } + ) + node.nil? ? nil : node.attributes['InResponseTo'] + end + end + + # @return [String|nil] Destination attribute from the SAML Response. + # + def destination + @destination ||= begin + node = REXML::XPath.first( + document, + "/p:Response", + { "p" => PROTOCOL } + ) + node.nil? ? nil : node.attributes['Destination'] + end + end + + # @return [Array] The Audience elements from the Contitions of the SAML Response. + # + def audiences + @audiences ||= begin + nodes = xpath_from_signed_assertion('/a:Conditions/a:AudienceRestriction/a:Audience') + nodes.map { |node| Utils.element_text(node) }.reject(&:empty?) + end + end - def validate(soft = true) - valid_saml?(document, soft) && - validate_response_state(soft) && - validate_conditions(soft) && - validate_issuer(soft) && - document.validate_document(get_fingerprint, soft) && - validate_success_status(soft) + # returns the allowed clock drift on timing validation + # @return [Float] + def allowed_clock_drift + options[:allowed_clock_drift].to_f.abs + Float::EPSILON + end + + # Checks if the SAML Response contains or not an EncryptedAssertion element + # @return [Boolean] True if the SAML Response contains an EncryptedAssertion element + # + def assertion_encrypted? + ! REXML::XPath.first( + document, + "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", + SAML_NAMESPACES + ).nil? + end + + def response_id + id(document) + end + + def assertion_id + @assertion_id ||= begin + node = xpath_first_from_signed_assertion("") + node.nil? ? nil : node.attributes['ID'] + end end - def validate_success_status(soft = true) - if success? - true + private + + # Validates the SAML Response (calls several validation methods) + # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true) + # @return [Boolean] True if the SAML Response is valid, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate(collect_errors = false) + reset_errors! + return false unless validate_response_state + + validations = [ + :validate_version, + :validate_id, + :validate_success_status, + :validate_num_assertion, + :validate_signed_elements, + :validate_structure, + :validate_no_duplicated_attributes, + :validate_in_response_to, + :validate_one_conditions, + :validate_conditions, + :validate_one_authnstatement, + :validate_audience, + :validate_destination, + :validate_issuer, + :validate_session_expiration, + :validate_subject_confirmation, + :validate_name_id, + :validate_signature + ] + + if collect_errors + validations.each { |validation| send(validation) } + @errors.empty? else - soft ? false : validation_error(status_message) + validations.all? { |validation| send(validation) } end end - def validate_structure(soft = true) - xml = Nokogiri::XML(self.document.to_s) - SamlMessage.schema.validate(xml).map do |error| - if soft - @errors << "Schema validation failed" - break false - else - error_message = [error.message, xml.to_s].join("\n\n") + # Validates the Status of the SAML Response + # @return [Boolean] True if the SAML Response contains a Success code, otherwise False if soft == false + # @raise [ValidationError] if soft == false and validation fails + # + def validate_success_status + return true if success? + + error_msg = 'The status code of the Response was not Success' + status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code, status_message) + append_error(status_error_msg) + end + + # Validates the SAML Response against the specified schema. + # @return [Boolean] True if the XML is valid, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_structure + structure_error_msg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd" + + check_malformed_doc = check_malformed_doc_enabled? + unless valid_saml?(document, soft, check_malformed_doc) + return append_error(structure_error_msg) + end - @errors << error_message - validation_error(error_message) + unless decrypted_document.nil? + unless valid_saml?(decrypted_document, soft, check_malformed_doc) + return append_error(structure_error_msg) end end + + true end - def validate_response_state(soft = true) - if response.empty? - return soft ? false : validation_error("Blank response") + # Validates that the SAML Response provided in the initialization is not empty, + # also check that the setting and the IdP cert were also provided + # @return [Boolean] True if the required info is found, false otherwise + # + def validate_response_state + return append_error("Blank response") if response.nil? || response.empty? + + return append_error("No settings on response") if settings.nil? + + if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? && settings.idp_cert_multi.nil? + return append_error("No fingerprint or certificate on settings") end - if settings.nil? - return soft ? false : validation_error("No settings on response") + true + end + + # Validates that the SAML Response contains an ID + # If fails, the error is added to the errors array. + # @return [Boolean] True if the SAML Response contains an ID, otherwise returns False + # + def validate_id + unless response_id + return append_error("Missing ID attribute on SAML Response") end - if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? - return soft ? false : validation_error("No fingerprint or certificate on settings") + true + end + + # Validates the SAML version (2.0) + # If fails, the error is added to the errors array. + # @return [Boolean] True if the SAML Response is 2.0, otherwise returns False + # + def validate_version + unless version(document) == "2.0" + return append_error("Unsupported SAML version") end true end - def xpath_first_from_signed_assertion(subelt=nil) - node = REXML::XPath.first( - document, - "/p:Response/a:Assertion[@ID=$id]#{subelt}", - { "p" => PROTOCOL, "a" => ASSERTION }, - { 'id' => document.signed_element_id } + # Validates that the SAML Response only contains a single Assertion (encrypted or not). + # If fails, the error is added to the errors array. + # @return [Boolean] True if the SAML Response contains one unique Assertion, otherwise False + # + def validate_num_assertion + error_msg = "SAML Response must contain 1 assertion" + assertions = REXML::XPath.match( + document, + "//a:Assertion", + { "a" => ASSERTION } ) - node ||= REXML::XPath.first( - document, - "/p:Response[@ID=$id]/a:Assertion#{subelt}", - { "p" => PROTOCOL, "a" => ASSERTION }, - { 'id' => document.signed_element_id } + encrypted_assertions = REXML::XPath.match( + document, + "//a:EncryptedAssertion", + { "a" => ASSERTION } ) - node + + unless assertions.size + encrypted_assertions.size == 1 + return append_error(error_msg) + end + + unless decrypted_document.nil? + assertions = REXML::XPath.match( + decrypted_document, + "//a:Assertion", + { "a" => ASSERTION } + ) + unless assertions.size == 1 + return append_error(error_msg) + end + end + + true end - def get_fingerprint - if settings.idp_cert - cert = OpenSSL::X509::Certificate.new(settings.idp_cert) - Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":") - else - settings.idp_cert_fingerprint + # Validates that there are not duplicated attributes + # If fails, the error is added to the errors array + # @return [Boolean] True if there are no duplicated attribute elements, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_no_duplicated_attributes + if options[:check_duplicated_attributes] + begin + attributes + rescue ValidationError => e + return append_error(e.message) + end + end + + true + end + + # Validates the Signed elements + # If fails, the error is added to the errors array + # @return [Boolean] True if there is 1 or 2 Elements signed in the SAML Response + # an are a Response or an Assertion Element, otherwise False if soft=True + # + def validate_signed_elements + signature_nodes = REXML::XPath.match( + decrypted_document.nil? ? document : decrypted_document, + "//ds:Signature", + {"ds"=>DSIG} + ) + signed_elements = [] + verified_seis = [] + verified_ids = [] + signature_nodes.each do |signature_node| + signed_element = signature_node.parent.name + if signed_element != 'Response' && signed_element != 'Assertion' + return append_error("Invalid Signature Element '#{signed_element}'. SAML Response rejected") + end + + if signature_node.parent.attributes['ID'].nil? + return append_error("Signed Element must contain an ID. SAML Response rejected") + end + + id = signature_node.parent.attributes.get_attribute("ID").value + if verified_ids.include?(id) + return append_error("Duplicated ID. SAML Response rejected") + end + verified_ids.push(id) + + # Check that reference URI matches the parent ID and no duplicate References or IDs + ref = REXML::XPath.first(signature_node, ".//ds:Reference", {"ds"=>DSIG}) + if ref + uri = ref.attributes.get_attribute("URI") + if uri && !uri.value.empty? + sei = uri.value[1..-1] + + unless sei == id + return append_error("Found an invalid Signed Element. SAML Response rejected") + end + + if verified_seis.include?(sei) + return append_error("Duplicated Reference URI. SAML Response rejected") + end + + verified_seis.push(sei) + end + end + + signed_elements << signed_element + end + + unless signature_nodes.length < 3 && !signed_elements.empty? + return append_error("Found an unexpected number of Signature Element. SAML Response rejected") + end + + if settings.security[:want_assertions_signed] && !(signed_elements.include? "Assertion") + return append_error("The Assertion of the Response is not signed and the SP requires it") + end + + true + end + + # Validates if the provided request_id match the inResponseTo value. + # If fails, the error is added to the errors array + # @return [Boolean] True if there is no request_id or it match, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_in_response_to + return true unless options.has_key? :matches_request_id + return true if options[:matches_request_id].nil? + return true unless options[:matches_request_id] != in_response_to + + error_msg = "The InResponseTo of the Response: #{in_response_to}, does not match the ID of the AuthNRequest sent by the SP: #{options[:matches_request_id]}" + append_error(error_msg) + end + + # Validates the Audience, (If the Audience match the Service Provider EntityID) + # If the response was initialized with the :skip_audience option, this validation is skipped, + # If fails, the error is added to the errors array + # @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_audience + return true if options[:skip_audience] + return true if settings.sp_entity_id.nil? || settings.sp_entity_id.empty? + + if audiences.empty? + return true unless settings.security[:strict_audience_validation] + return append_error("Invalid Audiences. The element contained only empty elements. Expected audience #{settings.sp_entity_id}.") + end + + unless audiences.include? settings.sp_entity_id + s = audiences.count > 1 ? 's' : ''; + error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.sp_entity_id}" + return append_error(error_msg) + end + + true + end + + # Validates the Destination, (If the SAML Response is received where expected). + # If the response was initialized with the :skip_destination option, this validation is skipped, + # If fails, the error is added to the errors array + # @return [Boolean] True if there is a Destination element that matches the Consumer Service URL, otherwise False + # + def validate_destination + return true if destination.nil? + return true if options[:skip_destination] + + if destination.empty? + error_msg = "The response has an empty Destination value" + return append_error(error_msg) + end + + return true if settings.assertion_consumer_service_url.nil? || settings.assertion_consumer_service_url.empty? + + unless OneLogin::RubySaml::Utils.uri_match?(destination, settings.assertion_consumer_service_url) + error_msg = "The response was received at #{destination} instead of #{settings.assertion_consumer_service_url}" + return append_error(error_msg) + end + + true + end + + # Checks that the samlp:Response/saml:Assertion/saml:Conditions element exists and is unique. + # (If the response was initialized with the :skip_conditions option, this validation is skipped) + # If fails, the error is added to the errors array + # @return [Boolean] True if there is a conditions element and is unique + # + def validate_one_conditions + return true if options[:skip_conditions] + + conditions_nodes = xpath_from_signed_assertion('/a:Conditions') + unless conditions_nodes.size == 1 + error_msg = "The Assertion must include one Conditions element" + return append_error(error_msg) end + + true + end + + # Checks that the samlp:Response/saml:Assertion/saml:AuthnStatement element exists and is unique. + # If fails, the error is added to the errors array + # @return [Boolean] True if there is a authnstatement element and is unique + # + def validate_one_authnstatement + return true if options[:skip_authnstatement] + + authnstatement_nodes = xpath_from_signed_assertion('/a:AuthnStatement') + unless authnstatement_nodes.size == 1 + error_msg = "The Assertion must include one AuthnStatement element" + return append_error(error_msg) + end + + true end - def validate_conditions(soft = true) + # Validates the Conditions. (If the response was initialized with the :skip_conditions option, this validation is skipped, + # If the response was initialized with the :allowed_clock_drift option, the timing validations are relaxed by the allowed_clock_drift value) + # @return [Boolean] True if satisfies the conditions, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_conditions return true if conditions.nil? return true if options[:skip_conditions] now = Time.now.utc - if not_before && (now + (options[:allowed_clock_drift] || 0)) < not_before - @errors << "Current time is earlier than NotBefore condition #{(now + (options[:allowed_clock_drift] || 0))} < #{not_before})" - return soft ? false : validation_error("Current time is earlier than NotBefore condition") + if not_before && now < (not_before - allowed_clock_drift) + error_msg = "Current time is earlier than NotBefore condition (#{now} < #{not_before}#{" - #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})" + return append_error(error_msg) end - if not_on_or_after && now >= not_on_or_after - @errors << "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after})" - return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition") + if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift) + error_msg = "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after}#{" + #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})" + return append_error(error_msg) end true end - def validate_issuer(soft = true) + # Validates the Issuer (Of the SAML Response and the SAML Assertion) + # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the response is invalid or not) + # @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_issuer return true if settings.idp_entity_id.nil? - unless URI.parse(issuer) == URI.parse(settings.idp_entity_id) - return soft ? false : validation_error("Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>") + begin + obtained_issuers = issuers + rescue ValidationError => e + return append_error(e.message) + end + + obtained_issuers.each do |issuer| + unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id) + error_msg = "Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>" + return append_error(error_msg) + end + end + + true + end + + # Validates that the Session haven't expired (If the response was initialized with the :allowed_clock_drift option, + # this time validation is relaxed by the allowed_clock_drift value) + # If fails, the error is added to the errors array + # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the response is invalid or not) + # @return [Boolean] True if the SessionNotOnOrAfter of the AuthnStatement is valid, otherwise (when expired) False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_session_expiration + return true if session_expires_at.nil? + + now = Time.now.utc + unless now < (session_expires_at + allowed_clock_drift) + error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response" + return append_error(error_msg) + end + + true + end + + # Validates if exists valid SubjectConfirmation (If the response was initialized with the :allowed_clock_drift option, + # timimg validation are relaxed by the allowed_clock_drift value. If the response was initialized with the + # :skip_subject_confirmation option, this validation is skipped) + # There is also an optional Recipient check + # If fails, the error is added to the errors array + # @return [Boolean] True if exists a valid SubjectConfirmation, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_subject_confirmation + return true if options[:skip_subject_confirmation] + valid_subject_confirmation = false + + subject_confirmation_nodes = xpath_from_signed_assertion('/a:Subject/a:SubjectConfirmation') + + now = Time.now.utc + subject_confirmation_nodes.each do |subject_confirmation| + if subject_confirmation.attributes.include? "Method" and subject_confirmation.attributes['Method'] != 'urn:oasis:names:tc:SAML:2.0:cm:bearer' + next + end + + confirmation_data_node = REXML::XPath.first( + subject_confirmation, + 'a:SubjectConfirmationData', + { "a" => ASSERTION } + ) + + next unless confirmation_data_node + + attrs = confirmation_data_node.attributes + next if (attrs.include? "InResponseTo" and attrs['InResponseTo'] != in_response_to) || + (attrs.include? "NotBefore" and now < (parse_time(confirmation_data_node, "NotBefore") - allowed_clock_drift)) || + (attrs.include? "NotOnOrAfter" and now >= (parse_time(confirmation_data_node, "NotOnOrAfter") + allowed_clock_drift)) || + (attrs.include? "Recipient" and !options[:skip_recipient_check] and settings and attrs['Recipient'] != settings.assertion_consumer_service_url) + + valid_subject_confirmation = true + break + end + + if !valid_subject_confirmation + error_msg = "A valid SubjectConfirmation was not found on this Response" + return append_error(error_msg) + end + + true + end + + # Validates the NameID element + def validate_name_id + if name_id_node.nil? + if settings.security[:want_name_id] + return append_error("No NameID element found in the assertion of the Response") + end + else + if name_id.nil? || name_id.empty? + return append_error("An empty NameID value found") + end + + unless settings.sp_entity_id.nil? || settings.sp_entity_id.empty? || name_id_spnamequalifier.nil? || name_id_spnamequalifier.empty? + if name_id_spnamequalifier != settings.sp_entity_id + return append_error("SPNameQualifier value does not match the SP entityID value.") + end + end + end + + true + end + + def doc_to_validate + # If the response contains the signature, and the assertion was encrypted, validate the original SAML Response + # otherwise, review if the decrypted assertion contains a signature + sig_elements = REXML::XPath.match( + document, + "/p:Response[@ID=$id]/ds:Signature", + { "p" => PROTOCOL, "ds" => DSIG }, + { 'id' => document.signed_element_id } + ) + + use_original = sig_elements.size == 1 || decrypted_document.nil? + doc = use_original ? document : decrypted_document + if !doc.processed + doc.cache_referenced_xml(@soft, check_malformed_doc_enabled?) + end + + return doc + end + + # Validates the Signature + # @return [Boolean] True if not contains a Signature or if the Signature is valid, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_signature + error_msg = "Invalid Signature on SAML Response" + + doc = doc_to_validate + + sig_elements = REXML::XPath.match( + document, + "/p:Response[@ID=$id]/ds:Signature", + { "p" => PROTOCOL, "ds" => DSIG }, + { 'id' => document.signed_element_id } + ) + + # Check signature node inside assertion + if sig_elements.nil? || sig_elements.size == 0 + sig_elements = REXML::XPath.match( + doc, + "/p:Response/a:Assertion[@ID=$id]/ds:Signature", + SAML_NAMESPACES.merge({"ds"=>DSIG}), + { 'id' => doc.signed_element_id } + ) end + + if sig_elements.size != 1 + if sig_elements.size == 0 + append_error("Signed element id ##{doc.signed_element_id} is not found") + else + append_error("Signed element id ##{doc.signed_element_id} is found more than once") + end + return append_error(error_msg) + end + + old_errors = @errors.clone + + idp_certs = settings.get_idp_cert_multi + if idp_certs.nil? || idp_certs[:signing].empty? + opts = {} + opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm + idp_cert = settings.get_idp_cert + fingerprint = settings.get_fingerprint + opts[:cert] = idp_cert + + if fingerprint && doc.validate_document(fingerprint, @soft, opts) + if settings.security[:check_idp_cert_expiration] + if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert) + error_msg = "IdP x509 certificate expired" + return append_error(error_msg) + end + end + else + return append_error(error_msg) + end + else + valid = false + expired = false + idp_certs[:signing].each do |idp_cert| + valid = doc.validate_document_with_cert(idp_cert, true) + if valid + if settings.security[:check_idp_cert_expiration] + if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert) + expired = true + end + end + + # At least one certificate is valid, restore the old accumulated errors + @errors = old_errors + break + end + + end + if expired + error_msg = "IdP x509 certificate expired" + return append_error(error_msg) + end + unless valid + # Remove duplicated errors + @errors = @errors.uniq + return append_error(error_msg) + end + end + true end + def name_id_node + @name_id_node ||= + begin + encrypted_node = xpath_first_from_signed_assertion('/a:Subject/a:EncryptedID') + if encrypted_node + decrypt_nameid(encrypted_node) + else + xpath_first_from_signed_assertion('/a:Subject/a:NameID') + end + end + end + + def get_cached_signed_assertion + xml = doc_to_validate.referenced_xml + empty_doc = REXML::Document.new + + return empty_doc if xml.nil? # when no signature/reference is found, return empty document + + root = REXML::Document.new(xml).root + + if root.attributes["ID"] != doc_to_validate.signed_element_id + return empty_doc + end + + assertion = empty_doc + if root.name == "Response" + if REXML::XPath.first(root, "a:Assertion", {"a" => ASSERTION}) + assertion = REXML::XPath.first(root, "a:Assertion", {"a" => ASSERTION}) + elsif REXML::XPath.first(root, "a:EncryptedAssertion", {"a" => ASSERTION}) + assertion = decrypt_assertion(REXML::XPath.first(root, "a:EncryptedAssertion", {"a" => ASSERTION})) + end + elsif root.name == "Assertion" + assertion = root + end + + assertion + end + + def signed_assertion + @signed_assertion ||= get_cached_signed_assertion + end + + # Extracts the first appearance that matchs the subelt (pattern) + # Search on any Assertion that is signed, or has a Response parent signed + # @param subelt [String] The XPath pattern + # @return [REXML::Element | nil] If any matches, return the Element + # + def xpath_first_from_signed_assertion(subelt=nil) + doc = signed_assertion + node = REXML::XPath.first( + doc, + "./#{subelt}", + SAML_NAMESPACES + ) + node + end + + # Extracts all the appearances that matchs the subelt (pattern) + # Search on any Assertion that is signed, or has a Response parent signed + # @param subelt [String] The XPath pattern + # @return [Array of REXML::Element] Return all matches + # + def xpath_from_signed_assertion(subelt=nil) + doc = signed_assertion + node = REXML::XPath.match( + doc, + "./#{subelt}", + SAML_NAMESPACES + ) + node + end + + # Generates the decrypted_document + # @return [XMLSecurity::SignedDocument] The SAML Response with the assertion decrypted + # + def generate_decrypted_document + if settings.nil? || settings.get_sp_decryption_keys.empty? + raise ValidationError.new('An EncryptedAssertion found and no SP private key found on the settings to decrypt it. Be sure you provided the :settings parameter at the initialize method') + end + + # Marshal at Ruby 1.8.7 throw an Exception + if RUBY_VERSION < "1.9" + document_copy = XMLSecurity::SignedDocument.new(response, errors) + else + document_copy = Marshal.load(Marshal.dump(document)) + end + + decrypt_assertion_from_document(document_copy) + end + + # Obtains a SAML Response with the EncryptedAssertion element decrypted + # @param document_copy [XMLSecurity::SignedDocument] A copy of the original SAML Response with the encrypted assertion + # @return [XMLSecurity::SignedDocument] The SAML Response with the assertion decrypted + # + def decrypt_assertion_from_document(document_copy) + response_node = REXML::XPath.first( + document_copy, + "/p:Response/", + { "p" => PROTOCOL } + ) + encrypted_assertion_node = REXML::XPath.first( + document_copy, + "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", + SAML_NAMESPACES + ) + response_node.add(decrypt_assertion(encrypted_assertion_node)) + encrypted_assertion_node.remove + XMLSecurity::SignedDocument.new(response_node.to_s) + end + + # Decrypts an EncryptedAssertion element + # @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element + # @return [REXML::Document] The decrypted EncryptedAssertion element + # + def decrypt_assertion(encrypted_assertion_node) + decrypt_element(encrypted_assertion_node, /(.*<\/(\w+:)?Assertion>)/m) + end + + # Decrypts an EncryptedID element + # @param encrypted_id_node [REXML::Element] The EncryptedID element + # @return [REXML::Document] The decrypted EncrypedtID element + # + def decrypt_nameid(encrypted_id_node) + decrypt_element(encrypted_id_node, /(.*<\/(\w+:)?NameID>)/m) + end + + # Decrypts an EncryptedAttribute element + # @param encrypted_attribute_node [REXML::Element] The EncryptedAttribute element + # @return [REXML::Document] The decrypted EncryptedAttribute element + # + def decrypt_attribute(encrypted_attribute_node) + decrypt_element(encrypted_attribute_node, /(.*<\/(\w+:)?Attribute>)/m) + end + + # Decrypt an element + # @param encrypt_node [REXML::Element] The encrypted element + # @param regexp [Regexp] The regular expression to extract the decrypted data + # @return [REXML::Document] The decrypted element + # + def decrypt_element(encrypt_node, regexp) + if settings.nil? || settings.get_sp_decryption_keys.empty? + raise ValidationError.new('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it') + end + + if encrypt_node.name == 'EncryptedAttribute' + node_header = '' + else + node_header = '' + end + + elem_plaintext = OneLogin::RubySaml::Utils.decrypt_multi(encrypt_node, settings.get_sp_decryption_keys) + + # If we get some problematic noise in the plaintext after decrypting. + # This quick regexp parse will grab only the Element and discard the noise. + elem_plaintext = elem_plaintext.match(regexp)[0] + + # To avoid namespace errors if saml namespace is not defined + # create a parent node first with the namespace defined + elem_plaintext = node_header + elem_plaintext + '' + doc = REXML::Document.new(elem_plaintext) + doc.root[0] + end + + # Parse the attribute of a given node in Time format + # @param node [REXML:Element] The node + # @param attribute [String] The attribute name + # @return [Time|nil] The parsed value + # def parse_time(node, attribute) if node && node.attributes[attribute] Time.parse(node.attributes[attribute]) end end + + def check_malformed_doc_enabled? + check_malformed_doc?(settings) + end end end end diff --git a/lib/onelogin/ruby-saml/saml_message.rb b/lib/onelogin/ruby-saml/saml_message.rb index b2c8fcf3e..18d4fc0ff 100644 --- a/lib/onelogin/ruby-saml/saml_message.rb +++ b/lib/onelogin/ruby-saml/saml_message.rb @@ -4,89 +4,167 @@ require 'nokogiri' require 'rexml/document' require 'rexml/xpath' -require 'thread' +require "onelogin/ruby-saml/error_handling" +# Only supports SAML 2.0 module OneLogin module RubySaml + + # SAML2 Message + # class SamlMessage include REXML - ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion" - PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol" + ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze + PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol".freeze - BASE64_FORMAT = %r(\A[A-Za-z0-9+/]{4}*[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=?\Z) + BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z) + # @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema + # def self.schema - @schema ||= Mutex.new.synchronize do - Dir.chdir(File.expand_path("../../../schemas", __FILE__)) do - ::Nokogiri::XML::Schema(File.read("saml-schema-protocol-2.0.xsd")) - end + path = File.expand_path("../../../schemas/saml-schema-protocol-2.0.xsd", __FILE__) + File.open(path) do |file| + ::Nokogiri::XML::Schema(file) end end - def valid_saml?(document, soft = true) - xml = Nokogiri::XML(document.to_s) + # @return [String|nil] Gets the Version attribute from the SAML Message if exists. + # + def version(document) + @version ||= begin + node = REXML::XPath.first( + document, + "/p:AuthnRequest | /p:Response | /p:LogoutResponse | /p:LogoutRequest", + { "p" => PROTOCOL } + ) + node.nil? ? nil : node.attributes['Version'] + end + end - SamlMessage.schema.validate(xml).map do |error| - break false if soft - validation_error("#{error.message}\n\n#{xml.to_s}") + # @return [String|nil] Gets the ID attribute from the SAML Message if exists. + # + def id(document) + @id ||= begin + node = REXML::XPath.first( + document, + "/p:AuthnRequest | /p:Response | /p:LogoutResponse | /p:LogoutRequest", + { "p" => PROTOCOL } + ) + node.nil? ? nil : node.attributes['ID'] end end - def validation_error(message) - raise ValidationError.new(message) + # Validates the SAML Message against the specified schema. + # @param document [REXML::Document] The message that will be validated + # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the message is invalid or not) + # @param check_malformed_doc [Boolean] check_malformed_doc Enable or Disable the check for malformed XML + # @return [Boolean] True if the XML is valid, otherwise False, if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def valid_saml?(document, soft = true, check_malformed_doc = true) + begin + xml = XMLSecurity::BaseDocument.safe_load_xml(document, check_malformed_doc) + rescue StandardError => error + return false if soft + raise ValidationError.new("XML load failed: #{error.message}") + end + + SamlMessage.schema.validate(xml).map do |schema_error| + return false if soft + raise ValidationError.new("#{schema_error.message}\n\n#{xml}") + end end private - ## - # Take a SAML object provided by +saml+, determine its status and return - # a decoded XML as a String. + # Base64 decode and try also to inflate a SAML Message + # @param saml [String] The deflated and encoded SAML Message + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @return [String] The plain SAML Message # - # Since SAML decided to use the RFC1951 and therefor has no zlib markers, - # the only reliable method of deciding whether we have a zlib stream or not - # is to try and inflate it and fall back to the base64 decoded string if - # the stream contains errors. - def decode_raw_saml(saml) + def decode_raw_saml(saml, settings = nil) + settings = OneLogin::RubySaml::Settings.new if settings.nil? + if saml.bytesize > settings.message_max_bytesize + raise ValidationError.new("Encoded SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected") + end + return saml unless base64_encoded?(saml) decoded = decode(saml) begin - inflate(decoded) + message = inflate(decoded) rescue - decoded + message = decoded end + + if message.bytesize > settings.message_max_bytesize + raise ValidationError.new("SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected") + end + + message end + # Deflate, base64 encode and url-encode a SAML Message (To be used in the HTTP-redirect binding) + # @param saml [String] The plain SAML Message + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @return [String] The deflated and encoded SAML Message (encoded if the compression is requested) + # def encode_raw_saml(saml, settings) saml = deflate(saml) if settings.compress_request - CGI.escape(Base64.encode64(saml)) + CGI.escape(encode(saml)) end - def decode(encoded) - Base64.decode64(encoded) + # Base 64 decode method + # @param string [String] The string message + # @return [String] The decoded string + # + def decode(string) + Base64.decode64(string) end - def encode(encoded) - Base64.encode64(encoded).gsub(/\n/, "") + # Base 64 encode method + # @param string [String] The string + # @return [String] The encoded string + # + def encode(string) + if Base64.respond_to?('strict_encode64') + Base64.strict_encode64(string) + else + Base64.encode64(string).gsub(/\n/, "") + end end # Check if a string is base64 encoded - # # @param string [String] string to check the encoding of # @return [true, false] whether or not the string is base64 encoded + # def base64_encoded?(string) - !!string.gsub(/[\r\n]|\\r|\\n/, "").match(BASE64_FORMAT) + !!string.gsub(/[\r\n]|\\r|\\n|\s/, "").match(BASE64_FORMAT) end + # Inflate method + # @param deflated [String] The string + # @return [String] The inflated string + # def inflate(deflated) Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(deflated) end + # Deflate method + # @param inflated [String] The string + # @return [String] The deflated string + # def deflate(inflated) Zlib::Deflate.deflate(inflated, 9)[2..-5] end + + def check_malformed_doc?(settings) + default_value = OneLogin::RubySaml::Settings::DEFAULTS[:check_malformed_doc] + + settings.nil? ? default_value : settings.check_malformed_doc + end end end end diff --git a/lib/onelogin/ruby-saml/setting_error.rb b/lib/onelogin/ruby-saml/setting_error.rb new file mode 100644 index 000000000..847766bf8 --- /dev/null +++ b/lib/onelogin/ruby-saml/setting_error.rb @@ -0,0 +1,6 @@ +module OneLogin + module RubySaml + class SettingError < StandardError + end + end +end \ No newline at end of file diff --git a/lib/onelogin/ruby-saml/settings.rb b/lib/onelogin/ruby-saml/settings.rb index 6288461fd..16870f8f6 100644 --- a/lib/onelogin/ruby-saml/settings.rb +++ b/lib/onelogin/ruby-saml/settings.rb @@ -1,17 +1,29 @@ require "xml_security" require "onelogin/ruby-saml/attribute_service" require "onelogin/ruby-saml/utils" +require "onelogin/ruby-saml/validation_error" +# Only supports SAML 2.0 module OneLogin module RubySaml + + # SAML2 Toolkit Settings + # class Settings - def initialize(overrides = {}) - config = DEFAULTS.merge(overrides) + def initialize(overrides = {}, keep_security_attributes = false) + if keep_security_attributes + security_attributes = overrides.delete(:security) || {} + config = DEFAULTS.merge(overrides) + config[:security] = DEFAULTS[:security].merge(security_attributes) + else + config = DEFAULTS.merge(overrides) + end + config.each do |k,v| - acc = "#{k.to_s}=".to_sym - if self.respond_to? acc + acc = "#{k}=".to_sym + if respond_to? acc value = v.is_a?(Hash) ? v.dup : v - self.send(acc, value) + send(acc, value) end end @attribute_consuming_service = AttributeService.new @@ -19,106 +31,356 @@ def initialize(overrides = {}) # IdP Data attr_accessor :idp_entity_id - attr_accessor :idp_sso_target_url - attr_accessor :idp_slo_target_url + attr_writer :idp_sso_service_url + attr_writer :idp_slo_service_url + attr_accessor :idp_slo_response_service_url attr_accessor :idp_cert attr_accessor :idp_cert_fingerprint + attr_accessor :idp_cert_fingerprint_algorithm + attr_accessor :idp_cert_multi + attr_accessor :idp_attribute_names + attr_accessor :idp_name_qualifier + attr_accessor :valid_until # SP Data - attr_accessor :issuer + attr_writer :sp_entity_id attr_accessor :assertion_consumer_service_url - attr_accessor :assertion_consumer_service_binding + attr_reader :assertion_consumer_service_binding + attr_writer :single_logout_service_url attr_accessor :sp_name_qualifier attr_accessor :name_identifier_format attr_accessor :name_identifier_value + attr_accessor :name_identifier_value_requested attr_accessor :sessionindex attr_accessor :compress_request attr_accessor :compress_response attr_accessor :double_quote_xml_attribute_values + attr_accessor :message_max_bytesize + attr_accessor :check_malformed_doc attr_accessor :passive - attr_accessor :protocol_binding + attr_reader :protocol_binding attr_accessor :attributes_index attr_accessor :force_authn - attr_accessor :security attr_accessor :certificate attr_accessor :private_key + attr_accessor :sp_cert_multi attr_accessor :authn_context attr_accessor :authn_context_comparison attr_accessor :authn_context_decl_ref attr_reader :attribute_consuming_service - # Compability + # Work-flow + attr_accessor :security + attr_accessor :soft + # Deprecated + attr_accessor :certificate_new attr_accessor :assertion_consumer_logout_service_url - attr_accessor :assertion_consumer_logout_service_binding + attr_reader :assertion_consumer_logout_service_binding + attr_accessor :issuer + attr_accessor :idp_sso_target_url + attr_accessor :idp_slo_target_url + + # @return [String] IdP Single Sign On Service URL + # + def idp_sso_service_url + @idp_sso_service_url || @idp_sso_target_url + end + + # @return [String] IdP Single Logout Service URL + # + def idp_slo_service_url + @idp_slo_service_url || @idp_slo_target_url + end + + # @return [String] IdP Single Sign On Service Binding + # + def idp_sso_service_binding + @idp_sso_service_binding || idp_binding_from_embed_sign + end + + # Setter for IdP Single Sign On Service Binding + # @param value [String, Symbol]. + # + def idp_sso_service_binding=(value) + @idp_sso_service_binding = get_binding(value) + end + + # @return [String] IdP Single Logout Service Binding + # + def idp_slo_service_binding + @idp_slo_service_binding || idp_binding_from_embed_sign + end + + # Setter for IdP Single Logout Service Binding + # @param value [String, Symbol]. + # + def idp_slo_service_binding=(value) + @idp_slo_service_binding = get_binding(value) + end - def single_logout_service_url() - val = nil - if @single_logout_service_url.nil? - if @assertion_consumer_logout_service_url - val = @assertion_consumer_logout_service_url + # @return [String] SP Entity ID + # + def sp_entity_id + @sp_entity_id || @issuer + end + + # Setter for SP Protocol Binding + # @param value [String, Symbol]. + # + def protocol_binding=(value) + @protocol_binding = get_binding(value) + end + + # Setter for SP Assertion Consumer Service Binding + # @param value [String, Symbol]. + # + def assertion_consumer_service_binding=(value) + @assertion_consumer_service_binding = get_binding(value) + end + + # @return [String] Single Logout Service URL. + # + def single_logout_service_url + @single_logout_service_url || @assertion_consumer_logout_service_url + end + + # @return [String] Single Logout Service Binding. + # + def single_logout_service_binding + @single_logout_service_binding || @assertion_consumer_logout_service_binding + end + + # Setter for Single Logout Service Binding. + # + # (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect") + # @param value [String, Symbol] + # + def single_logout_service_binding=(value) + @single_logout_service_binding = get_binding(value) + end + + # @deprecated Setter for legacy Single Logout Service Binding parameter. + # + # (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect") + # @param value [String, Symbol] + # + def assertion_consumer_logout_service_binding=(value) + @assertion_consumer_logout_service_binding = get_binding(value) + end + + # Calculates the fingerprint of the IdP x509 certificate. + # @return [String] The fingerprint + # + def get_fingerprint + idp_cert_fingerprint || begin + idp_cert = get_idp_cert + if idp_cert + fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(idp_cert_fingerprint_algorithm).new + fingerprint_alg.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":") end - else - val = @single_logout_service_url end - val end - # setter - def single_logout_service_url=(val) - @single_logout_service_url = val + # @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it) + # + def get_idp_cert + OneLogin::RubySaml::Utils.build_cert_object(idp_cert) end - def single_logout_service_binding() - val = nil - if @single_logout_service_binding.nil? - if @assertion_consumer_logout_service_binding - val = @assertion_consumer_logout_service_binding + # @return [Hash with 2 arrays of OpenSSL::X509::Certificate] Build multiple IdP certificates from the settings. + # + def get_idp_cert_multi + return nil if idp_cert_multi.nil? || idp_cert_multi.empty? + + raise ArgumentError.new("Invalid value for idp_cert_multi") unless idp_cert_multi.is_a?(Hash) + + certs = {:signing => [], :encryption => [] } + + [:signing, :encryption].each do |type| + certs_for_type = idp_cert_multi[type] || idp_cert_multi[type.to_s] + next if !certs_for_type || certs_for_type.empty? + + certs_for_type.each do |idp_cert| + certs[type].push(OneLogin::RubySaml::Utils.build_cert_object(idp_cert)) end - else - val = @single_logout_service_binding end - val + + certs end - # setter - def single_logout_service_binding=(val) - @single_logout_service_binding = val + # @return [Hash>>] + # Build the SP certificates and private keys from the settings. If + # check_sp_cert_expiration is true, only returns certificates and private keys + # that are not expired. + def get_sp_certs + certs = get_all_sp_certs + return certs unless security[:check_sp_cert_expiration] + + active_certs = { signing: [], encryption: [] } + certs.each do |use, pairs| + next if pairs.empty? + + pairs = pairs.select { |cert, _| !cert || OneLogin::RubySaml::Utils.is_cert_active(cert) } + raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.") if pairs.empty? + + active_certs[use] = pairs.freeze + end + active_certs.freeze + end + + # @return [Array] + # The SP signing certificate and private key. + def get_sp_signing_pair + get_sp_certs[:signing].first end + # @return [OpenSSL::X509::Certificate] The SP signing certificate. + # @deprecated Use get_sp_signing_pair or get_sp_certs instead. def get_sp_cert - cert = nil - if self.certificate - formated_cert = OneLogin::RubySaml::Utils.format_cert(self.certificate) - cert = OpenSSL::X509::Certificate.new(formated_cert) - end - cert + node = get_sp_signing_pair + node[0] if node end - def get_sp_key - private_key = nil - if self.private_key - formated_private_key = OneLogin::RubySaml::Utils.format_private_key(self.private_key) - private_key = OpenSSL::PKey::RSA.new(formated_private_key) - end - private_key + # @return [OpenSSL::PKey::RSA] The SP signing key. + def get_sp_signing_key + node = get_sp_signing_pair + node[1] if node end - private + # @deprecated Use get_sp_signing_key or get_sp_certs instead. + alias_method :get_sp_key, :get_sp_signing_key + + # @return [Array] The SP decryption keys. + def get_sp_decryption_keys + ary = get_sp_certs[:encryption].map { |pair| pair[1] } + ary.compact! + ary.uniq!(&:to_pem) + ary.freeze + end + + # @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings. + # + # @deprecated Use get_sp_certs instead + def get_sp_cert_new + node = get_sp_certs[:signing].last + node[0] if node + end + + def idp_binding_from_embed_sign + security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect] + end + + def get_binding(value) + return unless value + + Utils::BINDINGS[value.to_sym] || value + end DEFAULTS = { - :assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze, - :single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze, + :assertion_consumer_service_binding => Utils::BINDINGS[:post], + :single_logout_service_binding => Utils::BINDINGS[:redirect], + :idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1, :compress_request => true, :compress_response => true, - :security => { - :authn_requests_signed => false, - :logout_requests_signed => false, - :logout_responses_signed => false, - :metadata_signed => false, - :embed_sign => false, - :digest_method => XMLSecurity::Document::SHA1, - :signature_method => XMLSecurity::Document::RSA_SHA1 - }.freeze, + :message_max_bytesize => 250000, + :soft => true, + :check_malformed_doc => true, :double_quote_xml_attribute_values => false, + + :security => { + :authn_requests_signed => false, + :logout_requests_signed => false, + :logout_responses_signed => false, + :want_assertions_signed => false, + :want_assertions_encrypted => false, + :want_name_id => false, + :metadata_signed => false, + :embed_sign => false, # Deprecated + :digest_method => XMLSecurity::Document::SHA1, + :signature_method => XMLSecurity::Document::RSA_SHA1, + :check_idp_cert_expiration => false, + :check_sp_cert_expiration => false, + :strict_audience_validation => false, + :lowercase_url_encoding => false + }.freeze }.freeze + + private + + # @return [Hash>>] + # Build the SP certificates and private keys from the settings. Returns all + # certificates and private keys, even if they are expired. + def get_all_sp_certs + validate_sp_certs_params! + get_sp_certs_multi || get_sp_certs_single + end + + # Validate certificate, certificate_new, private_key, and sp_cert_multi params. + def validate_sp_certs_params! + multi = sp_cert_multi && !sp_cert_multi.empty? + cert = certificate && !certificate.empty? + cert_new = certificate_new && !certificate_new.empty? + pk = private_key && !private_key.empty? + if multi && (cert || cert_new || pk) + raise ArgumentError.new("Cannot specify both sp_cert_multi and certificate, certificate_new, private_key parameters") + end + end + + # Get certs from certificate, certificate_new, and private_key parameters. + def get_sp_certs_single + certs = { :signing => [], :encryption => [] } + + sp_key = OneLogin::RubySaml::Utils.build_private_key_object(private_key) + cert = OneLogin::RubySaml::Utils.build_cert_object(certificate) + if cert || sp_key + ary = [cert, sp_key].freeze + certs[:signing] << ary + certs[:encryption] << ary + end + + cert_new = OneLogin::RubySaml::Utils.build_cert_object(certificate_new) + if cert_new + ary = [cert_new, sp_key].freeze + certs[:signing] << ary + certs[:encryption] << ary + end + + certs + end + + # Get certs from get_sp_cert_multi parameter. + def get_sp_certs_multi + return if sp_cert_multi.nil? || sp_cert_multi.empty? + + raise ArgumentError.new("sp_cert_multi must be a Hash") unless sp_cert_multi.is_a?(Hash) + + certs = { :signing => [], :encryption => [] }.freeze + + [:signing, :encryption].each do |type| + certs_for_type = sp_cert_multi[type] || sp_cert_multi[type.to_s] + next if !certs_for_type || certs_for_type.empty? + + unless certs_for_type.is_a?(Array) && certs_for_type.all? { |cert| cert.is_a?(Hash) } + raise ArgumentError.new("sp_cert_multi :#{type} node must be an Array of Hashes") + end + + certs_for_type.each do |pair| + cert = pair[:certificate] || pair['certificate'] || pair[:cert] || pair['cert'] + key = pair[:private_key] || pair['private_key'] || pair[:key] || pair['key'] + + unless cert && key + raise ArgumentError.new("sp_cert_multi :#{type} node Hashes must specify keys :certificate and :private_key") + end + + certs[type] << [ + OneLogin::RubySaml::Utils.build_cert_object(cert), + OneLogin::RubySaml::Utils.build_private_key_object(key) + ].freeze + end + end + + certs.each { |_, ary| ary.freeze } + certs + end end end end + diff --git a/lib/onelogin/ruby-saml/slo_logoutrequest.rb b/lib/onelogin/ruby-saml/slo_logoutrequest.rb index 2cbb88540..bf88cde56 100644 --- a/lib/onelogin/ruby-saml/slo_logoutrequest.rb +++ b/lib/onelogin/ruby-saml/slo_logoutrequest.rb @@ -7,62 +7,342 @@ # Only supports SAML 2.0 module OneLogin module RubySaml + + # SAML2 Logout Request (SLO IdP initiated, Parser) + # class SloLogoutrequest < SamlMessage - attr_reader :options - attr_reader :request + include ErrorHandling + + # OneLogin::RubySaml::Settings Toolkit settings + attr_accessor :settings + attr_reader :document + attr_reader :request + attr_reader :options + + attr_accessor :soft + # Constructs the Logout Request. A Logout Request Object that is an extension of the SamlMessage class. + # @param request [String] A UUEncoded Logout Request from the IdP. + # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object + # Or :allowed_clock_drift for the logout request validation process to allow a clock drift when checking dates with + # Or :relax_signature_validation to accept signatures if no idp certificate registered on settings + # + # @raise [ArgumentError] If Request is nil + # def initialize(request, options = {}) raise ArgumentError.new("Request cannot be nil") if request.nil? - @options = options - @request = decode_raw_saml(request) + + @errors = [] + @options = options + @soft = true + unless options[:settings].nil? + @settings = options[:settings] + unless @settings.soft.nil? + @soft = @settings.soft + end + end + + @request = decode_raw_saml(request, settings) @document = REXML::Document.new(@request) end - def is_valid? - validate + def request_id + id(document) end - def validate! - validate(false) + # Validates the Logout Request with the default values (soft = true) + # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. + # @return [Boolean] TRUE if the Logout Request is valid + # + def is_valid?(collect_errors = false) + validate(collect_errors) end - # The value of the user identifier as designated by the initialization request response + # @return [String] Gets the NameID of the Logout Request. + # def name_id - @name_id ||= begin - node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION }) - node.nil? ? nil : node.text + @name_id ||= Utils.element_text(name_id_node) + end + + alias_method :nameid, :name_id + + # @return [String] Gets the NameID Format of the Logout Request. + # + def name_id_format + @name_id_format ||= + if name_id_node && name_id_node.attribute("Format") + name_id_node.attribute("Format").value + end + end + + alias_method :nameid_format, :name_id_format + + def name_id_node + @name_id_node ||= + begin + encrypted_node = REXML::XPath.first(document, "/p:LogoutRequest/a:EncryptedID", { "p" => PROTOCOL, "a" => ASSERTION }) + if encrypted_node + node = decrypt_nameid(encrypted_node) + else + node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION }) + end + end + end + + # Decrypts an EncryptedID element + # @param encrypted_id_node [REXML::Element] The EncryptedID element + # @return [REXML::Document] The decrypted EncrypedtID element + # + def decrypt_nameid(encrypted_id_node) + + if settings.nil? || settings.get_sp_decryption_keys.empty? + raise ValidationError.new('An ' + encrypted_id_node.name + ' found and no SP private key found on the settings to decrypt it') end + + elem_plaintext = OneLogin::RubySaml::Utils.decrypt_multi(encrypted_id_node, settings.get_sp_decryption_keys) + # If we get some problematic noise in the plaintext after decrypting. + # This quick regexp parse will grab only the Element and discard the noise. + elem_plaintext = elem_plaintext.match(/(.*<\/(\w+:)?NameID>)/m)[0] + + # To avoid namespace errors if saml namespace is not defined + # create a parent node first with the namespace defined + node_header = '' + elem_plaintext = node_header + elem_plaintext + '' + doc = REXML::Document.new(elem_plaintext) + doc.root[0] end + # @return [String|nil] Gets the ID attribute from the Logout Request. if exists. + # def id - return @id if @id - element = REXML::XPath.first(document, "/p:LogoutRequest", { - "p" => PROTOCOL} ) - return nil if element.nil? - return element.attributes["ID"] + super(document) end + # @return [String] Gets the Issuer from the Logout Request. + # def issuer @issuer ||= begin - node = REXML::XPath.first(document, "/p:LogoutRequest/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION }) - node.nil? ? nil : node.text + node = REXML::XPath.first( + document, + "/p:LogoutRequest/a:Issuer", + { "p" => PROTOCOL, "a" => ASSERTION } + ) + Utils.element_text(node) + end + end + + # @return [Time|nil] Gets the NotOnOrAfter Attribute value if exists. + # + def not_on_or_after + @not_on_or_after ||= begin + node = REXML::XPath.first( + document, + "/p:LogoutRequest", + { "p" => PROTOCOL } + ) + if node && node.attributes["NotOnOrAfter"] + Time.parse(node.attributes["NotOnOrAfter"]) + end end end + # @return [Array] Gets the SessionIndex if exists (Supported multiple values). Empty Array if none found + # + def session_indexes + nodes = REXML::XPath.match( + document, + "/p:LogoutRequest/p:SessionIndex", + { "p" => PROTOCOL } + ) + + nodes.map { |node| Utils.element_text(node) } + end + private - def validate(soft = true) - valid_saml?(document, soft) && validate_request_state(soft) + # returns the allowed clock drift on timing validation + # @return [Float] + def allowed_clock_drift + options[:allowed_clock_drift].to_f.abs + Float::EPSILON end - def validate_request_state(soft = true) - if request.empty? - return soft ? false : validation_error("Blank request") + # Hard aux function to validate the Logout Request + # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true) + # @return [Boolean] TRUE if the Logout Request is valid + # @raise [ValidationError] if soft == false and validation fails + # + def validate(collect_errors = false) + reset_errors! + + validations = [ + :validate_request_state, + :validate_id, + :validate_version, + :validate_structure, + :validate_not_on_or_after, + :validate_issuer, + :validate_signature + ] + + if collect_errors + validations.each { |validation| send(validation) } + @errors.empty? + else + validations.all? { |validation| send(validation) } end + end + + # Validates that the Logout Request contains an ID + # If fails, the error is added to the errors array. + # @return [Boolean] True if the Logout Request contains an ID, otherwise returns False + # + def validate_id + unless id + return append_error("Missing ID attribute on Logout Request") + end + true end + # Validates the SAML version (2.0) + # If fails, the error is added to the errors array. + # @return [Boolean] True if the Logout Request is 2.0, otherwise returns False + # + def validate_version + unless version(document) == "2.0" + return append_error("Unsupported SAML version") + end + + true + end + + # Validates the time. (If the logout request was initialized with the :allowed_clock_drift + # option, the timing validations are relaxed by the allowed_clock_drift value) + # If fails, the error is added to the errors array + # @return [Boolean] True if satisfies the conditions, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_not_on_or_after + now = Time.now.utc + + if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift) + return append_error("Current time is on or after NotOnOrAfter (#{now} >= #{not_on_or_after}#{" + #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})") + end + + true + end + + # Validates the Logout Request against the specified schema. + # @return [Boolean] True if the XML is valid, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_structure + check_malformed_doc = check_malformed_doc?(settings) + unless valid_saml?(document, soft, check_malformed_doc) + return append_error("Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd") + end + + true + end + + # Validates that the Logout Request provided in the initialization is not empty, + # @return [Boolean] True if the required info is found, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_request_state + return append_error("Blank logout request") if request.nil? || request.empty? + + true + end + + # Validates the Issuer of the Logout Request + # If fails, the error is added to the errors array + # @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_issuer + return true if settings.nil? || settings.idp_entity_id.nil? || issuer.nil? + + unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id) + return append_error("Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>") + end + + true + end + + # Validates the Signature if exists and GET parameters are provided + # @return [Boolean] True if not contains a Signature or if the Signature is valid, otherwise False if soft=True + # @raise [ValidationError] if soft == false and validation fails + # + def validate_signature + return true if options.nil? + return true unless options.has_key? :get_params + return true unless options[:get_params].has_key? 'Signature' + + options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params], settings.security[:lowercase_url_encoding]) + + if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil? + options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg']) + end + + idp_cert = settings.get_idp_cert + idp_certs = settings.get_idp_cert_multi + + if idp_cert.nil? && (idp_certs.nil? || idp_certs[:signing].empty?) + return options.has_key? :relax_signature_validation + end + + query_string = OneLogin::RubySaml::Utils.build_query_from_raw_parts( + :type => 'SAMLRequest', + :raw_data => options[:raw_get_params]['SAMLRequest'], + :raw_relay_state => options[:raw_get_params]['RelayState'], + :raw_sig_alg => options[:raw_get_params]['SigAlg'] + ) + + expired = false + if idp_certs.nil? || idp_certs[:signing].empty? + valid = OneLogin::RubySaml::Utils.verify_signature( + :cert => idp_cert, + :sig_alg => options[:get_params]['SigAlg'], + :signature => options[:get_params]['Signature'], + :query_string => query_string + ) + if valid && settings.security[:check_idp_cert_expiration] + if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert) + expired = true + end + end + else + valid = false + idp_certs[:signing].each do |signing_idp_cert| + valid = OneLogin::RubySaml::Utils.verify_signature( + :cert => signing_idp_cert, + :sig_alg => options[:get_params]['SigAlg'], + :signature => options[:get_params]['Signature'], + :query_string => query_string + ) + if valid + if settings.security[:check_idp_cert_expiration] + if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert) + expired = true + end + end + break + end + end + end + + if expired + error_msg = "IdP x509 certificate expired" + return append_error(error_msg) + end + unless valid + return append_error("Invalid Signature on Logout Request") + end + + true + end end end end diff --git a/lib/onelogin/ruby-saml/slo_logoutresponse.rb b/lib/onelogin/ruby-saml/slo_logoutresponse.rb index cd71e281c..9791a73d3 100644 --- a/lib/onelogin/ruby-saml/slo_logoutresponse.rb +++ b/lib/onelogin/ruby-saml/slo_logoutresponse.rb @@ -1,40 +1,76 @@ -require "uuid" - require "onelogin/ruby-saml/logging" + require "onelogin/ruby-saml/saml_message" +require "onelogin/ruby-saml/utils" +require "onelogin/ruby-saml/setting_error" +# Only supports SAML 2.0 module OneLogin module RubySaml + + # SAML2 Logout Response (SLO SP initiated, Parser) + # class SloLogoutresponse < SamlMessage - attr_reader :uuid # Can be obtained if neccessary + # Logout Response ID + attr_accessor :uuid + # Initializes the Logout Response. A SloLogoutresponse Object that is an extension of the SamlMessage class. + # Asigns an ID, a random uuid. + # def initialize - @uuid = "_" + UUID.new.generate + @uuid = OneLogin::RubySaml::Utils.uuid + end + + def response_id + @uuid end - def create(settings, request_id = nil, logout_message = nil, params = {}) - params = create_params(settings, request_id, logout_message, params) - params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?' + # Creates the Logout Response string. + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response + # @param logout_message [String] The Message to be placed as StatusMessage in the logout response + # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState + # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response + # @return [String] Logout Request string that includes the SAMLRequest + # + def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil) + params = create_params(settings, request_id, logout_message, params, logout_status_code) + params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?' + url = settings.idp_slo_response_service_url || settings.idp_slo_service_url saml_response = CGI.escape(params.delete("SAMLResponse")) response_params = "#{params_prefix}SAMLResponse=#{saml_response}" params.each_pair do |key, value| - response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}" + response_params << "&#{key}=#{CGI.escape(value.to_s)}" end - @logout_url = settings.idp_slo_target_url + response_params + raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty? + @logout_url = url + response_params end - def create_params(settings, request_id = nil, logout_message = nil, params = {}) + # Creates the Get parameters for the logout response. + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response + # @param logout_message [String] The Message to be placed as StatusMessage in the logout response + # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState + # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response + # @return [Hash] Parameters + # + def create_params(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil) # The method expects :RelayState but sometimes we get 'RelayState' instead. # Based on the HashWithIndifferentAccess value in Rails we could experience # conflicts so this line will solve them. relay_state = params[:RelayState] || params['RelayState'] - response_doc = create_logout_response_xml_doc(settings, request_id, logout_message) + if relay_state.nil? + params.delete(:RelayState) + params.delete('RelayState') + end + + response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code) response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values - response = "" + response = "".dup response_doc.write(response) Logging.debug "Created SLO Logout Response: #{response}" @@ -42,14 +78,18 @@ def create_params(settings, request_id = nil, logout_message = nil, params = {}) response = deflate(response) if settings.compress_response base64_response = encode(response) response_params = {"SAMLResponse" => base64_response} - - if settings.security[:logout_responses_signed] && !settings.security[:embed_sign] && settings.private_key - params['SigAlg'] = settings.security[:signature_method] - url_string = "SAMLResponse=#{CGI.escape(base64_response)}" - url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state - url_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" - private_key = settings.get_sp_key() - signature = private_key.sign(XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]).new, url_string) + sp_signing_key = settings.get_sp_signing_key + + if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && sp_signing_key + params['SigAlg'] = settings.security[:signature_method] + url_string = OneLogin::RubySaml::Utils.build_query( + :type => 'SAMLResponse', + :data => base64_response, + :relay_state => relay_state, + :sig_alg => params['SigAlg'] + ) + sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + signature = sp_signing_key.sign(sign_algorithm.new, url_string) params['Signature'] = encode(signature) end @@ -60,44 +100,63 @@ def create_params(settings, request_id = nil, logout_message = nil, params = {}) response_params end - def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil) + # Creates the SAMLResponse String. + # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings + # @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response + # @param logout_message [String] The Message to be placed as StatusMessage in the logout response + # @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response + # @return [String] The SAMLResponse String. + # + def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil, logout_status_code = nil) + document = create_xml_document(settings, request_id, logout_message, logout_status_code) + sign_document(document, settings) + end + + def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil) time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ') response_doc = XMLSecurity::Document.new response_doc.uuid = uuid + destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url + + root = response_doc.add_element 'samlp:LogoutResponse', { 'xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" } root.attributes['ID'] = uuid root.attributes['IssueInstant'] = time root.attributes['Version'] = '2.0' root.attributes['InResponseTo'] = request_id unless request_id.nil? - root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil? + root.attributes['Destination'] = destination unless destination.nil? or destination.empty? + + if settings.sp_entity_id != nil + issuer = root.add_element "saml:Issuer" + issuer.text = settings.sp_entity_id + end - # add success message + # add status status = root.add_element 'samlp:Status' - # success status code - status_code = status.add_element 'samlp:StatusCode' - status_code.attributes['Value'] = 'urn:oasis:names:tc:SAML:2.0:status:Success' + # status code + status_code ||= 'urn:oasis:names:tc:SAML:2.0:status:Success' + status_code_elem = status.add_element 'samlp:StatusCode' + status_code_elem.attributes['Value'] = status_code - # success status message + # status message logout_message ||= 'Successfully Signed Out' status_message = status.add_element 'samlp:StatusMessage' status_message.text = logout_message - if settings.issuer != nil - issuer = root.add_element "saml:Issuer" - issuer.text = settings.issuer - end + response_doc + end + def sign_document(document, settings) # embed signature - if settings.security[:logout_responses_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign] - private_key = settings.get_sp_key() - cert = settings.get_sp_cert() - response_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) + cert, private_key = settings.get_sp_signing_pair + if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && private_key && cert + document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method]) end - response_doc + document end end diff --git a/lib/onelogin/ruby-saml/utils.rb b/lib/onelogin/ruby-saml/utils.rb index 1b0728ad5..b66f6d77c 100644 --- a/lib/onelogin/ruby-saml/utils.rb +++ b/lib/onelogin/ruby-saml/utils.rb @@ -1,44 +1,453 @@ +if RUBY_VERSION < '1.9' + require 'uuid' +else + require 'securerandom' +end +require "openssl" + module OneLogin module RubySaml + + # SAML2 Auxiliary class + # class Utils - def self.format_cert(cert, heads=true) - cert = cert.delete("\n").delete("\r").delete("\x0D") - if cert - cert = cert.gsub('-----BEGIN CERTIFICATE-----', '') - cert = cert.gsub('-----END CERTIFICATE-----', '') - cert = cert.gsub(' ', '') - - if heads - cert = cert.scan(/.{1,64}/).join("\n")+"\n" - cert = "-----BEGIN CERTIFICATE-----\n" + cert + "-----END CERTIFICATE-----\n" + @@uuid_generator = UUID.new if RUBY_VERSION < '1.9' + + BINDINGS = { :post => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze, + :redirect => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze }.freeze + DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze + XENC = "http://www.w3.org/2001/04/xmlenc#".freeze + DURATION_FORMAT = %r(^ + (-?)P # 1: Duration sign + (?: + (?:(\d+)Y)? # 2: Years + (?:(\d+)M)? # 3: Months + (?:(\d+)D)? # 4: Days + (?:T + (?:(\d+)H)? # 5: Hours + (?:(\d+)M)? # 6: Minutes + (?:(\d+(?:[.,]\d+)?)S)? # 7: Seconds + )? + | + (\d+)W # 8: Weeks + ) + $)x.freeze + + UUID_PREFIX = '_' + @@prefix = '_' + + # Checks if the x509 cert provided is expired. + # + # @param cert [OpenSSL::X509::Certificate|String] The x509 certificate. + # @return [true|false] Whether the certificate is expired. + def self.is_cert_expired(cert) + cert = OpenSSL::X509::Certificate.new(cert) if cert.is_a?(String) + + cert.not_after < Time.now + end + + # Checks if the x509 cert provided has both started and has not expired. + # + # @param cert [OpenSSL::X509::Certificate|String] The x509 certificate. + # @return [true|false] Whether the certificate is currently active. + def self.is_cert_active(cert) + cert = OpenSSL::X509::Certificate.new(cert) if cert.is_a?(String) + now = Time.now + cert.not_before <= now && cert.not_after >= now + end + + # Interprets a ISO8601 duration value relative to a given timestamp. + # + # @param duration [String] The duration, as a string. + # @param timestamp [Integer] The unix timestamp we should apply the + # duration to. Optional, default to the + # current time. + # + # @return [Integer] The new timestamp, after the duration is applied. + # + def self.parse_duration(duration, timestamp=Time.now.utc) + return nil if RUBY_VERSION < '1.9' # 1.8.7 not supported + + matches = duration.match(DURATION_FORMAT) + + if matches.nil? + raise StandardError.new("Invalid ISO 8601 duration") + end + + sign = matches[1] == '-' ? -1 : 1 + + durYears, durMonths, durDays, durHours, durMinutes, durSeconds, durWeeks = + matches[2..8].map do |match| + if match + match = match.tr(',', '.').gsub(/\.0*\z/, '') + sign * (match.include?('.') ? match.to_f : match.to_i) + else + 0 + end end + + datetime = Time.at(timestamp).utc.to_datetime + datetime = datetime.next_year(durYears) + datetime = datetime.next_month(durMonths) + datetime = datetime.next_day((7*durWeeks) + durDays) + datetime.to_time.utc.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds + end + + # Return a properly formatted x509 certificate + # + # @param cert [String] The original certificate + # @return [String] The formatted certificate + # + def self.format_cert(cert) + # don't try to format an encoded certificate or if is empty or nil + if cert.respond_to?(:ascii_only?) + return cert if cert.nil? || cert.empty? || !cert.ascii_only? + else + return cert if cert.nil? || cert.empty? || cert.match(/\x0d/) + end + + if cert.scan(/BEGIN CERTIFICATE/).length > 1 + formatted_cert = [] + cert.scan(/-{5}BEGIN CERTIFICATE-{5}[\n\r]?.*?-{5}END CERTIFICATE-{5}[\n\r]?/m) {|c| + formatted_cert << format_cert(c) + } + formatted_cert.join("\n") + else + cert = cert.gsub(/\-{5}\s?(BEGIN|END) CERTIFICATE\s?\-{5}/, "") + cert = cert.gsub(/\r/, "") + cert = cert.gsub(/\n/, "") + cert = cert.gsub(/\s/, "") + cert = cert.scan(/.{1,64}/) + cert = cert.join("\n") + "-----BEGIN CERTIFICATE-----\n#{cert}\n-----END CERTIFICATE-----" end - cert end - def self.format_private_key(key, heads=true) - key = key.delete("\n").delete("\r").delete("\x0D") - if key - if key.index('-----BEGIN PRIVATE KEY-----') != nil - key = key.gsub('-----BEGIN PRIVATE KEY-----', '') - key = key.gsub('-----END PRIVATE KEY-----', '') - key = key.gsub(' ', '') - if heads - key = key.scan(/.{1,64}/).join("\n")+"\n" - key = "-----BEGIN PRIVATE KEY-----\n" + key + "-----END PRIVATE KEY-----\n" + # Return a properly formatted private key + # + # @param key [String] The original private key + # @return [String] The formatted private key + # + def self.format_private_key(key) + # don't try to format an encoded private key or if is empty + return key if key.nil? || key.empty? || key.match(/\x0d/) + + # is this an rsa key? + rsa_key = key.match("RSA PRIVATE KEY") + key = key.gsub(/\-{5}\s?(BEGIN|END)( RSA)? PRIVATE KEY\s?\-{5}/, "") + key = key.gsub(/\n/, "") + key = key.gsub(/\r/, "") + key = key.gsub(/\s/, "") + key = key.scan(/.{1,64}/) + key = key.join("\n") + key_label = rsa_key ? "RSA PRIVATE KEY" : "PRIVATE KEY" + "-----BEGIN #{key_label}-----\n#{key}\n-----END #{key_label}-----" + end + + # Given a certificate string, return an OpenSSL::X509::Certificate object. + # + # @param cert [String] The original certificate + # @return [OpenSSL::X509::Certificate] The certificate object + # + def self.build_cert_object(cert) + return nil if cert.nil? || cert.empty? + + OpenSSL::X509::Certificate.new(format_cert(cert)) + end + + # Given a private key string, return an OpenSSL::PKey::RSA object. + # + # @param cert [String] The original private key + # @return [OpenSSL::PKey::RSA] The private key object + # + def self.build_private_key_object(private_key) + return nil if private_key.nil? || private_key.empty? + + OpenSSL::PKey::RSA.new(format_private_key(private_key)) + end + + # Build the Query String signature that will be used in the HTTP-Redirect binding + # to generate the Signature + # @param params [Hash] Parameters to build the Query String + # @option params [String] :type 'SAMLRequest' or 'SAMLResponse' + # @option params [String] :data Base64 encoded SAMLRequest or SAMLResponse + # @option params [String] :relay_state The RelayState parameter + # @option params [String] :sig_alg The SigAlg parameter + # @return [String] The Query String + # + def self.build_query(params) + type, data, relay_state, sig_alg = [:type, :data, :relay_state, :sig_alg].map { |k| params[k]} + + url_string = "#{type}=#{CGI.escape(data)}" + url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state + url_string << "&SigAlg=#{CGI.escape(sig_alg)}" + end + + # Reconstruct a canonical query string from raw URI-encoded parts, to be used in verifying a signature + # + # @param params [Hash] Parameters to build the Query String + # @option params [String] :type 'SAMLRequest' or 'SAMLResponse' + # @option params [String] :raw_data URI-encoded, base64 encoded SAMLRequest or SAMLResponse, as sent by IDP + # @option params [String] :raw_relay_state URI-encoded RelayState parameter, as sent by IDP + # @option params [String] :raw_sig_alg URI-encoded SigAlg parameter, as sent by IDP + # @return [String] The Query String + # + def self.build_query_from_raw_parts(params) + type, raw_data, raw_relay_state, raw_sig_alg = [:type, :raw_data, :raw_relay_state, :raw_sig_alg].map { |k| params[k]} + + url_string = "#{type}=#{raw_data}" + url_string << "&RelayState=#{raw_relay_state}" if raw_relay_state + url_string << "&SigAlg=#{raw_sig_alg}" + end + + # Prepare raw GET parameters (build them from normal parameters + # if not provided). + # + # @param rawparams [Hash] Raw GET Parameters + # @param params [Hash] GET Parameters + # @param lowercase_url_encoding [bool] Lowercase URL Encoding (For ADFS urlencode compatiblity) + # @return [Hash] New raw parameters + # + def self.prepare_raw_get_params(rawparams, params, lowercase_url_encoding=false) + rawparams ||= {} + + if rawparams['SAMLRequest'].nil? && !params['SAMLRequest'].nil? + rawparams['SAMLRequest'] = escape_request_param(params['SAMLRequest'], lowercase_url_encoding) + end + if rawparams['SAMLResponse'].nil? && !params['SAMLResponse'].nil? + rawparams['SAMLResponse'] = escape_request_param(params['SAMLResponse'], lowercase_url_encoding) + end + if rawparams['RelayState'].nil? && !params['RelayState'].nil? + rawparams['RelayState'] = escape_request_param(params['RelayState'], lowercase_url_encoding) + end + if rawparams['SigAlg'].nil? && !params['SigAlg'].nil? + rawparams['SigAlg'] = escape_request_param(params['SigAlg'], lowercase_url_encoding) + end + + rawparams + end + + def self.escape_request_param(param, lowercase_url_encoding) + CGI.escape(param).tap do |escaped| + next unless lowercase_url_encoding + + escaped.gsub!(/%[A-Fa-f0-9]{2}/) { |match| match.downcase } + end + end + + # Validate the Signature parameter sent on the HTTP-Redirect binding + # @param params [Hash] Parameters to be used in the validation process + # @option params [OpenSSL::X509::Certificate] cert The IDP public certificate + # @option params [String] sig_alg The SigAlg parameter + # @option params [String] signature The Signature parameter (base64 encoded) + # @option params [String] query_string The full GET Query String to be compared + # @return [Boolean] True if the Signature is valid, False otherwise + # + def self.verify_signature(params) + cert, sig_alg, signature, query_string = [:cert, :sig_alg, :signature, :query_string].map { |k| params[k]} + signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(sig_alg) + return cert.public_key.verify(signature_algorithm.new, Base64.decode64(signature), query_string) + end + + # Build the status error message + # @param status_code [String] StatusCode value + # @param status_message [Strig] StatusMessage value + # @return [String] The status error message + def self.status_error_msg(error_msg, raw_status_code = nil, status_message = nil) + error_msg = error_msg.dup + + unless raw_status_code.nil? + if raw_status_code.include? "|" + status_codes = raw_status_code.split(' | ') + values = status_codes.collect do |status_code| + status_code.split(':').last end + printable_code = values.join(" => ") else - key = key.gsub('-----BEGIN RSA PRIVATE KEY-----', '') - key = key.gsub('-----END RSA PRIVATE KEY-----', '') - key = key.gsub(' ', '') - if heads - key = key.scan(/.{1,64}/).join("\n")+"\n" - key = "-----BEGIN RSA PRIVATE KEY-----\n" + key + "-----END RSA PRIVATE KEY-----\n" - end + printable_code = raw_status_code.split(':').last + end + error_msg << ', was ' + printable_code + end + + unless status_message.nil? + error_msg << ' -> ' + status_message + end + + error_msg + end + + # Obtains the decrypted string from an Encrypted node element in XML, + # given multiple private keys to try. + # @param encrypted_node [REXML::Element] The Encrypted element + # @param private_keys [Array] The Service provider private key + # @return [String] The decrypted data + def self.decrypt_multi(encrypted_node, private_keys) + raise ArgumentError.new('private_keys must be specified') if !private_keys || private_keys.empty? + + error = nil + private_keys.each do |key| + begin + return decrypt_data(encrypted_node, key) + rescue OpenSSL::PKey::PKeyError => e + error ||= e end end + + raise(error) if error + end + + # Obtains the decrypted string from an Encrypted node element in XML + # @param encrypted_node [REXML::Element] The Encrypted element + # @param private_key [OpenSSL::PKey::RSA] The Service provider private key + # @return [String] The decrypted data + def self.decrypt_data(encrypted_node, private_key) + encrypt_data = REXML::XPath.first( + encrypted_node, + "./xenc:EncryptedData", + { 'xenc' => XENC } + ) + symmetric_key = retrieve_symmetric_key(encrypt_data, private_key) + cipher_value = REXML::XPath.first( + encrypt_data, + "./xenc:CipherData/xenc:CipherValue", + { 'xenc' => XENC } + ) + node = Base64.decode64(element_text(cipher_value)) + encrypt_method = REXML::XPath.first( + encrypt_data, + "./xenc:EncryptionMethod", + { 'xenc' => XENC } + ) + algorithm = encrypt_method.attributes['Algorithm'] + retrieve_plaintext(node, symmetric_key, algorithm) + end + + # Obtains the symmetric key from the EncryptedData element + # @param encrypt_data [REXML::Element] The EncryptedData element + # @param private_key [OpenSSL::PKey::RSA] The Service provider private key + # @return [String] The symmetric key + def self.retrieve_symmetric_key(encrypt_data, private_key) + encrypted_key = REXML::XPath.first( + encrypt_data, + "./ds:KeyInfo/xenc:EncryptedKey | ./KeyInfo/xenc:EncryptedKey | //xenc:EncryptedKey[@Id=$id]", + { "ds" => DSIG, "xenc" => XENC }, + { "id" => self.retrieve_symetric_key_reference(encrypt_data) } + ) + + encrypted_symmetric_key_element = REXML::XPath.first( + encrypted_key, + "./xenc:CipherData/xenc:CipherValue", + "xenc" => XENC + ) + + cipher_text = Base64.decode64(element_text(encrypted_symmetric_key_element)) + + encrypt_method = REXML::XPath.first( + encrypted_key, + "./xenc:EncryptionMethod", + "xenc" => XENC + ) + + algorithm = encrypt_method.attributes['Algorithm'] + retrieve_plaintext(cipher_text, private_key, algorithm) + end + + def self.retrieve_symetric_key_reference(encrypt_data) + REXML::XPath.first( + encrypt_data, + "substring-after(./ds:KeyInfo/ds:RetrievalMethod/@URI, '#')", + { "ds" => DSIG } + ) + end + + # Obtains the deciphered text + # @param cipher_text [String] The ciphered text + # @param symmetric_key [String] The symmetric key used to encrypt the text + # @param algorithm [String] The encrypted algorithm + # @return [String] The deciphered text + def self.retrieve_plaintext(cipher_text, symmetric_key, algorithm) + case algorithm + when 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' then cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').decrypt + when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt + when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt + when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt + when 'http://www.w3.org/2009/xmlenc11#aes128-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(128, :GCM).decrypt + when 'http://www.w3.org/2009/xmlenc11#aes192-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(192, :GCM).decrypt + when 'http://www.w3.org/2009/xmlenc11#aes256-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(256, :GCM).decrypt + when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key + when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key + end + + if cipher + iv_len = cipher.iv_len + data = cipher_text[iv_len..-1] + cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1] + assertion_plaintext = cipher.update(data) + assertion_plaintext << cipher.final + elsif auth_cipher + iv_len, text_len, tag_len = auth_cipher.iv_len, cipher_text.length, 16 + data = cipher_text[iv_len..text_len-1-tag_len] + auth_cipher.padding = 0 + auth_cipher.key = symmetric_key + auth_cipher.iv = cipher_text[0..iv_len-1] + auth_cipher.auth_data = '' + auth_cipher.auth_tag = cipher_text[text_len-tag_len..-1] + assertion_plaintext = auth_cipher.update(data) + assertion_plaintext << auth_cipher.final + elsif rsa + rsa.private_decrypt(cipher_text) + elsif oaep + oaep.private_decrypt(cipher_text, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING) + else + cipher_text + end + end + + def self.set_prefix(value) + @@prefix = value + end + + def self.prefix + @@prefix + end + + def self.uuid + "#{prefix}" + (RUBY_VERSION < '1.9' ? "#{@@uuid_generator.generate}" : "#{SecureRandom.uuid}") + end + + # Given two strings, attempt to match them as URIs using Rails' parse method. If they can be parsed, + # then the fully-qualified domain name and the host should performa a case-insensitive match, per the + # RFC for URIs. If Rails can not parse the string in to URL pieces, return a boolean match of the + # two strings. This maintains the previous functionality. + # @return [Boolean] + def self.uri_match?(destination_url, settings_url) + dest_uri = URI.parse(destination_url) + acs_uri = URI.parse(settings_url) + + if dest_uri.scheme.nil? || acs_uri.scheme.nil? || dest_uri.host.nil? || acs_uri.host.nil? + raise URI::InvalidURIError + else + dest_uri.scheme.downcase == acs_uri.scheme.downcase && + dest_uri.host.downcase == acs_uri.host.downcase && + dest_uri.path == acs_uri.path && + dest_uri.query == acs_uri.query + end + rescue URI::InvalidURIError + original_uri_match?(destination_url, settings_url) end + # If Rails' URI.parse can't match to valid URL, default back to the original matching service. + # @return [Boolean] + def self.original_uri_match?(destination_url, settings_url) + destination_url == settings_url + end + + # Given a REXML::Element instance, return the concatenation of all child text nodes. Assumes + # that there all children other than text nodes can be ignored (e.g. comments). If nil is + # passed, nil will be returned. + def self.element_text(element) + element.texts.map(&:value).join if element + end end end -end \ No newline at end of file +end diff --git a/lib/onelogin/ruby-saml/version.rb b/lib/onelogin/ruby-saml/version.rb index 41b12e59f..d4a536d01 100644 --- a/lib/onelogin/ruby-saml/version.rb +++ b/lib/onelogin/ruby-saml/version.rb @@ -1,5 +1,5 @@ module OneLogin module RubySaml - VERSION = '0.9.1' + VERSION = '1.18.1' end end diff --git a/lib/schemas/saml-schema-metadata-2.0.xsd b/lib/schemas/saml-schema-metadata-2.0.xsd index 8d6ad0db4..b656d4f41 100644 --- a/lib/schemas/saml-schema-metadata-2.0.xsd +++ b/lib/schemas/saml-schema-metadata-2.0.xsd @@ -247,8 +247,6 @@ - - diff --git a/lib/schemas/xmldsig-core-schema.xsd b/lib/schemas/xmldsig-core-schema.xsd index dd5254bb1..63d781991 100644 --- a/lib/schemas/xmldsig-core-schema.xsd +++ b/lib/schemas/xmldsig-core-schema.xsd @@ -188,7 +188,7 @@ - + diff --git a/lib/xml_security.rb b/lib/xml_security.rb index b59a7fde5..db54ff345 100644 --- a/lib/xml_security.rb +++ b/lib/xml_security.rb @@ -29,14 +29,48 @@ require 'nokogiri' require "digest/sha1" require "digest/sha2" -require "onelogin/ruby-saml/validation_error" +require "onelogin/ruby-saml/utils" +require "onelogin/ruby-saml/error_handling" module XMLSecurity class BaseDocument < REXML::Document + REXML::Document::entity_expansion_limit = 0 C14N = "http://www.w3.org/2001/10/xml-exc-c14n#" DSIG = "http://www.w3.org/2000/09/xmldsig#" + NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT | + Nokogiri::XML::ParseOptions::NONET + + # Safety load the SAML Message XML + # @param document [REXML::Document] The message to be loaded + # @param check_malformed_doc [Boolean] check_malformed_doc Enable or Disable the check for malformed XML + # @return [Nokogiri::XML] The nokogiri document + # @raise [ValidationError] If there was a problem loading the SAML Message XML + def self.safe_load_xml(document, check_malformed_doc = true) + doc_str = document.to_s + if doc_str.include?(" error + raise StandardError.new(error.message) + end + + if xml.internal_subset + raise StandardError.new("Dangerous XML detected. No Doctype nodes allowed") + end + + unless xml.errors.empty? + raise StandardError.new("There were XML errors when parsing: #{xml.errors}") if check_malformed_doc + end + + xml + end def canon_algorithm(element) algorithm = element @@ -45,10 +79,14 @@ def canon_algorithm(element) end case algorithm - when "http://www.w3.org/2001/10/xml-exc-c14n#" then Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 - when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" then Nokogiri::XML::XML_C14N_1_0 - when "http://www.w3.org/2006/12/xml-c14n11" then Nokogiri::XML::XML_C14N_1_1 - else Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 + when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315", + "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" + Nokogiri::XML::XML_C14N_1_0 + when "http://www.w3.org/2006/12/xml-c14n11", + "http://www.w3.org/2006/12/xml-c14n11#WithComments" + Nokogiri::XML::XML_C14N_1_1 + else + Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 end end @@ -72,18 +110,18 @@ def algorithm(element) end class Document < BaseDocument - RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" - RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" - RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" - RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" + RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" + RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" + RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1" - SHA256 = "http://www.w3.org/2001/04/xmldsig-more#sha256" + SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256' SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384" - SHA512 = "http://www.w3.org/2001/04/xmldsig-more#sha512" + SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512' ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature" INC_PREFIX_LIST = "#default samlp saml ds xs xsi md" - attr_accessor :uuid + attr_writer :uuid def uuid @uuid ||= begin @@ -106,8 +144,8 @@ def uuid # # # - def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1) - noko = Nokogiri.parse(self.to_s) + def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1, check_malformed_doc = true) + noko = XMLSecurity::BaseDocument.safe_load_xml(self.to_s, check_malformed_doc) signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG) signed_info_element = signature_element.add_element("ds:SignedInfo") @@ -129,7 +167,8 @@ def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_ reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element)) # add SignatureValue - noko_sig_element = Nokogiri.parse(signature_element.to_s) + noko_sig_element = XMLSecurity::BaseDocument.safe_load_xml(signature_element.to_s, check_malformed_doc) + noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG) canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N)) @@ -146,15 +185,13 @@ def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_ x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "") # add the signature - issuer_element = self.elements["//saml:Issuer"] + issuer_element = elements["//saml:Issuer"] if issuer_element - self.root.insert_after issuer_element, signature_element + root.insert_after(issuer_element, signature_element) + elsif first_child = root.children[0] + root.insert_before(first_child, signature_element) else - if sp_sso_descriptor = self.elements["/md:EntityDescriptor"] - self.root.insert_before sp_sso_descriptor, signature_element - else - self.root.add_element(signature_element) - end + root.add_element(signature_element) end end @@ -166,104 +203,235 @@ def compute_signature(private_key, signature_algorithm, document) def compute_digest(document, digest_algorithm) digest = digest_algorithm.digest(document) - Base64.encode64(digest).strip! + Base64.encode64(digest).strip end end class SignedDocument < BaseDocument + include OneLogin::RubySaml::ErrorHandling - attr_accessor :signed_element_id - attr_accessor :errors + attr_writer :signed_element_id def initialize(response, errors = []) super(response) @errors = errors - extract_signed_element_id + reset_elements + end + + def reset_elements + @referenced_xml = nil + @cached_signed_info = nil + @signature = nil + @signature_algorithm = nil + @ref = nil + @processed = false end - def validate_document(idp_cert_fingerprint, soft = true) + def processed + @processed + end + + def referenced_xml + @referenced_xml + end + + def signed_element_id + @signed_element_id ||= extract_signed_element_id + end + + # Validates the referenced_xml, which is the signed part of the document + def validate_document(idp_cert_fingerprint, soft = true, options = {}) # get cert from response - cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG }) - unless cert_element - if soft - return false + cert_element = REXML::XPath.first( + self, + "//ds:X509Certificate", + { "ds"=>DSIG } + ) + + if cert_element + base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element) + cert_text = Base64.decode64(base64_cert) + begin + cert = OpenSSL::X509::Certificate.new(cert_text) + rescue OpenSSL::X509::CertificateError => _e + return append_error("Document Certificate Error", soft) + end + + if options[:fingerprint_alg] + fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new else - raise OneLogin::RubySaml::ValidationError.new("Certificate element missing in response (ds:X509Certificate)") + fingerprint_alg = OpenSSL::Digest.new('SHA1') + end + fingerprint = fingerprint_alg.hexdigest(cert.to_der) + + # check cert matches registered idp cert + if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase + return append_error("Fingerprint mismatch", soft) + end + base64_cert = Base64.encode64(cert.to_der) + else + if options[:cert] + base64_cert = Base64.encode64(options[:cert].to_pem) + else + if soft + return false + else + return append_error("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", soft) + end end end - base64_cert = cert_element.text - cert_text = Base64.decode64(base64_cert) - cert = OpenSSL::X509::Certificate.new(cert_text) + validate_signature(base64_cert, soft) + end - # check cert matches registered idp cert - fingerprint = Digest::SHA1.hexdigest(cert.to_der) + def validate_document_with_cert(idp_cert, soft = true) + # get cert from response + cert_element = REXML::XPath.first( + self, + "//ds:X509Certificate", + { "ds"=>DSIG } + ) + + if cert_element + base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element) + cert_text = Base64.decode64(base64_cert) + begin + cert = OpenSSL::X509::Certificate.new(cert_text) + rescue OpenSSL::X509::CertificateError => _e + return append_error("Document Certificate Error", soft) + end - if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase - @errors << "Fingerprint mismatch" - return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch")) + # check saml response cert matches provided idp cert + if idp_cert.to_pem != cert.to_pem + return append_error("Certificate of the Signature element does not match provided certificate", soft) + end end - validate_signature(base64_cert, soft) + encoded_idp_cert = Base64.encode64(idp_cert.to_pem) + validate_signature(encoded_idp_cert, true) end - def validate_signature(base64_cert, soft = true) - # validate references - - # check for inclusive namespaces - inclusive_namespaces = extract_inclusive_namespaces + def cache_referenced_xml(soft, check_malformed_doc = true) + reset_elements + @processed = true - document = Nokogiri.parse(self.to_s) + begin + nokogiri_document = XMLSecurity::BaseDocument.safe_load_xml(self, check_malformed_doc) + rescue StandardError => error + @errors << error.message + return false if soft + raise ValidationError.new("XML load failed: #{error.message}") + end - # create a working copy so we don't modify the original + # create a rexml document @working_copy ||= REXML::Document.new(self.to_s).root - # store and remove signature node - @sig_element ||= begin - element = REXML::XPath.first(@working_copy, "//ds:Signature", {"ds"=>DSIG}) - element.remove - end + # get signature node + sig_element = REXML::XPath.first( + @working_copy, + "//ds:Signature", + {"ds"=>DSIG} + ) - # verify signature - signed_info_element = REXML::XPath.first(@sig_element, "//ds:SignedInfo", {"ds"=>DSIG}) - noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG) + return if sig_element.nil? + + # signature method + sig_alg_value = REXML::XPath.first( + sig_element, + "./ds:SignedInfo/ds:SignatureMethod", + {"ds"=>DSIG} + ) + @signature_algorithm = algorithm(sig_alg_value) + + # get signature + base64_signature = REXML::XPath.first( + sig_element, + "./ds:SignatureValue", + {"ds" => DSIG} + ) + + return if base64_signature.nil? + + base64_signature_text = OneLogin::RubySaml::Utils.element_text(base64_signature) + @signature = base64_signature_text.nil? ? nil : Base64.decode64(base64_signature_text) + + # canonicalization method + canon_algorithm = canon_algorithm REXML::XPath.first( + sig_element, + './ds:SignedInfo/ds:CanonicalizationMethod', + 'ds' => DSIG + ) + + noko_sig_element = nokogiri_document.at_xpath('//ds:Signature', 'ds' => DSIG) noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG) - canon_algorithm = canon_algorithm REXML::XPath.first(@sig_element, '//ds:CanonicalizationMethod', 'ds' => DSIG) - canon_string = noko_signed_info_element.canonicalize(canon_algorithm) + + @cached_signed_info = noko_signed_info_element.canonicalize(canon_algorithm) + + ### Now get the @referenced_xml to use? + rexml_signed_info = REXML::Document.new(@cached_signed_info.to_s).root + noko_sig_element.remove + # get inclusive namespaces + inclusive_namespaces = extract_inclusive_namespaces + # check digests - REXML::XPath.each(@sig_element, "//ds:Reference", {"ds"=>DSIG}) do |ref| - uri = ref.attributes.get_attribute("URI").value + @ref = REXML::XPath.first(rexml_signed_info, "./ds:Reference", {"ds"=>DSIG}) + return if @ref.nil? - hashed_element = document.at_xpath("//*[@ID='#{uri[1..-1]}']") - canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod', 'ds' => DSIG) - canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces) + reference_nodes = nokogiri_document.xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id }) - digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod", 'ds' => DSIG)) + hashed_element = reference_nodes[0] + return if hashed_element.nil? - hash = digest_algorithm.digest(canon_hashed_element) - digest_value = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text) + canon_algorithm = canon_algorithm REXML::XPath.first( + rexml_signed_info, + './ds:CanonicalizationMethod', + { "ds" => DSIG } + ) - unless digests_match?(hash, digest_value) - @errors << "Digest mismatch" - return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Digest mismatch")) - end + canon_algorithm = process_transforms(@ref, canon_algorithm) + + @referenced_xml = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces) + end + + def validate_signature(base64_cert, soft = true) + if !@processed + cache_referenced_xml(soft) end - base64_signature = REXML::XPath.first(@sig_element, "//ds:SignatureValue", {"ds"=>DSIG}).text - signature = Base64.decode64(base64_signature) + return append_error("No Signature Algorithm Method found", soft) if @signature_algorithm.nil? + return append_error("No Signature node found", soft) if @signature.nil? + return append_error("No canonized SignedInfo ", soft) if @cached_signed_info.nil? + return append_error("No Reference node found", soft) if @ref.nil? + return append_error("No referenced XML", soft) if @referenced_xml.nil? # get certificate object - cert_text = Base64.decode64(base64_cert) - cert = OpenSSL::X509::Certificate.new(cert_text) - - # signature method - signature_algorithm = algorithm(REXML::XPath.first(signed_info_element, "//ds:SignatureMethod", {"ds"=>DSIG})) + cert_text = Base64.decode64(base64_cert) + cert = OpenSSL::X509::Certificate.new(cert_text) + + digest_algorithm = algorithm(REXML::XPath.first( + @ref, + "./ds:DigestMethod", + { "ds" => DSIG } + )) + hash = digest_algorithm.digest(@referenced_xml) + encoded_digest_value = REXML::XPath.first( + @ref, + "./ds:DigestValue", + { "ds" => DSIG } + ) + encoded_digest_value_text = OneLogin::RubySaml::Utils.element_text(encoded_digest_value) + digest_value = encoded_digest_value_text.nil? ? nil : Base64.decode64(encoded_digest_value_text) + + # Compare the computed "hash" with the "signed" hash + unless hash && hash == digest_value + return append_error("Digest mismatch", soft) + end - unless cert.public_key.verify(signature_algorithm.new, signature, canon_string) - @errors << "Key validation error" - return soft ? false : (raise OneLogin::RubySaml::ValidationError.new("Key validation error")) + # verify signature + unless cert.public_key.verify(@signature_algorithm.new, @signature, @cached_signed_info) + return append_error("Key validation error", soft) end return true @@ -271,21 +439,61 @@ def validate_signature(base64_cert, soft = true) private + def process_transforms(ref, canon_algorithm) + transforms = REXML::XPath.match( + ref, + "./ds:Transforms/ds:Transform", + { "ds" => DSIG } + ) + + transforms.each do |transform_element| + if transform_element.attributes && transform_element.attributes["Algorithm"] + algorithm = transform_element.attributes["Algorithm"] + case algorithm + when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315", + "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" + canon_algorithm = Nokogiri::XML::XML_C14N_1_0 + when "http://www.w3.org/2006/12/xml-c14n11", + "http://www.w3.org/2006/12/xml-c14n11#WithComments" + canon_algorithm = Nokogiri::XML::XML_C14N_1_1 + when "http://www.w3.org/2001/10/xml-exc-c14n#", + "http://www.w3.org/2001/10/xml-exc-c14n#WithComments" + canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 + end + end + end + + canon_algorithm + end + def digests_match?(hash, digest_value) hash == digest_value end def extract_signed_element_id - reference_element = REXML::XPath.first(self, "//ds:Signature/ds:SignedInfo/ds:Reference", {"ds"=>DSIG}) - self.signed_element_id = reference_element.attribute("URI").value[1..-1] unless reference_element.nil? + reference_element = REXML::XPath.first( + self, + "//ds:Signature/ds:SignedInfo/ds:Reference", + {"ds"=>DSIG} + ) + + return nil if reference_element.nil? + + sei = reference_element.attribute("URI").value[1..-1] + sei.nil? ? reference_element.parent.parent.parent.attribute("ID").value : sei end def extract_inclusive_namespaces - if element = REXML::XPath.first(self, "//ec:InclusiveNamespaces", { "ec" => C14N }) + element = REXML::XPath.first( + self, + "//ec:InclusiveNamespaces", + { "ec" => C14N } + ) + if element prefix_list = element.attributes.get_attribute("PrefixList").value prefix_list.split(" ") else - [] + nil end end diff --git a/ruby-saml.gemspec b/ruby-saml.gemspec index 1cc6a3bde..3cfd3e7cc 100644 --- a/ruby-saml.gemspec +++ b/ruby-saml.gemspec @@ -6,40 +6,94 @@ Gem::Specification.new do |s| s.version = OneLogin::RubySaml::VERSION s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["OneLogin LLC"] + s.authors = ["SAML Toolkit", "Sixto Martin"] + s.email = ['contact@iamdigitalservices.com', 'sixto.martin.garcia@gmail.com'] s.date = Time.now.strftime("%Y-%m-%d") - s.description = %q{SAML toolkit for Ruby on Rails} - s.email = %q{support@onelogin.com} + s.description = %q{SAML Ruby toolkit. Add SAML support to your Ruby software using this library} s.license = 'MIT' s.extra_rdoc_files = [ "LICENSE", - "README.md" + "README.md" ] - s.files = `git ls-files`.split("\n") - s.homepage = %q{http://github.com/onelogin/ruby-saml} - s.rubyforge_project = %q{http://www.rubygems.org/gems/ruby-saml} + s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + s.homepage = %q{https://github.com/saml-toolkits/ruby-saml} s.rdoc_options = ["--charset=UTF-8"] s.require_paths = ["lib"] s.rubygems_version = %q{1.3.7} s.required_ruby_version = '>= 1.8.7' s.summary = %q{SAML Ruby Tookit} - s.test_files = `git ls-files test/*`.split("\n") - - s.add_runtime_dependency('uuid', '~> 2.3') # Because runtime dependencies are determined at build time, we cannot make # Nokogiri's version dependent on the Ruby version, even though we would # have liked to constrain Ruby 1.8.7 to install only the 1.5.x versions. - s.add_runtime_dependency('nokogiri', '>= 1.5.10') + if defined?(JRUBY_VERSION) + if JRUBY_VERSION < '9.1.7.0' + s.add_runtime_dependency('nokogiri', '>= 1.8.2', '<= 1.8.5') + s.add_runtime_dependency('jruby-openssl', '>= 0.9.8') + s.add_runtime_dependency('json', '< 2.3.0') + elsif JRUBY_VERSION < '9.2.0.0' + s.add_runtime_dependency('nokogiri', '>= 1.9.1', '< 1.10.0') + elsif JRUBY_VERSION < '9.3.2.0' + s.add_runtime_dependency('nokogiri', '>= 1.11.4') + s.add_runtime_dependency('rexml') + else + s.add_runtime_dependency('nokogiri', '>= 1.13.10') + s.add_runtime_dependency('rexml') + end + elsif RUBY_VERSION < '1.9' + s.add_runtime_dependency('uuid') + s.add_runtime_dependency('nokogiri', '<= 1.5.11') + elsif RUBY_VERSION < '2.1' + s.add_runtime_dependency('nokogiri', '>= 1.5.10', '<= 1.6.8.1') + s.add_runtime_dependency('json', '< 2.3.0') + elsif RUBY_VERSION < '2.3' + s.add_runtime_dependency('nokogiri', '>= 1.9.1', '< 1.10.0') + elsif RUBY_VERSION < '2.5' + s.add_runtime_dependency('nokogiri', '>= 1.10.10', '< 1.11.0') + s.add_runtime_dependency('rexml') + elsif RUBY_VERSION < '2.6' + s.add_runtime_dependency('nokogiri', '>= 1.11.4') + s.add_runtime_dependency('rexml') + else + s.add_runtime_dependency('nokogiri', '>= 1.13.10') + s.add_runtime_dependency('rexml') + end + + if RUBY_VERSION >= '3.4.0' + s.add_runtime_dependency("logger") + s.add_runtime_dependency("base64") + s.add_runtime_dependency('mutex_m') + end + + s.add_development_dependency('simplecov', '<0.22.0') + if RUBY_VERSION < '2.4.1' + s.add_development_dependency('simplecov-lcov', '<0.8.0') + else + s.add_development_dependency('simplecov-lcov', '>0.7.0') + end - s.add_development_dependency('minitest', '~> 5.5') + s.add_development_dependency('minitest', '~> 5.5', '<5.19.0') s.add_development_dependency('mocha', '~> 0.14') - s.add_development_dependency('rake', '~> 10') + + if RUBY_VERSION < '2.0' + s.add_development_dependency('rake', '~> 10') + else + s.add_development_dependency('rake', '>= 12.3.3') + end + s.add_development_dependency('shoulda', '~> 2.11') s.add_development_dependency('systemu', '~> 2') - s.add_development_dependency('timecop', '<= 0.6.0') - if RUBY_VERSION < '1.9' + if RUBY_VERSION < '2.1' + s.add_development_dependency('timecop', '<= 0.6.0') + else + s.add_development_dependency('timecop', '~> 0.9') + end + + if defined?(JRUBY_VERSION) + # All recent versions of JRuby play well with pry + s.add_development_dependency('pry') + elsif RUBY_VERSION < '1.9' # 1.8.7 s.add_development_dependency('ruby-debug', '~> 0.10.4') elsif RUBY_VERSION < '2.0' diff --git a/test/attributes_test.rb b/test/attributes_test.rb new file mode 100644 index 000000000..b98b65b98 --- /dev/null +++ b/test/attributes_test.rb @@ -0,0 +1,30 @@ +require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper')) + +require 'onelogin/ruby-saml/attributes' + +class AttributesTest < Minitest::Test + describe 'Attributes' do + let(:attributes) do + OneLogin::RubySaml::Attributes.new({ + 'email' => ['tom@hanks.com'], + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname' => ['Tom'], + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname' => ['Hanks'] + }) + end + + it 'fetches string attribute' do + assert_equal('tom@hanks.com', attributes.fetch('email')) + end + + it 'fetches symbol attribute' do + assert_equal('tom@hanks.com', attributes.fetch(:email)) + end + + it 'fetches regexp attribute' do + assert_equal('Tom', attributes.fetch(/givenname/)) + assert_equal('Tom', attributes.fetch(/gi(.*)/)) + assert_nil(attributes.fetch(/^z.*/)) + assert_equal('Hanks', attributes.fetch(/surname/)) + end + end +end diff --git a/test/certificates/certificate.der b/test/certificates/certificate.der new file mode 100644 index 000000000..756d248e2 Binary files /dev/null and b/test/certificates/certificate.der differ diff --git a/test/certificates/r1_certificate2_base64 b/test/certificates/certificate_without_head_foot similarity index 100% rename from test/certificates/r1_certificate2_base64 rename to test/certificates/certificate_without_head_foot diff --git a/test/certificates/formatted_certificate b/test/certificates/formatted_certificate new file mode 100644 index 000000000..a36646b54 --- /dev/null +++ b/test/certificates/formatted_certificate @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE +BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh +MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx +wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz +c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC +mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw +Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N +4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G +CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ ++4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1 +2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ +-----END CERTIFICATE----- \ No newline at end of file diff --git a/test/certificates/formatted_chained_certificate b/test/certificates/formatted_chained_certificate new file mode 100644 index 000000000..23c3621c6 --- /dev/null +++ b/test/certificates/formatted_chained_certificate @@ -0,0 +1,42 @@ +-----BEGIN CERTIFICATE----- +MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE +BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh +MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx +wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz +c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC +mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw +Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N +4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G +CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ ++4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1 +2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE +BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh +MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx +wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz +c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC +mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw +Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N +4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G +CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ ++4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1 +2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE +BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh +MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx +wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz +c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC +mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw +Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N +4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G +CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ ++4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1 +2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ +-----END CERTIFICATE----- \ No newline at end of file diff --git a/test/certificates/formatted_private_key b/test/certificates/formatted_private_key new file mode 100644 index 000000000..ec5b39beb --- /dev/null +++ b/test/certificates/formatted_private_key @@ -0,0 +1,12 @@ +-----BEGIN PRIVATE KEY----- +MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3 +NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht +YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR +hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR +O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3 +B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe +3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL +d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO +5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK +GeW2AKaE6oqRqeVwGw4V +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/test/certificates/formatted_rsa_private_key b/test/certificates/formatted_rsa_private_key new file mode 100644 index 000000000..96a91d99c --- /dev/null +++ b/test/certificates/formatted_rsa_private_key @@ -0,0 +1,12 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3 +NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht +YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR +hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR +O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3 +B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe +3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL +d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO +5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK +GeW2AKaE6oqRqeVwGw4V +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/test/certificates/invalid_certificate1 b/test/certificates/invalid_certificate1 new file mode 100644 index 000000000..f6bd017ad --- /dev/null +++ b/test/certificates/invalid_certificate1 @@ -0,0 +1 @@ +-----BEGIN CERTIFICATE----- MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N 4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ +4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1 2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ -----END CERTIFICATE----- diff --git a/test/certificates/invalid_certificate2 b/test/certificates/invalid_certificate2 new file mode 100644 index 000000000..3764a4b23 --- /dev/null +++ b/test/certificates/invalid_certificate2 @@ -0,0 +1 @@ +MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UEBhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVhMDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIxwsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNzc28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jCmdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N 4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ +4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1 2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ diff --git a/test/certificates/invalid_certificate3 b/test/certificates/invalid_certificate3 new file mode 100644 index 000000000..5f8384057 --- /dev/null +++ b/test/certificates/invalid_certificate3 @@ -0,0 +1,12 @@ +MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE +BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh +MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx +wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz +c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC +mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw +Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N +4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G +CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ ++4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1 +2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ diff --git a/test/certificates/invalid_chained_certificate1 b/test/certificates/invalid_chained_certificate1 new file mode 100644 index 000000000..765c0dc72 --- /dev/null +++ b/test/certificates/invalid_chained_certificate1 @@ -0,0 +1 @@ +-----BEGIN CERTIFICATE-----MIICPDCCAaWgAwIBAgIIEiC/9HMAWW AwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UEBhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVhMDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIxwsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNzc28xJDAiBgNVBAMTG2MyNWE wMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jCmdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpwVvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0GCSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/12Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ-----END CERTIFICATE----------BEGIN CERTIFICATE-----MIICPDCCAaWgAw IBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UEBhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVhMDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIxwsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNzc28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jCmdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpwVvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0GCSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/12Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ-----END CERTIFICATE----------BEGIN CERTIFICATE-----MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UEBhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVhMDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIxwsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNzc28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jCmdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpwVvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0GCSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/12Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ-----END CERTIFICATE----- \ No newline at end of file diff --git a/test/certificates/invalid_private_key1 b/test/certificates/invalid_private_key1 new file mode 100644 index 000000000..44de66429 --- /dev/null +++ b/test/certificates/invalid_private_key1 @@ -0,0 +1 @@ +-----BEGIN PRIVATE KEY----- MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3 NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3 B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe 3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO 5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK GeW2AKaE6oqRqeVwGw4V -----END PRIVATE KEY----- \ No newline at end of file diff --git a/test/certificates/invalid_private_key2 b/test/certificates/invalid_private_key2 new file mode 100644 index 000000000..3bddb1fdb --- /dev/null +++ b/test/certificates/invalid_private_key2 @@ -0,0 +1 @@ +MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUhtYzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfRhg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQRO1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOLd3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCKGeW2AKaE6oqRqeVwGw4V \ No newline at end of file diff --git a/test/certificates/invalid_private_key3 b/test/certificates/invalid_private_key3 new file mode 100644 index 000000000..16889af5d --- /dev/null +++ b/test/certificates/invalid_private_key3 @@ -0,0 +1,10 @@ +MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3 +NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht +YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR +hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR +O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3 +B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe +3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL +d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO +5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK +GeW2AKaE6oqRqeVwGw4V \ No newline at end of file diff --git a/test/certificates/invalid_rsa_private_key1 b/test/certificates/invalid_rsa_private_key1 new file mode 100644 index 000000000..6bb66ce0b --- /dev/null +++ b/test/certificates/invalid_rsa_private_key1 @@ -0,0 +1 @@ +-----BEGIN RSA PRIVATE KEY----- MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3 NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3 B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe 3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO 5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK GeW2AKaE6oqRqeVwGw4V -----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/test/certificates/invalid_rsa_private_key2 b/test/certificates/invalid_rsa_private_key2 new file mode 100644 index 000000000..3bddb1fdb --- /dev/null +++ b/test/certificates/invalid_rsa_private_key2 @@ -0,0 +1 @@ +MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUhtYzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfRhg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQRO1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOLd3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCKGeW2AKaE6oqRqeVwGw4V \ No newline at end of file diff --git a/test/certificates/invalid_rsa_private_key3 b/test/certificates/invalid_rsa_private_key3 new file mode 100644 index 000000000..16889af5d --- /dev/null +++ b/test/certificates/invalid_rsa_private_key3 @@ -0,0 +1,10 @@ +MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3 +NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht +YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR +hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR +O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3 +B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe +3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL +d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO +5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK +GeW2AKaE6oqRqeVwGw4V \ No newline at end of file diff --git a/test/certificates/ruby-saml-2.crt b/test/certificates/ruby-saml-2.crt new file mode 100644 index 000000000..11696a8e6 --- /dev/null +++ b/test/certificates/ruby-saml-2.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICVDCCAb2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBHMQswCQYDVQQGEwJ1czEQ +MA4GA1UECAwHZXhhbXBsZTEQMA4GA1UECgwHZXhhbXBsZTEUMBIGA1UEAwwLZXhh +bXBsZS5jb20wHhcNMTcwNDA3MDgzMDAzWhcNMjcwNDA1MDgzMDAzWjBHMQswCQYD +VQQGEwJ1czEQMA4GA1UECAwHZXhhbXBsZTEQMA4GA1UECgwHZXhhbXBsZTEUMBIG +A1UEAwwLZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKhP +S4/0azxbQekHHewQGKD7Pivr3CDpsrKxY3xlVanxj427OwzOb5KUVzsDEazumt6s +ZFY8HfidsjXY4EYA4ZzyL7ciIAR5vlAsIYN9nJ4AwVDnN/RjVwj+TN6BqWPLpVIp +Hc6Dl005HyE0zJnk1DZDn2tQVrIzbD3FhCp7YeotAgMBAAGjUDBOMB0GA1UdDgQW +BBRYZx4thASfNvR/E7NsCF2IaZ7wIDAfBgNVHSMEGDAWgBRYZx4thASfNvR/E7Ns +CF2IaZ7wIDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBACz4aobx9aG3 +kh+rNyrlgM3K6dYfnKG1/YH5sJCAOvg8kDr0fQAQifH8lFVWumKUMoAe0bFTfwWt +p/VJ8MprrEJth6PFeZdczpuv+fpLcNj2VmNVJqvQYvS4m36OnBFh1QFZW8UrbFIf +dtm2nuZ+twSKqfKwjLdqcoX0p39h7Uw/ +-----END CERTIFICATE----- \ No newline at end of file diff --git a/test/helpers/certificate_helper.rb b/test/helpers/certificate_helper.rb new file mode 100644 index 000000000..e826fbfb5 --- /dev/null +++ b/test/helpers/certificate_helper.rb @@ -0,0 +1,44 @@ +require 'openssl' + +module CertificateHelper + extend self + + def generate_pair(not_before: nil, not_after: nil) + key = generate_key + cert = generate_cert(key, not_before: not_before, not_after: not_after) + [cert, key] + end + + def generate_pair_hash(not_before: nil, not_after: nil) + cert, key = generate_pair(not_before: not_before, not_after: not_after) + { certificate: cert.to_pem, private_key: key.to_pem } + end + + def generate_key + OpenSSL::PKey::RSA.new(1024) + end + + def generate_cert(key = generate_key, not_before: nil, not_after: nil) + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 0 + cert.not_before = not_before || Time.now - one_year + cert.not_after = not_after || Time.now + one_year + cert.public_key = key.public_key + cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-saml/CN=Ruby SAML CA" + cert.issuer = cert.subject # self-signed + factory = OpenSSL::X509::ExtensionFactory.new + factory.subject_certificate = cert + factory.issuer_certificate = cert + cert.add_extension factory.create_extension("basicConstraints","CA:TRUE", true) + cert.add_extension factory.create_extension("keyUsage","keyCertSign, cRLSign", true) + cert.sign(key, OpenSSL::Digest::SHA1.new) + cert + end + + private + + def one_year + 3600 * 24 * 365 + end +end diff --git a/test/idp_metadata_parser_test.rb b/test/idp_metadata_parser_test.rb index e36c8dac8..cb6d1f4d2 100644 --- a/test/idp_metadata_parser_test.rb +++ b/test/idp_metadata_parser_test.rb @@ -3,8 +3,17 @@ require 'onelogin/ruby-saml/idp_metadata_parser' class IdpMetadataParserTest < Minitest::Test + class MockSuccessResponse < Net::HTTPSuccess + # override parent's initialize + def initialize; end + + attr_accessor :body + end + + class MockFailureResponse < Net::HTTPNotFound + # override parent's initialize + def initialize; end - class MockResponse attr_accessor :body end @@ -12,20 +21,290 @@ class MockResponse it "extract settings details from xml" do idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + settings = idp_metadata_parser.parse(idp_metadata_descriptor) + + assert_equal "https://hello.example.com/access/saml/idp.xml", settings.idp_entity_id + assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint + assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format + assert_equal ["AuthToken", "SSOStartPage"], settings.idp_attribute_names + assert_equal '2014-04-17T18:02:33.910Z', settings.valid_until + end + + it "extract certificate from md:KeyDescriptor[@use='signing']" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor settings = idp_metadata_parser.parse(idp_metadata) + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint + end + + it "extract certificate from md:KeyDescriptor[@use='encryption']" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor + idp_metadata = idp_metadata.sub(/(.*?)<\/md:KeyDescriptor>/m, "") + settings = idp_metadata_parser.parse(idp_metadata) + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint + end + + it "extract certificate from md:KeyDescriptor" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor + idp_metadata = idp_metadata.sub(/(.*?)<\/md:KeyDescriptor>/m, "") + idp_metadata = idp_metadata.sub('', '') + settings = idp_metadata_parser.parse(idp_metadata) + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint + end + + it "extract SSO endpoint with no specific binding, it takes the first" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor3 + settings = idp_metadata_parser.parse(idp_metadata) + assert_equal "https://idp.example.com/idp/profile/Shibboleth/SSO", settings.idp_sso_service_url + assert_equal "urn:mace:shibboleth:1.0:profiles:AuthnRequest", settings.idp_sso_service_binding + end + + it "extract SSO endpoint with specific binding as a String" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor3 + options = {} + options[:sso_binding] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + options[:slo_binding] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + settings = idp_metadata_parser.parse(idp_metadata, options) + assert_equal "https://idp.example.com/idp/profile/SAML2/POST/SSO", settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", settings.idp_sso_service_binding + assert_nil settings.idp_slo_service_url + + options[:sso_binding] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' + options[:slo_binding] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' + settings = idp_metadata_parser.parse(idp_metadata, options) + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SLO", settings.idp_slo_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding + end + + it "extract SSO endpoint with specific binding as an Array" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor3 + options = {} + options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'] + options[:slo_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'] + settings = idp_metadata_parser.parse(idp_metadata, options) + assert_equal "https://idp.example.com/idp/profile/SAML2/POST/SSO", settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", settings.idp_sso_service_binding + assert_nil settings.idp_slo_service_url + + options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'] + options[:slo_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'] + settings = idp_metadata_parser.parse(idp_metadata, options) + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SLO", settings.idp_slo_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding + + options[:sso_binding] = ['invalid_binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'] + options[:slo_binding] = ['invalid_binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'] + settings = idp_metadata_parser.parse(idp_metadata, options) + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SLO", settings.idp_slo_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding + end + + it "extract NameIDFormat no specific priority, it takes the first" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor3 + settings = idp_metadata_parser.parse(idp_metadata) + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format + end + + it "extract NameIDFormat specific priority as a String" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor3 + options = {} + options[:name_id_format] = 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified' + settings = idp_metadata_parser.parse(idp_metadata, options) + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format + + options[:name_id_format] = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' + settings = idp_metadata_parser.parse(idp_metadata, options) + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", settings.name_identifier_format + + options[:name_id_format] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent' + settings = idp_metadata_parser.parse(idp_metadata, options) + assert_equal "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", settings.name_identifier_format + end + + it "extract NameIDFormat specific priority as an Array" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor3 + options = {} + options[:name_id_format] = ['urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'] + settings = idp_metadata_parser.parse(idp_metadata, options) + assert_equal "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", settings.name_identifier_format + + options[:name_id_format] = ['invalid', 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'] + settings = idp_metadata_parser.parse(idp_metadata, options) + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", settings.name_identifier_format + end + + it "uses settings options as hash for overrides" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor + settings = idp_metadata_parser.parse(idp_metadata, { + :settings => { + :security => { + :digest_method => XMLSecurity::Document::SHA256, + :signature_method => XMLSecurity::Document::RSA_SHA256 + } + } + }) + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint + assert_equal XMLSecurity::Document::SHA256, settings.security[:digest_method] + assert_equal XMLSecurity::Document::RSA_SHA256, settings.security[:signature_method] + end + + it "merges results into given settings object" do + settings = OneLogin::RubySaml::Settings.new(:security => { + :digest_method => XMLSecurity::Document::SHA256, + :signature_method => XMLSecurity::Document::RSA_SHA256 + }) + + OneLogin::RubySaml::IdpMetadataParser.new.parse(idp_metadata_descriptor, :settings => settings) - assert_equal "https://example.hello.com/access/saml/idp.xml", settings.idp_entity_id - assert_equal "https://example.hello.com/access/saml/login", settings.idp_sso_target_url assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint - assert_equal "https://example.hello.com/access/saml/logout", settings.idp_slo_target_url + assert_equal XMLSecurity::Document::SHA256, settings.security[:digest_method] + assert_equal XMLSecurity::Document::RSA_SHA256, settings.security[:signature_method] + end + end + + describe "parsing an IdP descriptor file into an Hash" do + it "extract settings details from xml" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + + metadata = idp_metadata_parser.parse_to_hash(idp_metadata_descriptor) + + assert_equal "https://hello.example.com/access/saml/idp.xml", metadata[:idp_entity_id] + assert_equal "https://hello.example.com/access/saml/login", metadata[:idp_sso_service_url] + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", metadata[:idp_sso_service_binding] + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", metadata[:idp_cert_fingerprint] + assert_equal "https://hello.example.com/access/saml/logout", metadata[:idp_slo_service_url] + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", metadata[:idp_slo_service_binding] + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", metadata[:name_identifier_format] + assert_equal ["AuthToken", "SSOStartPage"], metadata[:idp_attribute_names] + assert_equal '2014-04-17T18:02:33.910Z', metadata[:valid_until] + end + + it "extract certificate from md:KeyDescriptor[@use='signing']" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor + metadata = idp_metadata_parser.parse_to_hash(idp_metadata) + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", metadata[:idp_cert_fingerprint] + end + + it "extract certificate from md:KeyDescriptor[@use='encryption']" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor + idp_metadata = idp_metadata.sub(/(.*?)<\/md:KeyDescriptor>/m, "") + parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata) + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint] + end + + it "extract certificate from md:KeyDescriptor" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor + idp_metadata = idp_metadata.sub(/(.*?)<\/md:KeyDescriptor>/m, "") + idp_metadata = idp_metadata.sub('', '') + parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata) + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint] + end + + it "extract SSO endpoint with no specific binding, it takes the first" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor3 + metadata = idp_metadata_parser.parse_to_hash(idp_metadata) + assert_equal "https://idp.example.com/idp/profile/Shibboleth/SSO", metadata[:idp_sso_service_url] + assert_equal "urn:mace:shibboleth:1.0:profiles:AuthnRequest", metadata[:idp_sso_service_binding] + end + + it "extract SSO endpoint with specific binding" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor3 + options = {} + options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'] + parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, options) + assert_equal "https://idp.example.com/idp/profile/SAML2/POST/SSO", parsed_metadata[:idp_sso_service_url] + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", parsed_metadata[:idp_sso_service_binding] + + options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'] + parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, options) + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", parsed_metadata[:idp_sso_service_url] + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", parsed_metadata[:idp_sso_service_binding] + + options[:sso_binding] = ['invalid_binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'] + parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, options) + assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", parsed_metadata[:idp_sso_service_url] + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", parsed_metadata[:idp_sso_service_binding] + end + + it "ignores a given :settings hash" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata = idp_metadata_descriptor + parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, { + :settings => { + :security => { + :digest_method => XMLSecurity::Document::SHA256, + :signature_method => XMLSecurity::Document::RSA_SHA256 + } + } + }) + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint] + assert_nil parsed_metadata[:security] + end + + it "can extract certificates multiple times in sequence" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata1 = idp_metadata_descriptor + idp_metadata2 = idp_metadata_descriptor4 + metadata1 = idp_metadata_parser.parse_to_hash(idp_metadata1) + metadata2 = idp_metadata_parser.parse_to_hash(idp_metadata2) + + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", metadata1[:idp_cert_fingerprint] + assert_equal "CD:2B:2B:DA:FF:F5:DB:64:10:7C:AC:FD:FE:0F:CB:5D:73:5F:16:07", metadata2[:idp_cert_fingerprint] + end + end + + describe "parsing an IdP descriptor file with multiple signing certs" do + it "extract settings details from xml" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + + settings = idp_metadata_parser.parse(idp_metadata_descriptor2) + + assert_equal "https://hello.example.com/access/saml/idp.xml", settings.idp_entity_id + assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding + assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format + assert_equal ["AuthToken", "SSOStartPage"], settings.idp_attribute_names + + assert_nil settings.idp_cert_fingerprint + assert_nil settings.idp_cert + assert_equal 2, settings.idp_cert_multi.size + assert settings.idp_cert_multi.key?(:signing) + assert_equal 2, settings.idp_cert_multi[:signing].size + assert settings.idp_cert_multi.key?(:encryption) + assert_equal 1, settings.idp_cert_multi[:encryption].size end end describe "download and parse IdP descriptor file" do before do - mock_response = MockResponse.new - mock_response.body = idp_metadata + mock_response = MockSuccessResponse.new + mock_response.body = idp_metadata_descriptor @url = "https://example.com" uri = URI(@url) @@ -34,25 +313,446 @@ class MockResponse @http.expects(:request).returns(mock_response) end - it "extract settings from remote xml" do idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new settings = idp_metadata_parser.parse_remote(@url) - assert_equal "https://example.hello.com/access/saml/idp.xml", settings.idp_entity_id - assert_equal "https://example.hello.com/access/saml/login", settings.idp_sso_target_url + assert_equal "https://hello.example.com/access/saml/idp.xml", settings.idp_entity_id + assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint - assert_equal "https://example.hello.com/access/saml/logout", settings.idp_slo_target_url + assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format + assert_equal ["AuthToken", "SSOStartPage"], settings.idp_attribute_names + assert_equal '2014-04-17T18:02:33.910Z', settings.valid_until + assert_equal OpenSSL::SSL::VERIFY_PEER, @http.verify_mode + end + + it "accept self signed certificate if insturcted" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + idp_metadata_parser.parse_remote(@url, false) + + assert_equal OpenSSL::SSL::VERIFY_NONE, @http.verify_mode + end + end + + describe "download and parse IdP descriptor file into an Hash" do + before do + mock_response = MockSuccessResponse.new + mock_response.body = idp_metadata_descriptor + @url = "https://example.com" + uri = URI(@url) + + @http = Net::HTTP.new(uri.host, uri.port) + Net::HTTP.expects(:new).returns(@http) + @http.expects(:request).returns(mock_response) + end + + it "extract settings from remote xml" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + parsed_metadata = idp_metadata_parser.parse_remote_to_hash(@url) + + assert_equal "https://hello.example.com/access/saml/idp.xml", parsed_metadata[:idp_entity_id] + assert_equal "https://hello.example.com/access/saml/login", parsed_metadata[:idp_sso_service_url] + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", parsed_metadata[:idp_sso_service_binding] + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint] + assert_equal "https://hello.example.com/access/saml/logout", parsed_metadata[:idp_slo_service_url] + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", parsed_metadata[:idp_slo_service_binding] + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", parsed_metadata[:name_identifier_format] + assert_equal ["AuthToken", "SSOStartPage"], parsed_metadata[:idp_attribute_names] + assert_equal '2014-04-17T18:02:33.910Z', parsed_metadata[:valid_until] assert_equal OpenSSL::SSL::VERIFY_PEER, @http.verify_mode end it "accept self signed certificate if insturcted" do idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new - settings = idp_metadata_parser.parse_remote(@url, false) + idp_metadata_parser.parse_remote_to_hash(@url, false) assert_equal OpenSSL::SSL::VERIFY_NONE, @http.verify_mode end end + describe "download failure cases" do + it "raises an exception when the url has no scheme" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + + exception = assert_raises(ArgumentError) do + idp_metadata_parser.parse_remote("blahblah") + end + + assert_equal("url must begin with http or https", exception.message) + end + + it "raises an exception when unable to download metadata" do + mock_response = MockFailureResponse.new + @url = "https://example.com" + uri = URI(@url) + + @http = Net::HTTP.new(uri.host, uri.port) + Net::HTTP.expects(:new).returns(@http) + @http.expects(:request).returns(mock_response) + + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + + exception = assert_raises(OneLogin::RubySaml::HttpError) do + idp_metadata_parser.parse_remote("https://hello.example.com/access/saml/idp.xml") + end + + assert_match("Failed to fetch idp metadata", exception.message) + end + end + + describe "parsing metadata with and without ValidUntil and CacheDuration" do + before do + @idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + end + + it "if no ValidUntil or CacheDuration return nothing" do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor3) + assert_nil settings.valid_until + end + + it "if ValidUntil and not CacheDuration return ValidUntil value" do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor) + assert_equal '2014-04-17T18:02:33.910Z', settings.valid_until + end + + it "if no ValidUntil but CacheDuration return CacheDuration converted in ValidUntil" do + return if RUBY_VERSION < '1.9' + Timecop.freeze(Time.parse("2020-01-02T10:02:33Z", Time.now.utc)) do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor5) + assert_equal '2020-01-03T10:02:33Z', settings.valid_until + end + end + + it "if ValidUntil and CacheDuration return the sooner timestamp" do + return if RUBY_VERSION < '1.9' + + Timecop.freeze(Time.parse("2020-01-01T10:12:55Z", Time.now.utc)) do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor6) + assert_equal '2020-01-03T10:12:55Z', settings.valid_until + end + + Timecop.freeze(Time.parse("2020-01-01T10:12:55Z", Time.now.utc)) do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor6) + assert_equal '2020-01-03T10:12:55Z', settings.valid_until + end + + Timecop.freeze(Time.parse("2020-01-03T10:12:55Z", Time.now.utc)) do + settings = @idp_metadata_parser.parse(idp_metadata_descriptor6) + assert_equal '2020-01-04T18:02:33.910Z', settings.valid_until + end + end + + end + + describe "parsing metadata with many entity descriptors" do + before do + @idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + @idp_metadata = idp_metadata_multiple_descriptors2 + @settings = @idp_metadata_parser.parse(@idp_metadata) + end + + it "should find first descriptor" do + assert_equal "https://foo.example.com/access/saml/idp.xml", @settings.idp_entity_id + end + + it "should find named descriptor" do + entity_id = "https://bar.example.com/access/saml/idp.xml" + settings = @idp_metadata_parser.parse( + @idp_metadata, :entity_id => entity_id + ) + assert_equal entity_id, settings.idp_entity_id + end + + it "should retreive data" do + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", @settings.name_identifier_format + assert_equal "https://hello.example.com/access/saml/login", @settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_sso_service_binding + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", @settings.idp_cert_fingerprint + assert_equal "https://hello.example.com/access/saml/logout", @settings.idp_slo_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_slo_service_binding + assert_equal ["AuthToken", "SSOStartPage"], @settings.idp_attribute_names + assert_equal '2014-04-17T18:02:33.910Z', @settings.valid_until + end + + it "should handle multiple descriptors at once" do + settings = @idp_metadata_parser.parse_to_array(@idp_metadata) + assert_equal "https://foo.example.com/access/saml/idp.xml", settings.first[:idp_entity_id] + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.first[:idp_cert_fingerprint] + assert_equal '2014-04-17T18:02:33.910Z', settings.first[:valid_until] + assert_equal "https://bar.example.com/access/saml/idp.xml", settings.last[:idp_entity_id] + assert_equal "08:EB:6E:60:A2:14:4E:89:EC:FA:05:74:9D:72:BF:5D:BE:54:F0:1A", settings.last[:idp_cert_fingerprint] + assert_equal '2014-04-17T18:02:33.910Z', settings.last[:valid_until] + end + end + + describe "parsing metadata with no IDPSSODescriptor element" do + before do + @idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + @idp_metadata = no_idp_metadata_descriptor + end + + it "raise due no IDPSSODescriptor element" do + assert_raises(ArgumentError) { @idp_metadata_parser.parse(@idp_metadata) } + end + end + + describe "parsing metadata with IDPSSODescriptor with multiple certs" do + before do + @idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + @idp_metadata = idp_metadata_multiple_certs + @settings = @idp_metadata_parser.parse(@idp_metadata) + end + + it "should return a idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do + assert_nil @settings.idp_cert + assert_nil @settings.idp_cert_fingerprint + + expected_multi_cert = {} + expected_multi_cert[:signing] = ["MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==", "MIICZDCCAc2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBPMQswCQYDVQQGEwJ1czEUMBIGA1UECAwLZXhhbXBsZS5jb20xFDASBgNVBAoMC2V4YW1wbGUuY29tMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xNzA0MTUxNjMzMThaFw0xODA0MTUxNjMzMThaME8xCzAJBgNVBAYTAnVzMRQwEgYDVQQIDAtleGFtcGxlLmNvbTEUMBIGA1UECgwLZXhhbXBsZS5jb20xFDASBgNVBAMMC2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6GLkl5lDUZdHNDAojp5i24OoPlqrt5TGXJIPqAZYT1hQvJW5nv17MFDHrjmtEnmW4ACKEy0fAX80QWIcHunZSkbEGHb+NG/6oTi5RipXMvmHnfFnPJJ0AdtiLiPE478CV856gXekV4Xx5u3KrylcOgkpYsp0GMIQBDzleMUXlYQIDAQABo1AwTjAdBgNVHQ4EFgQUnP8vlYPGPL2n6ZzDYij2kMDC8wMwHwYDVR0jBBgwFoAUnP8vlYPGPL2n6ZzDYij2kMDC8wMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQAlQGAl+b8Cpot1g+65lLLjVoY7APJPWLW0klKQNlMU0s4MU+71Y3ExUEOXDAZgKcFoavb1fEOGMwEf38NaJAy1e/l6VNuixXShffq20ymqHQxOG0q8ujeNkgZF9k6XDfn/QZ3AD0o/IrCT7UMc/0QsfgIjWYxwCvp2syApc5CYfQ=="] + expected_multi_cert[:encryption] = ["MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw=="] + + assert_equal expected_multi_cert, @settings.idp_cert_multi + assert_equal "https://idp.examle.com/saml/metadata", @settings.idp_entity_id + assert_equal "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", @settings.name_identifier_format + assert_equal "https://idp.examle.com/saml/sso", @settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_sso_service_binding + assert_equal "https://idp.examle.com/saml/slo", @settings.idp_slo_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_slo_service_binding + end + end + + describe "parsing metadata with IDPSSODescriptor with multiple signing certs" do + before do + @idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + @idp_metadata = idp_metadata_multiple_signing_certs + @settings = @idp_metadata_parser.parse(@idp_metadata) + end + + it "should return a idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do + assert_nil @settings.idp_cert + assert_nil @settings.idp_cert_fingerprint + + expected_multi_cert = {} + expected_multi_cert[:signing] = ["MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==", "MIICZDCCAc2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBPMQswCQYDVQQGEwJ1czEUMBIGA1UECAwLZXhhbXBsZS5jb20xFDASBgNVBAoMC2V4YW1wbGUuY29tMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xNzA0MTUxNjMzMThaFw0xODA0MTUxNjMzMThaME8xCzAJBgNVBAYTAnVzMRQwEgYDVQQIDAtleGFtcGxlLmNvbTEUMBIGA1UECgwLZXhhbXBsZS5jb20xFDASBgNVBAMMC2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6GLkl5lDUZdHNDAojp5i24OoPlqrt5TGXJIPqAZYT1hQvJW5nv17MFDHrjmtEnmW4ACKEy0fAX80QWIcHunZSkbEGHb+NG/6oTi5RipXMvmHnfFnPJJ0AdtiLiPE478CV856gXekV4Xx5u3KrylcOgkpYsp0GMIQBDzleMUXlYQIDAQABo1AwTjAdBgNVHQ4EFgQUnP8vlYPGPL2n6ZzDYij2kMDC8wMwHwYDVR0jBBgwFoAUnP8vlYPGPL2n6ZzDYij2kMDC8wMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQAlQGAl+b8Cpot1g+65lLLjVoY7APJPWLW0klKQNlMU0s4MU+71Y3ExUEOXDAZgKcFoavb1fEOGMwEf38NaJAy1e/l6VNuixXShffq20ymqHQxOG0q8ujeNkgZF9k6XDfn/QZ3AD0o/IrCT7UMc/0QsfgIjWYxwCvp2syApc5CYfQ==","LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="] + + assert_equal expected_multi_cert, @settings.idp_cert_multi + assert_equal "https://idp.examle.com/saml/metadata", @settings.idp_entity_id + assert_equal "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", @settings.name_identifier_format + assert_equal "https://idp.examle.com/saml/sso", @settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_sso_service_binding + assert_equal "https://idp.examle.com/saml/slo", @settings.idp_slo_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_slo_service_binding + end + end + + describe "parsing metadata with IDPSSODescriptor with same signature cert and encrypt cert" do + before do + @idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + @idp_metadata = idp_metadata_same_sign_and_encrypt_cert + @settings = @idp_metadata_parser.parse(@idp_metadata) + end + + let(:expected_cert) do + "MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYD +VQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA2 +MDUxNzE2MjBaFw0xODA2MDUxNzE2MjBaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +DApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9u +ZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAse8rnep4qL2GmhH10pMQyJ2Jae+AQHyfgVjaQZ7Z +0QQog5jX91vcJRSMi0XWJnUtOr6lF0dq1+yckjZ92wyLrH+7fvngNO1aV4Mjk9sT +gf+iqMrae6y6fRxDt9PXrEFVjvd3vv7QTJf2FuIPy4vVP06Dt8EMkQIr8rmLmU0m +Tr1k2DkrdtdlCuNFTXuAu3QqfvNCRrRwfNObn9MP6JeOUdcGLJsBjGF8exfcN1SF +zRF0JFr3dmOlx761zK5liD0T1sYWnDquatj/JD9fZMbKecBKni1NglH/LVd+b6aJ +UAr5LulERULUjLqYJRKW31u91/4Qazdo9tbvwqyFxaoUrwIDAQABo4HUMIHRMAwG +A1UdEwEB/wQCMAAwHQYDVR0OBBYEFPWcXvQSlTXnzZD2xziuoUvrrDedMIGRBgNV +HSMEgYkwgYaAFPWcXvQSlTXnzZD2xziuoUvrrDedoWukaTBnMQswCQYDVQQGEwJV +UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREw +DwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAO +BgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB/8xe3rzqXQVxzHyAHu +AuPa73ClDoL1cko0Fp8CGcqEIyj6Te9gx5z6wyfv+Lo8RFvBLlnB1lXqbC+fTGcV +gG/4oKLJ5UwRFxInqpZPnOAudVNnd0PYOODn9FWs6u+OTIQIaIcPUv3MhB9lwHIJ +sTk/bs9xcru5TPyLIxLLd6ib/pRceKH2mTkzUd0DYk9CQNXXeoGx/du5B9nh3ClP +TbVakRzl3oswgI5MQIphYxkW70SopEh4kOFSRE1ND31NNIq1YrXlgtkguQBFsZWu +QOPR6cEwFZzP0tHTYbI839WgxX6hfhIUTUz6mLqq4+3P4BG3+1OXeVDg63y8Uh78 +1sE=" + end + + it "should return idp_cert and idp_cert_fingerprint and no idp_cert_multi" do + assert_equal(expected_cert, @settings.idp_cert) + assert_equal("2D:A9:40:88:28:EE:67:BB:4A:5B:E0:58:A7:CC:71:95:2D:1B:C9:D3", @settings.idp_cert_fingerprint) + assert_equal({ :signing => [expected_cert], :encryption => [expected_cert] }, @settings.idp_cert_multi) + assert_equal("https://app.onelogin.com/saml/metadata/383123", @settings.idp_entity_id) + assert_equal("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", @settings.name_identifier_format) + assert_equal("https://app.onelogin.com/trust/saml2/http-post/sso/383123", @settings.idp_sso_service_url) + assert_nil(@settings.idp_slo_service_url) + # TODO: next line can be changed to `assert_nil @settings.idp_slo_service_binding` after :embed_sign is removed. + assert_nil(@settings.instance_variable_get('@idp_slo_service_binding')) + end + end + + describe "parsing metadata with IDPSSODescriptor with different signature cert and encrypt cert" do + before do + @idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + @idp_metadata = idp_metadata_different_sign_and_encrypt_cert + @settings = @idp_metadata_parser.parse(@idp_metadata) + end + + it "should return a idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do + assert_nil @settings.idp_cert + assert_nil @settings.idp_cert_fingerprint + + expected_multi_cert = {} + expected_multi_cert[:signing] = ["MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYD +VQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA2 +MDUxNzE2MjBaFw0xODA2MDUxNzE2MjBaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +DApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9u +ZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAse8rnep4qL2GmhH10pMQyJ2Jae+AQHyfgVjaQZ7Z +0QQog5jX91vcJRSMi0XWJnUtOr6lF0dq1+yckjZ92wyLrH+7fvngNO1aV4Mjk9sT +gf+iqMrae6y6fRxDt9PXrEFVjvd3vv7QTJf2FuIPy4vVP06Dt8EMkQIr8rmLmU0m +Tr1k2DkrdtdlCuNFTXuAu3QqfvNCRrRwfNObn9MP6JeOUdcGLJsBjGF8exfcN1SF +zRF0JFr3dmOlx761zK5liD0T1sYWnDquatj/JD9fZMbKecBKni1NglH/LVd+b6aJ +UAr5LulERULUjLqYJRKW31u91/4Qazdo9tbvwqyFxaoUrwIDAQABo4HUMIHRMAwG +A1UdEwEB/wQCMAAwHQYDVR0OBBYEFPWcXvQSlTXnzZD2xziuoUvrrDedMIGRBgNV +HSMEgYkwgYaAFPWcXvQSlTXnzZD2xziuoUvrrDedoWukaTBnMQswCQYDVQQGEwJV +UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREw +DwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAO +BgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB/8xe3rzqXQVxzHyAHu +AuPa73ClDoL1cko0Fp8CGcqEIyj6Te9gx5z6wyfv+Lo8RFvBLlnB1lXqbC+fTGcV +gG/4oKLJ5UwRFxInqpZPnOAudVNnd0PYOODn9FWs6u+OTIQIaIcPUv3MhB9lwHIJ +sTk/bs9xcru5TPyLIxLLd6ib/pRceKH2mTkzUd0DYk9CQNXXeoGx/du5B9nh3ClP +TbVakRzl3oswgI5MQIphYxkW70SopEh4kOFSRE1ND31NNIq1YrXlgtkguQBFsZWu +QOPR6cEwFZzP0tHTYbI839WgxX6hfhIUTUz6mLqq4+3P4BG3+1OXeVDg63y8Uh78 +1sE="] + expected_multi_cert[:encryption] = ["MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw=="] + + assert_equal expected_multi_cert, @settings.idp_cert_multi + assert_equal "https://app.onelogin.com/saml/metadata/383123", @settings.idp_entity_id + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", @settings.name_identifier_format + assert_equal "https://app.onelogin.com/trust/saml2/http-post/sso/383123", @settings.idp_sso_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_sso_service_binding + assert_nil @settings.idp_slo_service_url + # TODO: next line can be changed to `assert_nil @settings.idp_slo_service_binding` after :embed_sign is removed. + assert_nil @settings.instance_variable_get('@idp_slo_service_binding') + end + end + + describe "metadata with different singlelogout response location" do + it "should return the responselocation if it exists" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + + settings = idp_metadata_parser.parse(idp_different_slo_response_location) + + assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding + assert_equal "https://hello.example.com/access/saml/logout/return", settings.idp_slo_response_service_url + end + + it "should set the responselocation to nil if it doesnt exist" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + + settings = idp_metadata_parser.parse(idp_without_slo_response_location) + + assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding + assert_nil settings.idp_slo_response_service_url + end + end end diff --git a/test/logging_test.rb b/test/logging_test.rb new file mode 100644 index 000000000..ba1070d0d --- /dev/null +++ b/test/logging_test.rb @@ -0,0 +1,62 @@ +require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) + +require 'onelogin/ruby-saml/logging' + +class LoggingTest < Minitest::Test + + describe "Logging" do + before do + OneLogin::RubySaml::Logging.logger = nil + end + + after do + OneLogin::RubySaml::Logging.logger = ::TEST_LOGGER + end + + describe "given no specific logging setup" do + it "prints to stdout" do + OneLogin::RubySaml::Logging::DEFAULT_LOGGER.expects(:debug).with('hi mom') + OneLogin::RubySaml::Logging.debug('hi mom') + end + end + + describe "given a Rails app" do + let(:logger) { mock('Logger') } + + before do + ::Rails = mock('Rails module') + ::Rails.stubs(:logger).returns(logger) + end + + after do + Object.instance_eval { remove_const(:Rails) } + end + + it "delegates to Rails" do + logger.expects(:debug).with('hi mom') + logger.expects(:info).with('sup?') + + OneLogin::RubySaml::Logging.debug('hi mom') + OneLogin::RubySaml::Logging.info('sup?') + end + end + + describe "given a specific Logger" do + let(:logger) { mock('Logger') } + + before { OneLogin::RubySaml::Logging.logger = logger } + + after do + OneLogin::RubySaml::Logging.logger = ::TEST_LOGGER + end + + it "delegates to the object" do + logger.expects(:debug).with('hi mom') + logger.expects(:info).with('sup?') + + OneLogin::RubySaml::Logging.debug('hi mom') + OneLogin::RubySaml::Logging.info('sup?') + end + end + end +end diff --git a/test/logout_requests/invalid_slo_request.xml b/test/logout_requests/invalid_slo_request.xml new file mode 100644 index 000000000..8df2ea638 --- /dev/null +++ b/test/logout_requests/invalid_slo_request.xml @@ -0,0 +1,6 @@ + +https://app.onelogin.com/saml/metadata/SOMEACCOUNT +https://app.onelogin.com/saml/metadata/SOMEACCOUNT +someone@example.org +someone2@example.org + diff --git a/test/responses/slo_request.xml b/test/logout_requests/slo_request.xml similarity index 100% rename from test/responses/slo_request.xml rename to test/logout_requests/slo_request.xml diff --git a/test/logout_requests/slo_request.xml.base64 b/test/logout_requests/slo_request.xml.base64 new file mode 100644 index 000000000..6e7691fb1 --- /dev/null +++ b/test/logout_requests/slo_request.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOkxvZ291dFJlcXVlc3QgVmVyc2lvbj0nMi4wJyBJRD0nX2MwMzQ4OTUwLTkzNWItMDEzMS0xMDYwLTc4MmJjYjU2ZmNhYScgeG1sbnM6c2FtbHA9J3VybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCcgSXNzdWVJbnN0YW50PScyMDE0LTAzLTIxVDE5OjIwOjEzJz4NCiAgPHNhbWw6SXNzdWVyIHhtbG5zOnNhbWw9J3VybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24nPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhL1NPTUVBQ0NPVU5UPC9zYW1sOklzc3Vlcj4NCiAgPHNhbWw6TmFtZUlEIHhtbG5zOnNhbWw9J3VybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24nPnNvbWVvbmVAZXhhbXBsZS5vcmc8L3NhbWw6TmFtZUlEPg0KPC9zYW1scDpMb2dvdXRSZXF1ZXN0Pg== \ No newline at end of file diff --git a/test/logout_requests/slo_request_deflated.xml.base64 b/test/logout_requests/slo_request_deflated.xml.base64 new file mode 100644 index 000000000..d00f7265d --- /dev/null +++ b/test/logout_requests/slo_request_deflated.xml.base64 @@ -0,0 +1 @@ +ndG7asMwFIDhvdB38KZJlmTHaSxs05B0COQCTdq1yKrqGqxLdWTI41dJOpgOHToKDv93DqpA6MHxre3sGJ7V16ggJK/KQ29NjbKUomSzrtGbpPlsURYUl3nRYspyhhmdU/ywyFrZFvMPKQRKznowwK/JGo3ecCugB26EVsCD5MflbstjlDtvg5V2iHWAUW0MBGFCBCmbYZrjjJ1YyTPKWY6a+7skqS5Rfh32E+ZvRQAoH+IlqPkMwQEnRDiXWqMG2/UmlVaTS4VoFcS7CIIcD7un5Wp1eNmfKjIhJzvsI7NZ/2cHsFpF+1GdhXaDSq3vfpBbMyK396//aL4B \ No newline at end of file diff --git a/test/logout_requests/slo_request_encrypted_nameid.xml b/test/logout_requests/slo_request_encrypted_nameid.xml new file mode 100644 index 000000000..f9b185e77 --- /dev/null +++ b/test/logout_requests/slo_request_encrypted_nameid.xml @@ -0,0 +1,6 @@ + + https://app.onelogin.com/saml/metadata/SOMEACCOUNT + L99BsKQL2iq5chjY+wRj6AH3jYxv9L4tscPJaDdsPWvPG47toC903oxEhjd1p9EMWkSPqD/HclvRhjcNVmhfUa3clTMM5PpZS1+oin2cDNFgKDkEaCXsGRgfn44uUKbEfUHNaljC72qh0lBLnoJe7ZkJHbFMbsA8Cd4UBtHzp4Y= + +dLZt52QiV39ltBeRNUev0jlD9ReI7lM3EDgfktPgKeIs6bvsLz9feWhlnydd+NjbwXTsBQjEhm80/O8szYZZZpQB3H+khA76HJoFeDdhDgnVMqeXVWVkeSjcDFHg6TPLPyydSNcsBPBOqP093xCF7je0PUgkK45cj6aN/hs7TckxCbeuOv/klz6jxc24TyNoGg3Z1TA/HlS2ePVY77LhQgqhsZIL52LTG3BjAHVvpzSXyuYbeR5OeiYIM028Xyl + + \ No newline at end of file diff --git a/test/logout_requests/slo_request_with_name_id_format.xml b/test/logout_requests/slo_request_with_name_id_format.xml new file mode 100644 index 000000000..ec2526df6 --- /dev/null +++ b/test/logout_requests/slo_request_with_name_id_format.xml @@ -0,0 +1,4 @@ + + https://app.onelogin.com/saml/metadata/SOMEACCOUNT + someone@example.org + diff --git a/test/logout_requests/slo_request_with_session_index.xml b/test/logout_requests/slo_request_with_session_index.xml new file mode 100644 index 000000000..30d13771f --- /dev/null +++ b/test/logout_requests/slo_request_with_session_index.xml @@ -0,0 +1,5 @@ + +https://app.onelogin.com/saml/metadata/SOMEACCOUNT +someone@example.org +_ea853497-c58a-408a-bc23-c849752d9741 + diff --git a/test/responses/logoutresponse_fixtures.rb b/test/logout_responses/logoutresponse_fixtures.rb similarity index 64% rename from test/responses/logoutresponse_fixtures.rb rename to test/logout_responses/logoutresponse_fixtures.rb index da40603e9..c1110228f 100644 --- a/test/responses/logoutresponse_fixtures.rb +++ b/test/logout_responses/logoutresponse_fixtures.rb @@ -1,6 +1,6 @@ #encoding: utf-8 -def default_response_opts +def default_logout_response_opts { :uuid => "_28024690-000e-0130-b6d2-38f6b112be8b", :issue_instant => Time.now.strftime('%Y-%m-%dT%H:%M:%SZ'), @@ -8,8 +8,8 @@ def default_response_opts } end -def valid_response(opts = {}) - opts = default_response_opts.merge!(opts) +def valid_logout_response_document(opts = {}) + opts = default_logout_response_opts.merge(opts) "" end -def unsuccessful_response(opts = {}) - opts = default_response_opts.merge!(opts) +def unsuccessful_logout_response_document(opts = {}) + opts = default_logout_response_opts.merge(opts) "" end -def invalid_xml_response +def unsuccessful_logout_response_with_message_document(opts = {}) + opts = default_logout_response_opts.merge(opts) + + " + #{opts[:settings].issuer} + + + + Logoutrequest expired + + " +end + +def invalid_xml_logout_response_document " @@ -58,8 +77,8 @@ def settings :single_logout_service_url => "http://app.muda.no/sso/consume_logout", :issuer => "http://app.muda.no", :sp_name_qualifier => "http://sso.muda.no", - :idp_sso_target_url => "http://sso.muda.no/sso", - :idp_slo_target_url => "http://sso.muda.no/slo", + :idp_sso_service_url => "http://sso.muda.no/sso", + :idp_slo_service_url => "http://sso.muda.no/slo", :idp_cert_fingerprint => "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00", :name_identifier_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", } diff --git a/test/logoutrequest_test.rb b/test/logoutrequest_test.rb index 07c657784..08a159766 100644 --- a/test/logoutrequest_test.rb +++ b/test/logoutrequest_test.rb @@ -7,80 +7,84 @@ class RequestTest < Minitest::Test describe "Logoutrequest" do let(:settings) { OneLogin::RubySaml::Settings.new } - it "create the deflated SAMLRequest URL parameter" do - settings.idp_slo_target_url = "http://unauth.com/logout" + before do + settings.idp_slo_service_url = "http://unauth.com/logout" settings.name_identifier_value = "f00f00" + end + it "create the deflated SAMLRequest URL parameter" do unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings) - assert unauth_url =~ /^http:\/\/unauth\.com\/logout\?SAMLRequest=/ + assert_match(/^http:\/\/unauth\.com\/logout\?SAMLRequest=/, unauth_url) inflated = decode_saml_request_payload(unauth_url) - - assert_match /^ nil }) - assert unauth_url =~ /&hello=$/ + assert_match(/&hello=$/, unauth_url) unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :foo => "bar" }) - assert unauth_url =~ /&foo=bar$/ + assert_match(/&foo=bar$/, unauth_url) + end + + it "RelayState cases" do + unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :RelayState => nil }) + assert !unauth_url.include?('RelayState') + + unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :RelayState => "http://example.com" }) + assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com') + + unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { 'RelayState' => nil }) + assert !unauth_url.include?('RelayState') + + unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { 'RelayState' => "http://example.com" }) + assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com') end it "set sessionindex" do - settings.idp_slo_target_url = "http://example.com" - sessionidx = UUID.new.generate + settings.idp_slo_service_url = "http://example.com" + sessionidx = OneLogin::RubySaml::Utils.uuid settings.sessionindex = sessionidx - unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :name_id => "there" }) + unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :nameid => "there" }) inflated = decode_saml_request_payload(unauth_url) - assert_match /), inflated end it "set name_identifier_value" do - settings = OneLogin::RubySaml::Settings.new - settings.idp_slo_target_url = "http://example.com" settings.name_identifier_format = "transient" name_identifier_value = "abc123" settings.name_identifier_value = name_identifier_value - unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :name_id => "there" }) + unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :nameid => "there" }) inflated = decode_saml_request_payload(unauth_url) - assert_match /), inflated end describe "when the target url doesn't contain a query string" do it "create the SAMLRequest parameter correctly" do - settings = OneLogin::RubySaml::Settings.new - settings.idp_slo_target_url = "http://example.com" - settings.name_identifier_value = "f00f00" - unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings) - assert unauth_url =~ /^http:\/\/example.com\?SAMLRequest/ + assert_match(/^http:\/\/unauth.com\/logout\?SAMLRequest/, unauth_url) end end describe "when the target url contains a query string" do it "create the SAMLRequest parameter correctly" do - settings = OneLogin::RubySaml::Settings.new - settings.idp_slo_target_url = "http://example.com?field=value" - settings.name_identifier_value = "f00f00" + settings.idp_slo_service_url = "http://example.com?field=value" unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings) - assert unauth_url =~ /^http:\/\/example.com\?field=value&SAMLRequest/ + assert_match(/^http:\/\/example.com\?field=value&SAMLRequest/, unauth_url) end end describe "consumation of logout may need to track the transaction" do it "have access to the request uuid" do - settings = OneLogin::RubySaml::Settings.new - settings.idp_slo_target_url = "http://example.com?field=value" - settings.name_identifier_value = "f00f00" + settings.idp_slo_service_url = "http://example.com?field=value" unauth_req = OneLogin::RubySaml::Logoutrequest.new unauth_url = unauth_req.create(settings) @@ -90,16 +94,66 @@ class RequestTest < Minitest::Test end end - describe "when the settings indicate to sign (embedded) logout request" do - it "created a signed logout request" do - settings = OneLogin::RubySaml::Settings.new - settings.idp_slo_target_url = "http://example.com?field=value" - settings.name_identifier_value = "f00f00" - # sign the logout request + describe "playgin with preix" do + it "creates request with ID prefixed with default '_'" do + request = OneLogin::RubySaml::Logoutrequest.new + + assert_match(/^_/, request.uuid) + end + + it "creates request with ID is prefixed, when :id_prefix is passed" do + OneLogin::RubySaml::Utils::set_prefix("test") + request = OneLogin::RubySaml::Logoutrequest.new + assert_match(/^test/, request.uuid) + OneLogin::RubySaml::Utils::set_prefix("_") + end + end + + describe "signing with HTTP-POST binding" do + before do settings.security[:logout_requests_signed] = true - settings.security[:embed_sign] = true + settings.idp_slo_service_binding = :post + settings.idp_sso_service_binding = :redirect settings.certificate = ruby_saml_cert_text settings.private_key = ruby_saml_key_text + end + + it "doesn't sign through create_xml_document" do + unauth_req = OneLogin::RubySaml::Logoutrequest.new + inflated = unauth_req.create_xml_document(settings).to_s + + refute_match %r[([a-zA-Z0-9/+=]+)], inflated + refute_match %r[], inflated + refute_match %r[], inflated + end + + it "sign unsigned request" do + unauth_req = OneLogin::RubySaml::Logoutrequest.new + unauth_req_doc = unauth_req.create_xml_document(settings) + inflated = unauth_req_doc.to_s + + refute_match %r[([a-zA-Z0-9/+=]+)], inflated + refute_match %r[], inflated + refute_match %r[], inflated + + inflated = unauth_req.sign_document(unauth_req_doc, settings).to_s + + assert_match %r[([a-zA-Z0-9/+=]+)], inflated + assert_match %r[], inflated + assert_match %r[], inflated + end + + it "signs through create_logout_request_xml_doc" do + unauth_req = OneLogin::RubySaml::Logoutrequest.new + inflated = unauth_req.create_logout_request_xml_doc(settings).to_s + + assert_match %r[([a-zA-Z0-9/+=]+)], inflated + assert_match %r[], inflated + assert_match %r[], inflated + end + + it "create a signed logout request" do + settings.compress_request = true unauth_req = OneLogin::RubySaml::Logoutrequest.new unauth_url = unauth_req.create(settings) @@ -110,45 +164,106 @@ class RequestTest < Minitest::Test assert_match %r[], inflated end - it "create a signed logout request with 256 digest and signature methods" do - settings = OneLogin::RubySaml::Settings.new + it "create an uncompressed signed logout request" do + settings.compress_request = false + + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings) + request_xml = Base64.decode64(params["SAMLRequest"]) + + assert_match %r[([a-zA-Z0-9/+=]+)], request_xml + assert_match %r[], request_xml + assert_match %r[], request_xml + end + + it "create a signed logout request with 256 digest and signature method" do settings.compress_request = false - settings.idp_slo_target_url = "http://example.com?field=value" - settings.name_identifier_value = "f00f00" - # sign the logout request - settings.security[:logout_requests_signed] = true - settings.security[:embed_sign] = true settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + settings.security[:digest_method] = XMLSecurity::Document::SHA256 + + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings) + request_xml = Base64.decode64(params["SAMLRequest"]) + assert_match %r[([a-zA-Z0-9/+=]+)], request_xml + assert_match %r[], request_xml + assert_match %r[], request_xml + end + + it "create a signed logout request with 512 digest and signature method RSA_SHA384" do + settings.compress_request = false + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384 settings.security[:digest_method] = XMLSecurity::Document::SHA512 - settings.certificate = ruby_saml_cert_text - settings.private_key = ruby_saml_key_text params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings) request_xml = Base64.decode64(params["SAMLRequest"]) assert_match %r[([a-zA-Z0-9/+=]+)], request_xml - assert_match %r[], request_xml - assert_match %r[], request_xml + assert_match %r[], request_xml + assert_match %r[], request_xml + end + + it "create a signed logout request using the first certificate and key" do + settings.compress_request = false + settings.certificate = nil + settings.private_key = nil + settings.sp_cert_multi = { + signing: [ + { certificate: ruby_saml_cert_text, private_key: ruby_saml_key_text }, + CertificateHelper.generate_pair_hash + ] + } + + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings) + request_xml = Base64.decode64(params["SAMLRequest"]) + + assert_match %r[([a-zA-Z0-9/+=]+)], request_xml + assert_match %r[], request_xml + assert_match %r[], request_xml + end + + it "create a signed logout request using the first valid certificate and key when :check_sp_cert_expiration is true" do + settings.compress_request = false + settings.certificate = nil + settings.private_key = nil + settings.security[:check_sp_cert_expiration] = true + settings.sp_cert_multi = { + signing: [ + { certificate: ruby_saml_cert_text, private_key: ruby_saml_key_text }, + CertificateHelper.generate_pair_hash + ] + } + + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings) + request_xml = Base64.decode64(params["SAMLRequest"]) + + assert_match %r[([a-zA-Z0-9/+=]+)], request_xml + assert_match %r[], request_xml + assert_match %r[], request_xml + end + + it "raises error when no valid certs and :check_sp_cert_expiration is true" do + settings.security[:check_sp_cert_expiration] = true + + assert_raises(OneLogin::RubySaml::ValidationError, 'The SP certificate expired.') do + OneLogin::RubySaml::Logoutrequest.new.create_params(settings) + end end end - describe "#create_params when the settings indicate to sign the logout request" do - def setup - @settings = OneLogin::RubySaml::Settings.new - @settings.compress_request = false - @settings.idp_sso_target_url = "http://example.com?field=value" - @settings.name_identifier_value = "f00f00" - @settings.security[:logout_requests_signed] = true - @settings.security[:embed_sign] = false - @settings.certificate = ruby_saml_cert_text - @settings.private_key = ruby_saml_key_text - @cert = OpenSSL::X509::Certificate.new(ruby_saml_cert_text) + describe "signing with HTTP-Redirect binding" do + + let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) } + + before do + settings.security[:logout_requests_signed] = true + settings.idp_slo_service_binding = :redirect + settings.idp_sso_service_binding = :post + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text end - it "create a signature parameter with RSA_SHA1 and validate it" do + it "create a signature parameter with RSA_SHA1 / SHA1 and validate it" do settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 - params = OneLogin::RubySaml::Logoutrequest.new.create_params(@settings, :RelayState => 'http://example.com') + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') assert params['SAMLRequest'] assert params[:RelayState] assert params['Signature'] @@ -160,13 +275,13 @@ def setup signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 - assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end - it "create a signature parameter with RSA_SHA256 and validate it" do - @settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + it "create a signature parameter with RSA_SHA256 / SHA256 and validate it" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 - params = OneLogin::RubySaml::Logoutrequest.new.create_params(@settings, :RelayState => 'http://example.com') + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') assert params['Signature'] assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256 @@ -175,10 +290,140 @@ def setup query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) - assert_equal signature_algorithm, OpenSSL::Digest::SHA256 - assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + assert_equal signature_algorithm, OpenSSL::Digest::SHA256 + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + end + + it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384 + + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + assert params['Signature'] + assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA384 + + query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + assert_equal signature_algorithm, OpenSSL::Digest::SHA384 + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + end + + it "create a signature parameter with RSA_SHA512 / SHA512 and validate it" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512 + + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + assert params['Signature'] + assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA512 + + query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + assert_equal signature_algorithm, OpenSSL::Digest::SHA512 + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end + it "create a signature parameter using the first certificate and key" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.compress_request = false + settings.certificate = nil + settings.private_key = nil + settings.sp_cert_multi = { + signing: [ + { certificate: ruby_saml_cert_text, private_key: ruby_saml_key_text }, + CertificateHelper.generate_pair_hash + ] + } + + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + assert params['SAMLRequest'] + assert params[:RelayState] + assert params['Signature'] + assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + + query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + assert_equal signature_algorithm, OpenSSL::Digest::SHA1 + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + end + + it "raises error when no valid certs and :check_sp_cert_expiration is true" do + settings.security[:check_sp_cert_expiration] = true + + assert_raises(OneLogin::RubySaml::ValidationError, 'The SP certificate expired.') do + OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + end + end + end + + describe "DEPRECATED: signing with HTTP-POST binding via :embed_sign" do + + before do + # sign the logout request + settings.security[:logout_requests_signed] = true + settings.security[:embed_sign] = true + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + end + + it "created a signed logout request" do + settings.compress_request = true + + unauth_req = OneLogin::RubySaml::Logoutrequest.new + unauth_url = unauth_req.create(settings) + + inflated = decode_saml_request_payload(unauth_url) + assert_match %r[([a-zA-Z0-9/+=]+)], inflated + assert_match %r[], inflated + assert_match %r[], inflated + end + end + + describe "DEPRECATED: signing with HTTP-Redirect binding via :embed_sign" do + + let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) } + + before do + settings.security[:logout_requests_signed] = true + settings.security[:embed_sign] = false + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + end + + it "create a signature parameter with RSA_SHA1 / SHA1 and validate it" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + assert params['SAMLRequest'] + assert params[:RelayState] + assert params['Signature'] + assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + + query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + assert_equal signature_algorithm, OpenSSL::Digest::SHA1 + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + end + end + + describe "#manipulate request_id" do + it "be able to modify the request id" do + logoutrequest = OneLogin::RubySaml::Logoutrequest.new + request_id = logoutrequest.request_id + assert_equal request_id, logoutrequest.uuid + logoutrequest.uuid = "new_uuid" + assert_equal logoutrequest.request_id, logoutrequest.uuid + assert_equal "new_uuid", logoutrequest.request_id + end end end end diff --git a/test/logoutresponse_test.rb b/test/logoutresponse_test.rb index 6969b2b5e..7cc799918 100644 --- a/test/logoutresponse_test.rb +++ b/test/logoutresponse_test.rb @@ -1,110 +1,424 @@ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) require 'onelogin/ruby-saml/logoutresponse' -require 'responses/logoutresponse_fixtures' +require 'logout_responses/logoutresponse_fixtures' class RubySamlTest < Minitest::Test describe "Logoutresponse" do + + let(:valid_logout_response_without_settings) { OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document) } + let(:valid_logout_response) { OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings) } + describe "#new" do it "raise an exception when response is initialized with nil" do assert_raises(ArgumentError) { OneLogin::RubySaml::Logoutresponse.new(nil) } end it "default to empty settings" do - logoutresponse = OneLogin::RubySaml::Logoutresponse.new( valid_response) - assert_nil logoutresponse.settings + assert_nil valid_logout_response_without_settings.settings end it "accept constructor-injected settings" do - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response, settings) - refute_nil logoutresponse.settings + refute_nil valid_logout_response.settings end it "accept constructor-injected options" do - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response, nil, { :foo => :bar} ) + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, nil, { :foo => :bar} ) assert !logoutresponse.options.empty? end it "support base64 encoded responses" do - expected_response = valid_response - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(Base64.encode64(expected_response), settings) - - assert_equal expected_response, logoutresponse.response + generated_logout_response = valid_logout_response_document + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(Base64.encode64(generated_logout_response), settings) + assert_equal generated_logout_response, logoutresponse.response end end + describe "#validate_structure" do + it "invalidates when the logout response has an invalid xml" do + settings.soft = true + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(invalid_xml_logout_response_document, settings) + assert !logoutresponse.send(:validate_structure) + assert_includes logoutresponse.errors, "Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd" + end + + it "raise when the logout response has an invalid xml" do + settings.soft = false + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(invalid_xml_logout_response_document, settings) + assert_raises OneLogin::RubySaml::ValidationError do + logoutresponse.send(:validate_structure) + end + end + end + describe "#validate" do - it "validate the response" do - in_relation_to_request_id = random_id + describe "when soft=true" do + before do + settings.soft = true + end - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response({:uuid => in_relation_to_request_id}), settings) + it "validate the logout response" do + in_relation_to_request_id = random_id + opts = { :matches_request_id => in_relation_to_request_id} - assert logoutresponse.validate + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document({:uuid => in_relation_to_request_id}), settings, opts) - assert_equal settings.issuer, logoutresponse.issuer - assert_equal in_relation_to_request_id, logoutresponse.in_response_to + assert logoutresponse.validate - assert logoutresponse.success? - end + assert_equal settings.sp_entity_id, logoutresponse.issuer + assert_equal in_relation_to_request_id, logoutresponse.in_response_to - it "invalidate responses with wrong id when given option :matches_uuid" do + assert logoutresponse.success? + assert_empty logoutresponse.errors + end - expected_request_id = "_some_other_expected_uuid" - opts = { :matches_request_id => expected_request_id} + it "validate the logout response extended" do + in_relation_to_request_id = random_id + settings.idp_entity_id = 'http://app.muda.no' + opts = { :matches_request_id => in_relation_to_request_id} - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response, settings, opts) + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document({:uuid => in_relation_to_request_id}), settings, opts) + assert logoutresponse.validate + assert_equal in_relation_to_request_id, logoutresponse.in_response_to + assert logoutresponse.success? + assert_empty logoutresponse.errors + end - assert !logoutresponse.validate - refute_equal expected_request_id, logoutresponse.in_response_to - end + it "invalidate logout response when initiated with blank" do + logoutresponse = OneLogin::RubySaml::Logoutresponse.new("", settings) + assert !logoutresponse.validate + assert_includes logoutresponse.errors, "Blank logout response" + end - it "invalidate responses with wrong request status" do - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_response, settings) + it "invalidate logout response when initiated with no idp cert or fingerprint" do + settings.idp_cert_fingerprint = nil + settings.idp_cert = nil + settings.idp_cert_multi = nil + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings) + assert !logoutresponse.validate + assert_includes logoutresponse.errors, "No fingerprint or certificate on settings of the logout response" + end - assert !logoutresponse.validate - assert !logoutresponse.success? - end - end + it "invalidate logout response with wrong id when given option :matches_request_id" do + expected_request_id = "_some_other_expected_uuid" + opts = { :matches_request_id => expected_request_id} - describe "#validate!" do - it "validates good responses" do - in_relation_to_request_id = random_id + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings, opts) - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response({:uuid => in_relation_to_request_id}), settings) + assert !logoutresponse.validate + refute_equal expected_request_id, logoutresponse.in_response_to + assert_includes logoutresponse.errors, "The InResponseTo of the Logout Response: #{logoutresponse.in_response_to}, does not match the ID of the Logout Request sent by the SP: #{expected_request_id}" + end - logoutresponse.validate! - end + it "invalidate logout response with unexpected request status" do + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings) - it "raises validation error when matching for wrong request id" do + assert !logoutresponse.success? + assert !logoutresponse.validate + assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester" + end - expected_request_id = "_some_other_expected_id" - opts = { :matches_request_id => expected_request_id} + it "invalidate logout response with unexpected request status and status message" do + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_with_message_document, settings) - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response, settings, opts) + assert !logoutresponse.success? + assert !logoutresponse.validate + assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester -> Logoutrequest expired" + end - assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate! } - end + it "invalidate logout response when in lack of sp_entity_id setting" do + bad_settings = settings + bad_settings.issuer = nil + bad_settings.sp_entity_id = nil + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, bad_settings) + assert !logoutresponse.validate + assert_includes logoutresponse.errors, "No sp_entity_id in settings of the logout response" + end + + it "invalidate logout response with wrong issuer" do + in_relation_to_request_id = random_id + settings.idp_entity_id = 'http://invalid.issuer.example.com/' + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document({:uuid => in_relation_to_request_id}), settings) + assert !logoutresponse.validate + assert_includes logoutresponse.errors, "Doesn't match the issuer, expected: <#{logoutresponse.settings.idp_entity_id}>, but was: " + end - it "raise validation error for wrong request status" do - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_response, settings) + it "collect errors when collect_errors=true" do + settings.idp_entity_id = 'http://invalid.issuer.example.com/' + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings) + collect_errors = true + assert !logoutresponse.validate(collect_errors) + assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester" + assert_includes logoutresponse.errors, "Doesn't match the issuer, expected: <#{logoutresponse.settings.idp_entity_id}>, but was: " + end - assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate! } end - it "raise validation error when in bad state" do - # no settings - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_response) - assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate! } + describe "when soft=false" do + before do + settings.soft = false + end + + it "validates good logout response" do + in_relation_to_request_id = random_id + + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document({:uuid => in_relation_to_request_id}), settings) + assert logoutresponse.validate + assert_empty logoutresponse.errors + end + + it "raises validation error when response initiated with blank" do + logoutresponse = OneLogin::RubySaml::Logoutresponse.new("", settings) + + assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate } + assert_includes logoutresponse.errors, "Blank logout response" + end + + it "raises validation error when initiated with no idp cert or fingerprint" do + settings.idp_cert_fingerprint = nil + settings.idp_cert = nil + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings) + assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate } + assert_includes logoutresponse.errors, "No fingerprint or certificate on settings of the logout response" + end + + it "raises validation error when matching for wrong request id" do + + expected_request_id = "_some_other_expected_id" + opts = { :matches_request_id => expected_request_id} + + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings, opts) + assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate } + assert_includes logoutresponse.errors, "The InResponseTo of the Logout Response: #{logoutresponse.in_response_to}, does not match the ID of the Logout Request sent by the SP: #{expected_request_id}" + end + + it "raise validation error for wrong request status" do + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings) + + assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate } + assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester" + end + + it "raise validation error when in bad state" do + # no settings + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings) + assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate } + assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester" + end + + it "raise validation error when in lack of sp_entity_id setting" do + settings.issuer = nil + settings.sp_entity_id = nil + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings) + assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate } + assert_includes logoutresponse.errors, "No sp_entity_id in settings of the logout response" + end + + it "raise validation error when logout response with wrong issuer" do + in_relation_to_request_id = random_id + settings.idp_entity_id = 'http://invalid.issuer.example.com/' + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document({:uuid => in_relation_to_request_id}), settings) + assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate } + assert_includes logoutresponse.errors, "Doesn't match the issuer, expected: <#{logoutresponse.settings.idp_entity_id}>, but was: " + end end - it "raise validation error when in lack of issuer setting" do - bad_settings = settings - bad_settings.issuer = nil - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_response, bad_settings) - assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate! } + describe "#validate_signature" do + let (:params) { OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, random_id, "Custom Logout Message", :RelayState => 'http://example.com') } + + before do + settings.soft = true + settings.idp_slo_service_url = "http://example.com?field=value" + settings.security[:logout_responses_signed] = true + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + settings.idp_cert = ruby_saml_cert_text + end + + it "return true when no idp_cert is provided and option :relax_signature_validation is present" do + settings.idp_cert = nil + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + options[:relax_signature_validation] = true + logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + assert logoutresponse_sign_test.send(:validate_signature) + end + + it "return false when no idp_cert is provided and no option :relax_signature_validation is present" do + settings.idp_cert = nil + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + assert !logoutresponse_sign_test.send(:validate_signature) + end + + it "return true when valid RSA_SHA1 Signature" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + assert logoutresponse_sign_test.send(:validate_signature) + end + + it "return true when valid RSA_SHA256 Signature" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + assert logoutresponse.send(:validate_signature) + end + + it "return false when invalid RSA_SHA1 Signature" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + params['RelayState'] = 'http://invalid.example.com' + options = {} + options[:get_params] = params + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + assert !logoutresponse.send(:validate_signature) + end + + it "raise when invalid RSA_SHA1 Signature" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.soft = false + params['RelayState'] = 'http://invalid.example.com' + options = {} + options[:get_params] = params + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + + assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.send(:validate_signature) } + assert logoutresponse.errors.include? "Invalid Signature on Logout Response" + end + + it "raise when get_params encoding differs from what this library generates" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.soft = false + options = {} + options[:get_params] = params + options[:get_params]['RelayState'] = 'http://example.com' + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + # Assemble query string. + query = OneLogin::RubySaml::Utils.build_query( + :type => 'SAMLResponse', + :data => params['SAMLResponse'], + :relay_state => params['RelayState'], + :sig_alg => params['SigAlg'] + ) + # Modify the query string so that it encodes the same values, + # but with different percent-encoding. Sanity-check that they + # really are equialent before moving on. + original_query = query.dup + query.gsub!("example", "ex%61mple") + refute_equal(query, original_query) + assert_equal(CGI.unescape(query), CGI.unescape(original_query)) + # Make normalised signature based on our modified params. + sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) + params['Signature'] = Base64.encode64(signature).gsub(/\n/, "") + # Re-create the Logoutresponse based on these modified parameters, + # and ask it to validate the signature. It will do it incorrectly, + # because it will compute it based on re-encoded query parameters, + # rather than their original encodings. + options[:get_params] = params + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + assert_raises(OneLogin::RubySaml::ValidationError, "Invalid Signature on Logout Request") do + logoutresponse.send(:validate_signature) + end + end + + it "return true even if raw_get_params encoding differs from what this library generates" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.soft = false + options = {} + options[:get_params] = params + options[:get_params]['RelayState'] = 'http://example.com' + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + # Assemble query string. + query = OneLogin::RubySaml::Utils.build_query( + :type => 'SAMLResponse', + :data => params['SAMLResponse'], + :relay_state => params['RelayState'], + :sig_alg => params['SigAlg'] + ) + # Modify the query string so that it encodes the same values, + # but with different percent-encoding. Sanity-check that they + # really are equialent before moving on. + original_query = query.dup + query.gsub!("example", "ex%61mple") + refute_equal(query, original_query) + assert_equal(CGI.unescape(query), CGI.unescape(original_query)) + # Make normalised signature based on our modified params. + sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) + params['Signature'] = Base64.encode64(signature).gsub(/\n/, "") + # Re-create the Logoutresponse based on these modified parameters, + # and ask it to validate the signature. Provide the altered parameter + # in its raw URI-encoded form, so that we don't have to guess the value + # that contributed to the signature. + options[:get_params] = params + options[:get_params].delete("RelayState") + options[:raw_get_params] = { + "RelayState" => "http%3A%2F%2Fex%61mple.com", + } + logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + assert logoutresponse.send(:validate_signature) + end end - it "raise error for invalid xml" do - logoutresponse = OneLogin::RubySaml::Logoutresponse.new(invalid_xml_response, settings) + describe "#validate_signature" do + let (:params) { OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, random_id, "Custom Logout Message", :RelayState => 'http://example.com') } + + before do + settings.soft = true + settings.idp_slo_service_url = "http://example.com?field=value" + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:logout_responses_signed] = true + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + settings.idp_cert = nil + end + + it "return true when at least a idp_cert is valid" do + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + settings.idp_cert_multi = { + :signing => [ruby_saml_cert_text2, ruby_saml_cert_text], + :encryption => [] + } + logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + assert logoutresponse_sign_test.send(:validate_signature) + end + + it "return false when cert expired and check_idp_cert_expiration expired" do + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + settings.security[:check_idp_cert_expiration] = true + settings.idp_cert = nil + settings.idp_cert_multi = { + :signing => [ruby_saml_cert_text], + :encryption => [] + } + logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + assert !logoutresponse_sign_test.send(:validate_signature) + assert_includes logoutresponse_sign_test.errors, "IdP x509 certificate expired" + end - assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate! } + it "return false when none cert on idp_cert_multi is valid" do + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + settings.idp_cert_multi = { + :signing => [ruby_saml_cert_text2, ruby_saml_cert_text2], + :encryption => [] + } + logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options) + assert !logoutresponse_sign_test.send(:validate_signature) + assert_includes logoutresponse_sign_test.errors, "Invalid Signature on Logout Response" + end end end end diff --git a/test/metadata/idp_descriptor.xml b/test/metadata/idp_descriptor.xml new file mode 100644 index 000000000..3e339008b --- /dev/null +++ b/test/metadata/idp_descriptor.xml @@ -0,0 +1,26 @@ + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + diff --git a/test/metadata/idp_descriptor_2.xml b/test/metadata/idp_descriptor_2.xml new file mode 100644 index 000000000..4a11917dd --- /dev/null +++ b/test/metadata/idp_descriptor_2.xml @@ -0,0 +1,56 @@ + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + diff --git a/test/metadata/idp_descriptor_3.xml b/test/metadata/idp_descriptor_3.xml new file mode 100644 index 000000000..e8c2dde99 --- /dev/null +++ b/test/metadata/idp_descriptor_3.xml @@ -0,0 +1,18 @@ + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + \ No newline at end of file diff --git a/test/metadata/idp_descriptor_4.xml b/test/metadata/idp_descriptor_4.xml new file mode 100644 index 000000000..5e7a4ad39 --- /dev/null +++ b/test/metadata/idp_descriptor_4.xml @@ -0,0 +1,72 @@ + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + diff --git a/test/metadata/idp_descriptor_5.xml b/test/metadata/idp_descriptor_5.xml new file mode 100644 index 000000000..3bff97306 --- /dev/null +++ b/test/metadata/idp_descriptor_5.xml @@ -0,0 +1,72 @@ + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + diff --git a/test/metadata/idp_descriptor_6.xml b/test/metadata/idp_descriptor_6.xml new file mode 100644 index 000000000..fa6e21db0 --- /dev/null +++ b/test/metadata/idp_descriptor_6.xml @@ -0,0 +1,72 @@ + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + diff --git a/test/metadata/idp_different_slo_response_location.xml b/test/metadata/idp_different_slo_response_location.xml new file mode 100644 index 000000000..aa53cfc5b --- /dev/null +++ b/test/metadata/idp_different_slo_response_location.xml @@ -0,0 +1,26 @@ + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + diff --git a/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml b/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml new file mode 100644 index 000000000..df90353a2 --- /dev/null +++ b/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml @@ -0,0 +1,72 @@ + + + + + + + MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYD +VQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA2 +MDUxNzE2MjBaFw0xODA2MDUxNzE2MjBaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +DApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9u +ZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAse8rnep4qL2GmhH10pMQyJ2Jae+AQHyfgVjaQZ7Z +0QQog5jX91vcJRSMi0XWJnUtOr6lF0dq1+yckjZ92wyLrH+7fvngNO1aV4Mjk9sT +gf+iqMrae6y6fRxDt9PXrEFVjvd3vv7QTJf2FuIPy4vVP06Dt8EMkQIr8rmLmU0m +Tr1k2DkrdtdlCuNFTXuAu3QqfvNCRrRwfNObn9MP6JeOUdcGLJsBjGF8exfcN1SF +zRF0JFr3dmOlx761zK5liD0T1sYWnDquatj/JD9fZMbKecBKni1NglH/LVd+b6aJ +UAr5LulERULUjLqYJRKW31u91/4Qazdo9tbvwqyFxaoUrwIDAQABo4HUMIHRMAwG +A1UdEwEB/wQCMAAwHQYDVR0OBBYEFPWcXvQSlTXnzZD2xziuoUvrrDedMIGRBgNV +HSMEgYkwgYaAFPWcXvQSlTXnzZD2xziuoUvrrDedoWukaTBnMQswCQYDVQQGEwJV +UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREw +DwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAO +BgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB/8xe3rzqXQVxzHyAHu +AuPa73ClDoL1cko0Fp8CGcqEIyj6Te9gx5z6wyfv+Lo8RFvBLlnB1lXqbC+fTGcV +gG/4oKLJ5UwRFxInqpZPnOAudVNnd0PYOODn9FWs6u+OTIQIaIcPUv3MhB9lwHIJ +sTk/bs9xcru5TPyLIxLLd6ib/pRceKH2mTkzUd0DYk9CQNXXeoGx/du5B9nh3ClP +TbVakRzl3oswgI5MQIphYxkW70SopEh4kOFSRE1ND31NNIq1YrXlgtkguQBFsZWu +QOPR6cEwFZzP0tHTYbI839WgxX6hfhIUTUz6mLqq4+3P4BG3+1OXeVDg63y8Uh78 +1sE= + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + + + + + Support + support@onelogin.com + + \ No newline at end of file diff --git a/test/metadata/idp_metadata_multi_certs.xml b/test/metadata/idp_metadata_multi_certs.xml new file mode 100644 index 000000000..f993f64ad --- /dev/null +++ b/test/metadata/idp_metadata_multi_certs.xml @@ -0,0 +1,75 @@ + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + + + MIICZDCCAc2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBPMQswCQYDVQQGEwJ1czEUMBIGA1UECAwLZXhhbXBsZS5jb20xFDASBgNVBAoMC2V4YW1wbGUuY29tMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xNzA0MTUxNjMzMThaFw0xODA0MTUxNjMzMThaME8xCzAJBgNVBAYTAnVzMRQwEgYDVQQIDAtleGFtcGxlLmNvbTEUMBIGA1UECgwLZXhhbXBsZS5jb20xFDASBgNVBAMMC2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6GLkl5lDUZdHNDAojp5i24OoPlqrt5TGXJIPqAZYT1hQvJW5nv17MFDHrjmtEnmW4ACKEy0fAX80QWIcHunZSkbEGHb+NG/6oTi5RipXMvmHnfFnPJJ0AdtiLiPE478CV856gXekV4Xx5u3KrylcOgkpYsp0GMIQBDzleMUXlYQIDAQABo1AwTjAdBgNVHQ4EFgQUnP8vlYPGPL2n6ZzDYij2kMDC8wMwHwYDVR0jBBgwFoAUnP8vlYPGPL2n6ZzDYij2kMDC8wMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQAlQGAl+b8Cpot1g+65lLLjVoY7APJPWLW0klKQNlMU0s4MU+71Y3ExUEOXDAZgKcFoavb1fEOGMwEf38NaJAy1e/l6VNuixXShffq20ymqHQxOG0q8ujeNkgZF9k6XDfn/QZ3AD0o/IrCT7UMc/0QsfgIjWYxwCvp2syApc5CYfQ== + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + \ No newline at end of file diff --git a/test/metadata/idp_metadata_multi_signing_certs.xml b/test/metadata/idp_metadata_multi_signing_certs.xml new file mode 100644 index 000000000..3d74c402c --- /dev/null +++ b/test/metadata/idp_metadata_multi_signing_certs.xml @@ -0,0 +1,52 @@ + + + + + + + MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF +BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj +aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW +T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy +MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz +Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV +BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo +3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw +tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx +VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5 +L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t +1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/ +BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB +pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD +VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL +DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC +FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B +AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM +GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c +hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB +vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37 +MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ +WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw== + + + + + + + MIICZDCCAc2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBPMQswCQYDVQQGEwJ1czEUMBIGA1UECAwLZXhhbXBsZS5jb20xFDASBgNVBAoMC2V4YW1wbGUuY29tMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xNzA0MTUxNjMzMThaFw0xODA0MTUxNjMzMThaME8xCzAJBgNVBAYTAnVzMRQwEgYDVQQIDAtleGFtcGxlLmNvbTEUMBIGA1UECgwLZXhhbXBsZS5jb20xFDASBgNVBAMMC2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6GLkl5lDUZdHNDAojp5i24OoPlqrt5TGXJIPqAZYT1hQvJW5nv17MFDHrjmtEnmW4ACKEy0fAX80QWIcHunZSkbEGHb+NG/6oTi5RipXMvmHnfFnPJJ0AdtiLiPE478CV856gXekV4Xx5u3KrylcOgkpYsp0GMIQBDzleMUXlYQIDAQABo1AwTjAdBgNVHQ4EFgQUnP8vlYPGPL2n6ZzDYij2kMDC8wMwHwYDVR0jBBgwFoAUnP8vlYPGPL2n6ZzDYij2kMDC8wMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQAlQGAl+b8Cpot1g+65lLLjVoY7APJPWLW0klKQNlMU0s4MU+71Y3ExUEOXDAZgKcFoavb1fEOGMwEf38NaJAy1e/l6VNuixXShffq20ymqHQxOG0q8ujeNkgZF9k6XDfn/QZ3AD0o/IrCT7UMc/0QsfgIjWYxwCvp2syApc5CYfQ== + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + diff --git a/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml b/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml new file mode 100644 index 000000000..e7fd250be --- /dev/null +++ b/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml @@ -0,0 +1,71 @@ + + + + + + + MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYD +VQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA2 +MDUxNzE2MjBaFw0xODA2MDUxNzE2MjBaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +DApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9u +ZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAse8rnep4qL2GmhH10pMQyJ2Jae+AQHyfgVjaQZ7Z +0QQog5jX91vcJRSMi0XWJnUtOr6lF0dq1+yckjZ92wyLrH+7fvngNO1aV4Mjk9sT +gf+iqMrae6y6fRxDt9PXrEFVjvd3vv7QTJf2FuIPy4vVP06Dt8EMkQIr8rmLmU0m +Tr1k2DkrdtdlCuNFTXuAu3QqfvNCRrRwfNObn9MP6JeOUdcGLJsBjGF8exfcN1SF +zRF0JFr3dmOlx761zK5liD0T1sYWnDquatj/JD9fZMbKecBKni1NglH/LVd+b6aJ +UAr5LulERULUjLqYJRKW31u91/4Qazdo9tbvwqyFxaoUrwIDAQABo4HUMIHRMAwG +A1UdEwEB/wQCMAAwHQYDVR0OBBYEFPWcXvQSlTXnzZD2xziuoUvrrDedMIGRBgNV +HSMEgYkwgYaAFPWcXvQSlTXnzZD2xziuoUvrrDedoWukaTBnMQswCQYDVQQGEwJV +UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREw +DwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAO +BgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB/8xe3rzqXQVxzHyAHu +AuPa73ClDoL1cko0Fp8CGcqEIyj6Te9gx5z6wyfv+Lo8RFvBLlnB1lXqbC+fTGcV +gG/4oKLJ5UwRFxInqpZPnOAudVNnd0PYOODn9FWs6u+OTIQIaIcPUv3MhB9lwHIJ +sTk/bs9xcru5TPyLIxLLd6ib/pRceKH2mTkzUd0DYk9CQNXXeoGx/du5B9nh3ClP +TbVakRzl3oswgI5MQIphYxkW70SopEh4kOFSRE1ND31NNIq1YrXlgtkguQBFsZWu +QOPR6cEwFZzP0tHTYbI839WgxX6hfhIUTUz6mLqq4+3P4BG3+1OXeVDg63y8Uh78 +1sE= + + + + + + + MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzET +MBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYD +VQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA2 +MDUxNzE2MjBaFw0xODA2MDUxNzE2MjBaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +DApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9u +ZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAse8rnep4qL2GmhH10pMQyJ2Jae+AQHyfgVjaQZ7Z +0QQog5jX91vcJRSMi0XWJnUtOr6lF0dq1+yckjZ92wyLrH+7fvngNO1aV4Mjk9sT +gf+iqMrae6y6fRxDt9PXrEFVjvd3vv7QTJf2FuIPy4vVP06Dt8EMkQIr8rmLmU0m +Tr1k2DkrdtdlCuNFTXuAu3QqfvNCRrRwfNObn9MP6JeOUdcGLJsBjGF8exfcN1SF +zRF0JFr3dmOlx761zK5liD0T1sYWnDquatj/JD9fZMbKecBKni1NglH/LVd+b6aJ +UAr5LulERULUjLqYJRKW31u91/4Qazdo9tbvwqyFxaoUrwIDAQABo4HUMIHRMAwG +A1UdEwEB/wQCMAAwHQYDVR0OBBYEFPWcXvQSlTXnzZD2xziuoUvrrDedMIGRBgNV +HSMEgYkwgYaAFPWcXvQSlTXnzZD2xziuoUvrrDedoWukaTBnMQswCQYDVQQGEwJV +UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREw +DwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAO +BgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB/8xe3rzqXQVxzHyAHu +AuPa73ClDoL1cko0Fp8CGcqEIyj6Te9gx5z6wyfv+Lo8RFvBLlnB1lXqbC+fTGcV +gG/4oKLJ5UwRFxInqpZPnOAudVNnd0PYOODn9FWs6u+OTIQIaIcPUv3MhB9lwHIJ +sTk/bs9xcru5TPyLIxLLd6ib/pRceKH2mTkzUd0DYk9CQNXXeoGx/du5B9nh3ClP +TbVakRzl3oswgI5MQIphYxkW70SopEh4kOFSRE1ND31NNIq1YrXlgtkguQBFsZWu +QOPR6cEwFZzP0tHTYbI839WgxX6hfhIUTUz6mLqq4+3P4BG3+1OXeVDg63y8Uh78 +1sE= + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + + + + + Support + support@onelogin.com + + \ No newline at end of file diff --git a/test/metadata/idp_multiple_descriptors.xml b/test/metadata/idp_multiple_descriptors.xml new file mode 100644 index 000000000..bf0a30291 --- /dev/null +++ b/test/metadata/idp_multiple_descriptors.xml @@ -0,0 +1,59 @@ + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + + diff --git a/test/metadata/idp_multiple_descriptors_2.xml b/test/metadata/idp_multiple_descriptors_2.xml new file mode 100644 index 000000000..9d388e61a --- /dev/null +++ b/test/metadata/idp_multiple_descriptors_2.xml @@ -0,0 +1,59 @@ + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + + + + + + + MIICfDCCAeWgAwIBAgIBADANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwJ1czEYMBYGA1UECAwPYmFyLmV4YW1wbGUuY29tMRgwFgYDVQQKDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAMMD2Jhci5leGFtcGxlLmNvbTAeFw0xOTAzMjExMzAzMTJaFw0yOTAzMTgxMzAzMTJaMFsxCzAJBgNVBAYTAnVzMRgwFgYDVQQIDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAoMD2Jhci5leGFtcGxlLmNvbTEYMBYGA1UEAwwPYmFyLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDv7Xe9pxNYjxLhXhjQy7L6BbU0IVwQS9pbWqXYDtH3z1fIy+tWMEnE+XhuSFLilRcfn8Ksk0VeFLByQw2HfxAHY3O2dsCv5yLHye3/5bLKa+/pYu7r9ltE5JNjELAH1Yo0/SvAWX9Nuw6Ovw7D6frXUxBPhJaEQ+1VnatuZBUOfQIDAQABo1AwTjAdBgNVHQ4EFgQU6xbFedBYUDFTdXoQvRrELtH4jP8wHwYDVR0jBBgwFoAU6xbFedBYUDFTdXoQvRrELtH4jP8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBvDqYSONEhlDB4+k2+hMg03XCwINLh0UHWd+QfFO1yT6iZK+Duaq9pDBl97RO9sN6qHfOFSQXYBOj6S1Xf+6Orq1yeep/Uh+UrL7wzWYMzPryZxTYn8B1PbdXePRIZCzU7d7ueMnEfUoBj9HqhQphRYD9WgXP9/xzvu9JQjTNk9g== + + + + + + + MIICfDCCAeWgAwIBAgIBADANBgkqhkiG9w0BAQsFADBbMQswCQYDVQQGEwJ1czEYMBYGA1UECAwPYmFyLmV4YW1wbGUuY29tMRgwFgYDVQQKDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAMMD2Jhci5leGFtcGxlLmNvbTAeFw0xOTAzMjExMzAzMTJaFw0yOTAzMTgxMzAzMTJaMFsxCzAJBgNVBAYTAnVzMRgwFgYDVQQIDA9iYXIuZXhhbXBsZS5jb20xGDAWBgNVBAoMD2Jhci5leGFtcGxlLmNvbTEYMBYGA1UEAwwPYmFyLmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDv7Xe9pxNYjxLhXhjQy7L6BbU0IVwQS9pbWqXYDtH3z1fIy+tWMEnE+XhuSFLilRcfn8Ksk0VeFLByQw2HfxAHY3O2dsCv5yLHye3/5bLKa+/pYu7r9ltE5JNjELAH1Yo0/SvAWX9Nuw6Ovw7D6frXUxBPhJaEQ+1VnatuZBUOfQIDAQABo1AwTjAdBgNVHQ4EFgQU6xbFedBYUDFTdXoQvRrELtH4jP8wHwYDVR0jBBgwFoAU6xbFedBYUDFTdXoQvRrELtH4jP8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBvDqYSONEhlDB4+k2+hMg03XCwINLh0UHWd+QfFO1yT6iZK+Duaq9pDBl97RO9sN6qHfOFSQXYBOj6S1Xf+6Orq1yeep/Uh+UrL7wzWYMzPryZxTYn8B1PbdXePRIZCzU7d7ueMnEfUoBj9HqhQphRYD9WgXP9/xzvu9JQjTNk9g== + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + + diff --git a/test/metadata/idp_without_slo_response_location.xml b/test/metadata/idp_without_slo_response_location.xml new file mode 100644 index 000000000..d6af0a1c1 --- /dev/null +++ b/test/metadata/idp_without_slo_response_location.xml @@ -0,0 +1,26 @@ + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + + diff --git a/test/metadata/no_idp_descriptor.xml b/test/metadata/no_idp_descriptor.xml new file mode 100644 index 000000000..3e1599d4e --- /dev/null +++ b/test/metadata/no_idp_descriptor.xml @@ -0,0 +1,21 @@ + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + + + LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + + + + + + + diff --git a/test/metadata_test.rb b/test/metadata_test.rb index 573c20de2..9d8489ed8 100644 --- a/test/metadata_test.rb +++ b/test/metadata_test.rb @@ -12,7 +12,7 @@ class MetadataTest < Minitest::Test let(:acs) { REXML::XPath.first(xml_doc, "//md:AssertionConsumerService") } before do - settings.issuer = "https://example.com" + settings.sp_entity_id = "https://example.com" settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" settings.assertion_consumer_service_url = "https://foo.example/saml/consume" end @@ -32,11 +32,32 @@ class MetadataTest < Minitest::Test assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value - assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value + assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value + + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") end it "generates Service Provider Metadata" do - # assert correct xml declaration + settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + settings.single_logout_service_url = "https://foo.example/saml/sls" + xml_metadata = OneLogin::RubySaml::Metadata.new.generate(settings, false) + + start = " "urn:oasis:names:tc:SAML:2.0:metadata" + ) + end + let(:cert_nodes) do + REXML::XPath.match( xml_doc, "//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate", "md" => "urn:oasis:names:tc:SAML:2.0:metadata", "ds" => "http://www.w3.org/2000/09/xmldsig#" ) end - let(:cert) { OpenSSL::X509::Certificate.new(Base64.decode64(cert_node.text)) } + let(:cert) { OpenSSL::X509::Certificate.new(Base64.decode64(cert_nodes[0].text)) } before do - settings.security[:authn_requests_signed] = true settings.certificate = ruby_saml_cert_text end - it "generates Service Provider Metadata with X509Certificate" do - assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value + it "generates Service Provider Metadata with X509Certificate for sign" do + assert_equal 1, key_descriptors.length + assert_equal "signing", key_descriptors[0].attribute("use").value + + assert_equal 1, cert_nodes.length assert_equal ruby_saml_cert.to_der, cert.to_der + + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + end + + describe "and signed authentication requests" do + before do + settings.security[:authn_requests_signed] = true + end + + it "generates Service Provider Metadata with AuthnRequestsSigned" do + assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + end + end + + describe "and encrypted assertions" do + before do + settings.security[:want_assertions_encrypted] = true + end + + it "generates Service Provider Metadata with X509Certificate for encrypt" do + assert_equal 2, key_descriptors.length + + assert_equal "encryption", key_descriptors[1].attribute("use").value + + assert_equal 2, cert_nodes.length + assert_equal cert_nodes[0].text, cert_nodes[1].text + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + end end end - describe "when attribute service is configured" do + describe "with a future SP certificate" do + let(:key_descriptors) do + REXML::XPath.match( + xml_doc, + "//md:KeyDescriptor", + "md" => "urn:oasis:names:tc:SAML:2.0:metadata" + ) + end + let(:cert_nodes) do + REXML::XPath.match( + xml_doc, + "//md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate", + "md" => "urn:oasis:names:tc:SAML:2.0:metadata", + "ds" => "http://www.w3.org/2000/09/xmldsig#" + ) + end + + before do + settings.certificate = ruby_saml_cert_text + settings.certificate_new = ruby_saml_cert_text2 + end + + it "generates Service Provider Metadata with 2 X509Certificate for sign" do + assert_equal 2, key_descriptors.length + assert_equal "signing", key_descriptors[0].attribute("use").value + assert_equal "signing", key_descriptors[1].attribute("use").value + + cert = OpenSSL::X509::Certificate.new(Base64.decode64(cert_nodes[0].text)) + cert_new = OpenSSL::X509::Certificate.new(Base64.decode64(cert_nodes[1].text)) + + assert_equal 2, cert_nodes.length + assert_equal ruby_saml_cert.to_der, cert.to_der + assert_equal ruby_saml_cert2.to_der, cert_new.to_der + + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + end + + describe "and signed authentication requests" do + before do + settings.security[:authn_requests_signed] = true + end + + it "generates Service Provider Metadata with AuthnRequestsSigned" do + assert_equal "true", spsso_descriptor.attribute("AuthnRequestsSigned").value + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + end + end + + describe "and encrypted assertions" do + before do + settings.security[:want_assertions_encrypted] = true + end + + it "generates Service Provider Metadata with X509Certificate for encrypt" do + assert_equal 4, key_descriptors.length + assert_equal "signing", key_descriptors[0].attribute("use").value + assert_equal "signing", key_descriptors[1].attribute("use").value + assert_equal "encryption", key_descriptors[2].attribute("use").value + assert_equal "encryption", key_descriptors[3].attribute("use").value + + assert_equal 4, cert_nodes.length + assert_equal cert_nodes[0].text, cert_nodes[2].text + assert_equal cert_nodes[1].text, cert_nodes[3].text + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + end + end + + describe "with check_sp_cert_expiration and expired keys" do + before do + settings.security[:want_assertions_encrypted] = true + settings.security[:check_sp_cert_expiration] = true + valid_pair = CertificateHelper.generate_pair_hash + early_pair = CertificateHelper.generate_pair_hash(not_before: Time.now + 60) + expired_pair = CertificateHelper.generate_pair_hash(not_after: Time.now - 60) + settings.certificate = nil + settings.certificate_new = nil + settings.private_key = nil + settings.sp_cert_multi = { + signing: [valid_pair, early_pair, expired_pair], + encryption: [valid_pair, early_pair, expired_pair] + } + end + + it "generates Service Provider Metadata with X509Certificate for encrypt" do + assert_equal 2, key_descriptors.length + assert_equal "signing", key_descriptors[0].attribute("use").value + assert_equal "encryption", key_descriptors[1].attribute("use").value + + assert_equal 2, cert_nodes.length + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + end + end + end + + describe "when attribute service is configured with multiple attribute values" do let(:attr_svc) { REXML::XPath.first(xml_doc, "//md:AttributeConsumingService") } let(:req_attr) { REXML::XPath.first(xml_doc, "//md:RequestedAttribute") } before do settings.attribute_consuming_service.configure do service_name "Test Service" - add_attribute(:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value") + add_attribute(:name => 'Name', :name_format => 'Name Format', :friendly_name => 'Friendly Name', :attribute_value => ['Attribute Value One', false]) end end @@ -93,7 +274,57 @@ class MetadataTest < Minitest::Test assert_equal "Name", req_attr.attribute("Name").value assert_equal "Name Format", req_attr.attribute("NameFormat").value assert_equal "Friendly Name", req_attr.attribute("FriendlyName").value - assert_equal "Attribute Value", REXML::XPath.first(xml_doc, "//md:AttributeValue").text.strip + + attribute_values = REXML::XPath.match(xml_doc, "//saml:AttributeValue").map(&:text) + assert_equal "Attribute Value One", attribute_values[0] + assert_equal 'false', attribute_values[1] + + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + end + end + + describe "when attribute service is configured" do + let(:attr_svc) { REXML::XPath.first(xml_doc, '//md:AttributeConsumingService') } + let(:req_attr) { REXML::XPath.first(xml_doc, '//md:RequestedAttribute') } + + before do + settings.attribute_consuming_service.configure do + service_name "Test Service" + add_attribute(:name => 'active', :name_format => 'format', :friendly_name => 'Active', :attribute_value => true) + end + end + + it "generates attribute service" do + assert_equal "true", attr_svc.attribute("isDefault").value + assert_equal "1", attr_svc.attribute("index").value + assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test Service" + + assert_equal 'active', req_attr.attribute('Name').value + assert_equal 'format', req_attr.attribute('NameFormat').value + assert_equal 'Active', req_attr.attribute('FriendlyName').value + assert_equal 'true', REXML::XPath.first(xml_doc, '//saml:AttributeValue').text.strip + + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + end + + describe "#service_name" do + before do + settings.attribute_consuming_service.service_name("Test2 Service") + end + + it "change service name" do + assert_equal REXML::XPath.first(xml_doc, "//md:ServiceName").text.strip, "Test2 Service" + end + end + + describe "#service_index" do + before do + settings.attribute_consuming_service.service_index(2) + end + + it "change service index" do + assert_equal "2", attr_svc.attribute("index").value + end end end @@ -108,8 +339,11 @@ class MetadataTest < Minitest::Test assert_match %r[([a-zA-Z0-9/+=]+)]m, xml_text assert_match %r[], xml_text assert_match %r[], xml_text + signed_metadata = XMLSecurity::SignedDocument.new(xml_text) - assert signed_metadata.validate_document(ruby_saml_cert_fingerprint, false) + assert signed_metadata.validate_document(ruby_saml_cert_fingerprint, false) + + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") end describe "when digest and signature methods are specified" do @@ -121,11 +355,55 @@ class MetadataTest < Minitest::Test it "creates a signed metadata with specified digest and signature methods" do assert_match %r[([a-zA-Z0-9/+=]+)]m, xml_text assert_match %r[], xml_text - assert_match %r[], xml_text + assert_match %r[], xml_text + + signed_metadata = XMLSecurity::SignedDocument.new(xml_text) + assert signed_metadata.validate_document(ruby_saml_cert_fingerprint, false) + + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") + end + end + + describe "when custom metadata elements have been inserted" do + let(:xml_text) { subclass.new.generate(settings, false) } + let(:subclass) do + Class.new(OneLogin::RubySaml::Metadata) do + def add_extras(root, _settings) + idp = REXML::Element.new("md:IDPSSODescriptor") + idp.attributes['protocolSupportEnumeration'] = 'urn:oasis:names:tc:SAML:2.0:protocol' + + nid = REXML::Element.new("md:NameIDFormat") + nid.text = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' + idp.add_element(nid) + + sso = REXML::Element.new("md:SingleSignOnService") + sso.attributes['Binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + sso.attributes['Location'] = 'https://foobar.com/sso' + idp.add_element(sso) + root.insert_before(root.children[0], idp) + + org = REXML::Element.new("md:Organization") + org.add_element("md:OrganizationName", 'xml:lang' => "en-US").text = 'ACME Inc.' + org.add_element("md:OrganizationDisplayName", 'xml:lang' => "en-US").text = 'ACME' + org.add_element("md:OrganizationURL", 'xml:lang' => "en-US").text = 'https://www.acme.com' + root.insert_after(root.children[3], org) + end + end + end + + it "inserts signature as the first child of root element" do + first_child = xml_doc.root.children[0] + assert_equal first_child.prefix, 'ds' + assert_equal first_child.name, 'Signature' + + assert_match %r[([a-zA-Z0-9/+=]+)]m, xml_text + assert_match %r[], xml_text + assert_match %r[], xml_text - signed_metadata_2 = XMLSecurity::SignedDocument.new(xml_text) + signed_metadata = XMLSecurity::SignedDocument.new(xml_text) + assert signed_metadata.validate_document(ruby_saml_cert_fingerprint, false) - assert signed_metadata_2.validate_document(ruby_saml_cert_fingerprint, false) + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") end end end diff --git a/test/request_test.rb b/test/request_test.rb index 6adcc2d64..76e8a848b 100644 --- a/test/request_test.rb +++ b/test/request_test.rb @@ -1,15 +1,20 @@ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) require 'onelogin/ruby-saml/authrequest' +require 'onelogin/ruby-saml/setting_error' class RequestTest < Minitest::Test describe "Authrequest" do + let(:settings) { OneLogin::RubySaml::Settings.new } + + before do + settings.idp_sso_service_url = "http://example.com" + end + it "create the deflated SAMLRequest URL parameter" do - settings = OneLogin::RubySaml::Settings.new - settings.idp_sso_target_url = "http://example.com" auth_url = OneLogin::RubySaml::Authrequest.new.create(settings) - assert auth_url =~ /^http:\/\/example\.com\?SAMLRequest=/ + assert_match(/^http:\/\/example\.com\?SAMLRequest=/, auth_url) payload = CGI.unescape(auth_url.split("=").last) decoded = Base64.decode64(payload) @@ -18,12 +23,12 @@ class RequestTest < Minitest::Test zstream.finish zstream.close - assert_match /^') + assert inflated.include?("testuser@example.com") + assert inflated.include?("") + end + + it "accept extra parameters" do auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :hello => "there" }) - assert auth_url =~ /&hello=there$/ + assert_match(/&hello=there$/, auth_url) auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :hello => nil }) - assert auth_url =~ /&hello=$/ + assert_match(/&hello=$/, auth_url) + end + + it "RelayState cases" do + auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :RelayState => nil }) + assert !auth_url.include?('RelayState') + + auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { :RelayState => "http://example.com" }) + assert auth_url.include?('&RelayState=http%3A%2F%2Fexample.com') + + auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { 'RelayState' => nil }) + assert !auth_url.include?('RelayState') + + auth_url = OneLogin::RubySaml::Authrequest.new.create(settings, { 'RelayState' => "http://example.com" }) + assert auth_url.include?('&RelayState=http%3A%2F%2Fexample.com') + end + + it "creates request with ID prefixed with default '_'" do + request = OneLogin::RubySaml::Authrequest.new + + assert_match(/^_/, request.uuid) + end + + it "creates request with ID is prefixed, when :id_prefix is passed" do + OneLogin::RubySaml::Utils::set_prefix("test") + request = OneLogin::RubySaml::Authrequest.new + assert_match(/^test/, request.uuid) + OneLogin::RubySaml::Utils::set_prefix("_") + end + + describe "when the target url is not set" do + before do + settings.idp_sso_service_url = nil + end + + it "raises an error with a descriptive message" do + err = assert_raises OneLogin::RubySaml::SettingError do + OneLogin::RubySaml::Authrequest.new.create(settings) + end + assert_match(/idp_sso_service_url is not set/, err.message) + end end describe "when the target url doesn't contain a query string" do it "create the SAMLRequest parameter correctly" do - settings = OneLogin::RubySaml::Settings.new - settings.idp_sso_target_url = "http://example.com" auth_url = OneLogin::RubySaml::Authrequest.new.create(settings) - assert auth_url =~ /^http:\/\/example.com\?SAMLRequest/ + assert_match(/^http:\/\/example.com\?SAMLRequest/, auth_url) end end describe "when the target url contains a query string" do it "create the SAMLRequest parameter correctly" do - settings = OneLogin::RubySaml::Settings.new - settings.idp_sso_target_url = "http://example.com?field=value" + settings.idp_sso_service_url = "http://example.com?field=value" auth_url = OneLogin::RubySaml::Authrequest.new.create(settings) - assert auth_url =~ /^http:\/\/example.com\?field=value&SAMLRequest/ + assert_match(/^http:\/\/example.com\?field=value&SAMLRequest/, auth_url) end end - describe "#create_params when the settings indicate to sign (embebed) the request" do - it "create a signed request" do - settings = OneLogin::RubySaml::Settings.new + it "create the saml:AuthnContextClassRef element correctly" do + settings.authn_context = 'secure/name/password/uri' + auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings) + assert_match(/secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s) + end + + it "create multiple saml:AuthnContextClassRef elements correctly" do + settings.authn_context = ['secure/name/password/uri', 'secure/email/password/uri'] + auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings) + assert_match(/secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s) + assert_match(/secure\/email\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s) + end + + it "create the saml:AuthnContextClassRef with comparison exact" do + settings.authn_context = 'secure/name/password/uri' + auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings) + assert_match(/secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s) + end + + it "create the saml:AuthnContextClassRef with comparison minimun" do + settings.authn_context = 'secure/name/password/uri' + settings.authn_context_comparison = 'minimun' + auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings) + assert_match(/secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/, auth_doc.to_s) + end + + it "create the saml:AuthnContextDeclRef element correctly" do + settings.authn_context_decl_ref = 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' + auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings) + assert_match(/urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/, auth_doc.to_s) + end + + describe "#create_params signing with HTTP-POST binding" do + before do settings.compress_request = false - settings.idp_sso_target_url = "http://example.com?field=value" + settings.idp_sso_service_url = "http://example.com?field=value" + settings.idp_sso_service_binding = :post settings.security[:authn_requests_signed] = true - settings.security[:embed_sign] = true - settings.certificate = ruby_saml_cert_text + settings.certificate = ruby_saml_cert_text settings.private_key = ruby_saml_key_text + end + it "create a signed request" do params = OneLogin::RubySaml::Authrequest.new.create_params(settings) request_xml = Base64.decode64(params["SAMLRequest"]) assert_match %r[([a-zA-Z0-9/+=]+)], request_xml assert_match %r[], request_xml - assert_match %r[], request_xml end it "create a signed request with 256 digest and signature methods" do - settings = OneLogin::RubySaml::Settings.new - settings.compress_request = false - settings.idp_sso_target_url = "http://example.com?field=value" - settings.security[:authn_requests_signed] = true - settings.security[:embed_sign] = true settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 settings.security[:digest_method] = XMLSecurity::Document::SHA512 - settings.certificate = ruby_saml_cert_text - settings.private_key = ruby_saml_key_text params = OneLogin::RubySaml::Authrequest.new.create_params(settings) + request_xml = Base64.decode64(params["SAMLRequest"]) assert_match %r[([a-zA-Z0-9/+=]+)], request_xml assert_match %r[], request_xml - assert_match %r[], request_xml + assert_match %r[], request_xml + end + + it "creates a signed request using the first certificate and key" do + settings.certificate = nil + settings.private_key = nil + settings.sp_cert_multi = { + signing: [ + { certificate: ruby_saml_cert_text, private_key: ruby_saml_key_text }, + CertificateHelper.generate_pair_hash + ] + } + + params = OneLogin::RubySaml::Authrequest.new.create_params(settings) + + request_xml = Base64.decode64(params["SAMLRequest"]) + assert_match %r[([a-zA-Z0-9/+=]+)], request_xml + assert_match %r[], request_xml + end + + it "creates a signed request using the first valid certificate and key when :check_sp_cert_expiration is true" do + settings.certificate = nil + settings.private_key = nil + settings.security[:check_sp_cert_expiration] = true + settings.sp_cert_multi = { + signing: [ + { certificate: ruby_saml_cert_text, private_key: ruby_saml_key_text }, + CertificateHelper.generate_pair_hash + ] + } + + params = OneLogin::RubySaml::Authrequest.new.create_params(settings) + + request_xml = Base64.decode64(params["SAMLRequest"]) + assert_match %r[([a-zA-Z0-9/+=]+)], request_xml + assert_match %r[], request_xml + end + + it "raises error when no valid certs and :check_sp_cert_expiration is true" do + settings.security[:check_sp_cert_expiration] = true + + assert_raises(OneLogin::RubySaml::ValidationError, 'The SP certificate expired.') do + OneLogin::RubySaml::Authrequest.new.create_params(settings) + end end end - describe "#create_params when the settings indicate to sign the request" do - def setup - @settings = OneLogin::RubySaml::Settings.new - @settings.compress_request = false - @settings.idp_sso_target_url = "http://example.com?field=value" - @settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" - @settings.security[:authn_requests_signed] = true - @settings.security[:embed_sign] = false - @settings.certificate = ruby_saml_cert_text - @settings.private_key = ruby_saml_key_text - @cert = OpenSSL::X509::Certificate.new(ruby_saml_cert_text) + describe "#create_params signing with HTTP-Redirect binding" do + let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) } + + before do + settings.compress_request = false + settings.idp_sso_service_url = "http://example.com?field=value" + settings.idp_sso_service_binding = :redirect + settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" + settings.security[:authn_requests_signed] = true + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text end - + it "create a signature parameter with RSA_SHA1 and validate it" do - @settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 - params = OneLogin::RubySaml::Authrequest.new.create_params(@settings, :RelayState => 'http://example.com') + params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') assert params['SAMLRequest'] assert params[:RelayState] assert params['Signature'] @@ -209,13 +341,13 @@ def setup signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 - assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end it "create a signature parameter with RSA_SHA256 and validate it" do - @settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 - params = OneLogin::RubySaml::Authrequest.new.create_params(@settings, :RelayState => 'http://example.com') + params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') assert params['Signature'] assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256 @@ -225,21 +357,52 @@ def setup signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA256 - assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + end + + it "create a signature parameter using the first certificate and key" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.compress_request = false + settings.certificate = nil + settings.private_key = nil + settings.sp_cert_multi = { + signing: [ + { certificate: ruby_saml_cert_text, private_key: ruby_saml_key_text }, + CertificateHelper.generate_pair_hash + ] + } + + params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') + assert params['SAMLRequest'] + assert params[:RelayState] + assert params['Signature'] + assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + + query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + assert_equal signature_algorithm, OpenSSL::Digest::SHA1 + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + end + + it "raises error when no valid certs and :check_sp_cert_expiration is true" do + settings.security[:check_sp_cert_expiration] = true + + assert_raises(OneLogin::RubySaml::ValidationError, 'The SP certificate expired.') do + OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') + end end end it "create the saml:AuthnContextClassRef element correctly" do - settings = OneLogin::RubySaml::Settings.new - settings.idp_sso_target_url = "http://example.com" settings.authn_context = 'secure/name/password/uri' auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings) assert auth_doc.to_s =~ /secure\/name\/password\/uri<\/saml:AuthnContextClassRef>/ end it "create the saml:AuthnContextClassRef with comparison exact" do - settings = OneLogin::RubySaml::Settings.new - settings.idp_sso_target_url = "http://example.com" settings.authn_context = 'secure/name/password/uri' auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings) assert auth_doc.to_s =~ /urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/ end + + it "create multiple saml:AuthnContextDeclRef elements correctly " do + settings.authn_context_decl_ref = ['name/password/uri', 'example/decl/ref'] + auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings) + assert auth_doc.to_s =~ /name\/password\/uri<\/saml:AuthnContextDeclRef>/ + assert auth_doc.to_s =~ /example\/decl\/ref<\/saml:AuthnContextDeclRef>/ + end + + describe "DEPRECATED: #create_params signing with HTTP-POST binding via :embed_sign" do + before do + settings.compress_request = false + settings.idp_sso_service_url = "http://example.com?field=value" + settings.security[:authn_requests_signed] = true + settings.security[:embed_sign] = true + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + end + + it "create a signed request" do + params = OneLogin::RubySaml::Authrequest.new.create_params(settings) + request_xml = Base64.decode64(params["SAMLRequest"]) + assert_match %r[([a-zA-Z0-9/+=]+)], request_xml + assert_match %r[], request_xml + end + end + + describe "DEPRECATED: #create_params signing with HTTP-Redirect binding via :embed_sign" do + let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) } + + before do + settings.compress_request = false + settings.idp_sso_service_url = "http://example.com?field=value" + settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" + settings.security[:authn_requests_signed] = true + settings.security[:embed_sign] = false + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + end + + it "create a signature parameter with RSA_SHA1 and validate it" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + + params = OneLogin::RubySaml::Authrequest.new.create_params(settings, :RelayState => 'http://example.com') + assert params['SAMLRequest'] + assert params[:RelayState] + assert params['Signature'] + assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + + query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + assert_equal signature_algorithm, OpenSSL::Digest::SHA1 + + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + end + end + + describe "#manipulate request_id" do + it "be able to modify the request id" do + authnrequest = OneLogin::RubySaml::Authrequest.new + request_id = authnrequest.request_id + assert_equal request_id, authnrequest.uuid + authnrequest.uuid = "new_uuid" + assert_equal authnrequest.request_id, authnrequest.uuid + assert_equal "new_uuid", authnrequest.request_id + end + end end end diff --git a/test/response_test.rb b/test/response_test.rb index 0ab697635..8acf90a44 100644 --- a/test/response_test.rb +++ b/test/response_test.rb @@ -5,319 +5,1323 @@ class RubySamlTest < Minitest::Test describe "Response" do + + let(:settings) { OneLogin::RubySaml::Settings.new } + let(:response) { OneLogin::RubySaml::Response.new(response_document_without_recipient) } + let(:response_without_recipient) { OneLogin::RubySaml::Response.new(signed_response_document_without_recipient) } + let(:response_without_attributes) { OneLogin::RubySaml::Response.new(response_document_without_attributes) } + let(:response_with_multiple_attribute_statements) { OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_statements)) } + let(:response_without_reference_uri) { OneLogin::RubySaml::Response.new(response_document_without_reference_uri) } + let(:response_with_signed_assertion) { OneLogin::RubySaml::Response.new(response_document_with_signed_assertion) } + let(:response_with_ds_namespace_at_the_root) { OneLogin::RubySaml::Response.new(response_document_with_ds_namespace_at_the_root)} + let(:response_unsigned) { OneLogin::RubySaml::Response.new(response_document_unsigned) } + let(:response_wrapped) { OneLogin::RubySaml::Response.new(response_document_wrapped) } + let(:response_multiple_attr_values) { OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) } + let(:response_valid_signed) { OneLogin::RubySaml::Response.new(response_document_valid_signed) } + let(:response_valid_signed_without_recipient) { OneLogin::RubySaml::Response.new(response_document_valid_signed, {:skip_recipient_check => true })} + let(:response_valid_signed_without_x509certificate) { OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate) } + let(:response_no_id) { OneLogin::RubySaml::Response.new(read_invalid_response("no_id.xml.base64")) } + let(:response_no_version) { OneLogin::RubySaml::Response.new(read_invalid_response("no_saml2.xml.base64")) } + let(:response_multi_assertion) { OneLogin::RubySaml::Response.new(read_invalid_response("multiple_assertions.xml.base64")) } + let(:response_no_conditions) { OneLogin::RubySaml::Response.new(read_invalid_response("no_conditions.xml.base64")) } + let(:response_no_conditions_with_skip) { OneLogin::RubySaml::Response.new(read_invalid_response("no_conditions.xml.base64"), { :skip_conditions => true }) } + let(:response_no_authnstatement) { OneLogin::RubySaml::Response.new(read_invalid_response("no_authnstatement.xml.base64")) } + let(:response_no_authnstatement_with_skip) { OneLogin::RubySaml::Response.new(read_invalid_response("no_authnstatement.xml.base64"), {:skip_authnstatement => true}) } + let(:response_empty_destination) { OneLogin::RubySaml::Response.new(read_invalid_response("empty_destination.xml.base64")) } + let(:response_empty_destination_with_skip) { OneLogin::RubySaml::Response.new(read_invalid_response("empty_destination.xml.base64"), {:skip_destination => true}) } + let(:response_no_status) { OneLogin::RubySaml::Response.new(read_invalid_response("no_status.xml.base64")) } + let(:response_no_statuscode) { OneLogin::RubySaml::Response.new(read_invalid_response("no_status_code.xml.base64")) } + let(:response_statuscode_responder) { OneLogin::RubySaml::Response.new(read_invalid_response("status_code_responder.xml.base64")) } + let(:response_statuscode_responder_and_msg) { OneLogin::RubySaml::Response.new(read_invalid_response("status_code_responer_and_msg.xml.base64")) } + let(:response_double_statuscode) { OneLogin::RubySaml::Response.new(response_document_double_status_code) } + let(:response_encrypted_attrs) { OneLogin::RubySaml::Response.new(response_document_encrypted_attrs) } + let(:response_no_signed_elements) { OneLogin::RubySaml::Response.new(read_invalid_response("no_signature.xml.base64")) } + let(:response_multiple_signed) { OneLogin::RubySaml::Response.new(read_invalid_response("multiple_signed.xml.base64")) } + let(:response_audience_self_closed) { OneLogin::RubySaml::Response.new(read_response("response_audience_self_closed_tag.xml.base64")) } + let(:response_invalid_audience) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_audience.xml.base64")) } + let(:response_invalid_audience_with_skip) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_audience.xml.base64"), {:skip_audience => true}) } + let(:response_invalid_signed_element) { OneLogin::RubySaml::Response.new(read_invalid_response("response_invalid_signed_element.xml.base64")) } + let(:response_invalid_issuer_assertion) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_issuer_assertion.xml.base64")) } + let(:response_invalid_issuer_message) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_issuer_message.xml.base64")) } + let(:response_no_issuer_response) { OneLogin::RubySaml::Response.new(read_invalid_response("no_issuer_response.xml.base64")) } + let(:response_no_issuer_assertion) { OneLogin::RubySaml::Response.new(read_invalid_response("no_issuer_assertion.xml.base64")) } + let(:response_no_nameid) { OneLogin::RubySaml::Response.new(read_invalid_response("no_nameid.xml.base64")) } + let(:response_empty_nameid) { OneLogin::RubySaml::Response.new(read_invalid_response("empty_nameid.xml.base64")) } + let(:response_wrong_spnamequalifier) { OneLogin::RubySaml::Response.new(read_invalid_response("wrong_spnamequalifier.xml.base64")) } + let(:response_duplicated_attributes) { OneLogin::RubySaml::Response.new(read_invalid_response("duplicated_attributes.xml.base64")) } + let(:response_no_subjectconfirmation_data) { OneLogin::RubySaml::Response.new(read_invalid_response("no_subjectconfirmation_data.xml.base64")) } + let(:response_no_subjectconfirmation_method) { OneLogin::RubySaml::Response.new(read_invalid_response("no_subjectconfirmation_method.xml.base64")) } + let(:response_invalid_subjectconfirmation_inresponse) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_inresponse.xml.base64")) } + let(:response_invalid_subjectconfirmation_recipient) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_recipient.xml.base64")) } + let(:response_invalid_subjectconfirmation_nb) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_nb.xml.base64")) } + let(:response_invalid_subjectconfirmation_noa) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_noa.xml.base64")) } + let(:response_invalid_signature_position) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_signature_position.xml.base64")) } + let(:response_encrypted_nameid) { OneLogin::RubySaml::Response.new(response_document_encrypted_nameid) } + + def generate_audience_error(expected, actual) + s = actual.count > 1 ? 's' : ''; + return "Invalid Audience#{s}. The audience#{s} #{actual.join(',')}, did not match the expected audience #{expected}" + end + it "raise an exception when response is initialized with nil" do assert_raises(ArgumentError) { OneLogin::RubySaml::Response.new(nil) } end + it "not filter available options only" do + options = { :skip_destination => true, :foo => :bar } + response = OneLogin::RubySaml::Response.new(response_document_valid_signed, options) + assert_includes response.options.keys, :skip_destination + assert_includes response.options.keys, :foo + end + it "be able to parse a document which contains ampersands" do XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true) OneLogin::RubySaml::Response.any_instance.stubs(:validate_conditions).returns(true) - response = OneLogin::RubySaml::Response.new(ampersands_response) - settings = OneLogin::RubySaml::Settings.new - settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783' - response.settings = settings - response.validate! + ampersands_response = OneLogin::RubySaml::Response.new(ampersands_document) + ampersands_response.settings = settings + ampersands_response.settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783' + + assert !ampersands_response.is_valid? + assert_includes ampersands_response.errors, "SAML Response must contain 1 assertion" + end + + describe "Prevent node text with comment attack (VU#475445)" do + before do + @response = OneLogin::RubySaml::Response.new(read_response('response_node_text_attack.xml.base64')) + end + + it "receives the full NameID when there is an injected comment" do + assert_equal "support@onelogin.com", @response.name_id + end + + it "receives the full AttributeValue when there is an injected comment" do + assert_equal "smith", @response.attributes["surname"] + end + end + + describe "Another test to prevent with comment attack (VU#475445)" do + before do + @response = OneLogin::RubySaml::Response.new(read_response('response_node_text_attack2.xml.base64'), {:skip_recipient_check => true }) + @response.settings = settings + @response.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + end + + it "receives the full NameID when there is an injected comment, validates the response" do + assert_equal "test@onelogin.com", @response.name_id + end + end + + describe "Another test with CDATA injected" do + before do + @response = OneLogin::RubySaml::Response.new(read_response('response_node_text_attack3.xml.base64'), {:skip_recipient_check => true }) + @response.settings = settings + @response.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + end + + it "it normalizes CDATA but reject SAMLResponse due signature invalidation" do + assert_equal "test@onelogin.com.evil.com", @response.name_id + assert !@response.is_valid? + assert_includes @response.errors, "Invalid Signature on SAML Response" + end + end + + describe "Prevent XEE attack" do + before do + @response = OneLogin::RubySaml::Response.new(fixture(:attackxee)) + end + + it "false when evil attack vector is present, soft = true" do + @response.soft = true + assert !@response.send(:validate_structure) + assert_includes @response.errors, "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd" + end + + it "raise when evil attack vector is present, soft = false " do + @response.soft = false + + error_msg = "XML load failed: Dangerous XML detected. No Doctype nodes allowed" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + @response.send(:validate_structure) + end + end end it "adapt namespace" do - response = OneLogin::RubySaml::Response.new(response_document) - refute_nil response.name_id - response = OneLogin::RubySaml::Response.new(response_document_2) - refute_nil response.name_id - response = OneLogin::RubySaml::Response.new(response_document_3) - refute_nil response.name_id + refute_nil response.nameid + refute_nil response_without_attributes.nameid + refute_nil response_with_signed_assertion.nameid end it "default to raw input when a response is not Base64 encoded" do - decoded = Base64.decode64(response_document_2) - response = OneLogin::RubySaml::Response.new(decoded) - assert response.document + decoded = Base64.decode64(response_document_without_attributes) + response_from_raw = OneLogin::RubySaml::Response.new(decoded) + assert response_from_raw.document end describe "Assertion" do it "only retreive an assertion with an ID that matches the signature's reference URI" do - response = OneLogin::RubySaml::Response.new(wrapped_response_2) - response.stubs(:conditions).returns(nil) - settings = OneLogin::RubySaml::Settings.new + response_wrapped.stubs(:conditions).returns(nil) settings.idp_cert_fingerprint = signature_fingerprint_1 - response.settings = settings - assert_nil response.name_id + response_wrapped.settings = settings + assert_nil response_wrapped.nameid end end - describe "#validate!" do - it "raise when encountering a condition that prevents the document from being valid" do - response = OneLogin::RubySaml::Response.new(response_document) - assert_raises(OneLogin::RubySaml::ValidationError) do - response.validate! + describe "#is_valid?" do + describe "soft = false" do + + before do + response.soft = false + response_valid_signed.soft = false + end + + it "raise when response is initialized with blank data" do + blank_response = OneLogin::RubySaml::Response.new('') + blank_response.soft = false + error_msg = "Blank response" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + blank_response.is_valid? + end + assert_includes blank_response.errors, error_msg + end + + it "raise when settings have not been set" do + error_msg = "No settings on response" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response.is_valid? + end + assert_includes response.errors, error_msg + end + + it "raise when No fingerprint or certificate on settings" do + settings.idp_cert_fingerprint = nil + settings.idp_cert = nil + settings.idp_cert_multi = nil + response.settings = settings + error_msg = "No fingerprint or certificate on settings" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response.is_valid? + end + assert_includes response.errors, error_msg + end + + it "raise when signature wrapping attack" do + response_wrapped.stubs(:conditions).returns(nil) + response_wrapped.stubs(:validate_subject_confirmation).returns(true) + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_wrapped.settings = settings + assert !response_wrapped.is_valid? + end + + it "raise when no signature" do + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_no_signed_elements.settings = settings + response_no_signed_elements.soft = false + error_msg = "Found an unexpected number of Signature Element. SAML Response rejected" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response_no_signed_elements.is_valid? + end + end + + it "raise when multiple signatures" do + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_multiple_signed.settings = settings + response_multiple_signed.soft = false + error_msg = "Duplicated ID. SAML Response rejected" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response_multiple_signed.is_valid? + end + end + + it "validate SAML 2.0 XML structure" do + resp_xml = Base64.decode64(response_document_unsigned).gsub(/emailAddress/,'test') + response_unsigned_mod = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml)) + response_unsigned_mod.stubs(:conditions).returns(nil) + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_unsigned_mod.settings = settings + response_unsigned_mod.soft = false + assert_raises(OneLogin::RubySaml::ValidationError, 'Digest mismatch') do + response_unsigned_mod.is_valid? + end + end + + it "raise when encountering a condition that prevents the document from being valid" do + settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + response_without_recipient.settings = settings + response_without_recipient.soft = false + error_msg = "Current time is on or after NotOnOrAfter condition" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response_without_recipient.is_valid? + end + assert !response_without_recipient.errors.empty? + assert_includes response_without_recipient.errors[0], error_msg + end + + it "raise when encountering a SAML Response with bad formatted" do + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_without_attributes.settings = settings + response_without_attributes.soft = false + assert_raises(OneLogin::RubySaml::ValidationError) do + response_without_attributes.is_valid? + end + end + + it "raise when the inResponseTo value does not match the Request ID" do + settings.soft = false + settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + opts = {} + opts[:settings] = settings + opts[:matches_request_id] = "invalid_request_id" + response_valid_signed = OneLogin::RubySaml::Response.new(response_document_valid_signed, opts) + error_msg = "The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response_valid_signed.is_valid? + end + assert_includes response_valid_signed.errors, error_msg + end + + it "raise when there is no valid audience" do + settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + settings.sp_entity_id = 'invalid' + response_valid_signed.settings = settings + response_valid_signed.soft = false + error_msg = generate_audience_error(response_valid_signed.settings.sp_entity_id, ['https://someone.example.com/audience']) + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response_valid_signed.is_valid? + end + + assert_includes response_valid_signed.errors, error_msg + end + + it "raise when no ID present in the SAML Response" do + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_no_id.settings = settings + response_no_id.soft = false + error_msg = "Missing ID attribute on SAML Response" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response_no_id.is_valid? + end + assert_includes response_no_id.errors, error_msg + end + + it "raise when no 2.0 Version present in the SAML Response" do + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_no_version.settings = settings + response_no_version.soft = false + error_msg = "Unsupported SAML version" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response_no_version.is_valid? + end + assert_includes response_no_version.errors, error_msg end end - end - describe "#validate_structure" do - it "raise when encountering a condition that prevents the document from being valid" do - response = OneLogin::RubySaml::Response.new(response_document_2) - response.send(:validate_structure) - assert response.errors.include? "Schema validation failed" + describe "soft = true" do + before do + response.soft = true + response_valid_signed.soft = true + end + + it "return true when the response is initialized with valid data" do + response_valid_signed_without_recipient.stubs(:conditions).returns(nil) + response_valid_signed_without_recipient.settings = settings + response_valid_signed_without_recipient.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + assert response_valid_signed_without_recipient.is_valid? + assert_empty response_valid_signed_without_recipient.errors + end + + it "return true when the response is initialized with valid data and using certificate instead of fingerprint" do + response_valid_signed_without_recipient.stubs(:conditions).returns(nil) + response_valid_signed_without_recipient.settings = settings + response_valid_signed_without_recipient.settings.idp_cert = ruby_saml_cert_text + assert response_valid_signed_without_recipient.is_valid? + assert_empty response_valid_signed_without_recipient.errors + end + + it "return false when response is initialized with blank data" do + blank_response = OneLogin::RubySaml::Response.new('') + blank_response.soft = true + assert !blank_response.is_valid? + assert_includes blank_response.errors, "Blank response" + end + + it "return false if settings have not been set" do + assert !response.is_valid? + assert_includes response.errors, "No settings on response" + end + + it "return false if fingerprint or certificate not been set on settings" do + response.settings = settings + assert !response.is_valid? + assert_includes response.errors, "No fingerprint or certificate on settings" + end + + it "should be idempotent when the response is initialized with invalid data" do + response_unsigned.stubs(:conditions).returns(nil) + response_unsigned.settings = settings + assert !response_unsigned.is_valid? + assert !response_unsigned.is_valid? + end + + it "should be idempotent when the response is initialized with valid data" do + response_valid_signed_without_recipient.stubs(:conditions).returns(nil) + response_valid_signed_without_recipient.settings = settings + response_valid_signed_without_recipient.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + assert response_valid_signed_without_recipient.is_valid? + assert response_valid_signed_without_recipient.is_valid? + end + + it "not allow signature wrapping attack" do + response_wrapped.stubs(:conditions).returns(nil) + response_wrapped.stubs(:validate_subject_confirmation).returns(true) + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_wrapped.settings = settings + assert !response_wrapped.is_valid? + end + + it "support dynamic namespace resolution on signature elements" do + no_signature_response = OneLogin::RubySaml::Response.new(fixture("inclusive_namespaces.xml")) + no_signature_response.stubs(:conditions).returns(nil) + no_signature_response.stubs(:validate_subject_confirmation).returns(true) + no_signature_response.settings = settings + no_signature_response.settings.idp_cert_fingerprint = "A0:C7:DB:B7:90:E3:47:6D:3C:5D:D2:36:F9:F2:06:0B:1F:D6:E2:53" + assert no_signature_response.is_valid? + end + + it "validate ADFS assertions" do + adfs_response = OneLogin::RubySaml::Response.new(fixture(:adfs_response_sha256)) + adfs_response.stubs(:conditions).returns(nil) + adfs_response.stubs(:validate_subject_confirmation).returns(true) + settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA" + adfs_response.settings = settings + adfs_response.soft = true + assert adfs_response.is_valid? + end + + it "validate SAML 2.0 XML structure" do + resp_xml = Base64.decode64(response_document_unsigned).gsub(/emailAddress/,'test') + response_unsigned_mod = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml)) + response_unsigned_mod.stubs(:conditions).returns(nil) + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_unsigned_mod.settings = settings + response_unsigned_mod.soft = true + assert !response_unsigned_mod.is_valid? + end + + it "return false when encountering a condition that prevents the document from being valid" do + settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + response_without_recipient.settings = settings + error_msg = "Current time is on or after NotOnOrAfter condition" + assert !response_without_recipient.is_valid? + assert !response_without_recipient.errors.empty? + assert_includes response_without_recipient.errors[0], error_msg + end + + it "return false when encountering a SAML Response with bad formatted" do + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_without_attributes.settings = settings + response_without_attributes.soft = true + error_msg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd" + response_without_attributes.is_valid? + assert_includes response_without_attributes.errors, error_msg + end + + it "return false when the inResponseTo value does not match the Request ID" do + settings.soft = true + settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + opts = {} + opts[:settings] = settings + opts[:matches_request_id] = "invalid_request_id" + response_valid_signed = OneLogin::RubySaml::Response.new(response_document_valid_signed, opts) + response_valid_signed.is_valid? + assert_includes response_valid_signed.errors, "The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id" + end + + it "return false when there is no valid audience" do + settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + settings.sp_entity_id = 'invalid' + response_valid_signed.settings = settings + response_valid_signed.is_valid? + + assert_includes response_valid_signed.errors, generate_audience_error(response_valid_signed.settings.sp_entity_id, ['https://someone.example.com/audience']) + end + + it "return false when no ID present in the SAML Response" do + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_no_id.settings = settings + response_no_id.soft = true + response_no_id.is_valid? + assert_includes response_no_id.errors, "Missing ID attribute on SAML Response" + end + + it "return false when no 2.0 Version present in the SAML Response" do + settings.idp_cert_fingerprint = signature_fingerprint_1 + response_no_version.settings = settings + response_no_version.soft = true + error_msg = "Unsupported SAML version" + response_no_version.is_valid? + assert_includes response_no_version.errors, error_msg + end + + it "return true when a nil URI is given in the ds:Reference" do + settings.idp_cert = ruby_saml_cert_text + settings.assertion_consumer_service_url = "http://localhost:9001/v1/users/authorize/saml" + response_without_reference_uri.settings = settings + response_without_reference_uri.stubs(:conditions).returns(nil) + response_without_reference_uri.is_valid? + assert_empty response_without_reference_uri.errors + assert 'saml@user.com', response_without_reference_uri.attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] + end + + it "collect errors when collect_errors=true" do + settings.idp_cert = ruby_saml_cert_text + settings.sp_entity_id = 'invalid' + response_invalid_subjectconfirmation_recipient.settings = settings + collect_errors = true + response_invalid_subjectconfirmation_recipient.is_valid?(collect_errors) + assert_includes response_invalid_subjectconfirmation_recipient.errors, generate_audience_error('invalid', ['http://stuff.com/endpoints/metadata.php']) + assert_includes response_invalid_subjectconfirmation_recipient.errors, "Invalid Signature on SAML Response" + end end end - describe "#is_valid?" do - it "return false when response is initialized with blank data" do - response = OneLogin::RubySaml::Response.new('') - assert !response.is_valid? + describe "#validate_audience" do + it "return true when the audience is valid" do + response.settings = settings + response.settings.sp_entity_id = '{audience}' + assert response.send(:validate_audience) + assert_empty response.errors end - it "return false if settings have not been set" do - response = OneLogin::RubySaml::Response.new(response_document) - assert !response.is_valid? + it "return true when the audience is self closing and strict audience validation is not enabled" do + response_audience_self_closed.settings = settings + response_audience_self_closed.settings.sp_entity_id = '{audience}' + assert response_audience_self_closed.send(:validate_audience) + assert_empty response_audience_self_closed.errors end - it "return true when the response is initialized with valid data" do - response = OneLogin::RubySaml::Response.new(response_document_4) - response.stubs(:conditions).returns(nil) - assert !response.is_valid? - settings = OneLogin::RubySaml::Settings.new - assert !response.is_valid? - response.settings = settings - assert !response.is_valid? - settings.idp_cert_fingerprint = signature_fingerprint_1 - assert response.is_valid? + it "return false when the audience is self closing and strict audience validation is enabled" do + response_audience_self_closed.settings = settings + response_audience_self_closed.settings.security[:strict_audience_validation] = true + response_audience_self_closed.settings.sp_entity_id = '{audience}' + refute response_audience_self_closed.send(:validate_audience) + assert_includes response_audience_self_closed.errors, "Invalid Audiences. The element contained only empty elements. Expected audience {audience}." end - it "should be idempotent when the response is initialized with invalid data" do - response = OneLogin::RubySaml::Response.new(response_document_4) - response.stubs(:conditions).returns(nil) - settings = OneLogin::RubySaml::Settings.new + it "return false when the audience is invalid" do response.settings = settings - assert !response.is_valid? - assert !response.is_valid? + response.settings.sp_entity_id = 'invalid_audience' + assert !response.send(:validate_audience) + assert_includes response.errors, generate_audience_error(response.settings.sp_entity_id, ['{audience}']) end + end - it "should be idempotent when the response is initialized with valid data" do - response = OneLogin::RubySaml::Response.new(response_document_4) - response.stubs(:conditions).returns(nil) - settings = OneLogin::RubySaml::Settings.new + describe "#validate_destination" do + it "return true when the destination of the SAML Response matches the assertion consumer service url" do response.settings = settings - settings.idp_cert_fingerprint = signature_fingerprint_1 - assert response.is_valid? - assert response.is_valid? + assert response.send(:validate_destination) + assert_empty response.errors end - it "return true when using certificate instead of fingerprint" do - response = OneLogin::RubySaml::Response.new(response_document_4) - response.stubs(:conditions).returns(nil) - settings = OneLogin::RubySaml::Settings.new + it "return false when the destination of the SAML Response does not match the assertion consumer service url" do response.settings = settings - settings.idp_cert = signature_1 - assert response.is_valid? + response.settings.assertion_consumer_service_url = 'invalid_acs' + assert !response.send(:validate_destination) + assert_includes response.errors, "The response was received at #{response.destination} instead of #{response.settings.assertion_consumer_service_url}" end - it "not allow signature wrapping attack" do - response = OneLogin::RubySaml::Response.new(response_document_4) - response.stubs(:conditions).returns(nil) - settings = OneLogin::RubySaml::Settings.new - settings.idp_cert_fingerprint = signature_fingerprint_1 - response.settings = settings - assert response.is_valid? - assert_equal response.name_id, "test@onelogin.com" + it "return false when the destination of the SAML Response is empty" do + response_empty_destination.settings = settings + assert !response_empty_destination.send(:validate_destination) + assert_includes response_empty_destination.errors, "The response has an empty Destination value" end - it "support dynamic namespace resolution on signature elements" do - response = OneLogin::RubySaml::Response.new(fixture("no_signature_ns.xml")) - response.stubs(:conditions).returns(nil) - settings = OneLogin::RubySaml::Settings.new - response.settings = settings - settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA" - XMLSecurity::SignedDocument.any_instance.expects(:validate_signature).returns(true) - assert response.validate! + it "return true when the destination of the SAML Response is empty but skip_destination option is used" do + response_empty_destination_with_skip.settings = settings + assert response_empty_destination_with_skip.send(:validate_destination) + assert_empty response_empty_destination.errors end - it "validate ADFS assertions" do - response = OneLogin::RubySaml::Response.new(fixture(:adfs_response_sha256)) - response.stubs(:conditions).returns(nil) - settings = OneLogin::RubySaml::Settings.new - settings.idp_cert_fingerprint = "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA" + it "returns true on a case insensitive match on the domain" do + response_valid_signed_without_x509certificate.settings = settings + response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url = 'http://APP.muDa.no/sso/consume' + assert response_valid_signed_without_x509certificate.send(:validate_destination) + assert_empty response_valid_signed_without_x509certificate.errors + end + + it "returns true on a case insensitive match on the scheme" do + response_valid_signed_without_x509certificate.settings = settings + response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url = 'HTTP://app.muda.no/sso/consume' + assert response_valid_signed_without_x509certificate.send(:validate_destination) + assert_empty response_valid_signed_without_x509certificate.errors + end + + it "returns false on a case insenstive match on the path" do + response_valid_signed_without_x509certificate.settings = settings + response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url = 'http://app.muda.no/SSO/consume' + assert !response_valid_signed_without_x509certificate.send(:validate_destination) + assert_includes response_valid_signed_without_x509certificate.errors, "The response was received at #{response_valid_signed_without_x509certificate.destination} instead of #{response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url}" + end + + it "returns true if it can't parse out a full URI." do + response_valid_signed_without_x509certificate.settings = settings + response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url = 'presenter' + assert !response_valid_signed_without_x509certificate.send(:validate_destination) + assert_includes response_valid_signed_without_x509certificate.errors, "The response was received at #{response_valid_signed_without_x509certificate.destination} instead of #{response_valid_signed_without_x509certificate.settings.assertion_consumer_service_url}" + end + end + + describe "#validate_issuer" do + it "return true when the issuer of the Message/Assertion matches the IdP entityId" do + response_valid_signed.settings = settings + assert response_valid_signed.send(:validate_issuer) + + response_valid_signed.settings.idp_entity_id = 'https://app.onelogin.com/saml2' + assert response_valid_signed.send(:validate_issuer) + end + + it "return false when the issuer of the Message does not match the IdP entityId" do + response_invalid_issuer_message.settings = settings + response_invalid_issuer_message.settings.idp_entity_id = 'http://idp.example.com/' + assert !response_invalid_issuer_message.send(:validate_issuer) + assert_includes response_invalid_issuer_message.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_message.settings.idp_entity_id}>, but was: " + end + + it "return false when the issuer of the Assertion does not match the IdP entityId" do + response_invalid_issuer_assertion.settings = settings + response_invalid_issuer_assertion.settings.idp_entity_id = 'http://idp.example.com/' + assert !response_invalid_issuer_assertion.send(:validate_issuer) + assert_includes response_invalid_issuer_assertion.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_assertion.settings.idp_entity_id}>, but was: " + end + end + + describe "#validate_num_assertion" do + it "return true when SAML Response contains 1 assertion" do + assert response.send(:validate_num_assertion) + assert_empty response.errors + end + + it "return false when no 2.0 Version present in the SAML Response" do + assert !response_multi_assertion.send(:validate_num_assertion) + assert_includes response_multi_assertion.errors, "SAML Response must contain 1 assertion" + end + end + + describe "validate_success_status" do + it "return true when the status is 'Success'" do + assert response.send(:validate_success_status) + assert_empty response.errors + end + + it "return false when no Status provided" do + assert !response_no_status.send(:validate_success_status) + assert_includes response_no_status.errors, "The status code of the Response was not Success" + end + + it "return false when no StatusCode provided" do + assert !response_no_statuscode.send(:validate_success_status) + assert_includes response_no_statuscode.errors, "The status code of the Response was not Success" + end + + it "return false when the status is not 'Success'" do + assert !response_statuscode_responder.send(:validate_success_status) + assert_includes response_statuscode_responder.errors, "The status code of the Response was not Success, was Responder" + end + + it "return false when the status is not 'Success', and shows the StatusMessage" do + assert !response_statuscode_responder_and_msg.send(:validate_success_status) + assert_includes response_statuscode_responder_and_msg.errors, "The status code of the Response was not Success, was Responder -> something_is_wrong" + end + + it "return false when the status is not 'Success'" do + assert !response_double_statuscode.send(:validate_success_status) + assert_includes response_double_statuscode.errors, "The status code of the Response was not Success, was Requester => UnsupportedBinding" + end + end + + describe "#validate_structure" do + it "return true when encountering a wellformed SAML Response" do + assert response.send(:validate_structure) + assert_empty response.errors + end + + it "return false when encountering a mailformed element that prevents the document from being valid" do + response_without_attributes.soft = true + response_without_attributes.send(:validate_structure) + assert response_without_attributes.errors.include? "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd" + end + + it "raise when encountering a mailformed element that prevents the document from being valid" do + response_without_attributes.soft = false + assert_raises(OneLogin::RubySaml::ValidationError) { + response_without_attributes.send(:validate_structure) + } + end + end + + describe "validate_formatted_x509_certificate" do + let(:response_with_formatted_x509certificate) { + OneLogin::RubySaml::Response.new(read_response("valid_response_with_formatted_x509certificate.xml.base64"), { + :skip_conditions => true, + :skip_subject_confirmation => true }) + } + + it "be able to parse the response wihout errors" do + response_with_formatted_x509certificate.settings = settings + response_with_formatted_x509certificate.settings.idp_cert = ruby_saml_cert_text + assert response_with_formatted_x509certificate.is_valid? + assert_empty response_with_formatted_x509certificate.errors + end + end + + describe "#validate_in_response_to" do + it "return true when the inResponseTo value matches the Request ID" do + response = OneLogin::RubySaml::Response.new(response_document_valid_signed, :settings => settings, :matches_request_id => "_fc4a34b0-7efb-012e-caae-782bcb13bb38") + assert response.send(:validate_in_response_to) + assert_empty response.errors + end + + it "return true when no Request ID is provided for checking" do + response = OneLogin::RubySaml::Response.new(response_document_valid_signed, :settings => settings) + assert response.send(:validate_in_response_to) + assert_empty response.errors + end + + it "return false when the inResponseTo value does not match the Request ID" do + response = OneLogin::RubySaml::Response.new(response_document_valid_signed, :settings => settings, :matches_request_id => "invalid_request_id") + assert !response.send(:validate_in_response_to) + assert_includes response.errors, "The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id" + end + end + + describe "#validate_audience" do + it "return true when the audience is valid" do + response_valid_signed.settings = settings + response_valid_signed.settings.sp_entity_id = "https://someone.example.com/audience" + assert response_valid_signed.send(:validate_audience) + assert_empty response_valid_signed.errors + end + + it "return true when there is not sp_entity_id defined" do + response_valid_signed.settings = settings + response_valid_signed.settings.sp_entity_id = nil + assert response_valid_signed.send(:validate_audience) + assert_empty response_valid_signed.errors + end + + it "return false when there is no valid audience" do + response_invalid_audience.settings = settings + response_invalid_audience.settings.sp_entity_id = "https://invalid.example.com/audience" + assert !response_invalid_audience.send(:validate_audience) + assert_includes response_invalid_audience.errors, generate_audience_error(response_invalid_audience.settings.sp_entity_id, ['http://invalid.audience.com']) + end + + it "return true when there is no valid audience but skip_destination option is used" do + response_invalid_audience_with_skip.settings = settings + response_invalid_audience_with_skip.settings.sp_entity_id = "https://invalid.example.com/audience" + assert response_invalid_audience_with_skip.send(:validate_audience) + assert_empty response_invalid_audience_with_skip.errors + end + end + + describe "#validate_issuer" do + it "return true when the issuer of the Message/Assertion matches the IdP entityId or it was empty" do + response_valid_signed.settings = settings + assert response_valid_signed.send(:validate_issuer) + assert_empty response_valid_signed.errors + + response_valid_signed.settings.idp_entity_id = 'https://app.onelogin.com/saml2' + assert response_valid_signed.send(:validate_issuer) + assert_empty response_valid_signed.errors + end + + it "return false when the issuer of the Message does not match the IdP entityId" do + response_invalid_issuer_message.settings = settings + response_invalid_issuer_message.settings.idp_entity_id = 'http://idp.example.com/' + assert !response_invalid_issuer_message.send(:validate_issuer) + assert_includes response_invalid_issuer_message.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_message.settings.idp_entity_id}>, but was: " + end + + it "return false when the issuer of the Assertion does not match the IdP entityId" do + response_invalid_issuer_assertion.settings = settings + response_invalid_issuer_assertion.settings.idp_entity_id = 'http://idp.example.com/' + assert !response_invalid_issuer_assertion.send(:validate_issuer) + assert_includes response_invalid_issuer_assertion.errors, "Doesn't match the issuer, expected: <#{response_invalid_issuer_assertion.settings.idp_entity_id}>, but was: " + end + + it "return false when the no issuer at the Response" do + response_no_issuer_response.settings = settings + response_no_issuer_response.settings.idp_entity_id = 'http://idp.example.com/' + assert !response_no_issuer_response.send(:validate_issuer) + assert_includes response_no_issuer_response.errors, "Issuer of the Response not found or multiple." + end + + it "return false when the no issuer at the Assertion" do + response_no_issuer_assertion.settings = settings + response_no_issuer_assertion.settings.idp_entity_id = 'http://idp.example.com/' + assert !response_no_issuer_assertion.send(:validate_issuer) + assert_includes response_no_issuer_assertion.errors, "Issuer of the Assertion not found or multiple." + end + end + + describe "#validate_subject_confirmation" do + it "return true when valid subject confirmation" do + response_valid_signed.settings = settings + response_valid_signed.settings.assertion_consumer_service_url = 'recipient' + assert response_valid_signed.send(:validate_subject_confirmation) + assert_empty response_valid_signed.errors + end + + it "return false when no subject confirmation data" do + response_no_subjectconfirmation_data.settings = settings + assert !response_no_subjectconfirmation_data.send(:validate_subject_confirmation) + assert_includes response_no_subjectconfirmation_data.errors, "A valid SubjectConfirmation was not found on this Response" + end + + it "return false when no valid subject confirmation method" do + response_no_subjectconfirmation_method.settings = settings + assert !response_no_subjectconfirmation_method.send(:validate_subject_confirmation) + assert_includes response_no_subjectconfirmation_method.errors, "A valid SubjectConfirmation was not found on this Response" + end + + it "return false when invalid inresponse" do + response_invalid_subjectconfirmation_inresponse.settings = settings + assert !response_invalid_subjectconfirmation_inresponse.send(:validate_subject_confirmation) + assert_includes response_invalid_subjectconfirmation_inresponse.errors, "A valid SubjectConfirmation was not found on this Response" + end + + it "return false when invalid NotBefore" do + response_invalid_subjectconfirmation_nb.settings = settings + assert !response_invalid_subjectconfirmation_nb.send(:validate_subject_confirmation) + assert_includes response_invalid_subjectconfirmation_nb.errors, "A valid SubjectConfirmation was not found on this Response" + end + + it "return false when invalid NotOnOrAfter" do + response_invalid_subjectconfirmation_noa.settings = settings + assert !response_invalid_subjectconfirmation_noa.send(:validate_subject_confirmation) + assert_includes response_invalid_subjectconfirmation_noa.errors, "A valid SubjectConfirmation was not found on this Response" + end + + it "return true when valid subject confirmation recipient" do + response_valid_signed.settings = settings + response_valid_signed.settings.assertion_consumer_service_url = 'recipient' + assert response_valid_signed.send(:validate_subject_confirmation) + assert_empty response_valid_signed.errors + assert_empty response_valid_signed.errors + end + + it "return false when invalid subject confirmation recipient" do + response_valid_signed.settings = settings + response_valid_signed.settings.assertion_consumer_service_url = 'not-the-recipient' + assert !response_valid_signed.send(:validate_subject_confirmation) + assert_includes response_valid_signed.errors, "A valid SubjectConfirmation was not found on this Response" + end + + it "return false when invalid subject confirmation recipient, but skipping the check(default)" do + response_valid_signed_without_recipient.settings = settings + response_valid_signed_without_recipient.settings.assertion_consumer_service_url = 'not-the-recipient' + assert response_valid_signed_without_recipient.send(:validate_subject_confirmation) + assert_empty response_valid_signed_without_recipient.errors + end + + it "return true when the skip_subject_confirmation option is passed and the subject confirmation is valid" do + opts = {} + opts[:skip_subject_confirmation] = true + response_with_skip = OneLogin::RubySaml::Response.new(response_document_valid_signed, opts) + response_with_skip.settings = settings + response_with_skip.settings.assertion_consumer_service_url = 'recipient' + Time.expects(:now).times(0) # ensures the test isn't run and thus Time.now.utc is never called within the test + assert response_with_skip.send(:validate_subject_confirmation) + assert_empty response_with_skip.errors + end + + it "return true when the skip_subject_confirmation option is passed and the response has an invalid subject confirmation" do + opts = {} + opts[:skip_subject_confirmation] = true + response_with_skip = OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_noa.xml.base64"), opts) + response_with_skip.settings = settings + Time.expects(:now).times(0) # ensures the test isn't run and thus Time.now.utc is never called within the test + assert response_with_skip.send(:validate_subject_confirmation) + assert_empty response_with_skip.errors + end + end + + describe "#validate_session_expiration" do + it "return true when the session has not expired" do + response_valid_signed.settings = settings + assert response_valid_signed.send(:validate_session_expiration) + assert_empty response_valid_signed.errors + end + + it "return false when the session has expired" do response.settings = settings - assert response.validate! + assert !response.send(:validate_session_expiration) + assert_includes response.errors, "The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response" end - it "validate the digest" do - response = OneLogin::RubySaml::Response.new(r1_response_document_6) - response.stubs(:conditions).returns(nil) - settings = OneLogin::RubySaml::Settings.new - settings.idp_cert = Base64.decode64(r1_signature_2) + it "returns true when the session has expired, but is still within the allowed_clock_drift" do + drift = (Time.now - Time.parse("2010-11-19T21:57:37Z")) * 60 # seconds ago that this assertion expired + drift += 10 # add a buffer of 10 seconds to make sure the test passes + opts = {} + opts[:allowed_clock_drift] = drift + + response_with_drift = OneLogin::RubySaml::Response.new(response_document_without_recipient, opts) + response_with_drift.settings = settings + assert response_with_drift.send(:validate_session_expiration) + assert_empty response_with_drift.errors + end + end + + describe "#validate_signature" do + it "return true when the signature is valid" do + settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + response_valid_signed.settings = settings + assert response_valid_signed.send(:validate_signature) + assert_empty response_valid_signed.errors + end + + it "return true when the signature is valid and ds namespace is at the root" do + settings.idp_cert_fingerprint = '5614657ab692b960480389723a36446a5fe1f7ec' + response_with_ds_namespace_at_the_root.settings = settings + assert response_with_ds_namespace_at_the_root.send(:validate_signature) + assert_empty response_with_ds_namespace_at_the_root.errors + end + + it "return true when the signature is valid and fingerprint provided" do + settings.idp_cert_fingerprint = '49:EC:3F:A4:71:8A:1E:C9:DB:70:A7:CC:33:36:96:F0:48:8C:4E:DA' + xml = 'PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIERlc3RpbmF0aW9uPSJodHRwczovL2NvZGVycGFkLmlvL3NhbWwvYWNzIiBJRD0iXzEwOGE1ZTg0MDllYzRjZjlhY2QxYzQ2OWU5ZDcxNGFkIiBJblJlc3BvbnNlVG89Il80ZmZmYWE2MC02OTZiLTAxMzMtMzg4Ni0wMjQxZjY1YzA2OTMiIElzc3VlSW5zdGFudD0iMjAxNS0xMS0wOVQyMzo1NTo0M1oiIFZlcnNpb249IjIuMCI+PHNhbWw6SXNzdWVyIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPmh0dHBzOi8vbG9naW4uaHVsdS5jb208L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIj48L2RzOkNhbm9uaWNhbGl6YXRpb25NZXRob2Q+PGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSI+PC9kczpTaWduYXR1cmVNZXRob2Q+PGRzOlJlZmVyZW5jZSBVUkk9IiNfMTA4YTVlODQwOWVjNGNmOWFjZDFjNDY5ZTlkNzE0YWQiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSI+PC9kczpUcmFuc2Zvcm0+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyI+PC9kczpUcmFuc2Zvcm0+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSI+PC9kczpEaWdlc3RNZXRob2Q+PGRzOkRpZ2VzdFZhbHVlPm9sQllXbTQyRi9oZm0xdHJYTHk2a3V6MXlMUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+dXNRTmY5WGpKTDRlOXVucnVCdWViSnQ3R0tXM2hJUk9teWVqTm1NMHM4WFhlWHN3WHc4U3ZCZi8zeDNNWEpkWnpNV0pOM3ExN2tGWHN2bTVna1JzbkE9PTwvZHM6U2lnbmF0dXJlVmFsdWU+PGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ1FEQ0NBZXFnQXdJQkFnSUpBSVZOdzVLRzR1aTFNQTBHQ1NxR1NJYjNEUUVCQlFVQU1Fd3hDekFKQmdOVkJBWVRBa2RDTVJJd0VBWURWUVFJRXdsQ1pYSnJjMmhwY21VeEVEQU9CZ05WQkFjVEIwNWxkMkoxY25reEZ6QVZCZ05WQkFvVERrMTVJRU52YlhCaGJua2dUSFJrTUI0WERURXlNVEF5TlRBMk1qY3pORm9YRFRJeU1UQXlNekEyTWpjek5Gb3dUREVMTUFrR0ExVUVCaE1DUjBJeEVqQVFCZ05WQkFnVENVSmxjbXR6YUdseVpURVFNQTRHQTFVRUJ4TUhUbVYzWW5WeWVURVhNQlVHQTFVRUNoTU9UWGtnUTI5dGNHRnVlU0JNZEdRd1hEQU5CZ2txaGtpRzl3MEJBUUVGQUFOTEFEQklBa0VBd1NOL2dpMzNSbXBBUW9MUWo3UDZ6QW5OVDBSbjdiakMzMjNuM3ExT25mdm52UjBmUWp2TnQ3ckRrQTVBdjVRbk02VjRZVU5Vbk1mYk9RcTBXTGJMU3dJREFRQUJvNEd1TUlHck1CMEdBMVVkRGdRV0JCUWZJSDFvZkJWcHNSQWNJTUsyaGJsN25nTVRZREI4QmdOVkhTTUVkVEJ6Z0JRZklIMW9mQlZwc1JBY0lNSzJoYmw3bmdNVFlLRlFwRTR3VERFTE1Ba0dBMVVFQmhNQ1IwSXhFakFRQmdOVkJBZ1RDVUpsY210emFHbHlaVEVRTUE0R0ExVUVCeE1IVG1WM1luVnllVEVYTUJVR0ExVUVDaE1PVFhrZ1EyOXRjR0Z1ZVNCTWRHU0NDUUNGVGNPU2h1TG90VEFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJCUVVBQTBFQXFvZ1YzdVBjbEtYRG1EWk1UN3ZsUFl4TEFxQ0dIWnRsQ3h6NGhNNEtTdGxEMi9HTmMxWGlMYjFoL0swQ0pMRG9zckVJYm0zd2lPMk12VEVSclZZU01RPT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiPjwvc2FtbHA6U3RhdHVzQ29kZT48L3NhbWxwOlN0YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9Il8wMTg4MmRhOTM2OTQ0ZDFlYTZlZmY0NDA2NTc2MzFiNSIgSXNzdWVJbnN0YW50PSIyMDE1LTExLTA5VDIzOjU1OjQzWiIgVmVyc2lvbj0iMi4wIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9sb2dpbi5odWx1LmNvbTwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiPjwvZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZD48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIj48L2RzOlNpZ25hdHVyZU1ldGhvZD48ZHM6UmVmZXJlbmNlIFVSST0iI18wMTg4MmRhOTM2OTQ0ZDFlYTZlZmY0NDA2NTc2MzFiNSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIj48L2RzOlRyYW5zZm9ybT48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIj48L2RzOlRyYW5zZm9ybT48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIj48L2RzOkRpZ2VzdE1ldGhvZD48ZHM6RGlnZXN0VmFsdWU+cmo2YzhucC9BUmV0ZkJ1dWVOSzNPS0xDYnowPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5hR05FemZHM1dLcExKc2ZLRGJSNmpva2d6OEFnZ0FIRVVESEZyd0dsTHVQeWpyNEl3M09NcFNkV2gyL01YK1F3M1dPTk5mNHJNalh5TGVZSFJIVGpMQT09PC9kczpTaWduYXR1cmVWYWx1ZT48ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDUURDQ0FlcWdBd0lCQWdJSkFJVk53NUtHNHVpMU1BMEdDU3FHU0liM0RRRUJCUVVBTUV3eEN6QUpCZ05WQkFZVEFrZENNUkl3RUFZRFZRUUlFd2xDWlhKcmMyaHBjbVV4RURBT0JnTlZCQWNUQjA1bGQySjFjbmt4RnpBVkJnTlZCQW9URGsxNUlFTnZiWEJoYm5rZ1RIUmtNQjRYRFRFeU1UQXlOVEEyTWpjek5Gb1hEVEl5TVRBeU16QTJNamN6TkZvd1RERUxNQWtHQTFVRUJoTUNSMEl4RWpBUUJnTlZCQWdUQ1VKbGNtdHphR2x5WlRFUU1BNEdBMVVFQnhNSFRtVjNZblZ5ZVRFWE1CVUdBMVVFQ2hNT1RYa2dRMjl0Y0dGdWVTQk1kR1F3WERBTkJna3Foa2lHOXcwQkFRRUZBQU5MQURCSUFrRUF3U04vZ2kzM1JtcEFRb0xRajdQNnpBbk5UMFJuN2JqQzMyM24zcTFPbmZ2bnZSMGZRanZOdDdyRGtBNUF2NVFuTTZWNFlVTlVuTWZiT1FxMFdMYkxTd0lEQVFBQm80R3VNSUdyTUIwR0ExVWREZ1FXQkJRZklIMW9mQlZwc1JBY0lNSzJoYmw3bmdNVFlEQjhCZ05WSFNNRWRUQnpnQlFmSUgxb2ZCVnBzUkFjSU1LMmhibDduZ01UWUtGUXBFNHdUREVMTUFrR0ExVUVCaE1DUjBJeEVqQVFCZ05WQkFnVENVSmxjbXR6YUdseVpURVFNQTRHQTFVRUJ4TUhUbVYzWW5WeWVURVhNQlVHQTFVRUNoTU9UWGtnUTI5dGNHRnVlU0JNZEdTQ0NRQ0ZUY09TaHVMb3RUQU1CZ05WSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkJRVUFBMEVBcW9nVjN1UGNsS1hEbURaTVQ3dmxQWXhMQXFDR0hadGxDeHo0aE00S1N0bEQyL0dOYzFYaUxiMWgvSzBDSkxEb3NyRUlibTN3aU8yTXZURVJyVllTTVE9PTwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1sOlN1YmplY3Q+PHNhbWw6TmFtZUlEIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIiBTUE5hbWVRdWFsaWZpZXI9Imh0dHBzOi8vY29kZXJwYWQuaW8iPm1hdHQuanVyaWtAaHVsdS5jb208L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBJblJlc3BvbnNlVG89Il80ZmZmYWE2MC02OTZiLTAxMzMtMzg4Ni0wMjQxZjY1YzA2OTMiIE5vdE9uT3JBZnRlcj0iMjAxNS0xMS0xMFQwMDoxMDo0M1oiIFJlY2lwaWVudD0iaHR0cHM6Ly9jb2RlcnBhZC5pby9zYW1sL2FjcyI+PC9zYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE1LTExLTA5VDIyOjU1OjQzWiIgTm90T25PckFmdGVyPSIyMDE1LTExLTEwVDAwOjEwOjQzWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL2NvZGVycGFkLmlvPC9zYW1sOkF1ZGllbmNlPjwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNS0xMS0wOVQyMzo1NTo0M1oiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpBdXRoblN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlIE5hbWU9IkdpdmVuLW5hbWUiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPk1hdHQ8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iU3VybmFtZSI+PHNhbWw6QXR0cmlidXRlVmFsdWU+SnVyaWs8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iRW1haWwiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlPm1hdHQuanVyaWtAaHVsdS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4=' + response_x = OneLogin::RubySaml::Response.new(xml) + response_x.settings = settings + assert response_x.send(:validate_signature) + assert_empty response_x.errors + end + + it "return false when no fingerprint" do + settings.idp_cert_fingerprint = nil + settings.idp_cert = nil response.settings = settings - assert response.validate! + assert !response.send(:validate_signature) + assert_includes response.errors, "Invalid Signature on SAML Response" end - it "validate SAML 2.0 XML structure" do - resp_xml = Base64.decode64(response_document_4).gsub(/emailAddress/,'test') - response = OneLogin::RubySaml::Response.new(Base64.encode64(resp_xml)) - response.stubs(:conditions).returns(nil) - settings = OneLogin::RubySaml::Settings.new + it "return false when the signature is invalid" do settings.idp_cert_fingerprint = signature_fingerprint_1 response.settings = settings - assert_raises(OneLogin::RubySaml::ValidationError, 'Digest mismatch'){ response.validate! } + assert !response.send(:validate_signature) + assert_includes response.errors, "Fingerprint mismatch" + assert_includes response.errors, "Invalid Signature on SAML Response" + end + + it "return false when no X509Certificate and not cert provided at settings" do + settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + settings.idp_cert = nil + response_valid_signed_without_x509certificate.settings = settings + assert !response_valid_signed_without_x509certificate.send(:validate_signature) + assert_includes response_valid_signed_without_x509certificate.errors, "Invalid Signature on SAML Response" + end + + it "return false when cert expired and check_idp_cert_expiration enabled" do + settings.idp_cert_fingerprint = nil + settings.idp_cert = ruby_saml_cert_text + settings.security[:check_idp_cert_expiration] = true + response_valid_signed.settings = settings + assert !response_valid_signed.send(:validate_signature) + assert_includes response_valid_signed.errors, "IdP x509 certificate expired" + end + + it "return false when X509Certificate and the cert provided at settings mismatches" do + settings.idp_cert_fingerprint = nil + settings.idp_cert = signature_1 + response_valid_signed_without_x509certificate.settings = settings + assert !response_valid_signed_without_x509certificate.send(:validate_signature) + assert_includes response_valid_signed_without_x509certificate.errors, "Key validation error" + assert_includes response_valid_signed_without_x509certificate.errors, "Invalid Signature on SAML Response" + end + + it "return false when X509Certificate has invalid content" do + settings.idp_cert_fingerprint = nil + settings.idp_cert = ruby_saml_cert_text + content = read_response('response_with_signed_message_and_assertion.xml') + content = content.sub(/.*<\/ds:X509Certificate>/, + "an-invalid-certificate") + response_invalid_x509certificate = OneLogin::RubySaml::Response.new(content) + response_invalid_x509certificate.settings = settings + assert !response_invalid_x509certificate.send(:validate_signature) + assert_includes response_invalid_x509certificate.errors, "Document Certificate Error" + assert_includes response_invalid_x509certificate.errors, "Invalid Signature on SAML Response" + end + + it "return true when X509Certificate and the cert provided at settings matches" do + settings.idp_cert_fingerprint = nil + settings.idp_cert = ruby_saml_cert_text + response_valid_signed_without_x509certificate.settings = settings + assert response_valid_signed_without_x509certificate.send(:validate_signature) + assert_empty response_valid_signed_without_x509certificate.errors + end + + it "return false when signature wrapping attack" do + signature_wrapping_attack = read_invalid_response("signature_wrapping_attack.xml.base64") + response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack) + response_wrapped.stubs(:conditions).returns(nil) + response_wrapped.stubs(:validate_subject_confirmation).returns(true) + settings.idp_cert_fingerprint = "afe71c28ef740bc87425be13a2263d37971da1f9" + response_wrapped.settings = settings + assert !response_wrapped.send(:validate_signature) + assert_includes response_wrapped.errors, "Invalid Signature on SAML Response" + assert_includes response_wrapped.errors, "Signed element id #pfxc3d2b542-0f7e-8767-8e87-5b0dc6913375 is not found" end end - describe "#name_id" do - it "extract the value of the name id element" do - response = OneLogin::RubySaml::Response.new(response_document) - assert_equal "support@onelogin.com", response.name_id + describe "#validate_signature with multiple idp certs" do + it "return true when at least a cert on idp_cert_multi is valid" do + settings.idp_cert_multi = { + :signing => [ruby_saml_cert_text2, ruby_saml_cert_text], + :encryption => [] + } + response_valid_signed.settings = settings + response_valid_signed.send(:validate_signature) + assert_empty response_valid_signed.errors + end + + it "return true when at least a cert on idp_cert_multi is valid and keys are strings" do + settings.idp_cert_multi = { + "signing" => [ruby_saml_cert_text2, ruby_saml_cert_text], + "encryption" => [] + } + response_valid_signed.settings = settings + assert response_valid_signed.send(:validate_signature) + assert_empty response_valid_signed.errors + end + + it "return false when none cert on idp_cert_multi is valid" do + settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + settings.idp_cert_multi = { + :signing => [ruby_saml_cert_text2, ruby_saml_cert_text2], + :encryption => [] + } + response_valid_signed.settings = settings + assert !response_valid_signed.send(:validate_signature) + assert_includes response_valid_signed.errors, "Certificate of the Signature element does not match provided certificate" + assert_includes response_valid_signed.errors, "Invalid Signature on SAML Response" + end + end + + describe "#validate nameid" do + it "return false when no nameid element and required by settings" do + settings.security[:want_name_id] = true + response_no_nameid.settings = settings + assert !response_no_nameid.send(:validate_name_id) + assert_includes response_no_nameid.errors, "No NameID element found in the assertion of the Response" + end + + it "return false when no nameid element and required by settings" do + response_empty_nameid.settings = settings + assert !response_empty_nameid.send(:validate_name_id) + assert_includes response_empty_nameid.errors, "An empty NameID value found" + end + + it "return false when no nameid value" do + response_empty_nameid.settings = settings + assert !response_empty_nameid.send(:validate_name_id) + assert_includes response_empty_nameid.errors, "An empty NameID value found" + end + + it "return false when wrong_spnamequalifier" do + settings.sp_entity_id = 'sp_entity_id' + response_wrong_spnamequalifier.settings = settings + assert !response_wrong_spnamequalifier.send(:validate_name_id) + assert_includes response_wrong_spnamequalifier.errors, "SPNameQualifier value does not match the SP entityID value." + end + + it "return true when no nameid element but not required by settings" do + settings.security[:want_name_id] = false + response_no_nameid.settings = settings + assert response_no_nameid.send(:validate_name_id) + end - response = OneLogin::RubySaml::Response.new(response_document_3) - assert_equal "someone@example.com", response.name_id + it "return true when nameid is valid and response_wrong_spnamequalifier matches the SP issuer" do + settings.sp_entity_id = 'wrong-sp-entityid' + response_wrong_spnamequalifier.settings = settings + assert response_wrong_spnamequalifier.send(:validate_name_id) + end + end + + describe "#nameid" do + it "extract the value of the name id element" do + assert_equal "support@onelogin.com", response.nameid + assert_equal "someone@example.com", response_with_signed_assertion.nameid end it "be extractable from an OpenSAML response" do - response = OneLogin::RubySaml::Response.new(fixture(:open_saml)) - assert_equal "someone@example.org", response.name_id + response_open_saml = OneLogin::RubySaml::Response.new(fixture(:open_saml)) + assert_equal "someone@example.org", response_open_saml.nameid end it "be extractable from a Simple SAML PHP response" do + response_ssp = OneLogin::RubySaml::Response.new(fixture(:simple_saml_php)) + assert_equal "someone@example.com", response_ssp.nameid + end + end + + describe "#name_id_format" do + it "extract the value of the name id element" do + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", response.name_id_format + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", response_with_signed_assertion.name_id_format + end + end + + describe "#sessionindex" do + it "extract the value of the sessionindex element" do response = OneLogin::RubySaml::Response.new(fixture(:simple_saml_php)) - assert_equal "someone@example.com", response.name_id + assert_equal "_51be37965feb5579d803141076936dc2e9d1d98ebf", response.sessionindex + end + end + + describe "#check_one_conditions" do + it "return false when none or more than one conditions element" do + response_no_conditions.soft = true + assert !response_no_conditions.send(:validate_one_conditions) + assert_includes response_no_conditions.errors, "The Assertion must include one Conditions element" + end + + it "return true when one conditions element" do + response.soft = true + assert response.send(:validate_one_conditions) + end + + it "return true when no conditions are present and skip_conditions is true" do + response_no_conditions_with_skip.soft = true + assert response_no_conditions_with_skip.send(:validate_one_conditions) + end + end + + describe "#check_one_authnstatement" do + it "return false when none or more than one authnstatement element" do + response_no_authnstatement.soft = true + assert !response_no_authnstatement.send(:validate_one_authnstatement) + assert_includes response_no_authnstatement.errors, "The Assertion must include one AuthnStatement element" + end + + it "return true when one authnstatement element" do + response.soft = true + assert response.send(:validate_one_authnstatement) + end + + it "return true when SAML Response is empty but skip_authstatement option is used" do + response_no_authnstatement_with_skip.soft = true + assert response_no_authnstatement_with_skip.send(:validate_one_authnstatement) + assert_empty response_empty_destination_with_skip.errors end end describe "#check_conditions" do it "check time conditions" do - response = OneLogin::RubySaml::Response.new(response_document) - assert !response.send(:validate_conditions, true) - response = OneLogin::RubySaml::Response.new(response_document_6) - assert response.send(:validate_conditions, true) - time = Time.parse("2011-06-14T18:25:01.516Z") - Time.stubs(:now).returns(time) - response = OneLogin::RubySaml::Response.new(response_document_5) - assert response.send(:validate_conditions, true) + response.soft = true + assert !response.send(:validate_conditions) + response_time_updated = OneLogin::RubySaml::Response.new(response_document_without_recipient_with_time_updated) + response_time_updated.soft = true + assert response_time_updated.send(:validate_conditions) + Timecop.freeze(Time.parse("2011-06-14T18:25:01.516Z")) do + response_with_saml2_namespace = OneLogin::RubySaml::Response.new(response_document_with_saml2_namespace) + response_with_saml2_namespace.soft = true + assert response_with_saml2_namespace.send(:validate_conditions) + end end - it "optionally allows for clock drift" do + it "optionally allows for clock drift on NotBefore" do + settings.soft = true + # The NotBefore condition in the document is 2011-06-14T18:21:01.516Z Timecop.freeze(Time.parse("2011-06-14T18:21:01Z")) do - response = OneLogin::RubySaml::Response.new( - response_document_5, - :allowed_clock_drift => 0.515 + special_response_with_saml2_namespace = OneLogin::RubySaml::Response.new( + response_document_with_saml2_namespace, + :allowed_clock_drift => 0.515, + :settings => settings ) - assert !response.send(:validate_conditions, true) - end + assert !special_response_with_saml2_namespace.send(:validate_conditions) - Timecop.freeze(Time.parse("2011-06-14T18:21:01Z")) do - response = OneLogin::RubySaml::Response.new( - response_document_5, + special_response_with_saml2_namespace = OneLogin::RubySaml::Response.new( + response_document_with_saml2_namespace, :allowed_clock_drift => 0.516 ) - assert response.send(:validate_conditions, true) + assert special_response_with_saml2_namespace.send(:validate_conditions) + + special_response_with_saml2_namespace = OneLogin::RubySaml::Response.new( + response_document_with_saml2_namespace, + :allowed_clock_drift => '0.515', + :settings => settings + ) + assert !special_response_with_saml2_namespace.send(:validate_conditions) + + special_response_with_saml2_namespace = OneLogin::RubySaml::Response.new( + response_document_with_saml2_namespace, + :allowed_clock_drift => '0.516' + ) + assert special_response_with_saml2_namespace.send(:validate_conditions) + end + end + + it "optionally allows for clock drift on NotOnOrAfter" do + # Java Floats behave differently than MRI + java = jruby? || truffleruby? + + settings.soft = true + + # The NotBefore condition in the document is 2011-06-1418:31:01.516Z + Timecop.freeze(Time.parse("2011-06-14T18:31:02Z")) do + special_response_with_saml2_namespace = OneLogin::RubySaml::Response.new( + response_document_with_saml2_namespace, + :allowed_clock_drift => 0.483, + :settings => settings + ) + assert !special_response_with_saml2_namespace.send(:validate_conditions) + + special_response_with_saml2_namespace = OneLogin::RubySaml::Response.new( + response_document_with_saml2_namespace, + :allowed_clock_drift => java ? 0.485 : 0.484 + ) + assert special_response_with_saml2_namespace.send(:validate_conditions) + + special_response_with_saml2_namespace = OneLogin::RubySaml::Response.new( + response_document_with_saml2_namespace, + :allowed_clock_drift => '0.483', + :settings => settings + ) + assert !special_response_with_saml2_namespace.send(:validate_conditions) + + special_response_with_saml2_namespace = OneLogin::RubySaml::Response.new( + response_document_with_saml2_namespace, + :allowed_clock_drift => java ? '0.485' : '0.484' + ) + assert special_response_with_saml2_namespace.send(:validate_conditions) end end end describe "#attributes" do it "extract the first attribute in a hash accessed via its symbol" do - response = OneLogin::RubySaml::Response.new(response_document) assert_equal "demo", response.attributes[:uid] end it "extract the first attribute in a hash accessed via its name" do - response = OneLogin::RubySaml::Response.new(response_document) assert_equal "demo", response.attributes["uid"] end it "extract all attributes" do - response = OneLogin::RubySaml::Response.new(response_document) assert_equal "demo", response.attributes[:uid] assert_equal "value", response.attributes[:another_value] end it "work for implicit namespaces" do - response = OneLogin::RubySaml::Response.new(response_document_3) - assert_equal "someone@example.com", response.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"] + assert_equal "someone@example.com", response_with_signed_assertion.attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"] end - it "not raise errors about nil/empty attributes for EncryptedAttributes" do - response = OneLogin::RubySaml::Response.new(response_document_7) - assert_equal 'Demo', response.attributes["first_name"] + it "extract attributes from all AttributeStatement tags" do + assert_equal "smith", response_with_multiple_attribute_statements.attributes[:surname] + assert_equal "bob", response_with_multiple_attribute_statements.attributes[:firstname] end it "not raise on responses without attributes" do - response = OneLogin::RubySaml::Response.new(response_document_4) - assert_equal OneLogin::RubySaml::Attributes.new, response.attributes + assert_equal OneLogin::RubySaml::Attributes.new, response_unsigned.attributes + end + + describe "#encrypted attributes" do + it "raise error when the assertion contains encrypted attributes but no private key to decrypt" do + settings.private_key = nil + response_encrypted_attrs.settings = settings + assert_raises(OneLogin::RubySaml::ValidationError, "An EncryptedAttribute found and no SP private key found on the settings to decrypt it") do + response_encrypted_attrs.attributes + end + end + + it "extract attributes when the assertion contains encrypted attributes and the private key is provided" do + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + response_encrypted_attrs.settings = settings + attributes = response_encrypted_attrs.attributes + assert_equal "test", attributes[:uid] + assert_equal "test@example.com", attributes[:mail] + end + end + + it "return false when validating a response with duplicate attributes" do + response_duplicated_attributes.settings = settings + response_duplicated_attributes.options[:check_duplicated_attributes] = true + assert !response_duplicated_attributes.send(:validate_no_duplicated_attributes) + assert_includes response_duplicated_attributes.errors, "Found an Attribute element with duplicated Name" + end + + it "return true when validating a response with duplicate attributes but skip check" do + response_duplicated_attributes.settings = settings + assert response_duplicated_attributes.send(:validate_no_duplicated_attributes) end describe "#multiple values" do it "extract single value as string" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) - assert_equal "demo", response.attributes[:uid] + assert_equal "demo", response_multiple_attr_values.attributes[:uid] end it "extract single value as string in compatibility mode off" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) OneLogin::RubySaml::Attributes.single_value_compatibility = false - assert_equal ["demo"], response.attributes[:uid] + assert_equal ["demo"], response_multiple_attr_values.attributes[:uid] # classes are not reloaded between tests so restore default OneLogin::RubySaml::Attributes.single_value_compatibility = true end it "extract first of multiple values as string for b/w compatibility" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) - assert_equal 'value1', response.attributes[:another_value] + assert_equal 'value1', response_multiple_attr_values.attributes[:another_value] end it "extract first of multiple values as string for b/w compatibility in compatibility mode off" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) OneLogin::RubySaml::Attributes.single_value_compatibility = false - assert_equal ['value1', 'value2'], response.attributes[:another_value] + assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes[:another_value] OneLogin::RubySaml::Attributes.single_value_compatibility = true end it "return array with all attributes when asked in XML order" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) - assert_equal ['value1', 'value2'], response.attributes.multi(:another_value) + assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value) end it "return array with all attributes when asked in XML order in compatibility mode off" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) OneLogin::RubySaml::Attributes.single_value_compatibility = false - assert_equal ['value1', 'value2'], response.attributes.multi(:another_value) + assert_equal ['value1', 'value2'], response_multiple_attr_values.attributes.multi(:another_value) OneLogin::RubySaml::Attributes.single_value_compatibility = true end it "return first of multiple values when multiple Attribute tags in XML" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) - assert_equal 'role1', response.attributes[:role] + assert_equal 'role1', response_multiple_attr_values.attributes[:role] end it "return first of multiple values when multiple Attribute tags in XML in compatibility mode off" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) OneLogin::RubySaml::Attributes.single_value_compatibility = false - assert_equal ['role1', 'role2', 'role3'], response.attributes[:role] + assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes[:role] OneLogin::RubySaml::Attributes.single_value_compatibility = true end it "return all of multiple values in reverse order when multiple Attribute tags in XML" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) - assert_equal ['role1', 'role2', 'role3'], response.attributes.multi(:role) + assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role) end it "return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) OneLogin::RubySaml::Attributes.single_value_compatibility = false - assert_equal ['role1', 'role2', 'role3'], response.attributes.multi(:role) + assert_equal ['role1', 'role2', 'role3'], response_multiple_attr_values.attributes.multi(:role) + OneLogin::RubySaml::Attributes.single_value_compatibility = true + end + + it "return all of multiple values when multiple Attribute tags in multiple AttributeStatement tags" do + OneLogin::RubySaml::Attributes.single_value_compatibility = false + assert_equal ['role1', 'role2', 'role3'], response_with_multiple_attribute_statements.attributes.multi(:role) OneLogin::RubySaml::Attributes.single_value_compatibility = true end it "return nil value correctly" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) - assert_nil response.attributes[:attribute_with_nil_value] + assert_nil response_multiple_attr_values.attributes[:attribute_with_nil_value] end it "return nil value correctly when not in compatibility mode off" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) OneLogin::RubySaml::Attributes.single_value_compatibility = false - assert_equal [nil], response.attributes[:attribute_with_nil_value] + assert_equal [nil], response_multiple_attr_values.attributes[:attribute_with_nil_value] OneLogin::RubySaml::Attributes.single_value_compatibility = true end @@ -327,22 +1331,20 @@ class RubySamlTest < Minitest::Test end it "return multiple values from [] when not in compatibility mode off" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) OneLogin::RubySaml::Attributes.single_value_compatibility = false - assert_equal ["", "valuePresent", nil, nil], response.attributes[:attribute_with_nils_and_empty_strings] + assert_equal ["", "valuePresent", nil, nil], response_multiple_attr_values.attributes[:attribute_with_nils_and_empty_strings] OneLogin::RubySaml::Attributes.single_value_compatibility = true end it "check what happens when trying retrieve attribute that does not exists" do - response = OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) - assert_equal nil, response.attributes[:attribute_not_exists] - assert_equal nil, response.attributes.single(:attribute_not_exists) - assert_equal nil, response.attributes.multi(:attribute_not_exists) + assert_nil response_multiple_attr_values.attributes[:attribute_not_exists] + assert_nil response_multiple_attr_values.attributes.single(:attribute_not_exists) + assert_nil response_multiple_attr_values.attributes.multi(:attribute_not_exists) OneLogin::RubySaml::Attributes.single_value_compatibility = false - assert_equal nil, response.attributes[:attribute_not_exists] - assert_equal nil, response.attributes.single(:attribute_not_exists) - assert_equal nil, response.attributes.multi(:attribute_not_exists) + assert_nil response_multiple_attr_values.attributes[:attribute_not_exists] + assert_nil response_multiple_attr_values.attributes.single(:attribute_not_exists) + assert_nil response_multiple_attr_values.attributes.multi(:attribute_not_exists) OneLogin::RubySaml::Attributes.single_value_compatibility = true end @@ -351,62 +1353,445 @@ class RubySamlTest < Minitest::Test describe "#session_expires_at" do it "extract the value of the SessionNotOnOrAfter attribute" do - response = OneLogin::RubySaml::Response.new(response_document) assert response.session_expires_at.is_a?(Time) + end - response = OneLogin::RubySaml::Response.new(response_document_2) - assert_nil response.session_expires_at + it "return nil when the value of the SessionNotOnOrAfter is not set" do + assert_nil response_without_attributes.session_expires_at end end - describe "#issuer" do - it "return the issuer inside the response assertion" do - response = OneLogin::RubySaml::Response.new(response_document) - assert_equal "https://app.onelogin.com/saml/metadata/13590", response.issuer + describe "#authn_instant" do + it "extract the value of the AuthnInstant attribute" do + assert_equal "2010-11-18T21:57:37Z", response.authn_instant end + end - it "return the issuer inside the response" do - response = OneLogin::RubySaml::Response.new(response_document_2) - assert_equal "wibble", response.issuer + describe "#authn_context_class_ref" do + it "extract the value of the AuthnContextClassRef attribute" do + assert_equal "urn:oasis:names:tc:SAML:2.0:ac:classes:Password", response.authn_context_class_ref end end describe "#success" do it "find a status code that says success" do - response = OneLogin::RubySaml::Response.new(response_document) response.success? end end describe '#xpath_first_from_signed_assertion' do it 'not allow arbitrary code execution' do + $evalled = nil malicious_response_document = fixture('response_eval', false) - response = OneLogin::RubySaml::Response.new(malicious_response_document) - response.send(:xpath_first_from_signed_assertion) - assert_equal($evalled, nil) + malicious_response = OneLogin::RubySaml::Response.new(malicious_response_document) + malicious_response.send(:xpath_first_from_signed_assertion) + assert_nil $evalled end end describe '#sign_document' do - it 'Sign an unsigned SAML Response XML and initiate the SAML object with it' do + it 'sign an unsigned SAML Response XML and initiate the SAML object with it' do xml = Base64.decode64(fixture("test_sign.xml")) document = XMLSecurity::Document.new(xml) - formated_cert = OneLogin::RubySaml::Utils.format_cert(ruby_saml_cert_text) - cert = OpenSSL::X509::Certificate.new(formated_cert) + formatted_cert = OneLogin::RubySaml::Utils.format_cert(ruby_saml_cert_text) + cert = OpenSSL::X509::Certificate.new(formatted_cert) - formated_private_key = OneLogin::RubySaml::Utils.format_private_key(ruby_saml_key_text) - private_key = OpenSSL::PKey::RSA.new(formated_private_key) + formatted_private_key = OneLogin::RubySaml::Utils.format_private_key(ruby_saml_key_text) + private_key = OpenSSL::PKey::RSA.new(formatted_private_key) document.sign_document(private_key, cert) - saml_response = OneLogin::RubySaml::Response.new(document.to_s) - settings = OneLogin::RubySaml::Settings.new + signed_response = OneLogin::RubySaml::Response.new(document.to_s) + settings.assertion_consumer_service_url = "http://recipient" settings.idp_cert = ruby_saml_cert_text - saml_response.settings = settings - time = Time.parse("2015-03-18T04:50:24Z") - Time.stubs(:now).returns(time) - saml_response.validate! + signed_response.settings = settings + Timecop.freeze(Time.parse("2015-03-18T04:50:24Z")) do + assert signed_response.is_valid? + end + assert_empty signed_response.errors + end + end + + describe '#want_assertion_signed' do + before do + settings.security[:want_assertions_signed] = true + @signed_assertion = OneLogin::RubySaml::Response.new(response_document_with_signed_assertion, :settings => settings) + @no_signed_assertion = OneLogin::RubySaml::Response.new(response_document_valid_signed, :settings => settings) + end + + it 'returns false if :want_assertion_signed enabled and Assertion not signed' do + assert !@no_signed_assertion.send(:validate_signed_elements) + assert_includes @no_signed_assertion.errors, "The Assertion of the Response is not signed and the SP requires it" + end + + it 'returns true if :want_assertion_signed enabled and Assertion is signed' do + assert @signed_assertion.send(:validate_signed_elements) + assert_empty @signed_assertion.errors + end + end + + describe "retrieve nameID" do + it 'is possible when nameID inside the assertion' do + response_valid_signed.settings = settings + assert_equal "test@onelogin.com", response_valid_signed.nameid + end + + it 'is not possible when encryptID inside the assertion but no private key' do + response_encrypted_nameid.settings = settings + assert_raises(OneLogin::RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do + assert_equal "test@onelogin.com", response_encrypted_nameid.nameid + end + + assert_raises(OneLogin::RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", response_encrypted_nameid.name_id_format + end + end + + it 'is possible when encryptID inside the assertion and settings has the private key' do + settings.private_key = ruby_saml_key_text + response_encrypted_nameid.settings = settings + assert_equal "test@onelogin.com", response_encrypted_nameid.nameid + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", response_encrypted_nameid.name_id_format + end + + end + + describe 'try to initialize an encrypted response' do + it 'raise if an encrypted assertion is found and no sp private key to decrypt it' do + error_msg = "An EncryptedAssertion found and no SP private key found on the settings to decrypt it. Be sure you provided the :settings parameter at the initialize method" + + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion) + end + + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings) + end + + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response3 = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion) + response3.settings + end + end + + it 'raise if an encrypted assertion is found and the sp private key is wrong' do + settings.certificate = ruby_saml_cert_text + wrong_private_key = ruby_saml_key_text.sub!('A', 'B') + settings.private_key = wrong_private_key + + error_msg = "Neither PUB key nor PRIV key: nested asn1 error" + assert_raises(OpenSSL::PKey::RSAError, error_msg) do + OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings) + end + end + + it 'return true if an encrypted assertion is found and settings initialized with private_key' do + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings) + assert response.decrypted_document + + response2 = OneLogin::RubySaml::Response.new(signed_message_encrypted_signed_assertion, :settings => settings) + assert response2.decrypted_document + + response3 = OneLogin::RubySaml::Response.new(unsigned_message_encrypted_signed_assertion, :settings => settings) + assert response3.decrypted_document + + response4 = OneLogin::RubySaml::Response.new(unsigned_message_encrypted_unsigned_assertion, :settings => settings) + assert response4.decrypted_document + + assert OneLogin::RubySaml::Response.new( + Base64.encode64(File.read('test/responses/unsigned_encrypted_adfs.xml')), + :settings => settings + ).decrypted_document + end + end + + describe "retrieve nameID and attributes from encrypted assertion" do + + before do + settings.idp_cert_fingerprint = 'EE:17:4E:FB:A8:81:71:12:0D:2A:78:43:BC:E7:0C:07:58:79:F4:F4' + settings.sp_entity_id = 'http://rubysaml.com:3000/saml/metadata' + settings.assertion_consumer_service_url = 'http://rubysaml.com:3000/saml/acs' + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + end + + it 'is possible when signed_message_encrypted_unsigned_assertion' do + response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings) + Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do + assert response.is_valid? + assert_empty response.errors + assert_equal "test", response.attributes[:uid] + assert_equal "98e2bb61075e951b37d6b3be6954a54b340d86c7", response.nameid + end + end + + it 'is possible when signed_message_encrypted_signed_assertion' do + response = OneLogin::RubySaml::Response.new(signed_message_encrypted_signed_assertion, :settings => settings) + Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do + assert response.is_valid? + assert_empty response.errors + assert_equal "test", response.attributes[:uid] + assert_equal "98e2bb61075e951b37d6b3be6954a54b340d86c7", response.nameid + end + end + + it 'is possible when unsigned_message_encrypted_signed_assertion' do + response = OneLogin::RubySaml::Response.new(unsigned_message_encrypted_signed_assertion, :settings => settings) + Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do + assert response.is_valid? + assert_empty response.errors + assert_equal "test", response.attributes[:uid] + assert_equal "98e2bb61075e951b37d6b3be6954a54b340d86c7", response.nameid + end + end + + it 'is not possible when unsigned_message_encrypted_unsigned_assertion' do + response = OneLogin::RubySaml::Response.new(unsigned_message_encrypted_unsigned_assertion, :settings => settings) + Timecop.freeze(Time.parse("2015-03-19T14:30:31Z")) do + assert !response.is_valid? + assert_includes response.errors, "Found an unexpected number of Signature Element. SAML Response rejected" + end + end + end + + describe "#decrypt_assertion" do + before do + settings.private_key = ruby_saml_key_text + end + + describe "check right settings" do + + it "is not possible to decrypt the assertion if no private key" do + response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings) + + encrypted_assertion_node = REXML::XPath.first( + response.document, + "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", + { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + ) + response.settings.private_key = nil + + error_msg = "An EncryptedAssertion found and no SP private key found on the settings to decrypt it" + assert_raises(OneLogin::RubySaml::ValidationError, error_msg) do + response.send(:decrypt_assertion, encrypted_assertion_node) + end + end + + it "is not possible to decrypt the assertion if private key has expired and :check_sp_expiration is true" do + settings.certificate = ruby_saml_cert_text + settings.security[:check_sp_cert_expiration] = true + assert_raises(OneLogin::RubySaml::ValidationError, "The SP certificate expired.") do + OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings) + end + end + + it "is possible to decrypt the assertion if private key" do + response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings) + + encrypted_assertion_node = REXML::XPath.first( + response.document, + "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", + { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + ) + decrypted = response.send(:decrypt_assertion, encrypted_assertion_node) + + encrypted_assertion_node2 = REXML::XPath.first( + decrypted, + "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", + { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + ) + assert_nil encrypted_assertion_node2 + assert decrypted.name, "Assertion" + end + + it "is possible to decrypt the assertion with one invalid and one valid private key" do + settings.private_key = nil + settings.sp_cert_multi = { + encryption: [ + CertificateHelper.generate_pair_hash, + { certificate: ruby_saml_cert_text, private_key: ruby_saml_key_text } + ] + } + response = OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings) + + encrypted_assertion_node = REXML::XPath.first( + response.document, + "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", + { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + ) + decrypted = response.send(:decrypt_assertion, encrypted_assertion_node) + + assert decrypted.name, "Assertion" + end + + it "is possible to decrypt the assertion if private key provided and EncryptedKey RetrievalMethod presents in response" do + settings.private_key = ruby_saml_key_text + resp = read_response('response_with_retrieval_method.xml') + response = OneLogin::RubySaml::Response.new(resp, :settings => settings) + + encrypted_assertion_node = REXML::XPath.first( + response.document, + "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", + { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + ) + decrypted = response.send(:decrypt_assertion, encrypted_assertion_node) + + encrypted_assertion_node2 = REXML::XPath.first( + decrypted, + "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", + { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + ) + + assert_nil encrypted_assertion_node2 + assert decrypted.name, "Assertion" + end + + it "is possible to decrypt the assertion if private key but no saml namespace on the Assertion Element that is inside the EncryptedAssertion" do + unsigned_message_encrypted_assertion_without_saml_namespace = read_response('unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64') + response = OneLogin::RubySaml::Response.new(unsigned_message_encrypted_assertion_without_saml_namespace, :settings => settings) + encrypted_assertion_node = REXML::XPath.first( + response.document, + "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", + { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + ) + decrypted = response.send(:decrypt_assertion, encrypted_assertion_node) + + encrypted_assertion_node2 = REXML::XPath.first( + decrypted, + "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", + { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + ) + assert_nil encrypted_assertion_node2 + assert decrypted.name, "Assertion" + end + end + + describe "check different encrypt methods supported" do + it "EncryptionMethod DES-192 && Key Encryption Algorithm RSA-1_5" do + unsigned_message_des192_encrypted_signed_assertion = read_response('unsigned_message_des192_encrypted_signed_assertion.xml.base64') + response = OneLogin::RubySaml::Response.new(unsigned_message_des192_encrypted_signed_assertion, :settings => settings) + assert_equal "test", response.attributes[:uid] + assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + end + + it "EncryptionMethod AES-128 && Key Encryption Algorithm RSA-OAEP-MGF1P" do + unsigned_message_aes128_encrypted_signed_assertion = read_response('unsigned_message_aes128_encrypted_signed_assertion.xml.base64') + response = OneLogin::RubySaml::Response.new(unsigned_message_aes128_encrypted_signed_assertion, :settings => settings) + assert_equal "test", response.attributes[:uid] + assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + end + + it "EncryptionMethod AES-192 && Key Encryption Algorithm RSA-OAEP-MGF1P" do + unsigned_message_aes192_encrypted_signed_assertion = read_response('unsigned_message_aes192_encrypted_signed_assertion.xml.base64') + response = OneLogin::RubySaml::Response.new(unsigned_message_aes192_encrypted_signed_assertion, :settings => settings) + assert_equal "test", response.attributes[:uid] + assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + end + + it "EncryptionMethod AES-256 && Key Encryption Algorithm RSA-OAEP-MGF1P" do + unsigned_message_aes256_encrypted_signed_assertion = read_response('unsigned_message_aes256_encrypted_signed_assertion.xml.base64') + response = OneLogin::RubySaml::Response.new(unsigned_message_aes256_encrypted_signed_assertion, :settings => settings) + assert_equal "test", response.attributes[:uid] + assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + end + + it "EncryptionMethod AES-128-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P" do + return unless OpenSSL::Cipher.ciphers.include? 'AES-128-GCM' + unsigned_message_aes128gcm_encrypted_signed_assertion = read_response('unsigned_message_aes128gcm_encrypted_signed_assertion.xml.base64') + response = OneLogin::RubySaml::Response.new(unsigned_message_aes128gcm_encrypted_signed_assertion, :settings => settings) + assert_equal "test", response.attributes[:uid] + assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + end + + it "EncryptionMethod AES-192-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P" do + return unless OpenSSL::Cipher.ciphers.include? 'AES-192-GCM' + unsigned_message_aes192gcm_encrypted_signed_assertion = read_response('unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64') + response = OneLogin::RubySaml::Response.new(unsigned_message_aes192gcm_encrypted_signed_assertion, :settings => settings) + assert_equal "test", response.attributes[:uid] + assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + end + + it "EncryptionMethod AES-256-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P" do + return unless OpenSSL::Cipher.ciphers.include? 'AES-256-GCM' + unsigned_message_aes256gcm_encrypted_signed_assertion = read_response('unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64') + response = OneLogin::RubySaml::Response.new(unsigned_message_aes256gcm_encrypted_signed_assertion, :settings => settings) + assert_equal "test", response.attributes[:uid] + assert_equal "_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7", response.nameid + end + end + + end + + describe "#status_code" do + it 'urn:oasis:names:tc:SAML:2.0:status:Responder' do + assert_equal response_statuscode_responder.status_code, 'urn:oasis:names:tc:SAML:2.0:status:Responder' + end + + it 'urn:oasis:names:tc:SAML:2.0:status:Requester and urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding' do + assert_equal response_double_statuscode.status_code, 'urn:oasis:names:tc:SAML:2.0:status:Requester | urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding' + end + end + describe "test qualified name id in attributes" do + + it "parsed the nameid" do + response = OneLogin::RubySaml::Response.new(read_response("signed_nameid_in_atts.xml"), :settings => settings) + response.settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783' + assert_empty response.errors + assert_equal "test", response.attributes[:uid] + assert_equal "http://idp.example.com/metadata.php/ZdrjpwEdw22vKoxWAbZB78/gQ7s=", response.attributes.single('urn:oid:1.3.6.1.4.1.5923.1.1.1.10') + end + end + + describe "test unqualified name id in attributes" do + + it "parsed the nameid" do + response = OneLogin::RubySaml::Response.new(read_response("signed_unqual_nameid_in_atts.xml"), :settings => settings) + response.settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783' + assert_empty response.errors + assert_equal "test", response.attributes[:uid] + assert_equal "ZdrjpwEdw22vKoxWAbZB78/gQ7s=", response.attributes.single('urn:oid:1.3.6.1.4.1.5923.1.1.1.10') + end + end + + describe "signature wrapping attack with encrypted assertion" do + it "should not be valid" do + settings.private_key = ruby_saml_key_text + signature_wrapping_attack = read_invalid_response("encrypted_new_attack.xml.base64") + response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings) + response_wrapped.stubs(:conditions).returns(nil) + response_wrapped.stubs(:validate_subject_confirmation).returns(true) + settings.idp_cert_fingerprint = "385b1eec71143f00db6af936e2ea12a28771d72c" + assert !response_wrapped.is_valid? + assert_includes response_wrapped.errors, "Found an invalid Signed Element. SAML Response rejected" + end + end + + describe "signature wrapping attack - concealed SAML response body" do + it "should not be valid" do + signature_wrapping_attack = read_invalid_response("response_with_concealed_signed_assertion.xml") + response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings) + settings.idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d' + response_wrapped.stubs(:conditions).returns(nil) + response_wrapped.stubs(:validate_subject_confirmation).returns(true) + assert !response_wrapped.is_valid? + assert_includes response_wrapped.errors, "SAML Response must contain 1 assertion" + end + end + + describe "signature wrapping attack - doubled signed assertion SAML response" do + it "should not be valid" do + signature_wrapping_attack = read_invalid_response("response_with_doubled_signed_assertion.xml") + response_wrapped = OneLogin::RubySaml::Response.new(signature_wrapping_attack, :settings => settings) + settings.idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d' + response_wrapped.stubs(:conditions).returns(nil) + response_wrapped.stubs(:validate_subject_confirmation).returns(true) + assert !response_wrapped.is_valid? + assert_includes response_wrapped.errors, "SAML Response must contain 1 assertion" end end end diff --git a/test/responses/attackxee.xml b/test/responses/attackxee.xml new file mode 100644 index 000000000..6755d14a4 --- /dev/null +++ b/test/responses/attackxee.xml @@ -0,0 +1,13 @@ + + + + + + + + + + +]> +&lol9; \ No newline at end of file diff --git a/test/responses/idp_descriptor.xml b/test/responses/idp_descriptor.xml deleted file mode 100644 index 402594aa1..000000000 --- a/test/responses/idp_descriptor.xml +++ /dev/null @@ -1,3 +0,0 @@ - -LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= -LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=urn:oasis:names:tc:SAML:1.1:nameid-format:unspecifiedurn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:2.0:nameid-format:persistent diff --git a/test/responses/inclusive_namespaces.xml b/test/responses/inclusive_namespaces.xml new file mode 100644 index 000000000..ecd6be020 --- /dev/null +++ b/test/responses/inclusive_namespaces.xml @@ -0,0 +1,18 @@ + + + http://login.example.com/issuer + + + + http://www.okta.com/k7xkhq0jUHUPQAXVMUAN4G+uveKmtiB1EkY5BAt+8lmQwjI=Q80N6FUr5/YPtEzRlRdMoPu+bL0MssDxNUY+yxykzbmxsI0joEo/SmmSgZrDYQKTllZk/KfzBMPFV9yBH4+mEzCU5E3xuCs99jZzafcw3K8mIMTJy1YHxjc359d27R5s50i9w5PHsusRov0MjQIoJ2w48Gy4EnYaViqBR3UVEqE=MIICnTCCAgagAwIBAgIGAUBGHxqUMA0GCSqGSIb3DQEBBQUAMIGRMQswCQYDVQQGEwJVUzETMBEG +A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU +MBIGA1UECwwLU1NPUHJvdmlkZXIxEjAQBgNVBAMMCWtsdWdsYWJzMjEcMBoGCSqGSIb3DQEJARYN +aW5mb0Bva3RhLmNvbTAeFw0xMzA4MDMyMTM4MzhaFw00MzA4MDMyMTM5MzhaMIGRMQswCQYDVQQG +EwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UE +CgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxEjAQBgNVBAMMCWtsdWdsYWJzMjEcMBoGCSqG +SIb3DQEJARYNaW5mb0Bva3RhLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAsCB9lJTH +qB7vdM5jeOH84cW8u7IHYv4/OAPYF0fBYe9wJy19CgyM2OgiASuAcItnH4WhB+io2ZPwb/Xwl7Uu +4XmUE0l+mkCNuDYp5fXTZxwv5G6HvkAxXZio0Rk9T0VETCroxgpS5LxQ/o/owjR39S7xzRnj6ddX +3Mq2yGjKyBcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQAB1qGNqSNLLWq+RPcP+wOaWtYpJOJ8/MbZ +EWWm9/KKHKXM6J/zgUUIXZi3czMeO+Y+X14PR8lGXoAHf5b/JavG9FmFvRn4fGa45VTVo2GfMN6K +aIKF0obeCbYi/QUf8B+Xi1tSIJm1VCKRE7nnliQ/TzGaNulgWeyTbVkG0/X8LQ==admin@kluglabs.comhttps://auth0145.auth0.comurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportAdmin diff --git a/test/responses/invalids/duplicated_attributes.xml.base64 b/test/responses/invalids/duplicated_attributes.xml.base64 new file mode 100644 index 000000000..9ee4d7a76 --- /dev/null +++ b/test/responses/invalids/duplicated_attributes.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDYwOGY2ZGIxLTJiN2MtNDljMi0yNmQ1LTZjM2YyODlkNGZmNyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdDo4MDgwL2phdmEtc2FtbC1qc3BzYW1wbGUvYWNzLmpzcCIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl81ZmU5ZDZlNDk5YjJmMDkxMzIwNmFhYjNmNzE5MTcyOTA0OWJiODA3Ij48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9zaW1wbGVzYW1sL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZng2MDhmNmRiMS0yYjdjLTQ5YzItMjZkNS02YzNmMjg5ZDRmZjciPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPnZxRk42SkJLVXQzL1ROY3p4dnZSQk1kSVhXQT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+Z1Z5VGtkQlRzR0ZZUEg1Y1R2endyaGRqZVpMK1BMbnkybzFabWtCclVkQlNMZFZCdDF0RXQ0YkNWWGFTOWpHKzBjVVBHb1NyOXAzRDF4ekp6eExNcHhlTThxSC9heHBSZkN0VUF6VFZTMXp6U2dLN2czUHpsN2orZjREV1JlRTF0TzVvNzBud1NxMGllTW1HbEpWelZJYW85WTNJWk1PcTNhK0ljVjE2SnVZPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDZ1RDQ0Flb0NDUUNiT2xyV0RkWDdGVEFOQmdrcWhraUc5dzBCQVFVRkFEQ0JoREVMTUFrR0ExVUVCaE1DVGs4eEdEQVdCZ05WQkFnVEQwRnVaSEpsWVhNZ1UyOXNZbVZ5WnpFTU1Bb0dBMVVFQnhNRFJtOXZNUkF3RGdZRFZRUUtFd2RWVGtsT1JWUlVNUmd3RmdZRFZRUURFdzltWldsa1pTNWxjbXhoYm1jdWJtOHhJVEFmQmdrcWhraUc5dzBCQ1FFV0VtRnVaSEpsWVhOQWRXNXBibVYwZEM1dWJ6QWVGdzB3TnpBMk1UVXhNakF4TXpWYUZ3MHdOekE0TVRReE1qQXhNelZhTUlHRU1Rc3dDUVlEVlFRR0V3Sk9UekVZTUJZR0ExVUVDQk1QUVc1a2NtVmhjeUJUYjJ4aVpYSm5NUXd3Q2dZRFZRUUhFd05HYjI4eEVEQU9CZ05WQkFvVEIxVk9TVTVGVkZReEdEQVdCZ05WQkFNVEQyWmxhV1JsTG1WeWJHRnVaeTV1YnpFaE1COEdDU3FHU0liM0RRRUpBUllTWVc1a2NtVmhjMEIxYm1sdVpYUjBMbTV2TUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FEaXZiaFI3UDUxNngvUzNCcUt4dXBRZTBMT05vbGl1cGlCT2VzQ08zU0hiRHJsMytxOUliZm5mbUUwNHJOdU1jUHNJeEIxNjFUZERwSWVzTENuN2M4YVBISVNLT3RQbEFlVFpTbmI4UUF1N2FSalpxMytQYnJQNXVXM1RjZkNHUHRLVHl0SE9nZS9PbEpibzA3OGRWaFhRMTRkMUVEd1hKVzFyUlh1VXQ0QzhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUNEVmZwODZIT2JxWStlOEJVb1dROStWTVF4MUFTRG9oQmp3T3NnMld5a1VxUlhGK2RMZmNVSDlkV1I2M0N0WklLRkRiU3ROb21QblF6N25iSytvbnlnd0JzcFZFYm5IdVVpaFpxM1pVZG11bVFxQ3c0VXZzLzFVdnEzb3JPby9XSlZoVHl2TGdGVksyUWFyUTQvNjdPWmZIZDdSK1BPQlhob3BoU012MVpPbzwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJwZnhmMzkyZTY1Ni02YjY1LTBkZDYtYWU4Yy1kYzdjOGY1YWYzZWUiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvc2ltcGxlc2FtbC9zYW1sMi9pZHAvbWV0YWRhdGEucGhwPC9zYW1sOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4NCiAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4ZjM5MmU2NTYtNmI2NS0wZGQ2LWFlOGMtZGM3YzhmNWFmM2VlIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPjxkczpEaWdlc3RWYWx1ZT5yV0k1dXVaVGFmZWZsb3pvVk9MUCtZUndRZUk9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPjJ6NUxybGk4cHI0U3dnMnVpN0F4cmRSV1F4ZUhsczk3U1hCSnFFQWlGSjlYRnExY1RXTjQzNi90THNFSFNvTUtNS1NRNE4xdzFpN3pMMnVKOTA1MjRUZ2FuSHgyTHRRSVJGcXZpNWI4OFdyWm1MUW00WW92V2RCem5qTW80eDZackJ1Q0lnd2NkL0xVMDdHU2JLQm44cmZFeVdrcHNsVmlmR2x2SXdkTHI1cz08L2RzOlNpZ25hdHVyZVZhbHVlPg0KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ2dUQ0NBZW9DQ1FDYk9scldEZFg3RlRBTkJna3Foa2lHOXcwQkFRVUZBRENCaERFTE1Ba0dBMVVFQmhNQ1RrOHhHREFXQmdOVkJBZ1REMEZ1WkhKbFlYTWdVMjlzWW1WeVp6RU1NQW9HQTFVRUJ4TURSbTl2TVJBd0RnWURWUVFLRXdkVlRrbE9SVlJVTVJnd0ZnWURWUVFERXc5bVpXbGtaUzVsY214aGJtY3VibTh4SVRBZkJna3Foa2lHOXcwQkNRRVdFbUZ1WkhKbFlYTkFkVzVwYm1WMGRDNXViekFlRncwd056QTJNVFV4TWpBeE16VmFGdzB3TnpBNE1UUXhNakF4TXpWYU1JR0VNUXN3Q1FZRFZRUUdFd0pPVHpFWU1CWUdBMVVFQ0JNUFFXNWtjbVZoY3lCVGIyeGlaWEpuTVF3d0NnWURWUVFIRXdOR2IyOHhFREFPQmdOVkJBb1RCMVZPU1U1RlZGUXhHREFXQmdOVkJBTVREMlpsYVdSbExtVnliR0Z1Wnk1dWJ6RWhNQjhHQ1NxR1NJYjNEUUVKQVJZU1lXNWtjbVZoYzBCMWJtbHVaWFIwTG01dk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRGl2YmhSN1A1MTZ4L1MzQnFLeHVwUWUwTE9Ob2xpdXBpQk9lc0NPM1NIYkRybDMrcTlJYmZuZm1FMDRyTnVNY1BzSXhCMTYxVGREcEllc0xDbjdjOGFQSElTS090UGxBZVRaU25iOFFBdTdhUmpacTMrUGJyUDV1VzNUY2ZDR1B0S1R5dEhPZ2UvT2xKYm8wNzhkVmhYUTE0ZDFFRHdYSlcxclJYdVV0NEM4UUlEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRHQkFDRFZmcDg2SE9icVkrZThCVW9XUTkrVk1ReDFBU0RvaEJqd09zZzJXeWtVcVJYRitkTGZjVUg5ZFdSNjNDdFpJS0ZEYlN0Tm9tUG5RejduYksrb255Z3dCc3BWRWJuSHVVaWhacTNaVWRtdW1RcUN3NFV2cy8xVXZxM29yT28vV0pWaFR5dkxnRlZLMlFhclE0LzY3T1pmSGQ3UitQT0JYaG9waFNNdjFaT288L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9qYXZhLXNhbWwtanNwc2FtcGxlL21ldGFkYXRhLmpzcCIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPjQ5Mjg4MjYxNWFjZjMxYzgwOTZiNjI3MjQ1ZDc2YWU1MzAzNmMwOTA8L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMjMtMDgtMjNUMDY6NTc6MDFaIiBSZWNpcGllbnQ9Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9qYXZhLXNhbWwtanNwc2FtcGxlL2Fjcy5qc3AiIEluUmVzcG9uc2VUbz0iT05FTE9HSU5fNWZlOWQ2ZTQ5OWIyZjA5MTMyMDZhYWIzZjcxOTE3MjkwNDliYjgwNyIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE0LTAyLTE5VDAxOjM2OjMxWiIgTm90T25PckFmdGVyPSIyMDIzLTA4LTIzVDA2OjU3OjAxWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwOi8vbG9jYWxob3N0OjgwODAvamF2YS1zYW1sLWpzcHNhbXBsZS9tZXRhZGF0YS5qc3A8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiIFNlc3Npb25JbmRleD0iXzYyNzNkNzdiOGNkZTBjMzMzZWM3OWQyMmE5ZmEwMDAzYjlmZTJkNzVjYiI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj50ZXN0PC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dGVzdDI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+VGVzdEBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJjbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+Smhvbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJzbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+RG9lPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImVkdVBlcnNvbkFmZmlsaWF0aW9uIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj51c2VyPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPmFkbWluPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/invalids/empty_destination.xml.base64 b/test/responses/invalids/empty_destination.xml.base64 new file mode 100644 index 000000000..352988ced --- /dev/null +++ b/test/responses/invalids/empty_destination.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDc2ZWY5MjAxLTY4OGItYzJkZC1mY2Q2LTQxMzEyNzE3ODk0OSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIiBEZXN0aW5hdGlvbj0iIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciPjxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDc2ZWY5MjAxLTY4OGItYzJkZC1mY2Q2LTQxMzEyNzE3ODk0OSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+TVJEd3dSTXZtalQ1VEhLUTBCNWRUNDVBNWhNPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5wRFlrTFNKM2Z3TUQ0cnJNbWF4cFUwQkZVemZHQlVwNklURmovejNOTnFrQmdaTzdBMGIvQlFGbVBOQ202UE82NGdYNmVySGhhMVQ3aW5PTGRIY2crT0Q2Z2h2R0lpbGJzM1RjUkRwUmVTVkpZVWRiUS9jVk85aC9VdWNielBqZ3gyb3dpakk2aVh1dXhYcmpVeHEzYS9DbHcyTGJiVWJHMCtmQStud0ZuOVE9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDk0Y2Q5YTMzLWQyOWMtMTMyMi1kYzMzLTFkOGU0ZDJiNTQzNSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIj48c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZng5NGNkOWEzMy1kMjljLTEzMjItZGMzMy0xZDhlNGQyYjU0MzUiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPnBYMkV3c1pVVUdCTGhYSTBVOVVMc3d0S3hDYz08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+TW5aVU04U0VmN3RVMlc1VGwvb0ZYTVBJYTZUVlcvUTczRmJUNUcxdW14eHZFRkM1UDlsR08reFVkdlBBTXdkTGc1aEN0R29QenB6amxCSnVFemhJU3VYblNZdkVCbllqdGJKVzcxcU9iM25WcTFjYVZtZXRhQjk4aUZzTDFvS0FWTVZ0Q0VST2E1SFpoT3VtQWJONU5qeHYvcUJlYW1lK0ExaStjV0FNaW13PTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDZ1RDQ0Flb0NDUUNiT2xyV0RkWDdGVEFOQmdrcWhraUc5dzBCQVFVRkFEQ0JoREVMTUFrR0ExVUVCaE1DVGs4eEdEQVdCZ05WQkFnVEQwRnVaSEpsWVhNZ1UyOXNZbVZ5WnpFTU1Bb0dBMVVFQnhNRFJtOXZNUkF3RGdZRFZRUUtFd2RWVGtsT1JWUlVNUmd3RmdZRFZRUURFdzltWldsa1pTNWxjbXhoYm1jdWJtOHhJVEFmQmdrcWhraUc5dzBCQ1FFV0VtRnVaSEpsWVhOQWRXNXBibVYwZEM1dWJ6QWVGdzB3TnpBMk1UVXhNakF4TXpWYUZ3MHdOekE0TVRReE1qQXhNelZhTUlHRU1Rc3dDUVlEVlFRR0V3Sk9UekVZTUJZR0ExVUVDQk1QUVc1a2NtVmhjeUJUYjJ4aVpYSm5NUXd3Q2dZRFZRUUhFd05HYjI4eEVEQU9CZ05WQkFvVEIxVk9TVTVGVkZReEdEQVdCZ05WQkFNVEQyWmxhV1JsTG1WeWJHRnVaeTV1YnpFaE1COEdDU3FHU0liM0RRRUpBUllTWVc1a2NtVmhjMEIxYm1sdVpYUjBMbTV2TUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FEaXZiaFI3UDUxNngvUzNCcUt4dXBRZTBMT05vbGl1cGlCT2VzQ08zU0hiRHJsMytxOUliZm5mbUUwNHJOdU1jUHNJeEIxNjFUZERwSWVzTENuN2M4YVBISVNLT3RQbEFlVFpTbmI4UUF1N2FSalpxMytQYnJQNXVXM1RjZkNHUHRLVHl0SE9nZS9PbEpibzA3OGRWaFhRMTRkMUVEd1hKVzFyUlh1VXQ0QzhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUNEVmZwODZIT2JxWStlOEJVb1dROStWTVF4MUFTRG9oQmp3T3NnMld5a1VxUlhGK2RMZmNVSDlkV1I2M0N0WklLRkRiU3ROb21QblF6N25iSytvbnlnd0JzcFZFYm5IdVVpaFpxM1pVZG11bVFxQ3c0VXZzLzFVdnEzb3JPby9XSlZoVHl2TGdGVksyUWFyUTQvNjdPWmZIZDdSK1BPQlhob3BoU012MVpPbzwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1sOlN1YmplY3Q+PHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+NDkyODgyNjE1YWNmMzFjODA5NmI2MjcyNDVkNzZhZTUzMDM2YzA5MDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiIFJlY2lwaWVudD0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNC0wMi0xOVQwMTozNjozMVoiIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPjwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNC0wMi0xOVQwMTozNzowMVoiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTQtMDItMTlUMDk6Mzc6MDFaIiBTZXNzaW9uSW5kZXg9Il82MjczZDc3YjhjZGUwYzMzM2VjNzlkMjJhOWZhMDAwM2I5ZmUyZDc1Y2IiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpBdXRoblN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c21hcnRpbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zbWFydGluQHlhY28uZXM8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iY24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPlNpeHRvMzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJzbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+TWFydGluMjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJlZHVQZXJzb25BZmZpbGlhdGlvbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dXNlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5hZG1pbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/invalids/empty_nameid.xml.base64 b/test/responses/invalids/empty_nameid.xml.base64 new file mode 100644 index 000000000..72350d1fe --- /dev/null +++ b/test/responses/invalids/empty_nameid.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDQ0MTM5Y2JkLWE2NTQtOWM1Mi00Njk3LTdjMDVkMzAyM2QyZiIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciPjxzYW1sOklzc3Vlcj5odHRwczovL3BpdGJ1bGsubm8taXAub3JnL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDQ0MTM5Y2JkLWE2NTQtOWM1Mi00Njk3LTdjMDVkMzAyM2QyZiI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+VEVFTFhxT0tmZVRqSFI5aUhPb2hrQWlCSDVVPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT51ZW1SeWgyQkcyTXBsbG5kWFNsV0tiaEgzZTRNQVd0VHNJYS9waWJndXZaRmhSTTVJNzUrRkFxYkl4UFVoWDlGYjlOTWRVRzdacWJJS2J0aitLZGxCdVlYaDdTdEIyQWMwY1VzamFQTHVLa2RTc0IzUzdESXFYRThmcEdNeHBSblNNZDZWc1RXM2RId3FYaTJiZklYblBDM0N0RjMwWUhXditwR081MFpCcjg9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDVhMTU1NmIwLTE1NmYtZjNhNS04OGUyLTc1MzRkNjdiNjg0MyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9zaW1wbGVzYW1sL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZng1YTE1NTZiMC0xNTZmLWYzYTUtODhlMi03NTM0ZDY3YjY4NDMiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPnhpTEtIa05OcllPWTdWOFhkSjVET3pQNFp0ND08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+WHZDRURGdDBJM1VXWlMwN3JWa1VmNTA0Mjg3ZHJTbEI2bDBSdS9OTWMzZFlIT2E1V0NCNXZRanpGVURMSFZSQWlueWR0WXh3ejRTN1NKd081V3RKVFdTOStQNU9SMnpRTjRpYVpnclVGRm5xV0FDZW4rUTMzaXZVaFY0elVTcDU0cjVVdUxLNE96UnVhNmhlWUYrM0Y5TXZMK3VPV2hFZVc3NXZjODk0VXlVPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDZ1RDQ0Flb0NDUUNiT2xyV0RkWDdGVEFOQmdrcWhraUc5dzBCQVFVRkFEQ0JoREVMTUFrR0ExVUVCaE1DVGs4eEdEQVdCZ05WQkFnVEQwRnVaSEpsWVhNZ1UyOXNZbVZ5WnpFTU1Bb0dBMVVFQnhNRFJtOXZNUkF3RGdZRFZRUUtFd2RWVGtsT1JWUlVNUmd3RmdZRFZRUURFdzltWldsa1pTNWxjbXhoYm1jdWJtOHhJVEFmQmdrcWhraUc5dzBCQ1FFV0VtRnVaSEpsWVhOQWRXNXBibVYwZEM1dWJ6QWVGdzB3TnpBMk1UVXhNakF4TXpWYUZ3MHdOekE0TVRReE1qQXhNelZhTUlHRU1Rc3dDUVlEVlFRR0V3Sk9UekVZTUJZR0ExVUVDQk1QUVc1a2NtVmhjeUJUYjJ4aVpYSm5NUXd3Q2dZRFZRUUhFd05HYjI4eEVEQU9CZ05WQkFvVEIxVk9TVTVGVkZReEdEQVdCZ05WQkFNVEQyWmxhV1JsTG1WeWJHRnVaeTV1YnpFaE1COEdDU3FHU0liM0RRRUpBUllTWVc1a2NtVmhjMEIxYm1sdVpYUjBMbTV2TUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FEaXZiaFI3UDUxNngvUzNCcUt4dXBRZTBMT05vbGl1cGlCT2VzQ08zU0hiRHJsMytxOUliZm5mbUUwNHJOdU1jUHNJeEIxNjFUZERwSWVzTENuN2M4YVBISVNLT3RQbEFlVFpTbmI4UUF1N2FSalpxMytQYnJQNXVXM1RjZkNHUHRLVHl0SE9nZS9PbEpibzA3OGRWaFhRMTRkMUVEd1hKVzFyUlh1VXQ0QzhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUNEVmZwODZIT2JxWStlOEJVb1dROStWTVF4MUFTRG9oQmp3T3NnMld5a1VxUlhGK2RMZmNVSDlkV1I2M0N0WklLRkRiU3ROb21QblF6N25iSytvbnlnd0JzcFZFYm5IdVVpaFpxM1pVZG11bVFxQ3c0VXZzLzFVdnEzb3JPby9XSlZoVHl2TGdGVksyUWFyUTQvNjdPWmZIZDdSK1BPQlhob3BoU012MVpPbzwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1sOlN1YmplY3Q+PHNhbWw6TmFtZUlEIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIi8+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiIFJlY2lwaWVudD0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNC0wMi0xOVQwMTozNjozMVoiIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxNC0wMi0xOVQwOTozNzowMVoiIFNlc3Npb25JbmRleD0iXzYyNzNkNzdiOGNkZTBjMzMzZWM3OWQyMmE5ZmEwMDAzYjlmZTJkNzVjYiI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zbWFydGluPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNtYXJ0aW5AeWFjby5lczwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJjbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+U2l4dG8zPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9InNuIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5NYXJ0aW4yPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImVkdVBlcnNvbkFmZmlsaWF0aW9uIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj51c2VyPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPmFkbWluPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/invalids/encrypted_new_attack.xml.base64 b/test/responses/invalids/encrypted_new_attack.xml.base64 new file mode 100644 index 000000000..506be6712 --- /dev/null +++ b/test/responses/invalids/encrypted_new_attack.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJuZXctaWQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAzLTIxVDEzOjQxOjA5WiIgRGVzdGluYXRpb249Imh0dHA6Ly9hcHAubXVkYS5uby9zc28vY29uc3VtZSIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl81ZDllMzE5YzFiOGE2N2RhNDgyMjc5NjRjMjhkMjgwZTc4NjBmODA0Ij4KICA8c2FtbDpJc3N1ZXI+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9zaW1wbGVzYW1sL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPgogIDxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPjxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4NTdlYTM0MDgtM2ExZS05YjY1LTYxZjktMDQ3MjAyODdlNmZlIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPjxkczpEaWdlc3RWYWx1ZT5wZFhNRGQzTlh4dmRDVnNFaTc3MCtJZVNUVnc9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+CiAgICA8ZHM6U2lnbmF0dXJlVmFsdWU+cjFYVm9hbitSL2RLVTR0a2FSTzRhNjl6UE5SSmVHNFhyVnpGbFdoZGhScmd5dWxjNGE2SHJ3UVJPUno2MGxsQjRlczV0RWZTQW5VTWF4d3FRVXowVmdKNUxkUG5XWmpxeFhTT3NidzNtNUoyZ1NrbmNVMWZUaURpdXVmS1poZUZEZG5ocDdvZzgzRGRORFczMTJVQ2FjWUlhRHQyMDVvZG1LT1V1TGFZbWFFPTwvZHM6U2lnbmF0dXJlVmFsdWU+CiAgICA8ZHM6S2V5SW5mbz4KICAgICAgPGRzOlg1MDlEYXRhPgogICAgICAgIDxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ1BqQ0NBYWVnQXdJQkFnSUJBREFOQmdrcWhraUc5dzBCQVEwRkFEQThNUXN3Q1FZRFZRUUdFd0oxY3pFUE1BMEdBMVVFQ0F3R1NHRjNZV2xwTVFzd0NRWURWUVFLREFKTlpURVBNQTBHQTFVRUF3d0diV1V1WTI5dE1CNFhEVEUyTURZeE5ERTBNREl4TTFvWERURTNNRFl4TkRFME1ESXhNMW93UERFTE1Ba0dBMVVFQmhNQ2RYTXhEekFOQmdOVkJBZ01Ca2hoZDJGcGFURUxNQWtHQTFVRUNnd0NUV1V4RHpBTkJnTlZCQU1NQm0xbExtTnZiVENCbnpBTkJna3Foa2lHOXcwQkFRRUZBQU9CalFBd2dZa0NnWUVBNXhXc25BbUlnQ3drd2JRb2RQNGVpTEFVT1BtdXVybFYyOXdoY0d0NkFjM2h2T1F0bk1tOWdkbE5KcnZqbHY0WmFHM0g2QTBBa3lzODExQW1kbStvS3ZlWHltRm9MRzRLSExNalRNSWZRdktPbDhJZC8rVXZ4NjlaZHcvMG91ZW1oSWFncHcxei9iT1h6TC9pLzNLeEdKZzhud2FWM2R4dGJQTlNGemNEdnkwQ0F3RUFBYU5RTUU0d0hRWURWUjBPQkJZRUZFNGdqbktCNXlKR1daTWN1NWxIbFJuU0FhZTJNQjhHQTFVZEl3UVlNQmFBRkU0Z2puS0I1eUpHV1pNY3U1bEhsUm5TQWFlMk1Bd0dBMVVkRXdRRk1BTUJBZjh3RFFZSktvWklodmNOQVFFTkJRQURnWUVBT3NtUng2dGtuVkRIQzhFK0VhczJlRjZPNEhtMTVZdDVYQWp6SVgzT2lFMnp2cW0zZk9rM0hOamNIT0FJRkI3TWR2cjYrMjNBUlhwWkZLaVMyK01rVXM1d21FekNMcVUvaFJPeWp5ajlQWUcxak1QckFIUE9wV2pWdGxXdUpzbE4yOEk2emlNOHVxK3VpdFRqSWR0OEpaNlAyZFd0b1RtRGdzVlVtRk0wbmFVPTwvZHM6WDUwOUNlcnRpZmljYXRlPgogICAgICA8L2RzOlg1MDlEYXRhPgogICAgPC9kczpLZXlJbmZvPgogIDwvZHM6U2lnbmF0dXJlPgogIDxzYW1scDpTdGF0dXM+CiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+CiAgICA8c2FtbHA6U3RhdHVzRGV0YWlsPgogICAgICA8c2FtbHA6TG9nb3V0UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDU3ZWEzNDA4LTNhMWUtOWI2NS02MWY5LTA0NzIwMjg3ZTZmZSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDctMThUMDE6MTM6MDZaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2FwcC5tdWRhLm5vL3Nzby9jb25zdW1lIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzIxZGY5MWE4OTc2Nzg3OWZjMGY3ZGY2YTE0OTBjNjAwMGM4MTY0NGQiPjxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PC9zYW1scDpMb2dvdXRSZXNwb25zZT4KICAgIDwvc2FtbHA6U3RhdHVzRGV0YWlsPgogIDwvc2FtbHA6U3RhdHVzPgogIAo8c2FtbDpFbmNyeXB0ZWRBc3NlcnRpb24+PHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiIFR5cGU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI0VsZW1lbnQiPjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyN0cmlwbGVkZXMtY2JjIi8+PGRzaWc6S2V5SW5mbyB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48eGVuYzpFbmNyeXB0ZWRLZXk+PHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS0xXzUiLz48eGVuYzpDaXBoZXJEYXRhPjx4ZW5jOkNpcGhlclZhbHVlPlhzZWhuemxaMUZaemhWK2JFSVhmUlJsSGo4OVpKS2EvdHFhLzBFb2lWZUZvU1A3Z09TcVYveU5WclRYdmkrYUxYUWo3TG5Sa1FhbS9sTlRMVmQxN2IxV1NUWlZ2NGdUbjhFMk1SWDN0bXI0eUhwUUZ1c1VIOFJqeFduR3lzZ2ZET1EvSGR4SDdhWDdpNVhvTS9KQUdMeU1GMWc4ZFdnc2I4ODN2aTByN09MTT08L3hlbmM6Q2lwaGVyVmFsdWU+PC94ZW5jOkNpcGhlckRhdGE+PC94ZW5jOkVuY3J5cHRlZEtleT48L2RzaWc6S2V5SW5mbz4KICAgPHhlbmM6Q2lwaGVyRGF0YT4KICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+VUlxazBXRmI2NWlRZUxPZDExU3JQTGJmeVovei80Q3phcFZqSERiRzFUOUprSjA4WHZRVjJPR1d2a1IyMUY1MEhVT0lkdFJ0RWVxdGdidnNRVjNhQldqVTN5cFFhYzliOFJLS2lRYVd1Y1R4K0lXZDNVVW9iTjAwRVFzNFlVU29JMFYwTEczQlRReThUVnVsTzhXdm8rcndEMHJxWGxMdDFvek1kcUFTN0VlTkxmNW5BTkR1bTN0OXZScys5cnFCSGw3eVFKdFo0eEl3a1ZJSHlsVkJYR3lRN2hSZVA5NVFWcTRmSWhMa0lkL2ZsKzltamE4VEdRME4rcE40Sk9YbVEwRUJyNG9IRHBRR0pGQlBsMzBlM04vTTZnVHNLQk1reit6RDd3Y1JLaU9TcDh2Zm1lMEVhRkR0Z01sZWVMcUVJWXpSUG1XRnFmMk8xMjY5R2pXRGtEcTd6WUxYRExUdmNlMUt6WjBudmNkbnpyMjdaVnU1Q3YzMWNqenlNdmVDT3dEcVEvZVZlWjBvTEp4Tlc2Y2xoT2NwK0pFQWZ1SUlsdHNjU3VMOERoKzRma2VCYUlRZmExMCtKcDBRVW1rSHZGemJab0tvblgvT0tTR0ZldmM1Rnc5ZUlOcFErTzAxUzU1MjhIaGJmY3JHR3BrSWh2YmlTM0xFZWhVNjA2dS9Ob0p3Um9FcWNzQjZXR21rUmUvb0hIU3Rib0ZtMmFKOGRmV2E5Sk5wUHVSeFVuTDZFeXhOTlhieUdrZGp1ZXh6U3haVkRZMDZRbU9odzZXcFVJY203dktraEVZL3YvWkFXN1dnQTVyVVUwS1lSQjlvTldCYUZQTjdjb1ZpM0prUnJkZjRqRUNzeXFTc1ZaRWR4L2tLSWJGSG53QVJ4a1pFSXdSZ1J0bE1WYit3cnpQWkJTT0hHcG1mSmJwNS9zbll1REN0RE13c3lTcG5HdFArYUZLNE1QWk5ORWdrWVJlYktlRXNiQjMxdVh2dzdMelZWY0V6cnVHWGRaMFN1dWR1VVBIVE0wZno0U2VQMERKclZEcnVuWGtDMEg1UDRwZTNVSVNSUDlUOVorSjg5N1lhRW1HckE5OHZuVEdWaE0wQXV1WFIrSTlJak05STlUZHFQUDV3bnhZclp0ZDFVc0JBaCtXRUNuM1JpZmNEWURzZm96eTNtZ1RzUVpxbW1ZZ1ltL1J2dStWdnliNlBBR3VkREFuTU1oanEyeUhxMThYem01elpWcTJFMVV5RU8yMjFyVmhqOGxSNVF0V0x3NHFyZmc3dkQ5ZHJwWWFiRkM4RGVZYVNFRzNsckxtNFAwNFBpMy9zOVo0NjNmRGxwWUVMMVlHVjhiWElhUWFDN2ZtRFIrTWRtaCtVTG1PU0owRXJYODA3UUQyZFlkWFdhQjZlRXNPeEpzMWRjU1VGTXlKN0ppSWR1clkweHJNNDR1d3oxUU9MeitxSkFQcjBDMitTRGozeFBZUFlqbVEyNkFMUit2M0Jldngzc1JSOXoyUnRROUlaVFY5SXVXc3RHU1U5N0laUXcyaWV3bGhZNkJMeHIwNDRVZWhsc2QvQ3d2U2JoYVZRQ3E0V0JqcU02RGc4ODlUVnJKN0x3bTlTZzVGOFphT2lQajZzc0NsMG1zS04zbGZyZVJQQS9mTkRCODJPSGVuOTNFeWg1THBxaGJsNzBVT2RVdlFaVGFJNWs3QjZrcHpuYjlIa1BkSHRuSE9LUmdMVE50MXhFUzQ1blhDSXFqVWxORGpZYWtaMnBuQU5OU3FMMlVYTFBLbVBXMVNyWkU1RVUybFRPUFp3TmJCZkF1ejRYZXFSWWRpQm1lR001dzBMWnd3TGE5a1NjeE81QUlUeE8wV1FMa0t4d1NrbCt6WlNiejJXbEx6RmxiZzNRRnZuLzN0NS92UDA1SzRNbzNEQWlnbTFPUHdCazlaV244MVNESkFPUkV0SnllRmxVTGl5dTFySW91WTNZSFhWZjY1aFZhc3ZFWjM3MDdsL3BSdXNIdWhOYVNaQ3dvSXV0VEYxZjhmN3k3SU1XSVBXUld5NWJLdDBudFRlK25VYWZvQk10cTdGcUVlTVg2TklGdUpHRzBVWkRNUVkrNzJpNXFSemt2QXpIUEhtR1pWdHZjdjg5N2M2ZzI1eXNyQjQwSHVqMDRFTUc5c2NhVEIyTk56R1k3Nm52c2JMNXRmanYydGFjN2ZmK2YrbnQrbTVvckhLQ3dRN3ZmSFBacUhlelN6azlFVVQ1aDV5Z1RvelVxRXkvNkptZW5KMHRZNWMzRmg2K0FNSkFicWdNamVjRTlkY1BSZEpyV3NTTXVNMllHZnVBY2syY2oycndIQmduelZiZUN6d3E4NnpzTEpQQXZ1VzlnbmRHVzIybi9Uc3hxb2R4ZWN2cTlURXRGd2hpSGdubTRvV0xzS2VpaHRrUnpPUUdFUkdIbTNXd3NXTWJtVVJUUDdnaFNNUytnellSUkVhalJYYzhONFhvRTlHVnZXOHRnUVVIMVM5aFE3M056MytmTTFuQ0dsWW1zTUN0SU5GWGNacm44WXlSRTNsMFZyOUtoczBYUkVnc0VqWVhpT2JWU05sKytZSHBkK044RXRKQ1ZFNkMrQ09wanlPUUhmNDZ4NElueHhkVk83ZmZpa1FDMHdKbHROaGJQb3hBMGtzZzNWNWlCNzhUNGJHVE16elNFa1JUTngyajhrWjJ1eHdxUFFZTFdvYmxTaEszdEpXRTk2M28xd3lOVmlhNmJUekF6cFR4eDVveHRKWjAza0hRM28zMExIV2Q1TXgwemJrWUdCWll3cTI5ZmVnQ0ozK2dreDR4V2NSc3ZHVlRiY296VXZUREp2bjlLd3pMdFR3aVJVV2tnOE9qdll3Vzd6bU9rWWVDeS9XdDRCRVNUcVd5Q0dKNU9RZ3VXd29FMitRem82VFVrRU5zT1ljMElpRGdxQ3BzaVl3OEsrLzNiTzFVSHVzQ2FBUjA3WWJaaVp0Q2FBTjBHbGRKZnR2RlhLRFQxTmwxWHpHaWpZeTdrS2x2TGxZd0M4UlN5d0hrK2xReEIwdVlhakNtV0ZtZmh2aVQxK2laR1VQdW9CWkpsMzRpeWF6ZEJZZi9kS3BsYVlzanJPcjdWMWV2d1JmVGNCVmQ1RzJLcGU1K0hhOXNzUm9NdHZKTUdBNy9CeUwxQ0lGaDNRbTVxNzNnL04zU0lzVGM0VE1sNDVtT2VYdEl3anBBekl1TEh0SUozc1J3aWFsNEtEVGMrNHdzZ1hzTmxEcTAwVERzMVhEbEJzSmovdHlEcUtuU3A2UU05S1BZL1lnd2ZTQTgwejNPa1hhVWx6cUFtZStXb3ljZHN6MEV5NGhDSE1Dd2NCeXRVc1VlODZKbWNEemFEL3A5RldwT0JJQitVeGhSRVhvS3NrYWVHZDlUVFJTbzk5QkJzblMrU1BBQXJ5R1o2TllhL2lnandZejdZaUo3cEdFaEpNdDBLZS9qUVdvUXBWQUFJcGNmdmZmR3ljQmtldUs3dUpXRGZVU3BMYVpkcnV6SXlIaFpTVldCcFBpYnVkWEI5VmtmNmgrcHNwUTY4RXBpOEtiZDZyMEh4aENyN0k2alJmQ0ZWR2x3UVlmUDdlQVQ1SUVuNFNNRTdTeEdKTGYzWTBDZmxvanlBbmJ1Z256OUNKVDBYeUc1TUswVzlpQzY2S0lXOVIvRVVBb0xCaVJmUUdzWGxCczRmajZ0NDRpOHpHRFVDalFPWnV1OWc3ZWJDTWlOb1hWRmlSZmJ5ZHkxbTVFYnhkVWJTM2tTMUVzTXZrQmx5T1JrV0hiRyt0b0tGbC91dFRwbUlBV3QyOHZ2L0U5bU44dmJKR3c1bUdINUJBTGg1OGNYdnE1TDFObHFNSHhCTDEwNzJuNFpIZ0J3bGdjTWlxbXpCeTd0eE9ZLzJpTzUwSk9aamdIUW42YnNqVmJjYyt2RGJsSUdVeGYxcmJZWUNYUHBWQ0J4bzNVa2N4VVZuUmdLN2ZOcm03bEZCSHRxR0M4L2dpblFlWXQ5bHV5cVFGTUNtNmUvQVh1eUVYbGNhSXREYUFsSnNPa1VyZ291NGQ5T1U0dnF6NzRwb2VSaFdlRFJaMlpYa2VGeVcveUtjRENxVzVubnZraUdieUtvTExDNXV6RlkxeXE3c0M3ZURoRlhIK1hmaEN4R0V3bHp0eC96ZXROQUdXWWJjWEwwd2lkREFzWkZabDJYMXhPL0JLWnQ2alpwV29ZNk1kSDl6eUYyYjRGbUJxTDhEdDQ8L3hlbmM6Q2lwaGVyVmFsdWU+CiAgIDwveGVuYzpDaXBoZXJEYXRhPgo8L3hlbmM6RW5jcnlwdGVkRGF0YT48L3NhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+Cg== diff --git a/test/responses/invalids/invalid_audience.xml.base64 b/test/responses/invalids/invalid_audience.xml.base64 new file mode 100644 index 000000000..a34b12c64 --- /dev/null +++ b/test/responses/invalids/invalid_audience.xml.base64 @@ -0,0 +1 @@ +tVdZc7JIF76fqvkPlu9lyrAqQiWpQVDjgoLglpsplgZRNmkQ5Nd/DS4xMcnMO1XflXL6bM9zTvfpfoK670XcDMAoDCCo5b4XQK4SPtfTOOBCHbqQC3QfQC4xOZWXxhz5iHNRHCahGXr1G5OfLXQIQZy4YVCvDcTnemTnFm1aOkO1Gi29RTRwlmo3GNwEDaqNt0HbZO2mBeq1BYghsnquIyfIFMIUDAKY6EGCRDiBDFsNgtEImmvSHEG/1WsigIkb6ElltUmSiMMwmKS2/WiGPgYCKwrdIIE3/3QTPkabCLkPLlRo4XP97yZjmIbN4A3GIOwGTpCgYbZJosG0SbRAUIZBtesvf/5Rqz2VBHBVdvHLOaZrRY8g1/3IA1XkJ+xW6cmCnOo6KM80vhBvwWvCWZY9ZtRjGDsYieM4hrMY0rGg6/w6RzzbA2sQ2GHlTtCDMHBN3XOLCr0Ekk1o1XjPCWM32fjfOCcwAi+dN0BuNkyCDn7VsSrEe5AqyX/p7kOuMdQbcKMTZ4+lvxmwQQwCE9Tms8Fz/de/7YQKohbrAbTD2IcfP38vKxAcgBdGwGrACziU4O85/JK1J+w+R9F1UEP+F/LOxL07WeheCl4gk6+1GbYdT/brLKeJwO603PgBpNLguUrgVrkSXCk/fX5qm2uBTxaa3do5/U471swEW+Zw7njzMBwvM2I77Oqy4fWKlfkgR2HHGc6MGJvpW7Gjkn5xzI6vM8cOpnvfsh+cZASWCmxJYTJ35R2+MXYjeTV0bGfnsPBgZw62kqfjKTZOj8lxMH+bBtmrsntdU0s3okMVV/Mo3Tnqcb7dFYw8zKarXQaDmFIPajovEkKa489XODf5//lHCWoEjleAqybOinqiXz+E8iSy0U5JwIs0GAj9QhD4tSIIijCZmMoq9ClyIfKTjrPbb3Zun83wDq/MezwCKikwE5S1uFCUfjcbLuZFdyzxuz5PzLtCRxLUOZ33NF7rOJNFhzc1seulb31vY/i9zCDzyCy6M4lvn/Q30kAjm57WZwN9SeeiyI9OdlDj8UX6VvCgl+H5RORxaSvlU1HJJbGnVzLto0zqDXKh4Icn+7XGewvtJtcBynWobbsLqSNVsTu5JKnL5k5f9tL1qnMw+l4hzbqZmFX6o262kQ1/IRmkFRnbriTx4SlnFG22bAbSoG9LPN4X1H1fHRiUqHRLjnie7k94Uei4CoKiiGHLf1DeDmsFy8d41xs7aTQiFNFcj2m7KZuopBNHZaP5YcHYhbYXXndtbTPOtR1NSgopmUM4BfPhQmbI9mhz9Lc9YZ87Mr1Is9mOjSPewx/8zbElydZxe+RbfYLexuJSbTWPcGxuRnSCHbKoKxa4qnjdsE/spMLzVJ8p3lRqFQOHZ8TtRJ+vlX02EHmF73zG1Dlh6vBjiTz0BVfBDv6D3jrQ+MNiRRbW/pUnFYw49AjXUIphk5aGwnSxPMCHg7Ky33oby8enskQOYnHOhPvDSN7LrXzKg+Go9YofZUai12PK1hP1MFj7vn1cCewuQV1eYJNj/FrIm3kwxI8Ag3N1lQeFkmemj7Fyxu9jRecpJUKbpskcjR42DeOjDB7ITbUtPjf6VXjaCtjtJvmwjd4HWsSpCRLByzS4lQmhBWrVjvt57sNKm1NT0wQQXuYAduf+NEH5yy3hPBFz6H57/q6ksWpugK833OpCYIL61eqfja7XEKZNEyxLoEOcoewGjVPNBiABmvQEmkE4ICwK0P/hGnLL2W9dDG7t1NTYAjM5yy7SCSJ5INZUufyjpGje2y6IEWTgeWHpsV7rofGjJ99XhngkKolrNexKlUOUuB5vWXFZpRcY+iAMwF83aZ6zPAX/lNE5TyEMbLf0VhbwNPF+bg7T5wygxyCuXx3+4LJs3NokTKbBNObtpIRM4iR+Qz17ugHOgOlGLihr8/+//2HvXGDfZX4pKvZFVU9wkbbllqqwRNgBqCqg6qxbeBRHl/DuKCDuKPhUHz613HL+I3RJ7Jo3Kd3rXLs0OKDOsh71s/ymB66qn5B/H+as8I7yA3g+TTZBeRYAHxWtVn1+v7twBnGgojZFfr6lgiSv74Gz6iCwQF4WlTAAxbCtpg2MZpNhrTZOETSBMy2WalkmCViLsNg2MOx7FlFeCEIC8uQL+t4XBQ+9dtCl6+XHB5HJmaUeEsvoJwtj60rjF67uqL7L5XblSuZHnhNUFiNNwOflO4Vauc2f6+WZUK/+/9OBUiFC1qX0cqQYSM+839nXINX0qKEjnkuOEYqWo3ckWgqcn86fj9Z3tFxWP3HyNfDL6mXolHdH7OOT+OV/ \ No newline at end of file diff --git a/test/responses/invalids/invalid_issuer_assertion.xml.base64 b/test/responses/invalids/invalid_issuer_assertion.xml.base64 new file mode 100644 index 000000000..217caa901 --- /dev/null +++ b/test/responses/invalids/invalid_issuer_assertion.xml.base64 @@ -0,0 +1 @@ +nVdbc6rKEn4/Vec/WK7HlOEuYiXZZwQ1XlARNCYvp2AYBOUmg4L++j3gJSYm2Xutp8Se7p7v+3pmunn4Kw/8yg4l2IvCxypzT1f/evrvfx6wGfhxc4pwHIUYVYhTiJuF8bG6TcJmZGIPN0MzQLiZwqYO1GGTvaebJsYoSUmq6lVI/HNMnERpBCO/WlEQTr3QTEsobprGTYrC6dZx7mEUUCi048gLU3z1nwnxfezG1UpPeazGTm41oAAFTqpZdVGqOQjaNQfaXI3nHUTWeIllG8Q5PBMzosfq/wXRgpYj0jXRYpwazbCoBhssUxMbLFlgOMviiiCMt6gX4tQM08cqSzNMja7XGNFg+KbANxn+rVqZn3UkvKpExkqlFLJZxiZPJ0qeHd+j3AxiH5XEHqhrpwcbN3VvSWTYJmfhbXzRI8uy+4y7j5IlxdI0TdESRXxs7C1/nXY8xSO7FzpRmU42wyj0oOl7h1JcFaVuZFeAv4wSL3WDb5IzFEMXyWsohzXI8OGvKlVu8b5JCfJfpvuANcFmDbsmc8pY5JsiByUohKgym/Yeq7/+bTlLikZihtiJkgB//Pl7qFC4Q34UI7uGz+QIwN9L+KVqD9QtRsVbkvP+J+KdhHtPMjf9LXqqd6e6PlU9TRjpfVEaL1iqTs0ZvK43HksA186l4SL58eenY3Mp8DFCUbUFMth+wM5TrMnLpSrxstwaTplpKxss4YzbTfmlx4DtIHvDq5k/cjXZnvLtfR8e7ux0YoxovIGZEHGrLb2b7CEVBuOpLwZrecP0WWMyVwHf6q1HL2OB995io6tCMx/frejp4KU/YGfOAFBB3p+arc0sdveD0Z0qhCN3NxyH00Hg530B9hXnsAWPFzpX+MmrRowDtL8QXAi0pJipefkhF4+XQ25Kip7UXk/uHmQZvGqyrMmjEdQWUcCxcwWMWsv1xl17XSmjW0CbdYDS0lUNZ7L2qsw1rdvO+vPZoT1UwboLmFlbbqmyPuPzjgGM1nI0bwFoKG1/+9b1XSvoZBabx/DQnqqgcfR31Z7BCr7RlULzhc8VBQyOcdgA9Hz7dgCok9H5SAG0ulLzsaLlqtIxS5vx0aZ2erl8AP1j/KsB/LlxhbVHsPaNVXuuttRy71auqvqLsDZfOtvXRWtndf2DOm1nSlb6D9qZO7GCuWqxdmyt2qoKoiNmstv0RQjVXtdRAd2V9U1X71mcorULjQDguyOgyC1PI1Q0JaoHd9rb7lWj8iHd9ofLbTxgNAW+DnlHmMB1hkdLXYpnu7noHIyN/LxuGO4wN9Y8q2qsCvt4jGb9+URkGwN3H6w68iZfTvj5NpuupSQGPn0XuPu6OrH3qz2odxl+lSgvel3Y4yF0B3xK7bK4rRxoXfPbUZdZqwff1wPx8KZziwQtgaisRubsVdtkPQVooPWZU+vIqQWGKrvryp5G7YI7s77j6bv5gj3Ym2fAahSz6zCepR36Aq/25fH8ZYfvdtrCeeu4dkCPJyrbS5SZGG12g8lmUs/HAPUH9Wd6PxFV/nXIOWaq73qvQeDsF7K0Tjl9d6BG++T5MHFnYZ/eIwrP9EUeHrQ8gwElTTKwSTQTcFqsrZ8FcW91qHGU7CfojnXLa/H5oF+Mx6tAXV+SD9fovaHFTT0lJnzuBtc2ObJRpbxxP7d9XHo39S2ECONzH6Bu0h87KDgPFqeOmH/XERlqoQ516KLArF58vX92rnlla4foMkuIDZ6RJIY84iLn1HiaE2qIRWQ2YEQa0oixOcT/6VjwzWAQ7kiPtu+90vrDjFAkuE6jb60Vgukp9dk6Ipr3lEqHdBwz/b4YzD1TWjzSXEvXJpHD84FtJ0VhKvqkSKRtCTTHQwmREvl+VKCqPuEoQFGI/ncF9YT0uPknRCecchQ6XrFTUc9jA/z5rMCgaSEzQUn1kvCHlMU5/rMZbxSl43CcACcteLI0S18VUzoWc4qgF3uoqPZvjajUuxbUd8jPZ4P6oqpHusTb9gpXXIBtIVIxVB67a6Rcky+Q3rBhbth8qg/Y2l4xDhDp0sSDV5BufZ5+YB+g1LRJFQriJzKXsE8qfL/lyeGd8QchwDZ1w+KZQAGpRaX8+f01pEWih06OM8nTC22UF0eCsRAnSnWBzJKCIEp2g+YYnqHFusTVbcgiyWZsqYEs5xL6raIse77sN4oSXIRCivL0CynfF2WffDuReezpx88r2ISFHzFPyJ8sSuyLjF+kupH6Bsv1ykXMjzqnpCzWNkWfl28cKsWVf6wWb0e1/P+fHp6SEYkurOenxyJ+8PaWXzYpG0uFvOjNdB+T3XLyiUmWwuVPb9HH6BtZzqufNPma+Hn13I+KsZL6+LX89Dc= \ No newline at end of file diff --git a/test/responses/invalids/invalid_issuer_message.xml.base64 b/test/responses/invalids/invalid_issuer_message.xml.base64 new file mode 100644 index 000000000..e63c0f42f --- /dev/null +++ b/test/responses/invalids/invalid_issuer_message.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDY1YzllMWQ5LTY2MjItNGE4MC1kMjNjLTE4ZWQwOTdkZTU1MCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaW52YWxpZC5pc3N1ZXIuZXhhbXBsZS5jb20vPC9zYW1sOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4NCiAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4NjVjOWUxZDktNjYyMi00YTgwLWQyM2MtMThlZDA5N2RlNTUwIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPjxkczpEaWdlc3RWYWx1ZT5TYmc4M0hkZXZwcTN5ajZJOVlRdDR3bkZGYnM9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPjRVbHROQWxmMHRwQWJEYnFnbkxUWXAxcFJRQ2NMbFZkZERCdDhYRC9WYmFzMTBJSkd2V09UeExqMkVMci9wbytuSWd5Zkw2ejlWcThidm5EVVNjT3o5bVg0OFFsclJaRXo0U3RGTzBCVWM2MjFreFpYM3ZMSXBoeHc1N3o0U2FvckVmZGRGOFFkazBPQTl1Z3dFb014Z2FqcklXbWRldC8zMTVBTGJFSk13VT08L2RzOlNpZ25hdHVyZVZhbHVlPg0KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ2dUQ0NBZW9DQ1FDYk9scldEZFg3RlRBTkJna3Foa2lHOXcwQkFRVUZBRENCaERFTE1Ba0dBMVVFQmhNQ1RrOHhHREFXQmdOVkJBZ1REMEZ1WkhKbFlYTWdVMjlzWW1WeVp6RU1NQW9HQTFVRUJ4TURSbTl2TVJBd0RnWURWUVFLRXdkVlRrbE9SVlJVTVJnd0ZnWURWUVFERXc5bVpXbGtaUzVsY214aGJtY3VibTh4SVRBZkJna3Foa2lHOXcwQkNRRVdFbUZ1WkhKbFlYTkFkVzVwYm1WMGRDNXViekFlRncwd056QTJNVFV4TWpBeE16VmFGdzB3TnpBNE1UUXhNakF4TXpWYU1JR0VNUXN3Q1FZRFZRUUdFd0pPVHpFWU1CWUdBMVVFQ0JNUFFXNWtjbVZoY3lCVGIyeGlaWEpuTVF3d0NnWURWUVFIRXdOR2IyOHhFREFPQmdOVkJBb1RCMVZPU1U1RlZGUXhHREFXQmdOVkJBTVREMlpsYVdSbExtVnliR0Z1Wnk1dWJ6RWhNQjhHQ1NxR1NJYjNEUUVKQVJZU1lXNWtjbVZoYzBCMWJtbHVaWFIwTG01dk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRGl2YmhSN1A1MTZ4L1MzQnFLeHVwUWUwTE9Ob2xpdXBpQk9lc0NPM1NIYkRybDMrcTlJYmZuZm1FMDRyTnVNY1BzSXhCMTYxVGREcEllc0xDbjdjOGFQSElTS090UGxBZVRaU25iOFFBdTdhUmpacTMrUGJyUDV1VzNUY2ZDR1B0S1R5dEhPZ2UvT2xKYm8wNzhkVmhYUTE0ZDFFRHdYSlcxclJYdVV0NEM4UUlEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRHQkFDRFZmcDg2SE9icVkrZThCVW9XUTkrVk1ReDFBU0RvaEJqd09zZzJXeWtVcVJYRitkTGZjVUg5ZFdSNjNDdFpJS0ZEYlN0Tm9tUG5RejduYksrb255Z3dCc3BWRWJuSHVVaWhacTNaVWRtdW1RcUN3NFV2cy8xVXZxM29yT28vV0pWaFR5dkxnRlZLMlFhclE0LzY3T1pmSGQ3UitQT0JYaG9waFNNdjFaT288L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+DQogIDwvc2FtbHA6U3RhdHVzPg0KICA8c2FtbDpBc3NlcnRpb24geG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiBJRD0icGZ4Nzg0MTk5MWMtYzczZi00MDM1LWUyZWUtYzE3MGMwZTFkM2U0IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDoxNFoiPg0KICAgIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+ICAgIA0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgU1BOYW1lUXVhbGlmaWVyPSJoZWxsby5jb20iIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOk5hbWVJRD4NCiAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDIwLTA2LTE3VDE0OjU5OjE0WiIgUmVjaXBpZW50PSJodHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9lbmRwb2ludHMvYWNzLnBocCIgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4Ii8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NFoiIE5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QxNDo1OToxNFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjA3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QyMjo1NDoxNFoiIFNlc3Npb25JbmRleD0iXzUxYmUzNzk2NWZlYjU1NzlkODAzMTQxMDc2OTM2ZGMyZTlkMWQ5OGViZiI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4= \ No newline at end of file diff --git a/test/responses/invalids/invalid_signature_position.xml.base64 b/test/responses/invalids/invalid_signature_position.xml.base64 new file mode 100644 index 000000000..1cc8af6d9 --- /dev/null +++ b/test/responses/invalids/invalid_signature_position.xml.base64 @@ -0,0 +1 @@ +pVbbkqpKEn2fiPkHw/1o2BQXQY1uz5SgthdUGrTVlwkoCkG5KIWCfv0p8LLbPt377DPzpJWVtXLlysosnv/IAr90xDHxovClzD6B8h+tf//rmZiBv2u+YbKLQoJL1Ckkzdz4Uj7EYTMyiUeaoRlg0kxQU4fqqMk9gaZJCI4TClX+cGT36zO7OEoiFPnlUl95Ke+czEJ1TjQdu4obDq7avGNV7bokVJEgAYB47ABUL5fmN84Ugx4l5ID7IUnMMKEmwApVIFaBYACuyXFNwK3KJQWTxAvNpDjlJsmuyTDmbvcUHGzzKYwYQiIG0XQPAaaA4S15I3op/9dBgskLFqhKmNIBLIeryDRxVapzFrJY3rL4erlVyNYsuMStPAK5hohC7EdrL3xCUcDkTtwz89H3qreemMmBPK7kyMaluekf8K9lJIV3Uz8ghAkpM61LhEfQJrwV6FqfjNylSNP0KeWfonjNcACwzEId6cjFgVm++3p/71z1iiIg/JcKXYrbqLGiBRy+KtR4scoCR6wi0TGrDduucTxmhUYd/VY5/0+xqVDWBqPkuhpTPftKqRvFgZl8LzT7xBYWz646hWuTpuz50LbjXPRWQq/Yfz7Gv0a+wD9GlqPQ8XKMvBoqTtzI/nWFUdC0sBnjuPw9kGImZmkcJZNwEkMnwXGuHg8+qCddmuENI2/n4Vze+Pb3fmm+Qv60daVAPWwv3yZ51DamquCiYOzPkOw15G/Qut3Sg035IExbMIk9dAn/sHMvOIkCTAV/wpkZ7Hxc1Ny8Ol0Z3898Wj+gM5/SucdL3DDvIBxQgUrF8m/GjE5vAkX4JtvaF6790MYZHTKs6NQk4FgI8GwNAMkEfK1mO9gULR4JIip/JEWpJjhLvjDJPh3Db9hp/XJSoybK/ah5Sn/SKLandBDTymLbiM2Qzr44uQv2BfgXew+2u2h36230tJ5tOqe8NR3Fh/j2ttjfDSLAgAZDfWzirX+U6dtUKt3OY7sfOlEBJ5thFHrI9L1zcVsv/VSC/jqKvcQNvh1cLMjBqzhDVcQK4Q/aA3mIn0EKkr8J98A1JmaVuCZ7RczxqG44zq9eafbWfyn/+N3HrkixqEo+dcjj8p+xwuGRTqcdtqvkllze9v8I8EvVnpm/clS8Ne2x/0W8q3A/QYoXsNU/CyvNJzwcrBSLJ/Ujmi0Y/g1VutpLQeCjc2G4S35Zfro29wJfTmjucBBE4YlM2X0QmpJewR12hLh4CDcbmZv0Ym2y1jfTnRGhQ6CJ7vJ1ezb0VSwlTJ8ZGhP9+Cpre4WNXcHXwUaZMp3D6xjKybaxlHyfm53WB4kbVaxusl9xDSVsxKOtbvdir9KXXt1sxIuS7UBdd8UKOUnv+Nixt6qTYkXj4IL2kdhu99Znyei83NP5wJ9+uFHjEJ/uCS5qoJG/CfeFnPegQzslwS2135d7Z1mGS02WNXk8RtoiCnhursBxe73du1uv10hBG2qzLlTauqqRVNaWylzTep10MJ+dOyMVbnuQnXXktirrMyHrGtBor8fzNkSG0vEPq57vWkE3tbhsh86dNxXWL/6u2je4mm/0GqH5LmSKAoeXc8SAYH5YnSHupiAbKxCoGzWbKFqmKl2zsBmPNrXbz+QzHFzOLw3oz40PXPuU68DYdOZqWy1itzNV1d9rW/O9e1gu2ker55/Vt06qpIX/sJO6UyuYqxZn76xNR1VhdOFMo72910K133NUCHqyvu/pfYtXtE6uEYRCbwwVue1pNBVNicSgoq2OS43JRqDjj9aH3ZDVFLQcCU5tirYpGa/1xm52nEvO2djLr9u64Y4yYytwqsapaEAmeDaYTyWuPnRPwaYr77P1VJgf0rdtI95BH1QC9ySqU/u0OUGxxwqbWHnXxdqJjJA7FBLmmO46yhnomt+JeuxWPfu+Hkjnlc4vYryGkrIZm7Oltk/7CtRg+3NO7UtObThSuWNP9jTmGFRM8SiAynzBne39K+Q0hj12Wc/SzoOaoA7kyfz9SCpHbeGsuq4dgMlU5fqxMpOi/XE43U/FbALxYCi+gtNUUoXliHfMRD/2l0HgnBZyY5vw+vHMjE/x63nqzsIBOGGGzPRFFp61LEUB05imcB9rJuS1nbZ9rUknq8tMovg0xRXOLdri80W/Gy+twHxskoc2un803z7+W38C \ No newline at end of file diff --git a/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 b/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 new file mode 100644 index 000000000..c9991c2f5 --- /dev/null +++ b/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDY3MjlmMTI0LTc3ZjQtMzVkMS0xMjQ5LTVhNjhhODZlZGEwZSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDY3MjlmMTI0LTc3ZjQtMzVkMS0xMjQ5LTVhNjhhODZlZGEwZSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+eUJXcmtUUjFtOGM1enVXOTQwMVVDV0VERHFnPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5MMmdNNHYyeDhKdUIzNEJ0bzdXS3VGeUIwSzkzME5DMDdiS1pMUkQ4cy8yK2VOa1RGajdRL2dWNTVqYTkwNzdZZGsrOTQ2RGtFZ3g3bkNYcEd2a1hLakpjV042MEZFYXNNREJrcHdGNXRISVh6WGg2NkF2NWpCbzRyTHc3M0ZSYUQwYW5nOU1WeW44VTdYWllrUkVHeTZraGxSdkU3emJwaWMzaTdLQVBZd2c9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iaW52YWxpZF9pbnJlc3BvbnNlIi8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NFoiIE5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QxNDo1OToxNFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjA3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QyMjo1NDoxNFoiIFNlc3Npb25JbmRleD0iXzUxYmUzNzk2NWZlYjU1NzlkODAzMTQxMDc2OTM2ZGMyZTlkMWQ5OGViZiI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4= \ No newline at end of file diff --git a/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 b/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 new file mode 100644 index 000000000..e7799e677 --- /dev/null +++ b/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGFhOWY2Y2I3LWZhODktZTBhOC02YTI3LTZhY2YwNzIyMDEyYSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2FwcC5tdWRhLm5vL3Nzby9jb25zdW1lIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiPg0KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZnhhYTlmNmNiNy1mYTg5LWUwYTgtNmEyNy02YWNmMDcyMjAxMmEiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPnZFU3d0czBJbEM0bG9SUnlTR1M4QktYSzhwOD08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+ZUVqd2RXZ1B3T1lRL3NYU1owQ1hvdjU1N25lQit3NDNOQWNncXdZckxrVmZIZVQxWTc4THgvUnRFQXU3c0ZvSXZleUVSVEhtVHFwTDlkVkZSWVhwemRYWFN1dytUYWdSVDFkNUxiakVONE5ibkNJbDNvYWZGdlBDZzhublVzNzIxTmVzNlBndFkxa3I4eTl6emlndS9hVlVXZ3FocUpac29BYnN3SEFlOVlBPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDZ1RDQ0Flb0NDUUNiT2xyV0RkWDdGVEFOQmdrcWhraUc5dzBCQVFVRkFEQ0JoREVMTUFrR0ExVUVCaE1DVGs4eEdEQVdCZ05WQkFnVEQwRnVaSEpsWVhNZ1UyOXNZbVZ5WnpFTU1Bb0dBMVVFQnhNRFJtOXZNUkF3RGdZRFZRUUtFd2RWVGtsT1JWUlVNUmd3RmdZRFZRUURFdzltWldsa1pTNWxjbXhoYm1jdWJtOHhJVEFmQmdrcWhraUc5dzBCQ1FFV0VtRnVaSEpsWVhOQWRXNXBibVYwZEM1dWJ6QWVGdzB3TnpBMk1UVXhNakF4TXpWYUZ3MHdOekE0TVRReE1qQXhNelZhTUlHRU1Rc3dDUVlEVlFRR0V3Sk9UekVZTUJZR0ExVUVDQk1QUVc1a2NtVmhjeUJUYjJ4aVpYSm5NUXd3Q2dZRFZRUUhFd05HYjI4eEVEQU9CZ05WQkFvVEIxVk9TVTVGVkZReEdEQVdCZ05WQkFNVEQyWmxhV1JsTG1WeWJHRnVaeTV1YnpFaE1COEdDU3FHU0liM0RRRUpBUllTWVc1a2NtVmhjMEIxYm1sdVpYUjBMbTV2TUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FEaXZiaFI3UDUxNngvUzNCcUt4dXBRZTBMT05vbGl1cGlCT2VzQ08zU0hiRHJsMytxOUliZm5mbUUwNHJOdU1jUHNJeEIxNjFUZERwSWVzTENuN2M4YVBISVNLT3RQbEFlVFpTbmI4UUF1N2FSalpxMytQYnJQNXVXM1RjZkNHUHRLVHl0SE9nZS9PbEpibzA3OGRWaFhRMTRkMUVEd1hKVzFyUlh1VXQ0QzhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUNEVmZwODZIT2JxWStlOEJVb1dROStWTVF4MUFTRG9oQmp3T3NnMld5a1VxUlhGK2RMZmNVSDlkV1I2M0N0WklLRkRiU3ROb21QblF6N25iSytvbnlnd0JzcFZFYm5IdVVpaFpxM1pVZG11bVFxQ3c0VXZzLzFVdnEzb3JPby9XSlZoVHl2TGdGVksyUWFyUTQvNjdPWmZIZDdSK1BPQlhob3BoU012MVpPbzwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4NCiAgPC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJwZng3ODQxOTkxYy1jNzNmLTQwMzUtZTJlZS1jMTcwYzBlMWQzZTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjE0WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHA6Ly9pZHAuZXhhbXBsZS5jb20vPC9zYW1sOklzc3Vlcj4gICAgDQogICAgPHNhbWw6U3ViamVjdD4NCiAgICAgIDxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9ImhlbGxvLmNvbSIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6TmFtZUlEPg0KICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPg0KICAgICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RCZWZvcmU9IjIwMjMtMDYtMTdUMTQ6NTk6MTRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9hcHAubXVkYS5uby9zc28vY29uc3VtZSIgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4Ii8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0wNi0xN1QxNDo1Mzo0NFoiIE5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QxNDo1OToxNFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDExLTA2LTE3VDE0OjU0OjA3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMS0wNi0xN1QyMjo1NDoxNFoiIFNlc3Npb25JbmRleD0iXzUxYmUzNzk2NWZlYjU1NzlkODAzMTQxMDc2OTM2ZGMyZTlkMWQ5OGViZiI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4= \ No newline at end of file diff --git a/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 b/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 new file mode 100644 index 000000000..2946ba8fc --- /dev/null +++ b/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGE3YjFlZGIwLTQwNzctNDU1OC0wNDVlLWU3OGUwNTc5NDViYSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeGE3YjFlZGIwLTQwNzctNDU1OC0wNDVlLWU3OGUwNTc5NDViYSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+YlBISEh5cm9IWXpNNElzWllpRFllcGkxUXQ4PTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5jMXhOV3o5RzR5MlR3ZjRHS1R5WDdIakNhM3BwT09LMkduV01wdTNSZWtreDk2K3NiV3d0c0duRkZkbS9DR2liRThnWWZIdFRIK2tQVDJqa2xPZmtjSVV1QXVGd3p4VTZGeGJOUW92aXVYUEZ6MlhVeWNlSFZPY2w0bWVoczZmZ2d4TXQySlEzaHZMUTZlckpnZWxPamNoSGdVZXNJb3AwV003RGZjQktxR1k9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL2FwcC5tdWRhLm5vL3Nzby9jb25zdW1lIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiLz4NCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 b/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 new file mode 100644 index 000000000..b2e6e80c3 --- /dev/null +++ b/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDc5MDA1OWViLWMzMjUtNjU3Yy01MjBmLTY2NTM5MGIxYmY3NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDc5MDA1OWViLWMzMjUtNjU3Yy01MjBmLTY2NTM5MGIxYmY3NyI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+cUYrL0p1ajgzMndYWXFSK0VTQXliVDRVT1VnPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5zMlc4MS9JZGlGaU80QXlpaTkyUHZkSEZ3c2VmVHV5eHhWUDNMSEFBNVZMZ1JGL0VrT3BsM2JQUFBmUGZOOFdPdkVwZ0R3QkdXNk00dCtiTDRYWmpNNjYxVWhQeW0yUTFYTG4zWU5qNVJ4d2JlWm9mVkdFVklGVGRwMG9sU1lQRUhXRldlYlQzOEF6VXQ1K2w3am9HQjk0WlhQRndDb1B0bEk2VG9FUCtQTWc9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMC0wNi0xN1QxNDo1OToxNFoiIFJlY2lwaWVudD0iaHR0cDovL2ludmFsaWQucmVjaXBlbnQuZXhhbXBsZS5jb20iIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCIvPg0KICAgICAgPC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMDYtMTdUMTQ6NTM6NDRaIiBOb3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMTQ6NTk6MTRaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDowN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMjEtMDYtMTdUMjI6NTQ6MTRaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/invalids/multiple_assertions.xml.base64 b/test/responses/invalids/multiple_assertions.xml.base64 new file mode 100644 index 000000000..228906e90 --- /dev/null +++ b/test/responses/invalids/multiple_assertions.xml.base64 @@ -0,0 +1,2 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIFZlcnNpb249IjIuMCIgSUQ9InBmeGE0NjU3NGRmLWIzYjAtYTA2YS0yM2M4LTYzNjQxMzE5ODc3MiIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzEzNTkwPC9zYW1sOklzc3Vlcj4NCiAgICA8ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgICAgIDxkczpTaWduZWRJbmZvPg0KICAgICAgICA8ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogICAgICAgIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4YTQ2NTc0ZGYtYjNiMC1hMDZhLTIzYzgtNjM2NDEzMTk4NzcyIj4NCiAgICAgICAgICA8ZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICAgIDxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPg0KICAgICAgICAgICAgPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICAgIDwvZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICA8ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz4NCiAgICAgICAgICA8ZHM6RGlnZXN0VmFsdWU+cEpRN01TL2VrNEtSUldHbXYvSDQzUmVIWU1zPTwvZHM6RGlnZXN0VmFsdWU+DQogICAgICAgIDwvZHM6UmVmZXJlbmNlPg0KICAgICAgPC9kczpTaWduZWRJbmZvPg0KICAgICAgPGRzOlNpZ25hdHVyZVZhbHVlPnlpdmVLY1BkRHB1RE5qNnNoclEzQUJ3ci9jQTNDcnlEMnBoRy94TFpzektXeFU1L21sYUt0OGV3YlpPZEtLdnRPczJwSEJ5NUR1YTNrOTRBRit6eEd5ZWw1Z09vd21veVhKcitBT3Ira1BPMHZsaTFWOG8zaFBQVVp3UmdTWDZROXBTMUNxUWdoS2lFYXNSeXlscXFKVWFQWXptT3pPRTgvWGxNa3dpV21PMD08L2RzOlNpZ25hdHVyZVZhbHVlPg0KICAgICAgPGRzOktleUluZm8+DQogICAgICAgIDxkczpYNTA5RGF0YT4NCiAgICAgICAgICA8ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUJyVENDQWFHZ0F3SUJBZ0lCQVRBREJnRUFNR2N4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUlV3RXdZRFZRUUhEQXhUWVc1MFlTQk5iMjVwWTJFeEVUQVBCZ05WQkFvTUNFOXVaVXh2WjJsdU1Sa3dGd1lEVlFRRERCQmhjSEF1YjI1bGJHOW5hVzR1WTI5dE1CNFhEVEV3TURNd09UQTVOVGcwTlZvWERURTFNRE13T1RBNU5UZzBOVm93WnpFTE1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ01Da05oYkdsbWIzSnVhV0V4RlRBVEJnTlZCQWNNREZOaGJuUmhJRTF2Ym1sallURVJNQThHQTFVRUNnd0lUMjVsVEc5bmFXNHhHVEFYQmdOVkJBTU1FR0Z3Y0M1dmJtVnNiMmRwYmk1amIyMHdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBT2pTdTFmalB5OGQ1dzRReUwxK3pkNGhJdzFNa2tmZjRXWS9UTEc4T1prVTVZVFNXbW1IUEQ1a3ZZSDV1b1hTLzZxUTgxcVhwUjJ3VjhDVG93WkpVTGcwOWRkUmRSbjhRc3FqMUZ5T0M1c2xFM3kyYloyb0Z1YTcyb2YvNDlmcHVqbkZUNktuUTYxQ0JNcWxEb1RRcU9UNjJ2R0o4blA2TVpXdkE2c3hxdWQ1QWdNQkFBRXdBd1lCQUFNQkFBPT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT4NCiAgICAgICAgPC9kczpYNTA5RGF0YT4NCiAgICAgIDwvZHM6S2V5SW5mbz4NCiAgICA8L2RzOlNpZ25hdHVyZT4NCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj5zdXBwb3J0QG9uZWxvZ2luLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiIFJlY2lwaWVudD0ie3JlY2lwaWVudH0iLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0xMS0xOFQyMTo1MjozN1oiIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+e2F1ZGllbmNlfTwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1NzozN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMTlUMjE6NTc6MzdaIiBTZXNzaW9uSW5kZXg9Il81MzFjMzJkMjgzYmRmZjdlMDRlNDg3YmNkYmM0ZGQ4ZCI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+ZGVtbzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0iYW5vdGhlcl92YWx1ZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+dmFsdWU8L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIFZlcnNpb249IjIuMCIgSUQ9InBmeGE0NjU3NGRmLWIzYjAtYTA2YS0yM2M4LTYzNjQxMzE5ODc3MyIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjQ1WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzEzNTkwPC9zYW1sOklzc3Vlcj4NCiAgICA8ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgICAgIDxkczpTaWduZWRJbmZvPg0KICAgICAgICA8ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogICAgICAgIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4YTQ2NTc0ZGYtYjNiMC1hMDZhLTIzYzgtNjM2NDEzMTk4NzcyIj4NCiAgICAgICAgICA8ZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICAgIDxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPg0KICAgICAgICAgICAgPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICAgIDwvZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICA8ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz4NCiAgICAgICAgICA8ZHM6RGlnZXN0VmFsdWU+cEpRN01TL2VrNEtSUldHbXYvSDQzUmVIWU1zPTwvZHM6RGlnZXN0VmFsdWU+DQogICAgICAgIDwvZHM6UmVmZXJlbmNlPg0KICAgICAgPC9kczpTaWduZWRJbmZvPg0KICAgICAgPGRzOlNpZ25hdHVyZVZhbHVlPnlpdmVLY1BkRHB1RE5qNnNoclEzQUJ3ci9jQTNDcnlEMnBoRy94TFpzektXeFU1L21sYUt0OGV3YlpPZEtLdnRPczJwSEJ5NUR1YTNrOTRBRit6eEd5ZWw1Z09vd21veVhKcitBT3Ira1BPMHZsaTFWOG8zaFBQVVp3UmdTWDZROXBTMUNxUWdoS2lFYXNSeXlscXFKVWFQWXptT3pPRTgvWGxNa3dpV21PMD08L2RzOlNpZ25hdHVyZVZhbHVlPg0KICAgICAgPGRzOktleUluZm8+DQogICAgICAgIDxkczpYNTA5RGF0YT4NCiAgICAgICAgICA8ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUJyVENDQWFHZ0F3SUJBZ0lCQVRBREJnRUFNR2N4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUlV3RXdZRFZRUUhEQXhUWVc1MFlTQk5iMjVwWTJFeEVUQVBCZ05WQkFvTUNFOXVaVXh2WjJsdU1Sa3dGd1lEVlFRRERCQmhjSEF1YjI1bGJHOW5hVzR1WTI5dE1CNFhEVEV3TURNd09UQTVOVGcwTlZvWERURTFNRE13T1RBNU5UZzBOVm93WnpFTE1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ01Da05oYkdsbWIzSnVhV0V4RlRBVEJnTlZCQWNNREZOaGJuUmhJRTF2Ym1sallURVJNQThHQTFVRUNnd0lUMjVsVEc5bmFXNHhHVEFYQmdOVkJBTU1FR0Z3Y0M1dmJtVnNiMmRwYmk1amIyMHdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBT2pTdTFmalB5OGQ1dzRReUwxK3pkNGhJdzFNa2tmZjRXWS9UTEc4T1prVTVZVFNXbW1IUEQ1a3ZZSDV1b1hTLzZxUTgxcVhwUjJ3VjhDVG93WkpVTGcwOWRkUmRSbjhRc3FqMUZ5T0M1c2xFM3kyYloyb0Z1YTcyb2YvNDlmcHVqbkZUNktuUTYxQ0JNcWxEb1RRcU9UNjJ2R0o4blA2TVpXdkE2c3hxdWQ1QWdNQkFBRXdBd1lCQUFNQkFBPT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT4NCiAgICAgICAgPC9kczpYNTA5RGF0YT4NCiAgICAgIDwvZHM6S2V5SW5mbz4NCiAgICA8L2RzOlNpZ25hdHVyZT4NCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj5zdXBwb3J0QG9uZWxvZ2luLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjo0NVoiIFJlY2lwaWVudD0ie3JlY2lwaWVudH0iLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0xMS0xOFQyMTo1MjozN1oiIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjo0NVoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+e2F1ZGllbmNlfTwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1Nzo0NVoiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMTlUMjE6NTc6NDVaIiBTZXNzaW9uSW5kZXg9Il81MzFjMzJkMjgzYmRmZjdlMDRlNDg3YmNkYmM0ZGQ4ZCI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+ZGVtbzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0iYW5vdGhlcl92YWx1ZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+dmFsdWU8L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg== + diff --git a/test/responses/invalids/multiple_signed.xml.base64 b/test/responses/invalids/multiple_signed.xml.base64 new file mode 100644 index 000000000..1bc4feca1 --- /dev/null +++ b/test/responses/invalids/multiple_signed.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJwZngwNWYzY2UxMC0xNjE1LWYzZWEtYTk4OC02MGUzODBiMzI5OWYiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiIgRGVzdGluYXRpb249Imh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl81ZmU5ZDZlNDk5YjJmMDkxMzIwNmFhYjNmNzE5MTcyOTA0OWJiODA3Ij48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9zaW1wbGVzYW1sL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZngwNWYzY2UxMC0xNjE1LWYzZWEtYTk4OC02MGUzODBiMzI5OWYiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPmpBZ290RjBKK1JLMS9LODd3MjRNTUMyK1pScz08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+cFhmM3Z3T1p2dGZtZjdNTWNPU0cwMzkyU213bnBJb0FqZ2dzVmErUlNJRE1Td0tTckwzcWw3SnlZQjVTaXZxL0xYODlUYXF5WTJ4MFBnTWl4YXY0bjFHMTFDM3NtbFJBTXJEZTZ2UnRJbUpVc2xTR2s5N3pQaHlvUStKNW9nUVBkNlZsTVR6OEtXemZxdE9QRGY1ZGwyWXlKcG9rZU9OVE0xemM0SkdNM0dBPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDZ1RDQ0Flb0NDUUNiT2xyV0RkWDdGVEFOQmdrcWhraUc5dzBCQVFVRkFEQ0JoREVMTUFrR0ExVUVCaE1DVGs4eEdEQVdCZ05WQkFnVEQwRnVaSEpsWVhNZ1UyOXNZbVZ5WnpFTU1Bb0dBMVVFQnhNRFJtOXZNUkF3RGdZRFZRUUtFd2RWVGtsT1JWUlVNUmd3RmdZRFZRUURFdzltWldsa1pTNWxjbXhoYm1jdWJtOHhJVEFmQmdrcWhraUc5dzBCQ1FFV0VtRnVaSEpsWVhOQWRXNXBibVYwZEM1dWJ6QWVGdzB3TnpBMk1UVXhNakF4TXpWYUZ3MHdOekE0TVRReE1qQXhNelZhTUlHRU1Rc3dDUVlEVlFRR0V3Sk9UekVZTUJZR0ExVUVDQk1QUVc1a2NtVmhjeUJUYjJ4aVpYSm5NUXd3Q2dZRFZRUUhFd05HYjI4eEVEQU9CZ05WQkFvVEIxVk9TVTVGVkZReEdEQVdCZ05WQkFNVEQyWmxhV1JsTG1WeWJHRnVaeTV1YnpFaE1COEdDU3FHU0liM0RRRUpBUllTWVc1a2NtVmhjMEIxYm1sdVpYUjBMbTV2TUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FEaXZiaFI3UDUxNngvUzNCcUt4dXBRZTBMT05vbGl1cGlCT2VzQ08zU0hiRHJsMytxOUliZm5mbUUwNHJOdU1jUHNJeEIxNjFUZERwSWVzTENuN2M4YVBISVNLT3RQbEFlVFpTbmI4UUF1N2FSalpxMytQYnJQNXVXM1RjZkNHUHRLVHl0SE9nZS9PbEpibzA3OGRWaFhRMTRkMUVEd1hKVzFyUlh1VXQ0QzhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUNEVmZwODZIT2JxWStlOEJVb1dROStWTVF4MUFTRG9oQmp3T3NnMld5a1VxUlhGK2RMZmNVSDlkV1I2M0N0WklLRkRiU3ROb21QblF6N25iSytvbnlnd0JzcFZFYm5IdVVpaFpxM1pVZG11bVFxQ3c0VXZzLzFVdnEzb3JPby9XSlZoVHl2TGdGVksyUWFyUTQvNjdPWmZIZDdSK1BPQlhob3BoU012MVpPbzwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZngwNWYzY2UxMC0xNjE1LWYzZWEtYTk4OC02MGUzODBiMzI5OWYiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPmpBZ290RjBKK1JLMS9LODd3MjRNTUMyK1pScz08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+cFhmM3Z3T1p2dGZtZjdNTWNPU0cwMzkyU213bnBJb0FqZ2dzVmErUlNJRE1Td0tTckwzcWw3SnlZQjVTaXZxL0xYODlUYXF5WTJ4MFBnTWl4YXY0bjFHMTFDM3NtbFJBTXJEZTZ2UnRJbUpVc2xTR2s5N3pQaHlvUStKNW9nUVBkNlZsTVR6OEtXemZxdE9QRGY1ZGwyWXlKcG9rZU9OVE0xemM0SkdNM0dBPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDZ1RDQ0Flb0NDUUNiT2xyV0RkWDdGVEFOQmdrcWhraUc5dzBCQVFVRkFEQ0JoREVMTUFrR0ExVUVCaE1DVGs4eEdEQVdCZ05WQkFnVEQwRnVaSEpsWVhNZ1UyOXNZbVZ5WnpFTU1Bb0dBMVVFQnhNRFJtOXZNUkF3RGdZRFZRUUtFd2RWVGtsT1JWUlVNUmd3RmdZRFZRUURFdzltWldsa1pTNWxjbXhoYm1jdWJtOHhJVEFmQmdrcWhraUc5dzBCQ1FFV0VtRnVaSEpsWVhOQWRXNXBibVYwZEM1dWJ6QWVGdzB3TnpBMk1UVXhNakF4TXpWYUZ3MHdOekE0TVRReE1qQXhNelZhTUlHRU1Rc3dDUVlEVlFRR0V3Sk9UekVZTUJZR0ExVUVDQk1QUVc1a2NtVmhjeUJUYjJ4aVpYSm5NUXd3Q2dZRFZRUUhFd05HYjI4eEVEQU9CZ05WQkFvVEIxVk9TVTVGVkZReEdEQVdCZ05WQkFNVEQyWmxhV1JsTG1WeWJHRnVaeTV1YnpFaE1COEdDU3FHU0liM0RRRUpBUllTWVc1a2NtVmhjMEIxYm1sdVpYUjBMbTV2TUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FEaXZiaFI3UDUxNngvUzNCcUt4dXBRZTBMT05vbGl1cGlCT2VzQ08zU0hiRHJsMytxOUliZm5mbUUwNHJOdU1jUHNJeEIxNjFUZERwSWVzTENuN2M4YVBISVNLT3RQbEFlVFpTbmI4UUF1N2FSalpxMytQYnJQNXVXM1RjZkNHUHRLVHl0SE9nZS9PbEpibzA3OGRWaFhRMTRkMUVEd1hKVzFyUlh1VXQ0QzhRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUNEVmZwODZIT2JxWStlOEJVb1dROStWTVF4MUFTRG9oQmp3T3NnMld5a1VxUlhGK2RMZmNVSDlkV1I2M0N0WklLRkRiU3ROb21QblF6N25iSytvbnlnd0JzcFZFYm5IdVVpaFpxM1pVZG11bVFxQ3c0VXZzLzFVdnEzb3JPby9XSlZoVHl2TGdGVksyUWFyUTQvNjdPWmZIZDdSK1BPQlhob3BoU012MVpPbzwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJwZnhiNGVjOWM4YS00OGViLWZkYTItN2Y3NC1mYTFhMTA1YTk5ZmUiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvc2ltcGxlc2FtbC9zYW1sMi9pZHAvbWV0YWRhdGEucGhwPC9zYW1sOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4NCiAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4YjRlYzljOGEtNDhlYi1mZGEyLTdmNzQtZmExYTEwNWE5OWZlIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPjxkczpEaWdlc3RWYWx1ZT5obnBUOWN1ZTZ0Qks0SVpPajUwZU1tM21DZ0k9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPnJBMGZMWjdLa2wzSndzTEtYRHdhRmNnNkxuS0hSeklVcng2bU5YMVRvOXpNQ3ZDVUQvb1BZVmFFZU10bnhZWTQ2ZnNtYU52c0tpdWdpeGRWekxMMzhLbUFZOHVNVGV6aEd2ejJWZUhVb3I0dTRoeGZRbHNPdDJmdXpBRTZEYnF2SVZLMHdqeDNSWTA4WC91T0NSOXVQY3B5NEJCc1EzeFBsNFF2aXBKalNOTT08L2RzOlNpZ25hdHVyZVZhbHVlPg0KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ2dUQ0NBZW9DQ1FDYk9scldEZFg3RlRBTkJna3Foa2lHOXcwQkFRVUZBRENCaERFTE1Ba0dBMVVFQmhNQ1RrOHhHREFXQmdOVkJBZ1REMEZ1WkhKbFlYTWdVMjlzWW1WeVp6RU1NQW9HQTFVRUJ4TURSbTl2TVJBd0RnWURWUVFLRXdkVlRrbE9SVlJVTVJnd0ZnWURWUVFERXc5bVpXbGtaUzVsY214aGJtY3VibTh4SVRBZkJna3Foa2lHOXcwQkNRRVdFbUZ1WkhKbFlYTkFkVzVwYm1WMGRDNXViekFlRncwd056QTJNVFV4TWpBeE16VmFGdzB3TnpBNE1UUXhNakF4TXpWYU1JR0VNUXN3Q1FZRFZRUUdFd0pPVHpFWU1CWUdBMVVFQ0JNUFFXNWtjbVZoY3lCVGIyeGlaWEpuTVF3d0NnWURWUVFIRXdOR2IyOHhFREFPQmdOVkJBb1RCMVZPU1U1RlZGUXhHREFXQmdOVkJBTVREMlpsYVdSbExtVnliR0Z1Wnk1dWJ6RWhNQjhHQ1NxR1NJYjNEUUVKQVJZU1lXNWtjbVZoYzBCMWJtbHVaWFIwTG01dk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRGl2YmhSN1A1MTZ4L1MzQnFLeHVwUWUwTE9Ob2xpdXBpQk9lc0NPM1NIYkRybDMrcTlJYmZuZm1FMDRyTnVNY1BzSXhCMTYxVGREcEllc0xDbjdjOGFQSElTS090UGxBZVRaU25iOFFBdTdhUmpacTMrUGJyUDV1VzNUY2ZDR1B0S1R5dEhPZ2UvT2xKYm8wNzhkVmhYUTE0ZDFFRHdYSlcxclJYdVV0NEM4UUlEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRHQkFDRFZmcDg2SE9icVkrZThCVW9XUTkrVk1ReDFBU0RvaEJqd09zZzJXeWtVcVJYRitkTGZjVUg5ZFdSNjNDdFpJS0ZEYlN0Tm9tUG5RejduYksrb255Z3dCc3BWRWJuSHVVaWhacTNaVWRtdW1RcUN3NFV2cy8xVXZxM29yT28vV0pWaFR5dkxnRlZLMlFhclE0LzY3T1pmSGQ3UitQT0JYaG9waFNNdjFaT288L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9Imh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvbWV0YWRhdGEucGhwIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+NDkyODgyNjE1YWNmMzFjODA5NmI2MjcyNDVkNzZhZTUzMDM2YzA5MDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiIFJlY2lwaWVudD0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNC0wMi0xOVQwMTozNjozMVoiIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxNC0wMi0xOVQwOTozNzowMVoiIFNlc3Npb25JbmRleD0iXzYyNzNkNzdiOGNkZTBjMzMzZWM3OWQyMmE5ZmEwMDAzYjlmZTJkNzVjYiI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zbWFydGluPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNtYXJ0aW5AeWFjby5lczwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJjbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+U2l4dG8zPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9InNuIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5NYXJ0aW4yPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImVkdVBlcnNvbkFmZmlsaWF0aW9uIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj51c2VyPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPmFkbWluPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/invalids/no_authnstatement.xml.base64 b/test/responses/invalids/no_authnstatement.xml.base64 new file mode 100644 index 000000000..d116b7b55 --- /dev/null +++ b/test/responses/invalids/no_authnstatement.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGRmNWRiN2JiLTYwZDgtMWZhNi00OTBhLWFjMWMyZThjYWFhMiIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciPjxzYW1sOklzc3Vlcj5odHRwczovL3BpdGJ1bGsubm8taXAub3JnL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeGRmNWRiN2JiLTYwZDgtMWZhNi00OTBhLWFjMWMyZThjYWFhMiI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+bGJTZmtFR0JsNmZEN0JBc1prU25wYmQyNGJFPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT56Y3YwSitsZ0V4R2tjSVVKYVp5ajdvZkFrY1VZc3dvckpiei9xdEo0WDBmSEtMYXB1eE0xYmlEbnJMTm5wUXhNSkJ3K092WG9sdWdHdVZBeEVyYmE5NTV2QlFtQTRCZXRZS0tKR09XcTkyMWpxKzVhdThtOWQzM2M1UTR6cDYzZld4UnRKV3AyVU05UnZ0aWd6enk2WWg0SE5yNVNkdUhzd1FJeFM2ZEQ2Lzg9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeGI0ZWM5YzhhLTQ4ZWItZmRhMi03Zjc0LWZhMWExMDVhOTlmZSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9zaW1wbGVzYW1sL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxzYW1sOlN1YmplY3Q+PHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9tZXRhZGF0YS5waHAiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj40OTI4ODI2MTVhY2YzMWM4MDk2YjYyNzI0NWQ3NmFlNTMwMzZjMDkwPC9zYW1sOk5hbWVJRD48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDIzLTA4LTIzVDA2OjU3OjAxWiIgUmVjaXBpZW50PSJodHRwczovL3BpdGJ1bGsubm8taXAub3JnL25ld29uZWxvZ2luL2RlbW8xL2luZGV4LnBocD9hY3MiIEluUmVzcG9uc2VUbz0iT05FTE9HSU5fNWZlOWQ2ZTQ5OWIyZjA5MTMyMDZhYWIzZjcxOTE3MjkwNDliYjgwNyIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE0LTAyLTE5VDAxOjM2OjMxWiIgTm90T25PckFmdGVyPSIyMDIzLTA4LTIzVDA2OjU3OjAxWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL3BpdGJ1bGsubm8taXAub3JnL25ld29uZWxvZ2luL2RlbW8xL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c21hcnRpbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zbWFydGluQHlhY28uZXM8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iY24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPlNpeHRvMzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJzbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+TWFydGluMjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJlZHVQZXJzb25BZmZpbGlhdGlvbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dXNlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5hZG1pbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/invalids/no_conditions.xml.base64 b/test/responses/invalids/no_conditions.xml.base64 new file mode 100644 index 000000000..4b73a83ee --- /dev/null +++ b/test/responses/invalids/no_conditions.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGQyMjJkZWI1LTZkMjktNWFiZC05NmM0LWFlOTk5ODZhYmVkNSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciPjxzYW1sOklzc3Vlcj5odHRwczovL3BpdGJ1bGsubm8taXAub3JnL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeGQyMjJkZWI1LTZkMjktNWFiZC05NmM0LWFlOTk5ODZhYmVkNSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+Sm03Qm5JTEJ3V2h2TW1ZTjd4WG01dDR0ZEZVPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5XRGw3K2RMclN4Ym95bTNWZzBXWTBrLzVDNWZxaDNNUWZPcXQraExDTXMwKzl3ekY4SHduWlJwLzRCMlJGOVBiUVAzc1d6VUY5QWNWeUErUFM4bU5aUnRzRzN4amFabE5BMWV3ZlQ3blFHZ1EvUkxLckhHeW9Bc3VaT0pLTDNqVjJiOGFSTE8rdSsrcmdoZUZSWm1wTkxVanBFTkdFZ3ZWc3ptcGN5aHFCd2c9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeGI0ZWM5YzhhLTQ4ZWItZmRhMi03Zjc0LWZhMWExMDVhOTlmZSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9zaW1wbGVzYW1sL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxzYW1sOlN1YmplY3Q+PHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9tZXRhZGF0YS5waHAiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj40OTI4ODI2MTVhY2YzMWM4MDk2YjYyNzI0NWQ3NmFlNTMwMzZjMDkwPC9zYW1sOk5hbWVJRD48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDIzLTA4LTIzVDA2OjU3OjAxWiIgUmVjaXBpZW50PSJodHRwczovL3BpdGJ1bGsubm8taXAub3JnL25ld29uZWxvZ2luL2RlbW8xL2luZGV4LnBocD9hY3MiIEluUmVzcG9uc2VUbz0iT05FTE9HSU5fNWZlOWQ2ZTQ5OWIyZjA5MTMyMDZhYWIzZjcxOTE3MjkwNDliYjgwNyIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNC0wMi0xOVQwMTozNzowMVoiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTQtMDItMTlUMDk6Mzc6MDFaIiBTZXNzaW9uSW5kZXg9Il82MjczZDc3YjhjZGUwYzMzM2VjNzlkMjJhOWZhMDAwM2I5ZmUyZDc1Y2IiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpBdXRoblN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c21hcnRpbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zbWFydGluQHlhY28uZXM8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iY24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPlNpeHRvMzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJzbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+TWFydGluMjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJlZHVQZXJzb25BZmZpbGlhdGlvbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dXNlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5hZG1pbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/invalids/no_id.xml.base64 b/test/responses/invalids/no_id.xml.base64 new file mode 100644 index 000000000..11db26539 --- /dev/null +++ b/test/responses/invalids/no_id.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAyLTE5VDAxOjA1OjQ5WiIgRGVzdGluYXRpb249Imh0dHBzOi8vZXhhbXBsZS5jb20vbmV3b25lbG9naW4vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl9hZjNkNGE3MTBmYzhiMzA1ODg0Yjk2ZDAwOTRhYjYyODgwMmY1NjkyIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9leGFtcGxlLmNvbS9zaW1wbGVzYW1sL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJwZng3ZTQ4ZmQ3NS1jZTJiLTYwZWQtMjllZS1lNzk4NDZjOTU5YzYiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAyLTE5VDAxOjA1OjQ5WiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vZXhhbXBsZS5jb20vc2ltcGxlc2FtbC9zYW1sMi9pZHAvbWV0YWRhdGEucGhwPC9zYW1sOklzc3Vlcj4NCjxzYW1sOlN1YmplY3Q+PHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaHR0cHM6Ly9leGFtcGxlLmNvbS9uZXdvbmVsb2dpbi9kZW1vMS9tZXRhZGF0YS5waHAiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj40OTI4ODI2MTVhY2YzMWM4MDk2YjYyNzI0NWQ3NmFlNTMwMzZjMDkwPC9zYW1sOk5hbWVJRD48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDE0LTAyLTE5VDAxOjEwOjQ5WiIgUmVjaXBpZW50PSJodHRwczovL2V4YW1wbGUuY29tL25ld29uZWxvZ2luL2RlbW8xL2luZGV4LnBocD9hY3MiIEluUmVzcG9uc2VUbz0iT05FTE9HSU5fYWYzZDRhNzEwZmM4YjMwNTg4NGI5NmQwMDk0YWI2Mjg4MDJmNTY5MiIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE0LTAyLTE5VDAxOjA1OjE5WiIgTm90T25PckFmdGVyPSIyMDE0LTAyLTE5VDAxOjEwOjQ5WiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL2V4YW1wbGUuY29tL25ld29uZWxvZ2luL2RlbW8xL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTQtMDItMThUMTk6NDI6MjBaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDE0LTAyLTE5VDA5OjA1OjQ5WiIgU2Vzc2lvbkluZGV4PSJfMGY0ZjE4OGRjMWJmZDNiZmVhMzZhMTYzNGE3NDQxNTgzYWZjM2IzNzgxIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJ1aWQiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnVzZXI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dXNlckBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJjbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dGVzdDwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJzbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dXNlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJlZHVQZXJzb25BZmZpbGlhdGlvbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dXNlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5hZG1pbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== diff --git a/test/responses/invalids/no_issuer_assertion.xml.base64 b/test/responses/invalids/no_issuer_assertion.xml.base64 new file mode 100644 index 000000000..46094a6fe --- /dev/null +++ b/test/responses/invalids/no_issuer_assertion.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDBlNmM5NjUzLTEwNjgtYzhjNS1iNzVjLWU2OTA1ZTE0M2Q0NCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciPjxzYW1sOklzc3Vlcj5odHRwczovL3BpdGJ1bGsubm8taXAub3JnL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDBlNmM5NjUzLTEwNjgtYzhjNS1iNzVjLWU2OTA1ZTE0M2Q0NCI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+KzhLSEl6dHh6SXNzMzNZZzlzRTVjTDlBRFpBPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5yK21CNi9NU3pSS0VGNi9NZGY4M29QeE9ZelFWQ2IvUVIvNlVieG10cmVqbnRFRnN2ZFZSckhmMmd5TUUyZTBGd21ta3JQbEtzcHl2ZDhXbVN2ckV0T0pZaERLRWRYUThtUnRmZWgvY1M4M3pFYmRGSG9ubTd2YkJiU2VxSDBIN2g3S1UxSStqeEwyZVRpQWlubkpHeWhhVHNmaVAxNzdXZmlXVmQ4SHBOY289PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeGI0ZWM5YzhhLTQ4ZWItZmRhMi03Zjc0LWZhMWExMDVhOTlmZSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIj48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9Imh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvbWV0YWRhdGEucGhwIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+NDkyODgyNjE1YWNmMzFjODA5NmI2MjcyNDVkNzZhZTUzMDM2YzA5MDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiIFJlY2lwaWVudD0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNC0wMi0xOVQwMTozNjozMVoiIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxNC0wMi0xOVQwOTozNzowMVoiIFNlc3Npb25JbmRleD0iXzYyNzNkNzdiOGNkZTBjMzMzZWM3OWQyMmE5ZmEwMDAzYjlmZTJkNzVjYiI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zbWFydGluPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNtYXJ0aW5AeWFjby5lczwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJjbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+U2l4dG8zPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9InNuIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5NYXJ0aW4yPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImVkdVBlcnNvbkFmZmlsaWF0aW9uIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj51c2VyPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPmFkbWluPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/invalids/no_issuer_response.xml.base64 b/test/responses/invalids/no_issuer_response.xml.base64 new file mode 100644 index 000000000..0e498d445 --- /dev/null +++ b/test/responses/invalids/no_issuer_response.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJwZnhmMTA1MTkwNy0wZDZjLWI0NjctZjBiNC1kMDI4YTU4ZjNmNzIiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiIgRGVzdGluYXRpb249Imh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl81ZmU5ZDZlNDk5YjJmMDkxMzIwNmFhYjNmNzE5MTcyOTA0OWJiODA3Ij48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiBJRD0icGZ4NGFhZGFlMTQtMmY5MC0xZDI1LWJlOTAtYjdjMzI3NzdkODU5IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wMi0xOVQwMTozNzowMVoiPjxzYW1sOklzc3Vlcj5odHRwczovL3BpdGJ1bGsubm8taXAub3JnL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDRhYWRhZTE0LTJmOTAtMWQyNS1iZTkwLWI3YzMyNzc3ZDg1OSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+T1R6Slg2cmNnUXdnM3dsOEZGMUZkUWFYY1QwPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5GQlRWMXVGVk1WQ0NXelNvdTFxK3kvMzRZVVp1RnlLUzFyaktEREV0aHNVV0ZnVU10S3pQcU9VOFc2enN2MmdZaG0xQ09qd01yenFZUG5WTGViWmtQZ0VNYUlRZW9DR1M0M0pqYllzWk9sakgxZWo5Z3Z6SDM3NHBZMUd6UUx1QXllYmxlL3B4ZmZSMEY5NklYbnFjbjFySnJQM1puR0k1RGcxV3BpbVphWTQ9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWw6U3ViamVjdD48c2FtbDpOYW1lSUQgU1BOYW1lUXVhbGlmaWVyPSJodHRwczovL3BpdGJ1bGsubm8taXAub3JnL25ld29uZWxvZ2luL2RlbW8xL21ldGFkYXRhLnBocCIgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPjQ5Mjg4MjYxNWFjZjMxYzgwOTZiNjI3MjQ1ZDc2YWU1MzAzNmMwOTA8L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMjMtMDgtMjNUMDY6NTc6MDFaIiBSZWNpcGllbnQ9Imh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl81ZmU5ZDZlNDk5YjJmMDkxMzIwNmFhYjNmNzE5MTcyOTA0OWJiODA3Ii8+PC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+PC9zYW1sOlN1YmplY3Q+PHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTQtMDItMTlUMDE6MzY6MzFaIiBOb3RPbk9yQWZ0ZXI9IjIwMjMtMDgtMjNUMDY6NTc6MDFaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjxzYW1sOkF1ZGllbmNlPmh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvbWV0YWRhdGEucGhwPC9zYW1sOkF1ZGllbmNlPjwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPjwvc2FtbDpDb25kaXRpb25zPjxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNC0wMi0xOVQwMTozNzowMVoiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTQtMDItMTlUMDk6Mzc6MDFaIiBTZXNzaW9uSW5kZXg9Il82MjczZDc3YjhjZGUwYzMzM2VjNzlkMjJhOWZhMDAwM2I5ZmUyZDc1Y2IiPjxzYW1sOkF1dGhuQ29udGV4dD48c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj48L3NhbWw6QXV0aG5Db250ZXh0Pjwvc2FtbDpBdXRoblN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlIE5hbWU9InVpZCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+c21hcnRpbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zbWFydGluQHlhY28uZXM8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iY24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPlNpeHRvMzwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJzbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+TWFydGluMjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJlZHVQZXJzb25BZmZpbGlhdGlvbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dXNlcjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5hZG1pbjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/invalids/no_nameid.xml.base64 b/test/responses/invalids/no_nameid.xml.base64 new file mode 100644 index 000000000..3c2b6d9be --- /dev/null +++ b/test/responses/invalids/no_nameid.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzMmFlZDY3LTgyMGYtNDI5Ni0wYzIwLTIwNWExMGRkNTc4NyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPg0KICAgICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMjAtMDYtMTdUMTQ6NTk6MTRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiLz4NCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg== diff --git a/test/responses/invalids/no_saml2.xml.base64 b/test/responses/invalids/no_saml2.xml.base64 new file mode 100644 index 000000000..71e7fbef6 --- /dev/null +++ b/test/responses/invalids/no_saml2.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlDQogICAgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMDpwcm90b2NvbCINCiAgICBNYWpvclZlcnNpb249IjEiIE1pbm9yVmVyc2lvbj0iMSINCiAgICBSZXNwb25zZUlEPSJiMDdiODA0Yy03YzI5LWVhMTYtNzMwMC00ZjNkNmY3OTI4YWMiDQogICAgSW5SZXNwb25zZVRvPSJhYWYyMzE5Ni0xNzczLTIxMTMtNDc0YS1mZTExNDQxMmFiNzIiDQogICAgSXNzdWVJbnN0YW50PSIyMDA2LTA3LTE3VDIyOjI2OjQxWiI+DQogIDxzYW1sOkFzc2VydGlvbg0KICAgIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMDphc3NlcnRpb24iDQogICAgTWFqb3JWZXJzaW9uPSIxIiBNaW5vclZlcnNpb249IjEiDQogICAgQXNzZXJ0aW9uSUQ9ImJ1R3hjRzRnSUxnNU5sb2N5TGNjRHo2aVhyVWEiDQogICAgSXNzdWVyPSJodHRwczovL2lkcC5leGFtcGxlLm9yZy9zYW1sIg0KICAgIElzc3VlSW5zdGFudD0iMjAwMi0wNi0xOVQxNzowNTozNy43OTVaIj4NCiAgICA8c2FtbDpDb25kaXRpb25zDQogICAgICBOb3RCZWZvcmU9IjIwMDItMDYtMTlUMTc6MDA6MzcuNzk1WiINCiAgICAgIE5vdE9uT3JBZnRlcj0iMjAwMi0wNi0xOVQxNzoxMDozNy43OTVaIi8+DQogICAgPHNhbWw6QXV0aGVudGljYXRpb25TdGF0ZW1lbnQNCiAgICAgIEF1dGhlbnRpY2F0aW9uTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjA6YW06cGFzc3dvcmQiDQogICAgICBBdXRoZW50aWNhdGlvbkluc3RhbnQ9IjIwMDItMDYtMTlUMTc6MDU6MTcuNzA2WiI+DQogICAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgICA8c2FtbDpOYW1lSWRlbnRpZmllcg0KICAgICAgICAgIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj4NCiAgICAgICAgICB1c2VyQGlkcC5leGFtcGxlLm9yZw0KICAgICAgICA8L3NhbWw6TmFtZUlkZW50aWZpZXI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgICAgICAgPHNhbWw6Q29uZmlybWF0aW9uTWV0aG9kPg0KICAgICAgICAgICAgdXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4wOmNtOmJlYXJlcg0KICAgICAgICAgIDwvc2FtbDpDb25maXJtYXRpb25NZXRob2Q+DQogICAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPC9zYW1sOkF1dGhlbnRpY2F0aW9uU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KICA8L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/invalids/no_signature.xml.base64 b/test/responses/invalids/no_signature.xml.base64 new file mode 100644 index 000000000..bed73c5c3 --- /dev/null +++ b/test/responses/invalids/no_signature.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJwZngwNWYzY2UxMC0xNjE1LWYzZWEtYTk4OC02MGUzODBiMzI5OWYiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiIgRGVzdGluYXRpb249Imh0dHBzOi8vZXhhbXBsZS5jb20vbmV3b25lbG9naW4vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl81ZmU5ZDZlNDk5YjJmMDkxMzIwNmFhYjNmNzE5MTcyOTA0OWJiODA3Ij48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9leGFtcGxlLmNvbS9zaW1wbGVzYW1sL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJwZnhiNGVjOWM4YS00OGViLWZkYTItN2Y3NC1mYTFhMTA1YTk5ZmUiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vZXhhbXBsZS5jb20vc2ltcGxlc2FtbC9zYW1sMi9pZHAvbWV0YWRhdGEucGhwPC9zYW1sOklzc3Vlcj48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9Imh0dHBzOi8vZXhhbXBsZS5jb20vbmV3b25lbG9naW4vZGVtbzEvbWV0YWRhdGEucGhwIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+NDkyODgyNjE1YWNmMzFjODA5NmI2MjcyNDVkNzZhZTUzMDM2YzA5MDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiIFJlY2lwaWVudD0iaHR0cHM6Ly9leGFtcGxlLmNvbS9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNC0wMi0xOVQwMTozNjozMVoiIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cHM6Ly9leGFtcGxlLmNvbS9uZXdvbmVsb2dpbi9kZW1vMS9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxNC0wMi0xOVQwOTozNzowMVoiIFNlc3Npb25JbmRleD0iXzYyNzNkNzdiOGNkZTBjMzMzZWM3OWQyMmE5ZmEwMDAzYjlmZTJkNzVjYiI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4= diff --git a/test/responses/invalids/no_status.xml.base64 b/test/responses/invalids/no_status.xml.base64 new file mode 100644 index 000000000..4f5905e91 --- /dev/null +++ b/test/responses/invalids/no_status.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgVmVyc2lvbj0iMi4wIiBJRD0icGZ4YTQ2NTc0ZGYtYjNiMC1hMDZhLTIzYzgtNjM2NDEzMTk4NzcyIiBJc3N1ZUluc3RhbnQ9IjIwMTAtMTEtMThUMjE6NTc6MzdaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29tL3NhbWwvbWV0YWRhdGEvMTM1OTA8L3NhbWw6SXNzdWVyPg0KICAgIDxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICAgICAgPGRzOlNpZ25lZEluZm8+DQogICAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgICAgICAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZnhhNDY1NzRkZi1iM2IwLWEwNmEtMjNjOC02MzY0MTMxOTg3NzIiPg0KICAgICAgICAgIDxkczpUcmFuc2Zvcm1zPg0KICAgICAgICAgICAgPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+DQogICAgICAgICAgICA8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgICAgICAgPC9kczpUcmFuc2Zvcm1zPg0KICAgICAgICAgIDxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPg0KICAgICAgICAgIDxkczpEaWdlc3RWYWx1ZT5wSlE3TVMvZWs0S1JSV0dtdi9INDNSZUhZTXM9PC9kczpEaWdlc3RWYWx1ZT4NCiAgICAgICAgPC9kczpSZWZlcmVuY2U+DQogICAgICA8L2RzOlNpZ25lZEluZm8+DQogICAgICA8ZHM6U2lnbmF0dXJlVmFsdWU+eWl2ZUtjUGREcHVETmo2c2hyUTNBQndyL2NBM0NyeUQycGhHL3hMWnN6S1d4VTUvbWxhS3Q4ZXdiWk9kS0t2dE9zMnBIQnk1RHVhM2s5NEFGK3p4R3llbDVnT293bW95WEpyK0FPcitrUE8wdmxpMVY4bzNoUFBVWndSZ1NYNlE5cFMxQ3FRZ2hLaUVhc1J5eWxxcUpVYVBZem1Pek9FOC9YbE1rd2lXbU8wPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQogICAgICA8ZHM6S2V5SW5mbz4NCiAgICAgICAgPGRzOlg1MDlEYXRhPg0KICAgICAgICAgIDxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQnJUQ0NBYUdnQXdJQkFnSUJBVEFEQmdFQU1HY3hDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SVXdFd1lEVlFRSERBeFRZVzUwWVNCTmIyNXBZMkV4RVRBUEJnTlZCQW9NQ0U5dVpVeHZaMmx1TVJrd0Z3WURWUVFEREJCaGNIQXViMjVsYkc5bmFXNHVZMjl0TUI0WERURXdNRE13T1RBNU5UZzBOVm9YRFRFMU1ETXdPVEE1TlRnME5Wb3daekVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhGVEFUQmdOVkJBY01ERk5oYm5SaElFMXZibWxqWVRFUk1BOEdBMVVFQ2d3SVQyNWxURzluYVc0eEdUQVhCZ05WQkFNTUVHRndjQzV2Ym1Wc2IyZHBiaTVqYjIwd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFPalN1MWZqUHk4ZDV3NFF5TDEremQ0aEl3MU1ra2ZmNFdZL1RMRzhPWmtVNVlUU1dtbUhQRDVrdllINXVvWFMvNnFRODFxWHBSMndWOENUb3daSlVMZzA5ZGRSZFJuOFFzcWoxRnlPQzVzbEUzeTJiWjJvRnVhNzJvZi80OWZwdWpuRlQ2S25RNjFDQk1xbERvVFFxT1Q2MnZHSjhuUDZNWld2QTZzeHF1ZDVBZ01CQUFFd0F3WUJBQU1CQUE9PTwvZHM6WDUwOUNlcnRpZmljYXRlPg0KICAgICAgICA8L2RzOlg1MDlEYXRhPg0KICAgICAgPC9kczpLZXlJbmZvPg0KICAgIDwvZHM6U2lnbmF0dXJlPg0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnN1cHBvcnRAb25lbG9naW4uY29tPC9zYW1sOk5hbWVJRD4NCiAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDEwLTExLTE4VDIyOjAyOjM3WiIgUmVjaXBpZW50PSJ7cmVjaXBpZW50fSIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTExLTE4VDIxOjUyOjM3WiIgTm90T25PckFmdGVyPSIyMDEwLTExLTE4VDIyOjAyOjM3WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT57YXVkaWVuY2V9PC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOVQyMTo1NzozN1oiIFNlc3Npb25JbmRleD0iXzUzMWMzMmQyODNiZGZmN2UwNGU0ODdiY2RiYzRkZDhkIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5kZW1vPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJhbm90aGVyX3ZhbHVlIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj52YWx1ZTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+ diff --git a/test/responses/invalids/no_status_code.xml.base64 b/test/responses/invalids/no_status_code.xml.base64 new file mode 100644 index 000000000..94c439b24 --- /dev/null +++ b/test/responses/invalids/no_status_code.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij4NCiAgPHNhbWxwOlN0YXR1cz48L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgVmVyc2lvbj0iMi4wIiBJRD0icGZ4YTQ2NTc0ZGYtYjNiMC1hMDZhLTIzYzgtNjM2NDEzMTk4NzcyIiBJc3N1ZUluc3RhbnQ9IjIwMTAtMTEtMThUMjE6NTc6MzdaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29tL3NhbWwvbWV0YWRhdGEvMTM1OTA8L3NhbWw6SXNzdWVyPg0KICAgIDxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICAgICAgPGRzOlNpZ25lZEluZm8+DQogICAgICAgIDxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgICAgICAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZnhhNDY1NzRkZi1iM2IwLWEwNmEtMjNjOC02MzY0MTMxOTg3NzIiPg0KICAgICAgICAgIDxkczpUcmFuc2Zvcm1zPg0KICAgICAgICAgICAgPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+DQogICAgICAgICAgICA8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgICAgICAgPC9kczpUcmFuc2Zvcm1zPg0KICAgICAgICAgIDxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPg0KICAgICAgICAgIDxkczpEaWdlc3RWYWx1ZT5wSlE3TVMvZWs0S1JSV0dtdi9INDNSZUhZTXM9PC9kczpEaWdlc3RWYWx1ZT4NCiAgICAgICAgPC9kczpSZWZlcmVuY2U+DQogICAgICA8L2RzOlNpZ25lZEluZm8+DQogICAgICA8ZHM6U2lnbmF0dXJlVmFsdWU+eWl2ZUtjUGREcHVETmo2c2hyUTNBQndyL2NBM0NyeUQycGhHL3hMWnN6S1d4VTUvbWxhS3Q4ZXdiWk9kS0t2dE9zMnBIQnk1RHVhM2s5NEFGK3p4R3llbDVnT293bW95WEpyK0FPcitrUE8wdmxpMVY4bzNoUFBVWndSZ1NYNlE5cFMxQ3FRZ2hLaUVhc1J5eWxxcUpVYVBZem1Pek9FOC9YbE1rd2lXbU8wPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQogICAgICA8ZHM6S2V5SW5mbz4NCiAgICAgICAgPGRzOlg1MDlEYXRhPg0KICAgICAgICAgIDxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQnJUQ0NBYUdnQXdJQkFnSUJBVEFEQmdFQU1HY3hDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SVXdFd1lEVlFRSERBeFRZVzUwWVNCTmIyNXBZMkV4RVRBUEJnTlZCQW9NQ0U5dVpVeHZaMmx1TVJrd0Z3WURWUVFEREJCaGNIQXViMjVsYkc5bmFXNHVZMjl0TUI0WERURXdNRE13T1RBNU5UZzBOVm9YRFRFMU1ETXdPVEE1TlRnME5Wb3daekVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhGVEFUQmdOVkJBY01ERk5oYm5SaElFMXZibWxqWVRFUk1BOEdBMVVFQ2d3SVQyNWxURzluYVc0eEdUQVhCZ05WQkFNTUVHRndjQzV2Ym1Wc2IyZHBiaTVqYjIwd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFPalN1MWZqUHk4ZDV3NFF5TDEremQ0aEl3MU1ra2ZmNFdZL1RMRzhPWmtVNVlUU1dtbUhQRDVrdllINXVvWFMvNnFRODFxWHBSMndWOENUb3daSlVMZzA5ZGRSZFJuOFFzcWoxRnlPQzVzbEUzeTJiWjJvRnVhNzJvZi80OWZwdWpuRlQ2S25RNjFDQk1xbERvVFFxT1Q2MnZHSjhuUDZNWld2QTZzeHF1ZDVBZ01CQUFFd0F3WUJBQU1CQUE9PTwvZHM6WDUwOUNlcnRpZmljYXRlPg0KICAgICAgICA8L2RzOlg1MDlEYXRhPg0KICAgICAgPC9kczpLZXlJbmZvPg0KICAgIDwvZHM6U2lnbmF0dXJlPg0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnN1cHBvcnRAb25lbG9naW4uY29tPC9zYW1sOk5hbWVJRD4NCiAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDEwLTExLTE4VDIyOjAyOjM3WiIgUmVjaXBpZW50PSJ7cmVjaXBpZW50fSIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTExLTE4VDIxOjUyOjM3WiIgTm90T25PckFmdGVyPSIyMDEwLTExLTE4VDIyOjAyOjM3WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT57YXVkaWVuY2V9PC9zYW1sOkF1ZGllbmNlPg0KICAgICAgPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9zYW1sOkNvbmRpdGlvbnM+DQogICAgPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOVQyMTo1NzozN1oiIFNlc3Npb25JbmRleD0iXzUzMWMzMmQyODNiZGZmN2UwNGU0ODdiY2RiYzRkZDhkIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5kZW1vPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJhbm90aGVyX3ZhbHVlIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj52YWx1ZTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+ diff --git a/test/responses/invalids/no_subjectconfirmation_data.xml.base64 b/test/responses/invalids/no_subjectconfirmation_data.xml.base64 new file mode 100644 index 000000000..146df03ab --- /dev/null +++ b/test/responses/invalids/no_subjectconfirmation_data.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDg0MDhlZmYyLTBmMTYtMTNlZC00YWY5LTc2NzBjOTE4ODBlZCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDg0MDhlZmYyLTBmMTYtMTNlZC00YWY5LTc2NzBjOTE4ODBlZCI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+dk9MRzc5SjArYXE0S25qYnJvc0ZGSDVsMTlFPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5qdU1aaldBSFc1ZGlLd3F2b1NxWi91QjBQVTR5SVVyWnBhWnlpcERJbTdnRTZJZlBCMzRjNzJybnJ5R0krM1AraytCSHVBbjk1dFJxNVgxZURaUFRWc0lZanRCUCtxM3cvdTJOQXJ2eTJtK1g2OXg3dGI1SVJWUVR1QmNpai9LeFNnRHg3QytJOE40ZytKakZCZDVoVVB5akVUNDVxc3pQazZnS3JMRUF0Tmc9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+ICAgICAgICANCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg== diff --git a/test/responses/invalids/no_subjectconfirmation_method.xml.base64 b/test/responses/invalids/no_subjectconfirmation_method.xml.base64 new file mode 100644 index 000000000..f1b3561fd --- /dev/null +++ b/test/responses/invalids/no_subjectconfirmation_method.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDg0MzIyNzdmLTdmNDYtZjRlMi1kZGExLThhZTU4ZTk1NjYwZSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3N0dWZmLmNvbS9lbmRwb2ludHMvZW5kcG9pbnRzL2Fjcy5waHAiIEluUmVzcG9uc2VUbz0iXzU3YmNiZjcwLTdiMWYtMDEyZS1jODIxLTc4MmJjYjEzYmIzOCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDg0MzIyNzdmLTdmNDYtZjRlMi1kZGExLThhZTU4ZTk1NjYwZSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+ZXprT1Viam5McXBvWGl3Z2tBNmFzRkkvOElBPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT54RVQ5M0YrWUpnM29MSFBFalRwNll0OC9HUTZlTWNMbHRxL09vVkNCcHFUWlc4MUNjYVVwQmtlY1lwSGpOR1JxZENscTF1dGorc0k5STkwUFNRekhuOWtBbkJydm4wcEhhMFpRWTF6emJxZ0hSbVlvQVhkMzBaS1Y4bW5UaEc1b2RBWE5xUzBsVUJ4RGppZDBRcTJRNUw0WjRWTlVNbTV6OGwva0hvM0FKTUU9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+DQogIDxzYW1scDpTdGF0dXM+DQogICAgPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPg0KICA8L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeDc4NDE5OTFjLWM3M2YtNDAzNS1lMmVlLWMxNzBjMGUxZDNlNCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIj4NCiAgICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS88L3NhbWw6SXNzdWVyPiAgICANCiAgICA8c2FtbDpTdWJqZWN0Pg0KICAgICAgPHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c29tZW9uZUBleGFtcGxlLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmhvbGRlci1vZi1rZXkiPg0KICAgICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMjAtMDYtMTdUMTQ6NTk6MTRaIiBSZWNpcGllbnQ9Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiLz4NCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDIxLTA2LTE3VDIyOjU0OjE0WiIgU2Vzc2lvbkluZGV4PSJfNTFiZTM3OTY1ZmViNTU3OWQ4MDMxNDEwNzY5MzZkYzJlOWQxZDk4ZWJmIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNvbWVvbmVAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogIDwvc2FtbDpBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/invalids/response_invalid_signed_element.xml.base64 b/test/responses/invalids/response_invalid_signed_element.xml.base64 new file mode 100644 index 000000000..e10fb8877 --- /dev/null +++ b/test/responses/invalids/response_invalid_signed_element.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJwZngwNWYzY2UxMC0xNjE1LWYzZWEtYTk4OC02MGUzODBiMzI5OWYiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiIgRGVzdGluYXRpb249Imh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl81ZmU5ZDZlNDk5YjJmMDkxMzIwNmFhYjNmNzE5MTcyOTA0OWJiODA3Ij48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9zaW1wbGVzYW1sL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxzYW1scDpTdGF0dXM+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDA1ZjNjZTEwLTE2MTUtZjNlYS1hOTg4LTYwZTM4MGIzMjk5ZiI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+akFnb3RGMEorUksxL0s4N3cyNE1NQzIrWlJzPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5wWGYzdndPWnZ0Zm1mN01NY09TRzAzOTJTbXducElvQWpnZ3NWYStSU0lETVN3S1NyTDNxbDdKeVlCNVNpdnEvTFg4OVRhcXlZMngwUGdNaXhhdjRuMUcxMUMzc21sUkFNckRlNnZSdEltSlVzbFNHazk3elBoeW9RK0o1b2dRUGQ2VmxNVHo4S1d6ZnF0T1BEZjVkbDJZeUpwb2tlT05UTTF6YzRKR00zR0E9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJwZnhiNGVjOWM4YS00OGViLWZkYTItN2Y3NC1mYTFhMTA1YTk5ZmUiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvc2ltcGxlc2FtbC9zYW1sMi9pZHAvbWV0YWRhdGEucGhwPC9zYW1sOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4NCiAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4YjRlYzljOGEtNDhlYi1mZGEyLTdmNzQtZmExYTEwNWE5OWZlIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPjxkczpEaWdlc3RWYWx1ZT5obnBUOWN1ZTZ0Qks0SVpPajUwZU1tM21DZ0k9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPnJBMGZMWjdLa2wzSndzTEtYRHdhRmNnNkxuS0hSeklVcng2bU5YMVRvOXpNQ3ZDVUQvb1BZVmFFZU10bnhZWTQ2ZnNtYU52c0tpdWdpeGRWekxMMzhLbUFZOHVNVGV6aEd2ejJWZUhVb3I0dTRoeGZRbHNPdDJmdXpBRTZEYnF2SVZLMHdqeDNSWTA4WC91T0NSOXVQY3B5NEJCc1EzeFBsNFF2aXBKalNOTT08L2RzOlNpZ25hdHVyZVZhbHVlPg0KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ2dUQ0NBZW9DQ1FDYk9scldEZFg3RlRBTkJna3Foa2lHOXcwQkFRVUZBRENCaERFTE1Ba0dBMVVFQmhNQ1RrOHhHREFXQmdOVkJBZ1REMEZ1WkhKbFlYTWdVMjlzWW1WeVp6RU1NQW9HQTFVRUJ4TURSbTl2TVJBd0RnWURWUVFLRXdkVlRrbE9SVlJVTVJnd0ZnWURWUVFERXc5bVpXbGtaUzVsY214aGJtY3VibTh4SVRBZkJna3Foa2lHOXcwQkNRRVdFbUZ1WkhKbFlYTkFkVzVwYm1WMGRDNXViekFlRncwd056QTJNVFV4TWpBeE16VmFGdzB3TnpBNE1UUXhNakF4TXpWYU1JR0VNUXN3Q1FZRFZRUUdFd0pPVHpFWU1CWUdBMVVFQ0JNUFFXNWtjbVZoY3lCVGIyeGlaWEpuTVF3d0NnWURWUVFIRXdOR2IyOHhFREFPQmdOVkJBb1RCMVZPU1U1RlZGUXhHREFXQmdOVkJBTVREMlpsYVdSbExtVnliR0Z1Wnk1dWJ6RWhNQjhHQ1NxR1NJYjNEUUVKQVJZU1lXNWtjbVZoYzBCMWJtbHVaWFIwTG01dk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRGl2YmhSN1A1MTZ4L1MzQnFLeHVwUWUwTE9Ob2xpdXBpQk9lc0NPM1NIYkRybDMrcTlJYmZuZm1FMDRyTnVNY1BzSXhCMTYxVGREcEllc0xDbjdjOGFQSElTS090UGxBZVRaU25iOFFBdTdhUmpacTMrUGJyUDV1VzNUY2ZDR1B0S1R5dEhPZ2UvT2xKYm8wNzhkVmhYUTE0ZDFFRHdYSlcxclJYdVV0NEM4UUlEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRHQkFDRFZmcDg2SE9icVkrZThCVW9XUTkrVk1ReDFBU0RvaEJqd09zZzJXeWtVcVJYRitkTGZjVUg5ZFdSNjNDdFpJS0ZEYlN0Tm9tUG5RejduYksrb255Z3dCc3BWRWJuSHVVaWhacTNaVWRtdW1RcUN3NFV2cy8xVXZxM29yT28vV0pWaFR5dkxnRlZLMlFhclE0LzY3T1pmSGQ3UitQT0JYaG9waFNNdjFaT288L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9Imh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvbWV0YWRhdGEucGhwIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+NDkyODgyNjE1YWNmMzFjODA5NmI2MjcyNDVkNzZhZTUzMDM2YzA5MDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiIFJlY2lwaWVudD0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNC0wMi0xOVQwMTozNjozMVoiIE5vdE9uT3JBZnRlcj0iMjAyMy0wOC0yM1QwNjo1NzowMVoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTAyLTE5VDAxOjM3OjAxWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxNC0wMi0xOVQwOTozNzowMVoiIFNlc3Npb25JbmRleD0iXzYyNzNkNzdiOGNkZTBjMzMzZWM3OWQyMmE5ZmEwMDAzYjlmZTJkNzVjYiI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5zbWFydGluPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNtYXJ0aW5AeWFjby5lczwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJjbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+U2l4dG8zPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9InNuIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5NYXJ0aW4yPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImVkdVBlcnNvbkFmZmlsaWF0aW9uIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj51c2VyPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPmFkbWluPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/invalids/response_with_concealed_signed_assertion.xml b/test/responses/invalids/response_with_concealed_signed_assertion.xml new file mode 100644 index 000000000..62c152ad4 --- /dev/null +++ b/test/responses/invalids/response_with_concealed_signed_assertion.xml @@ -0,0 +1,51 @@ + + + idp.example.com + + + + + idp.myexample.org + + + FA0AbR4w9oYdx7MFjERARVJAHps=GDH5jhCNX9PFxW+71SOJPyusAOwzECwmd57NDhvA/VKWHnV3PpvpNkOLyamoBNdZ4qxponnobg2zneLESrFnLJdJ1cgs51YvtBJTxKoA7oZMMNKReZFST8g7pDdrBC82n5rTdzxclaJkpwz1yjcho3K3TjxK+gU1svVrEKMUwyo= +MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl0+mhy6MPdyjyA6G14jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA7DjNaUYQqwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALM2vGCiQ/vm+a6v40+VX2zdqHA2Q/1vF1ibQzJ54MJCOVWvs+vQXfZFhdm0OPM2IrDU7oqvKPqP6xOAeJK6H0yP7M4YL3fatSvIYmmfyXC9kt3Svz/NyrHzPhUnJ0ye/sUSXxnzQxwcm/9PwAqrQaA3QpQkH57ybF/OoryPe+2h + + victim@example.com + + + + + + + example.com + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + + + + idp.myexample.org + + someone@example.org + + + + + + + example.com + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + + + diff --git a/test/responses/invalids/response_with_doubled_signed_assertion.xml b/test/responses/invalids/response_with_doubled_signed_assertion.xml new file mode 100644 index 000000000..f03e0682a --- /dev/null +++ b/test/responses/invalids/response_with_doubled_signed_assertion.xml @@ -0,0 +1,49 @@ + + + idp.example.com + + + + + idp.myexample.org + + + FA0AbR4w9oYdx7MFjERARVJAHps=GDH5jhCNX9PFxW+71SOJPyusAOwzECwmd57NDhvA/VKWHnV3PpvpNkOLyamoBNdZ4qxponnobg2zneLESrFnLJdJ1cgs51YvtBJTxKoA7oZMMNKReZFST8g7pDdrBC82n5rTdzxclaJkpwz1yjcho3K3TjxK+gU1svVrEKMUwyo= +MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl0+mhy6MPdyjyA6G14jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA7DjNaUYQqwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALM2vGCiQ/vm+a6v40+VX2zdqHA2Q/1vF1ibQzJ54MJCOVWvs+vQXfZFhdm0OPM2IrDU7oqvKPqP6xOAeJK6H0yP7M4YL3fatSvIYmmfyXC9kt3Svz/NyrHzPhUnJ0ye/sUSXxnzQxwcm/9PwAqrQaA3QpQkH57ybF/OoryPe+2h + + victim@example.com + + + + + + + example.com + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + + + idp.myexample.org + + someone@example.org + + + + + + + example.com + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + + diff --git a/test/responses/invalids/signature_wrapping_attack.xml.base64 b/test/responses/invalids/signature_wrapping_attack.xml.base64 new file mode 100644 index 000000000..dc2c9ca95 --- /dev/null +++ b/test/responses/invalids/signature_wrapping_attack.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJwZnhjM2QyYjU0Mi0wZjdlLTg3NjctOGU4Ny01YjBkYzY5MTMzNzUiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAzLTIxVDEzOjQxOjA5WiIgRGVzdGluYXRpb249Imh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl81ZDllMzE5YzFiOGE2N2RhNDgyMjc5NjRjMjhkMjgwZTc4NjBmODA0Ij48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9zaW1wbGVzYW1sL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0RldGFpbD48c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeGMzZDJiNTQyLTBmN2UtODc2Ny04ZTg3LTViMGRjNjkxMzM3NSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDMtMjFUMTM6NDE6MDlaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVkOWUzMTljMWI4YTY3ZGE0ODIyNzk2NGMyOGQyODBlNzg2MGY4MDQiPjxzYW1sOklzc3Vlcj5odHRwczovL3BpdGJ1bGsubm8taXAub3JnL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeGMzZDJiNTQyLTBmN2UtODc2Ny04ZTg3LTViMGRjNjkxMzM3NSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+MWRRRmlZVTBvMk9GN2MvUlZWOEdwZ2I0dTNJPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT53UmdCWE9xL0ZpTFpjMm11cmVUQy9qNnpZNzA5T2lrSjVIZVVTcnVIVGRZakVnOWFaeTFSYnhsS0lZRUlmWHBuWDdOQm9LeGZBTW0rTzBmc3JxT2pnY1l4VFZrcVpqT3I3MXFpWE5idHdqZUFrZFlTcGs1YnJzQWNuZmNQZHY4UVJlWXIzRDd0NVpWQ2dZdXZYUStkTkVMS2VhZzdlMUFTT3pWcU9kcDVaOVk9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9Il9jY2NkNjAyNDExNjY0MWZlNDhlMGFlMmM1MTIyMGQwMjc1NWY5NmM5OGQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAzLTIxVDEzOjQxOjA5WiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvc2ltcGxlc2FtbC9zYW1sMi9pZHAvbWV0YWRhdGEucGhwPC9zYW1sOklzc3Vlcj48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9Imh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvbWV0YWRhdGEucGhwIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnRyYW5zaWVudCI+X2I5OGY5OGJiMWFiNTEyY2VkNjUzYjU4YmFhZmY1NDM0NDhkYWVkNTM1ZDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMy0wOS0yMlQxOTowMTowOVoiIFJlY2lwaWVudD0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVkOWUzMTljMWI4YTY3ZGE0ODIyNzk2NGMyOGQyODBlNzg2MGY4MDQiLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNC0wMy0yMVQxMzo0MDozOVoiIE5vdE9uT3JBZnRlcj0iMjAyMy0wOS0yMlQxOTowMTowOVoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTAzLTIxVDEzOjQxOjA5WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAxNC0wMy0yMVQyMTo0MTowOVoiIFNlc3Npb25JbmRleD0iXzlmZTBjOGRjZDMzMDJlNzM2NGZjYWIyMmE1Mjc0OGViZjIyMjRkZjBhYSI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj50ZXN0PC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnRlc3RAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iY24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnRlc3Q8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0ic24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPndhYTI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iZWR1UGVyc29uQWZmaWxpYXRpb24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnVzZXI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+YWRtaW48L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT48L3NhbWxwOlN0YXR1c0RldGFpbD48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9Il9jY2NkNjAyNDExNjY0MWZlNDhlMGFlMmM1MTIyMGQwMjc1NWY5NmM5OGQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTAzLTIxVDEzOjQxOjA5WiI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvc2ltcGxlc2FtbC9zYW1sMi9pZHAvbWV0YWRhdGEucGhwPC9zYW1sOklzc3Vlcj48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9Imh0dHBzOi8vcGl0YnVsay5uby1pcC5vcmcvbmV3b25lbG9naW4vZGVtbzEvbWV0YWRhdGEucGhwIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnRyYW5zaWVudCI+X2I5OGY5OGJiMWFiNTEyY2VkNjUzYjU4YmFhZmY1NDM0NDhkYWVkNTM1ZDwvc2FtbDpOYW1lSUQ+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAyMy0wOS0yMlQxOTowMTowOVoiIFJlY2lwaWVudD0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVkOWUzMTljMWI4YTY3ZGE0ODIyNzk2NGMyOGQyODBlNzg2MGY4MDQiLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj48L3NhbWw6U3ViamVjdD48c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNC0wMy0yMVQxMzo0MDozOVoiIE5vdE9uT3JBZnRlcj0iMjAyMy0wOS0yMlQxOTowMTowOVoiPjxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PHNhbWw6QXVkaWVuY2U+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTAzLTIxVDEzOjQxOjA5WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyMy0wMy0yMVQyMTo0MTowOVoiIFNlc3Npb25JbmRleD0iXzlmZTBjOGRjZDMzMDJlNzM2NGZjYWIyMmE1Mjc0OGViZjIyMjRkZjBhYSI+PHNhbWw6QXV0aG5Db250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ+PC9zYW1sOkF1dGhuU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5oYWNrZXI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+aGFja2VyQGV4YW1wbGUuY29tPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImNuIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5oYWNrZXI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0ic24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPndhYTI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iZWR1UGVyc29uQWZmaWxpYXRpb24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnVzZXI8L3NhbWw6QXR0cmlidXRlVmFsdWU+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+YWRtaW48L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pjwvc2FtbDpBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4= \ No newline at end of file diff --git a/test/responses/invalids/status_code_responder.xml.base64 b/test/responses/invalids/status_code_responder.xml.base64 new file mode 100644 index 000000000..56996dd20 --- /dev/null +++ b/test/responses/invalids/status_code_responder.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlDQp4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIg0KQ29uc2VudD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNvbnNlbnQ6dW5zcGVjaWZpZWQiIERlc3RpbmF0aW9uPSINCmh0dHBzOi8vZXhhbXBsZS5jb20vb3BlbnNzby9Db25zdW1lci9tZXRhQWxpYXMvc3AiDQpJRD0iX2E3MWJiZjIyLTkwYTktNGE5Ni1iOWNlLWVhNWJhMzBhZWU2NSINCkluUmVzcG9uc2VUbz0iczIxMjAzYjI3ZDM4ZDBhMDdlYTJjNzEzZTdhMDA0NWNmOGQxZTMyODExIg0KSXNzdWVJbnN0YW50PSIyMDExLTA4LTI0VDE2OjM2OjMwLjM2NVoiIFZlcnNpb249IjIuMCI+PElzc3Vlcg0KeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPg0KaHR0cDovL2lkcC5leGFtcGxlLmNvbS9hZGZzL3NlcnZpY2VzL3RydXN0PC9Jc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZQ0KVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6UmVzcG9uZGVyIi8+PC9zYW1scDpTdGF0dXM+PC9zYW1scDpSZXNwb25zZT4NCg== diff --git a/test/responses/invalids/status_code_responer_and_msg.xml.base64 b/test/responses/invalids/status_code_responer_and_msg.xml.base64 new file mode 100644 index 000000000..d2697a692 --- /dev/null +++ b/test/responses/invalids/status_code_responer_and_msg.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlDQp4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIg0KQ29uc2VudD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNvbnNlbnQ6dW5zcGVjaWZpZWQiIERlc3RpbmF0aW9uPSINCmh0dHBzOi8vZXhhbXBsZS5jb20vb3BlbnNzby9Db25zdW1lci9tZXRhQWxpYXMvc3AiDQpJRD0iX2E3MWJiZjIyLTkwYTktNGE5Ni1iOWNlLWVhNWJhMzBhZWU2NSINCkluUmVzcG9uc2VUbz0iczIxMjAzYjI3ZDM4ZDBhMDdlYTJjNzEzZTdhMDA0NWNmOGQxZTMyODExIg0KSXNzdWVJbnN0YW50PSIyMDExLTA4LTI0VDE2OjM2OjMwLjM2NVoiIFZlcnNpb249IjIuMCI+PElzc3Vlcg0KeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPg0KaHR0cDovL2lkcC5leGFtcGxlLmNvbS9hZGZzL3NlcnZpY2VzL3RydXN0PC9Jc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpSZXNwb25kZXIiLz48c2FtbHA6U3RhdHVzTWVzc2FnZT5zb21ldGhpbmdfaXNfd3Jvbmc8L3NhbWxwOlN0YXR1c01lc3NhZ2U+PC9zYW1scDpTdGF0dXM+PC9zYW1scDpSZXNwb25zZT4= diff --git a/test/responses/invalids/wrong_spnamequalifier.xml.base64 b/test/responses/invalids/wrong_spnamequalifier.xml.base64 new file mode 100644 index 000000000..48e1fbff0 --- /dev/null +++ b/test/responses/invalids/wrong_spnamequalifier.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDEzNzFkZmU1LTdlMmYtMTdiNy1hODE1LTVmNWU5YTRiNjVkYSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9uZXdvbmVsb2dpbi9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzVmZTlkNmU0OTliMmYwOTEzMjA2YWFiM2Y3MTkxNzI5MDQ5YmI4MDciPjxzYW1sOklzc3Vlcj5odHRwczovL3BpdGJ1bGsubm8taXAub3JnL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDEzNzFkZmU1LTdlMmYtMTdiNy1hODE1LTVmNWU5YTRiNjVkYSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+c3lrMGNHOHAxOGpSMUtINThKNnR3Z0JNYlhzPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT4yaytTbmhjWWhSV0FLQWdLR2hyMVpZN1ZsSWtWSytsbEFKZncxVnllKzRkWFpZTGh0TkwrMUJ3bWRlVHlLY1BEYjNWSmZtNXRzRGFWRDNtTHVUd2E5L0EvUCt0ZnY0d0t6YVQrdmJvTDl0RVFnNFAwR3hmaWRzbkYrQUM0Z2lLb0VBRE5RbmlaamExR1hhM3VOdi85TEVYV1h4YmZaVGJPNmxLNnlhbGR6UkU9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNnVENDQWVvQ0NRQ2JPbHJXRGRYN0ZUQU5CZ2txaGtpRzl3MEJBUVVGQURDQmhERUxNQWtHQTFVRUJoTUNUazh4R0RBV0JnTlZCQWdURDBGdVpISmxZWE1nVTI5c1ltVnlaekVNTUFvR0ExVUVCeE1EUm05dk1SQXdEZ1lEVlFRS0V3ZFZUa2xPUlZSVU1SZ3dGZ1lEVlFRREV3OW1aV2xrWlM1bGNteGhibWN1Ym04eElUQWZCZ2txaGtpRzl3MEJDUUVXRW1GdVpISmxZWE5BZFc1cGJtVjBkQzV1YnpBZUZ3MHdOekEyTVRVeE1qQXhNelZhRncwd056QTRNVFF4TWpBeE16VmFNSUdFTVFzd0NRWURWUVFHRXdKT1R6RVlNQllHQTFVRUNCTVBRVzVrY21WaGN5QlRiMnhpWlhKbk1Rd3dDZ1lEVlFRSEV3TkdiMjh4RURBT0JnTlZCQW9UQjFWT1NVNUZWRlF4R0RBV0JnTlZCQU1URDJabGFXUmxMbVZ5YkdGdVp5NXViekVoTUI4R0NTcUdTSWIzRFFFSkFSWVNZVzVrY21WaGMwQjFibWx1WlhSMExtNXZNSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURpdmJoUjdQNTE2eC9TM0JxS3h1cFFlMExPTm9saXVwaUJPZXNDTzNTSGJEcmwzK3E5SWJmbmZtRTA0ck51TWNQc0l4QjE2MVRkRHBJZXNMQ243YzhhUEhJU0tPdFBsQWVUWlNuYjhRQXU3YVJqWnEzK1BiclA1dVczVGNmQ0dQdEtUeXRIT2dlL09sSmJvMDc4ZFZoWFExNGQxRUR3WEpXMXJSWHVVdDRDOFFJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBQ0RWZnA4NkhPYnFZK2U4QlVvV1E5K1ZNUXgxQVNEb2hCandPc2cyV3lrVXFSWEYrZExmY1VIOWRXUjYzQ3RaSUtGRGJTdE5vbVBuUXo3bmJLK29ueWd3QnNwVkVibkh1VWloWnEzWlVkbXVtUXFDdzRVdnMvMVV2cTNvck9vL1dKVmhUeXZMZ0ZWSzJRYXJRNC82N09aZkhkN1IrUE9CWGhvcGhTTXYxWk9vPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgSUQ9InBmeGI0ZWM5YzhhLTQ4ZWItZmRhMi03Zjc0LWZhMWExMDVhOTlmZSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9waXRidWxrLm5vLWlwLm9yZy9zaW1wbGVzYW1sL3NhbWwyL2lkcC9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxzYW1sOlN1YmplY3Q+PHNhbWw6TmFtZUlEIFNQTmFtZVF1YWxpZmllcj0id3Jvbmctc3AtZW50aXR5aWQiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIj50ZXN0QGV4YW1wbGUuY29tPC9zYW1sOk5hbWVJRD48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+PHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDIzLTA4LTIzVDA2OjU3OjAxWiIgUmVjaXBpZW50PSJodHRwczovL3BpdGJ1bGsubm8taXAub3JnL25ld29uZWxvZ2luL2RlbW8xL2luZGV4LnBocD9hY3MiIEluUmVzcG9uc2VUbz0iT05FTE9HSU5fNWZlOWQ2ZTQ5OWIyZjA5MTMyMDZhYWIzZjcxOTE3MjkwNDliYjgwNyIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE0LTAyLTE5VDAxOjM2OjMxWiIgTm90T25PckFmdGVyPSIyMDIzLTA4LTIzVDA2OjU3OjAxWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL3BpdGJ1bGsubm8taXAub3JnL25ld29uZWxvZ2luL2RlbW8xL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT48L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48L3NhbWw6Q29uZGl0aW9ucz48c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTQtMDItMTlUMDE6Mzc6MDFaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDE0LTAyLTE5VDA5OjM3OjAxWiIgU2Vzc2lvbkluZGV4PSJfNjI3M2Q3N2I4Y2RlMGMzMzNlYzc5ZDIyYTlmYTAwMDNiOWZlMmQ3NWNiIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PHNhbWw6QXR0cmlidXRlU3RhdGVtZW50PjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJ1aWQiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnNtYXJ0aW48L3NhbWw6QXR0cmlidXRlVmFsdWU+PC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0ibWFpbCIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dGVzdEBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJjbiIgTmFtZUZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmF0dHJuYW1lLWZvcm1hdDpiYXNpYyI+PHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+U2l4dG8zPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9InNuIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj5NYXJ0aW4yPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PHNhbWw6QXR0cmlidXRlIE5hbWU9ImVkdVBlcnNvbkFmZmlsaWF0aW9uIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj51c2VyPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPmFkbWluPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/response4.xml.base64 b/test/responses/response_assertion_wrapped.xml.base64 similarity index 100% rename from test/responses/response4.xml.base64 rename to test/responses/response_assertion_wrapped.xml.base64 diff --git a/test/responses/response_audience_self_closed_tag.xml.base64 b/test/responses/response_audience_self_closed_tag.xml.base64 new file mode 100644 index 000000000..1bac221e3 --- /dev/null +++ b/test/responses/response_audience_self_closed_tag.xml.base64 @@ -0,0 +1 @@ +tVdbc7JIE77fqv0Plu9lynBUhEpSi6DGAwqCp9xscRgQ5SQDgvz6b8BDTEyy+27Vd6U03T3P8/T09PAEdd+LuBmAURhAUMt9L4BcZXyup3HAhTp0IRfoPoBcYnIqL4058hHnojhMQjP06jchP0foEII4ccOgXhuIz/XIzi3atHSGajVaeoto4CzVbjC4CRpUG2+DtsnaTQvUawsQQxT1XEdJUCiEKRgEMNGDBJlwAgW2GgSjETTXpDmCfqvXRAATN9CTKmqTJBGHYTBJbfvRDH0MBFYUukECb/7pJnyMNhFKH1yk0MLn+t9NxjANm8EbjEHYDZwgQcNsk0SDaZPoBUEZBtWuv/z5R632VArAVejil/OarhU9glz3Iw9UKz9ht05PFuRU10E40/givAWvgLMse8yoxzB2MBLHcQxnMeRjQdf5dV7xHA+sQWCHVTpBD8LANXXPLSr2Ekg2oVXjPSeM3WTjf5OcwAi8TN4AudkwCTr4VceqJd4XqUD+y3QfsMZQb8CNTpwzlvlmwAYxCExQm88Gz/Vf/3YnVBS1WA+gHcY+/Pj4e6hAcABeGAGrAS/kEMDfS/ilak/YPUbRddCG/C/inYV7T7LQvRS8QCZfazNsO57s11lOE4HdabnxA0ilwXMF4Na5MlwlPz1+2jbXAp8iNLu1c/qddqyZCbbM4dzx5mE4XmbEdtjVZcPrFSvzQY7CjjOcGTE207diRyX94pgdX2eOHUz3vmU/OMkILBXYksJk7so7fGPsRvJq6NjOzmHhwc4cbCVPx1NsnB6T42D+Ng2yV2X3uqaWbkSHKq7mUbpz1ON8uysYeZhNV7sMBjGlHtR0XiSENMefr3Ru8P/5R0lqBI5Xgqsmzop6ol8fhPIkslGnJOBFGgyEfiEI/FoRBEWYTExlFfoUuRD5ScfZ7Tc7t89meIdX5j0eEZUUmAnKWlwoSr+bDRfzojuW+F2fJ+ZdoSMJ6pzOexqvdZzJosObmtj10re+tzH8XmaQeWQW3ZnEt0/+G2mgkU1P67OBvqRzUeRHpzio8fgifSt40MvwfCLyuLSV8qmo5JLY0yub9tEm9Qa5UPDDU/xa472FdoN1gLAOtW13IXWkau1OLknqsrnTl710veocjL5XSLNuJmaV/6ibbWTDX0gGaUXGtitJfHjCjFabLZuBNOjbEo/3BXXfVwcGJSrdUiOep/sTXhQ6roKoKGLY8h+Ut8NawfIx3vXGThqNCEU012PabsomKunEUdloflgwdqHthdddW9uMc21Hk5JCSuYQTsF8uJAZsj3aHP1tT9jnjkwv0my2Y+OI9/AHf3NsSbJ13B75Vp+gt7G4VFvNIxybmxGdYIcs6ooFripeN+wTO6nwPNVnijeVWsXA4RlxO9Hna2WfDURe4TufOXVOnDr8WCIPfcFVsIP/oLcONP6wWJGFtX/lSQUjDj3CNZRi2KSloTBdLA/w4aCs7LfexvLxqSyRg1icM+H+MJL3ciuf8mA4ar3iR5mR6PWYsvVEPQzWvm8fVwK7S9AuL7DJMX4t5M08GOJHgMG5usqDQskz08dYOeP3saLzlBKhpmkyR6OHTcP4KIMHclO1xeeNfjWeWgG7bZIPbfQ+0CJOTZAJXqbBrU0ILVCrOu7nuQ8rb05NTRNAeJkD2F360wTlL7eE80TMofvt+buSxqq5Ab7ecKsLgQnq16h/DrpeQ5g2TbAsgQ5xhrIbNE41G4AEaNITaAbhgLAoQP+Ha8itZr91MbiNU1NjC8zkbLtYJ0jkgVhT5fKPkqJ5b7sgRpSB54Vlxnqth8aPnnxfGeKRqCyu1bArVw5J4nq8ZcVllV5g6IMwAH/dwDyjPC3+CdEZpxAGtltmKwt4mng/bw7T5wygxyCuXxP+kLLcuLVJmEyDaczbSUmZxEn8Rnr2dAOcAdONXFDW5v9//8PetcC+Q34pKvZFVU90kbfllq6wZNgBqCqg2lm39CiOLundSUDcSfCpPnxqueX8R+yS2DVvIN373PH5Pvjs8I79AyU+TTZB2eHAR6WoVY/f9wzOIGYq2nwoz7cESfJ6yz+7DgIL5GWpCANQDNtq2sBoNhnWauMUQRM402KplmWSgLUIi20Dw77XBuFCFBKQJ1+I8v5S8NA3DLpKvfz4mWNyZumHzDL6ycLYusr4Rao7qe+w3L65ivlR5wSVxUgT8Pn1nUOtbN7netnp9er/Px0TFSMUXVovB4WB/Mz7fr0uUs2EGjq4ueQYodVy9HWIXgXOT6fKx+g7WS5vP2nyNfHL28soKW+E2McP3Zf/AQ== diff --git a/test/responses/response_double_status_code.xml.base64 b/test/responses/response_double_status_code.xml.base64 new file mode 100644 index 000000000..07daa39e2 --- /dev/null +++ b/test/responses/response_double_status_code.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9InBmeGJjODI2YWZkLWU5ZmUtZDNmYi1kODc0LWM0NzAwYzNlZjBjOCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2FwcC5tdWRhLm5vL3Nzby9jb25zdW1lIiBJblJlc3BvbnNlVG89Il9mYzRhMzRiMC03ZWZiLTAxMmUtY2FhZS03ODJiY2IxM2JiMzgiPjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbDI8L3NhbWw6SXNzdWVyPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6UmVxdWVzdGVyIj4gIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlVuc3VwcG9ydGVkQmluZGluZyIgLz48L3NhbWxwOlN0YXR1c0NvZGU+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgVmVyc2lvbj0iMi4wIiBJRD0icGZ4OTUxNmIwZjMtNDUzNi0xMGY2LWM2ZmEtOWRkNTIzZTE0OThjIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29tL3NhbWwyPC9zYW1sOklzc3Vlcj48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+dGVzdEBvbmVsb2dpbi5jb208L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwOTAtMDYtMDRUMDI6Mjc6MDJaIiBSZWNpcGllbnQ9InJlY2lwaWVudCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDExLTA2LTA0VDAyOjE3OjAyWiIgTm90T25PckFmdGVyPSIyMDkwLTA2LTA0VDAyOjI3OjAyWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL3NvbWVvbmUuZXhhbXBsZS5jb20vYXVkaWVuY2U8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTA2LTA0VDAyOjIyOjAyWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjA5MC0wNi0wNVQwMjoyMjowMloiIFNlc3Npb25JbmRleD0iXzE2ZjU3MGZiYzAzMTUwMDdhMDM1NWRmZWE2YjNjNDZjIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/response_encrypted_attrs.xml.base64 b/test/responses/response_encrypted_attrs.xml.base64 new file mode 100644 index 000000000..89746a231 --- /dev/null +++ b/test/responses/response_encrypted_attrs.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgRGVzdGluYXRpb249Imh0dHA6Ly9zdHVmZi5jb20vZW5kcG9pbnRzL2VuZHBvaW50cy9hY3MucGhwIiBJRD0icGZ4ODM2M2M2YTctZmQ1MC0yNjA0LTdmZTctZTZhNjgxYWE3ZmMxIiBJblJlc3BvbnNlVG89Il81N2JjYmY3MC03YjFmLTAxMmUtYzgyMS03ODJiY2IxM2JiMzgiIElzc3VlSW5zdGFudD0iMjAxMS0wNi0xN1QxNDo1NDoxNFoiIFZlcnNpb249IjIuMCI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDgzNjNjNmE3LWZkNTAtMjYwNC03ZmU3LWU2YTY4MWFhN2ZjMSI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+cEJ6cTlYdzBMeFBoK0x0M3VuTmdaQ081UzJjPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5wcmZlQWRzRE9mUUZUTEwwYk01WEFOQXh3cmNZZE9RTjF3U1g2eEtoSkRHU2I5V2NPUE1Pa3ZOczdRSUZyaE1nNEZpdXJtc0VnMlE1OTFRYkRVUStoNW5SbFo1NHRVcFh1eDBTOHFYSmxnd0JHWjJUWCtKeDk3TTB4NkJBS3I0cm05K0RuOGk1QW1zdU5QRUVieFhBdm5JRWJVOTNVem5wd3IyNG1OUllpdFU9PC9kczpTaWduYXR1cmVWYWx1ZT4NCjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNHekNDQVlRQ0NRQ05OY1FYb20zMlZEQU5CZ2txaGtpRzl3MEJBUVVGQURCU01Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQk1DU1U0eEZUQVRCZ05WQkFjVERFbHVaR2xoYm1Gd2IyeHBjekVSTUE4R0ExVUVDaE1JVDI1bFRHOW5hVzR4RERBS0JnTlZCQXNUQTBWdVp6QWVGdzB4TkRBME1qTXhPRFF4TURGYUZ3MHhOVEEwTWpNeE9EUXhNREZhTUZJeEN6QUpCZ05WQkFZVEFsVlRNUXN3Q1FZRFZRUUlFd0pKVGpFVk1CTUdBMVVFQnhNTVNXNWthV0Z1WVhCdmJHbHpNUkV3RHdZRFZRUUtFd2hQYm1WTWIyZHBiakVNTUFvR0ExVUVDeE1EUlc1bk1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRG82bStRWnZZUS94TDBFbExndXBLMVFEY1lMNGY1UGNrd3NOZ1M5cFV2VjdmelRxQ0hrOFRoTHhUazQyTVEyTWNKc09lVUpWUDcyOEtoeW1qRkNxeGdQNFZ1d1JrOXJwQWwwK21oeTZNUGR5anlBNkcxNGpyRFdTNjV5c0xjaEs0dC92d3BFRHowU1FsRW9HMWtNemxsU203elpTM1hyZWdBN0RqTmFVWVFxd0lEQVFBQk1BMEdDU3FHU0liM0RRRUJCUVVBQTRHQkFMTTJ2R0NpUS92bSthNnY0MCtWWDJ6ZHFIQTJRLzF2RjFpYlF6SjU0TUpDT1ZXdnMrdlFYZlpGaGRtME9QTTJJckRVN29xdktQcVA2eE9BZUpLNkgweVA3TTRZTDNmYXRTdklZbW1meVhDOWt0M1N2ei9OeXJIelBoVW5KMHllL3NVU1h4bnpReHdjbS85UHdBcXJRYUEzUXBRa0g1N3liRi9Pb3J5UGUrMmg8L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+DQogIDwvc2FtbHA6U3RhdHVzPg0KICA8c2FtbDpBc3NlcnRpb24geG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiBJRD0icGZ4Nzg0MTk5MWMtYzczZi00MDM1LWUyZWUtYzE3MGMwZTFkM2U0IiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MTRaIiBWZXJzaW9uPSIyLjAiPg0KICAgIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tLzwvc2FtbDpJc3N1ZXI+ICAgIA0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiIFNQTmFtZVF1YWxpZmllcj0iaGVsbG8uY29tIj5zb21lb25lQGV4YW1wbGUuY29tPC9zYW1sOk5hbWVJRD4NCiAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgSW5SZXNwb25zZVRvPSJfNTdiY2JmNzAtN2IxZi0wMTJlLWM4MjEtNzgyYmNiMTNiYjM4IiBOb3RPbk9yQWZ0ZXI9IjIwNTEtMDYtMTdUMTQ6NTk6MTRaIiBSZWNpcGllbnQ9ImhodHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHAiLz4NCiAgICAgIDwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPg0KICAgIDwvc2FtbDpTdWJqZWN0Pg0KICAgIDxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDEwLTA2LTE3VDE0OjUzOjQ0WiIgTm90T25PckFmdGVyPSIyMDUxLTA2LTE3VDE0OjU5OjE0WiI+DQogICAgICA8c2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgICAgICA8c2FtbDpBdWRpZW5jZT5odHRwOi8vc3R1ZmYuY29tL2VuZHBvaW50cy9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTEtMDYtMTdUMTQ6NTQ6MDdaIiBTZXNzaW9uSW5kZXg9Il81MWJlMzc5NjVmZWI1NTc5ZDgwMzE0MTA3NjkzNmRjMmU5ZDFkOThlYmYiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwNTEtMDYtMTdUMjI6NTQ6MTRaIj4NCiAgICAgIDxzYW1sOkF1dGhuQ29udGV4dD4NCiAgICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICANCiAgICA8c2FtbDpFbmNyeXB0ZWRBdHRyaWJ1dGU+PHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiIFR5cGU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI0VsZW1lbnQiPjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNhZXMxMjgtY2JjIi8+PGRzaWc6S2V5SW5mbyB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48eGVuYzpFbmNyeXB0ZWRLZXk+PHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS0xXzUiLz48eGVuYzpDaXBoZXJEYXRhPjx4ZW5jOkNpcGhlclZhbHVlPmdZYnoxVm5Bcml1bXpqTnoweGNwc0o2QnRkOGtxNzlnb0dqNm5pcWlHY3dMV1NYTTBwaTRQNUd1K2tPejFubWRtSHlnUGlXN3RqalFOTWxBYVFvN1IvNVFURjh6a3F6SXltdGY3a0VpMkhQN29FbHMya1VoOWJ5WkR0akhqdTlueVY4ajlacXlydnZyaFVXNS90RmJHZkJ5UXdYOGhiVGl1TXV5eXcxeTltVT08L3hlbmM6Q2lwaGVyVmFsdWU+PC94ZW5jOkNpcGhlckRhdGE+PC94ZW5jOkVuY3J5cHRlZEtleT48L2RzaWc6S2V5SW5mbz4NCiAgIDx4ZW5jOkNpcGhlckRhdGE+DQogICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5YWVhaQzNwUDArekFsazZYSGM4aERqQTk4R3MxNWRCaU5SZC9nU3U2MEw1blkzS3NpSkd0c0gzcGlmU2dLN2ZIWisyTzJrYUV3SmszY0djSThYc0VEcnk1YWp0NnRQT0RIRkZGeDFmV1o1SEQ5L1I4RG01U3pQSG9QMmxET2lBZWgwd3pUQjVWdUdrbU5nUmYyNjBkNkpDVndxY0RVc3FrZWJkMWkvTzd6V3VoUjFDRmZZK0l3c1l2bUdXMUlqdUQxcXVJUmdzNnFTSEt1SnhrWlM1NGp0RHhFaUR0VGN0aXhVaytlMHhHRXZUVUxKVFA5eDN5anZEV09FNGJpelZsSFJpVnVmMzRaVWJuV2hURlFiKzBIUT09PC94ZW5jOkNpcGhlclZhbHVlPg0KICAgPC94ZW5jOkNpcGhlckRhdGE+DQo8L3hlbmM6RW5jcnlwdGVkRGF0YT48L3NhbWw6RW5jcnlwdGVkQXR0cmlidXRlPg0KICAgICAgDQo8c2FtbDpFbmNyeXB0ZWRBdHRyaWJ1dGU+PHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiIFR5cGU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI0VsZW1lbnQiPjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNhZXMxMjgtY2JjIi8+PGRzaWc6S2V5SW5mbyB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48eGVuYzpFbmNyeXB0ZWRLZXk+PHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS0xXzUiLz48eGVuYzpDaXBoZXJEYXRhPjx4ZW5jOkNpcGhlclZhbHVlPndqOVRBbTE4aGNkTGpqdnZ6bzdnbFF0djI3ME5FWS9wR1NpeVhIN0dXNGFWRllWWG5EbEVhSFRadFlmUnFXVXFJMCtENG5FZ1l2dzZzMEIxNzZpcWtNME1QS0ZUdUx5cDBxU2tuVDhYVmVJSmVHQ0xON1NPRFFBTE8ySlNCVk15aFNhM0MrSDN2MExITWhOVzdRMU5VeDA2WEFMYXRnTFBqc3RFaVhqYU41ST08L3hlbmM6Q2lwaGVyVmFsdWU+PC94ZW5jOkNpcGhlckRhdGE+PC94ZW5jOkVuY3J5cHRlZEtleT48L2RzaWc6S2V5SW5mbz4NCiAgIDx4ZW5jOkNpcGhlckRhdGE+DQogICAgICA8eGVuYzpDaXBoZXJWYWx1ZT4xSy9SSHFjMHN1ekc5ZUo2ditHUXljTjh5RWtTc2lFdHFFRGZvYnZ6Qkx2UFQrYno4SVRNaDlCL3FJZno2TnhUREdPejQ1WW12WC92aHhoclZiYVcwRUpLemFsQXVRN0ZpMTlOSEl1NkllcW16a29JMGlQMzNsamZjRy91N1lPNmhJeDl5OU1qeWF5MjNFcEh5akIvcVNLaTF1emJzV2JLV21rWU8yUS9TM0NVZ242Mm40b01UQkowcFdKdEY5Y0NzcWxhZ0owZXBpYkl6VksrWDFJRm1aOE85cjF2d040ZzdZNmovNUZGbjNBQnhKT0hhZVNucU95dUU2N1RhSzdmdk9tMHNNU1RBVkV1NWlqWk9Fdk5LRVB0enRHZituNlFiSXF2bmxSTllNST08L3hlbmM6Q2lwaGVyVmFsdWU+DQogICA8L3hlbmM6Q2lwaGVyRGF0YT4NCjwveGVuYzpFbmNyeXB0ZWREYXRhPjwvc2FtbDpFbmNyeXB0ZWRBdHRyaWJ1dGU+PC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/response_encrypted_nameid.xml.base64 b/test/responses/response_encrypted_nameid.xml.base64 new file mode 100644 index 000000000..2dec37fc8 --- /dev/null +++ b/test/responses/response_encrypted_nameid.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9InBmeDUxOWI1Y2JiLWNiNmYtOTQzNS0xNjNmLWJkMzVjZTM1YzNmMCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2FwcC5tdWRhLm5vL3Nzby9jb25zdW1lIiBJblJlc3BvbnNlVG89Il9mYzRhMzRiMC03ZWZiLTAxMmUtY2FhZS03ODJiY2IxM2JiMzgiPjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbDI8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZng1MTliNWNiYi1jYjZmLTk0MzUtMTYzZi1iZDM1Y2UzNWMzZjAiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPjBTRFRrNXNYWjdoMW9YUWVRMm5YY3BLZnZoTT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+WENPVmk4U2c0MllRS25oMWpNTWYvV0dVcDh5Q1dFQWV4UE5taVNWT0M2dUFBUGc5WWwySUt4Um1SeGczcHpVK0o5SzlTRUVEOEJWenJERTZ4VDlxV1JUbXZ1WExqemE0TndvRmFGWllIc3ZzN0FPR3l5UEJjT3Z2R3JoM2RGWmVTUzF5U2tVc3FBWW5Wck54emRkRVFZa2trRmNxQkNqZ3dnd0Z5Vlpvbkc4PTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDR3pDQ0FZUUNDUUNOTmNRWG9tMzJWREFOQmdrcWhraUc5dzBCQVFVRkFEQlNNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0JNQ1NVNHhGVEFUQmdOVkJBY1RERWx1WkdsaGJtRndiMnhwY3pFUk1BOEdBMVVFQ2hNSVQyNWxURzluYVc0eEREQUtCZ05WQkFzVEEwVnVaekFlRncweE5EQTBNak14T0RReE1ERmFGdzB4TlRBME1qTXhPRFF4TURGYU1GSXhDekFKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJRXdKSlRqRVZNQk1HQTFVRUJ4TU1TVzVrYVdGdVlYQnZiR2x6TVJFd0R3WURWUVFLRXdoUGJtVk1iMmRwYmpFTU1Bb0dBMVVFQ3hNRFJXNW5NSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURvNm0rUVp2WVEveEwwRWxMZ3VwSzFRRGNZTDRmNVBja3dzTmdTOXBVdlY3ZnpUcUNIazhUaEx4VGs0Mk1RMk1jSnNPZVVKVlA3MjhLaHltakZDcXhnUDRWdXdSazlycEFsMCttaHk2TVBkeWp5QTZHMTRqckRXUzY1eXNMY2hLNHQvdndwRUR6MFNRbEVvRzFrTXpsbFNtN3paUzNYcmVnQTdEak5hVVlRcXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBTE0ydkdDaVEvdm0rYTZ2NDArVlgyemRxSEEyUS8xdkYxaWJReko1NE1KQ09WV3ZzK3ZRWGZaRmhkbTBPUE0ySXJEVTdvcXZLUHFQNnhPQWVKSzZIMHlQN000WUwzZmF0U3ZJWW1tZnlYQzlrdDNTdnovTnlySHpQaFVuSjB5ZS9zVVNYeG56UXh3Y20vOVB3QXFyUWFBM1FwUWtINTd5YkYvT29yeVBlKzJoPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgVmVyc2lvbj0iMi4wIiBJRD0icGZ4OTUxNmIwZjMtNDUzNi0xMGY2LWM2ZmEtOWRkNTIzZTE0OThjIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29tL3NhbWwyPC9zYW1sOklzc3Vlcj48c2FtbDpTdWJqZWN0PjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMzAtMDYtMDRUMDI6Mjc6MDJaIiBSZWNpcGllbnQ9InJlY2lwaWVudCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjxzYW1sOkVuY3J5cHRlZElEPjx4ZW5jOkVuY3J5cHRlZERhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIiB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTI4LWNiYyIvPjxkc2lnOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PHhlbmM6RW5jcnlwdGVkS2V5Pjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNyc2EtMV81Ii8+PHhlbmM6Q2lwaGVyRGF0YT48eGVuYzpDaXBoZXJWYWx1ZT5ZUkdFZGF2dWpSNlYwNUZsWERHbmxCK1VUWTFjak9DallkYlhKN2JBZHFURWxDTyt1eHl0aytnMWVTTGVuczhJcjlZaVBNNUorUWU5cXo0TkdORXdyNjV6aDM5L0ZJVXNMQ3BhaXQ3QjZXM2lFcmR4aVUrSUN1cUw3TCtNSmlGVHZiVG90NVdleWZvVkFnSE94Z1BodDRONlZSL3BhYzRDdFZEQ0ZBbDlEMjA9PC94ZW5jOkNpcGhlclZhbHVlPjwveGVuYzpDaXBoZXJEYXRhPjwveGVuYzpFbmNyeXB0ZWRLZXk+PC9kc2lnOktleUluZm8+DQogICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+dFdQZEV1dXZmSjh3WVBhOFVUQTRvR2htRENQTjFhQzVkUUFEN0g5SkhWQm5VS3Y0UkljNEQ3SnVJem12bXlyalZGWmRGNW15K3cvUGd3dWlOVGdpOUxid01iSW5adW1HbDhlSndFblBaVXBPQ0w1dDNXbEdKbU85OVVNejZQUVNLeGlGSU1DYzcrQXlRQmpjdTEzaUxWeU5TbFQyWDMxRXBOaW5jQ3FzSldvPTwveGVuYzpDaXBoZXJWYWx1ZT4NCiAgIDwveGVuYzpDaXBoZXJEYXRhPg0KPC94ZW5jOkVuY3J5cHRlZERhdGE+PC9zYW1sOkVuY3J5cHRlZElEPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDExLTA2LTA0VDAyOjE3OjAyWiIgTm90T25PckFmdGVyPSIyMDMwLTA2LTA0VDAyOjI3OjAyWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL3NvbWVvbmUuZXhhbXBsZS5jb20vYXVkaWVuY2U8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTA2LTA0VDAyOjIyOjAyWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAzMC0wNi0wNVQwMjoyMjowMloiIFNlc3Npb25JbmRleD0iXzE2ZjU3MGZiYzAzMTUwMDdhMDM1NWRmZWE2YjNjNDZjIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/response_node_text_attack.xml.base64 b/test/responses/response_node_text_attack.xml.base64 new file mode 100644 index 000000000..ba9f2f126 --- /dev/null +++ b/test/responses/response_node_text_attack.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJHT1NBTUxSMTI5MDExNzQ1NzE3OTQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIFZlcnNpb249IjIuMCIgSUQ9InBmeGE0NjU3NGRmLWIzYjAtYTA2YS0yM2M4LTYzNjQxMzE5ODc3MiIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhLzEzNTkwPC9zYW1sOklzc3Vlcj4NCiAgICA8ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgICAgIDxkczpTaWduZWRJbmZvPg0KICAgICAgICA8ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogICAgICAgIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4YTQ2NTc0ZGYtYjNiMC1hMDZhLTIzYzgtNjM2NDEzMTk4NzcyIj4NCiAgICAgICAgICA8ZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICAgIDxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPg0KICAgICAgICAgICAgPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgICAgICAgIDwvZHM6VHJhbnNmb3Jtcz4NCiAgICAgICAgICA8ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz4NCiAgICAgICAgICA8ZHM6RGlnZXN0VmFsdWU+cEpRN01TL2VrNEtSUldHbXYvSDQzUmVIWU1zPTwvZHM6RGlnZXN0VmFsdWU+DQogICAgICAgIDwvZHM6UmVmZXJlbmNlPg0KICAgICAgPC9kczpTaWduZWRJbmZvPg0KICAgICAgPGRzOlNpZ25hdHVyZVZhbHVlPnlpdmVLY1BkRHB1RE5qNnNoclEzQUJ3ci9jQTNDcnlEMnBoRy94TFpzektXeFU1L21sYUt0OGV3YlpPZEtLdnRPczJwSEJ5NUR1YTNrOTRBRnp4R3llbDVnT293bW95WEpyQU9ya1BPMHZsaTFWOG8zaFBQVVp3UmdTWDZROXBTMUNxUWdoS2lFYXNSeXlscXFKVWFQWXptT3pPRTgvWGxNa3dpV21PMD08L2RzOlNpZ25hdHVyZVZhbHVlPg0KICAgICAgPGRzOktleUluZm8+DQogICAgICAgIDxkczpYNTA5RGF0YT4NCiAgICAgICAgICA8ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUJyVENDQWFHZ0F3SUJBZ0lCQVRBREJnRUFNR2N4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUlV3RXdZRFZRUUhEQXhUWVc1MFlTQk5iMjVwWTJFeEVUQVBCZ05WQkFvTUNFOXVaVXh2WjJsdU1Sa3dGd1lEVlFRRERCQmhjSEF1YjI1bGJHOW5hVzR1WTI5dE1CNFhEVEV3TURNd09UQTVOVGcwTlZvWERURTFNRE13T1RBNU5UZzBOVm93WnpFTE1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ01Da05oYkdsbWIzSnVhV0V4RlRBVEJnTlZCQWNNREZOaGJuUmhJRTF2Ym1sallURVJNQThHQTFVRUNnd0lUMjVsVEc5bmFXNHhHVEFYQmdOVkJBTU1FR0Z3Y0M1dmJtVnNiMmRwYmk1amIyMHdnWjh3RFFZSktvWklodmNOQVFFQkJRQURnWTBBTUlHSkFvR0JBT2pTdTFmalB5OGQ1dzRReUwxemQ0aEl3MU1ra2ZmNFdZL1RMRzhPWmtVNVlUU1dtbUhQRDVrdllINXVvWFMvNnFRODFxWHBSMndWOENUb3daSlVMZzA5ZGRSZFJuOFFzcWoxRnlPQzVzbEUzeTJiWjJvRnVhNzJvZi80OWZwdWpuRlQ2S25RNjFDQk1xbERvVFFxT1Q2MnZHSjhuUDZNWld2QTZzeHF1ZDVBZ01CQUFFd0F3WUJBQU1CQUE9PTwvZHM6WDUwOUNlcnRpZmljYXRlPg0KICAgICAgICA8L2RzOlg1MDlEYXRhPg0KICAgICAgPC9kczpLZXlJbmZvPg0KICAgIDwvZHM6U2lnbmF0dXJlPg0KICAgIDxzYW1sOlN1YmplY3Q+DQogICAgICA8c2FtbDpOYW1lSUQgRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoxLjE6bmFtZWlkLWZvcm1hdDplbWFpbEFkZHJlc3MiPnN1cHBvcnQ8IS0tIGF0dGFjayEgLS0+QG9uZWxvZ2luLmNvbTwvc2FtbDpOYW1lSUQ+DQogICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uIE1ldGhvZD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmNtOmJlYXJlciI+DQogICAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb25EYXRhIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiIFJlY2lwaWVudD0ie3JlY2lwaWVudH0iLz48L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxMC0xMS0xOFQyMTo1MjozN1oiIE5vdE9uT3JBZnRlcj0iMjAxMC0xMS0xOFQyMjowMjozN1oiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+e2F1ZGllbmNlfTwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxMC0xMS0xOFQyMTo1NzozN1oiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMTlUMjE6NTc6MzdaIiBTZXNzaW9uSW5kZXg9Il81MzFjMzJkMjgzYmRmZjdlMDRlNDg3YmNkYmM0ZGQ4ZCI+DQogICAgICA8c2FtbDpBdXRobkNvbnRleHQ+DQogICAgICAgIDxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9zYW1sOkF1dGhuQ29udGV4dD4NCiAgICA8L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9InN1cm5hbWUiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnM8IS0tIGF0dGFjayEgLS0+bWl0aDwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0iYW5vdGhlcl92YWx1ZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+dmFsdWUxPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTp0eXBlPSJ4czpzdHJpbmciPnZhbHVlMjwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0icm9sZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+cm9sZTE8L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICA8L3NhbWw6QXR0cmlidXRlPg0KICAgIDwvc2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgPHNhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9ImZpcnN0bmFtZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOnR5cGU9InhzOnN0cmluZyI+Ym9iPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4gIA0KICAgICAgPHNhbWw6QXR0cmlidXRlIE5hbWU9ImF0dHJpYnV0ZV93aXRoX25pbF92YWx1ZSI+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOm5pbD0idHJ1ZSIvPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJhdHRyaWJ1dGVfd2l0aF9uaWxzX2FuZF9lbXB0eV9zdHJpbmdzIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUvPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZT52YWx1ZVByZXNlbnQ8L3NhbWw6QXR0cmlidXRlVmFsdWU+DQogICAgICAgIDxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeHNpOm5pbD0idHJ1ZSIvPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhzaTpuaWw9IjEiLz4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/response_node_text_attack2.xml.base64 b/test/responses/response_node_text_attack2.xml.base64 new file mode 100644 index 000000000..59d049c36 --- /dev/null +++ b/test/responses/response_node_text_attack2.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9InBmeGJjODI2YWZkLWU5ZmUtZDNmYi1kODc0LWM0NzAwYzNlZjBjOCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2FwcC5tdWRhLm5vL3Nzby9jb25zdW1lIiBJblJlc3BvbnNlVG89Il9mYzRhMzRiMC03ZWZiLTAxMmUtY2FhZS03ODJiY2IxM2JiMzgiPjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbDI8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZnhiYzgyNmFmZC1lOWZlLWQzZmItZDg3NC1jNDcwMGMzZWYwYzgiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPkl6NFpRbHMzQUpaRGIzczh2Y1VYLzNSYytGUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+UWhLSm1vbnlzUDFxbW5hN1MrZUUxTGMycktBampDMk9HclFPZ1NqUHBUb2N1bVE2aFlIa3pUU1pyN3QvSS9LVE9TdkhDUXFEMXJoNGxTMGpEUC9FdUhOQUN0azlZN2xsMlV5Z3U3MkwrYkZ0cVoyOURuOXJMa1NkR3JpK0k3SGh4TDM2N2RmQVNTaDYrc3k3V2V2RWRrTWZ3ZURRMkFYL3NhNkJCR2d6N1RFPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDR3pDQ0FZUUNDUUNOTmNRWG9tMzJWREFOQmdrcWhraUc5dzBCQVFVRkFEQlNNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0JNQ1NVNHhGVEFUQmdOVkJBY1RERWx1WkdsaGJtRndiMnhwY3pFUk1BOEdBMVVFQ2hNSVQyNWxURzluYVc0eEREQUtCZ05WQkFzVEEwVnVaekFlRncweE5EQTBNak14T0RReE1ERmFGdzB4TlRBME1qTXhPRFF4TURGYU1GSXhDekFKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJRXdKSlRqRVZNQk1HQTFVRUJ4TU1TVzVrYVdGdVlYQnZiR2x6TVJFd0R3WURWUVFLRXdoUGJtVk1iMmRwYmpFTU1Bb0dBMVVFQ3hNRFJXNW5NSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURvNm0rUVp2WVEveEwwRWxMZ3VwSzFRRGNZTDRmNVBja3dzTmdTOXBVdlY3ZnpUcUNIazhUaEx4VGs0Mk1RMk1jSnNPZVVKVlA3MjhLaHltakZDcXhnUDRWdXdSazlycEFsMCttaHk2TVBkeWp5QTZHMTRqckRXUzY1eXNMY2hLNHQvdndwRUR6MFNRbEVvRzFrTXpsbFNtN3paUzNYcmVnQTdEak5hVVlRcXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBTE0ydkdDaVEvdm0rYTZ2NDArVlgyemRxSEEyUS8xdkYxaWJReko1NE1KQ09WV3ZzK3ZRWGZaRmhkbTBPUE0ySXJEVTdvcXZLUHFQNnhPQWVKSzZIMHlQN000WUwzZmF0U3ZJWW1tZnlYQzlrdDNTdnovTnlySHpQaFVuSjB5ZS9zVVNYeG56UXh3Y20vOVB3QXFyUWFBM1FwUWtINTd5YkYvT29yeVBlKzJoPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgVmVyc2lvbj0iMi4wIiBJRD0icGZ4OTUxNmIwZjMtNDUzNi0xMGY2LWM2ZmEtOWRkNTIzZTE0OThjIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29tL3NhbWwyPC9zYW1sOklzc3Vlcj48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+dGVzdDwhLS0gYXR0YWNrIC0tPkBvbmVsb2dpbi5jb208L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMzAtMDYtMDRUMDI6Mjc6MDJaIiBSZWNpcGllbnQ9InJlY2lwaWVudCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDExLTA2LTA0VDAyOjE3OjAyWiIgTm90T25PckFmdGVyPSIyMDMwLTA2LTA0VDAyOjI3OjAyWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL3NvbWVvbmUuZXhhbXBsZS5jb20vYXVkaWVuY2U8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTA2LTA0VDAyOjIyOjAyWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAzMC0wNi0wNVQwMjoyMjowMloiIFNlc3Npb25JbmRleD0iXzE2ZjU3MGZiYzAzMTUwMDdhMDM1NWRmZWE2YjNjNDZjIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/response_node_text_attack3.xml.base64 b/test/responses/response_node_text_attack3.xml.base64 new file mode 100644 index 000000000..3ecce91e6 --- /dev/null +++ b/test/responses/response_node_text_attack3.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9InBmeGJjODI2YWZkLWU5ZmUtZDNmYi1kODc0LWM0NzAwYzNlZjBjOCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2FwcC5tdWRhLm5vL3Nzby9jb25zdW1lIiBJblJlc3BvbnNlVG89Il9mYzRhMzRiMC03ZWZiLTAxMmUtY2FhZS03ODJiY2IxM2JiMzgiPjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbDI8L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZnhiYzgyNmFmZC1lOWZlLWQzZmItZDg3NC1jNDcwMGMzZWYwYzgiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPkl6NFpRbHMzQUpaRGIzczh2Y1VYLzNSYytGUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+UWhLSm1vbnlzUDFxbW5hN1MrZUUxTGMycktBampDMk9HclFPZ1NqUHBUb2N1bVE2aFlIa3pUU1pyN3QvSS9LVE9TdkhDUXFEMXJoNGxTMGpEUC9FdUhOQUN0azlZN2xsMlV5Z3U3MkwrYkZ0cVoyOURuOXJMa1NkR3JpK0k3SGh4TDM2N2RmQVNTaDYrc3k3V2V2RWRrTWZ3ZURRMkFYL3NhNkJCR2d6N1RFPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDR3pDQ0FZUUNDUUNOTmNRWG9tMzJWREFOQmdrcWhraUc5dzBCQVFVRkFEQlNNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0JNQ1NVNHhGVEFUQmdOVkJBY1RERWx1WkdsaGJtRndiMnhwY3pFUk1BOEdBMVVFQ2hNSVQyNWxURzluYVc0eEREQUtCZ05WQkFzVEEwVnVaekFlRncweE5EQTBNak14T0RReE1ERmFGdzB4TlRBME1qTXhPRFF4TURGYU1GSXhDekFKQmdOVkJBWVRBbFZUTVFzd0NRWURWUVFJRXdKSlRqRVZNQk1HQTFVRUJ4TU1TVzVrYVdGdVlYQnZiR2x6TVJFd0R3WURWUVFLRXdoUGJtVk1iMmRwYmpFTU1Bb0dBMVVFQ3hNRFJXNW5NSUdmTUEwR0NTcUdTSWIzRFFFQkFRVUFBNEdOQURDQmlRS0JnUURvNm0rUVp2WVEveEwwRWxMZ3VwSzFRRGNZTDRmNVBja3dzTmdTOXBVdlY3ZnpUcUNIazhUaEx4VGs0Mk1RMk1jSnNPZVVKVlA3MjhLaHltakZDcXhnUDRWdXdSazlycEFsMCttaHk2TVBkeWp5QTZHMTRqckRXUzY1eXNMY2hLNHQvdndwRUR6MFNRbEVvRzFrTXpsbFNtN3paUzNYcmVnQTdEak5hVVlRcXdJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0R0JBTE0ydkdDaVEvdm0rYTZ2NDArVlgyemRxSEEyUS8xdkYxaWJReko1NE1KQ09WV3ZzK3ZRWGZaRmhkbTBPUE0ySXJEVTdvcXZLUHFQNnhPQWVKSzZIMHlQN000WUwzZmF0U3ZJWW1tZnlYQzlrdDNTdnovTnlySHpQaFVuSjB5ZS9zVVNYeG56UXh3Y20vOVB3QXFyUWFBM1FwUWtINTd5YkYvT29yeVBlKzJoPC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgVmVyc2lvbj0iMi4wIiBJRD0icGZ4OTUxNmIwZjMtNDUzNi0xMGY2LWM2ZmEtOWRkNTIzZTE0OThjIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDYtMDRUMDI6MjI6MDJaIj48c2FtbDpJc3N1ZXI+aHR0cHM6Ly9hcHAub25lbG9naW4uY29tL3NhbWwyPC9zYW1sOklzc3Vlcj48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+dGVzdEBvbmVsb2dpbi5jb208IVtDREFUQVsuZXZpbC5jb21dXT48L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMzAtMDYtMDRUMDI6Mjc6MDJaIiBSZWNpcGllbnQ9InJlY2lwaWVudCIvPjwvc2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDpTdWJqZWN0PjxzYW1sOkNvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDExLTA2LTA0VDAyOjE3OjAyWiIgTm90T25PckFmdGVyPSIyMDMwLTA2LTA0VDAyOjI3OjAyWiI+PHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj48c2FtbDpBdWRpZW5jZT5odHRwczovL3NvbWVvbmUuZXhhbXBsZS5jb20vYXVkaWVuY2U8L3NhbWw6QXVkaWVuY2U+PC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+PC9zYW1sOkNvbmRpdGlvbnM+PHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0aG5JbnN0YW50PSIyMDE0LTA2LTA0VDAyOjIyOjAyWiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAzMC0wNi0wNVQwMjoyMjowMloiIFNlc3Npb25JbmRleD0iXzE2ZjU3MGZiYzAzMTUwMDdhMDM1NWRmZWE2YjNjNDZjIj48c2FtbDpBdXRobkNvbnRleHQ+PHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmRQcm90ZWN0ZWRUcmFuc3BvcnQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+PC9zYW1sOkF1dGhuQ29udGV4dD48L3NhbWw6QXV0aG5TdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/response_unsigned_xml_base64 b/test/responses/response_unsigned_xml_base64 new file mode 100644 index 000000000..ab02e395a --- /dev/null +++ b/test/responses/response_unsigned_xml_base64 @@ -0,0 +1 @@ +rVXbbtowGL6ftHeIcg+JnSNWS8eKJiH1tFJV024mx/5TPCVxFDuDd9vFHmmvMDsESqGwaeoNkJ////IdfPj989fZxaosnB/QKCGrcxcNfdeBikkuqqdzt9X5IHUvxu/fnSlaFjW5B1XLSoFjpipFuqJpayoiqRKKVLQERTQj88n1FcFDn9SN1JLJwt0ZOT1BlYJGGzquM5ueu99wHPk4zMLYxzmLU+ajMGIxjFiUIJ7mMUYjiFkOYKg/boRgK2SmVAuzSmlaaVPyERr48QAFDygmPiY4+uo6U1BaVFR3Uwuta+J5hWS0WEilvXpRDyxjjxnVbQlDUzC41caHB2kIBjgMMU1ZwFFGU6AoiJlrPHOczjXS0WjGFlwZdMFrT4myLqBDth+4K5agKaea2pecebujW6yazDXVreoqe7VLycF5pEULp/1VXTeZt4yBUq7jreG9A/w1+8kmjj7AlRJbo5bL5XAZDGXz5GHfR96X66s5W0BJB6JznYG7nfr7UJ93HGQ+BQoMZ1kWRmmeIBQFOUapzxLMwmAUIB6wEf6fvHeNe6NgNmjzNvsOTPe1TfXG+D+bOvM7++NzSwuRC2jO3c3Kcp1PsimpPp4ZGqKuIvgg71qJ8UoUE84bm9+4kVJ/gBW1xIdMlj3D9Yv32PQcL2WVCwtlY70GvZD89JphJcmANtC4W8ATkFPjlXMj9W1120xybeXuhZGsN989MFELsGm94dbrV3RH0TvGcROd90p2a2GmmwvbqqyWj2DMhwMhiERWyD+I3Uti0nIjnIGRoxvBdigd9ow3XvRst/U9mccx+4ZnSS+UTlq9qOzGh9Jk4XSPr24jTMKEBIERPDdLz+Ac0R0++P72hO1bZxWHlYksGo1CyrIIp0mKkjT3cRbHEI8CDhkEoZ/gKBnFjGN2aJnhZSRoWOnxvlPPf10W5ga5h3x88pJhhNk+U74zX0vZ8K2Jr0AdGL3D5IXDL63cOVmfz1F7m3ovr9PxHw== \ No newline at end of file diff --git a/test/responses/response_with_ds_namespace_at_the_root.xml.base64 b/test/responses/response_with_ds_namespace_at_the_root.xml.base64 new file mode 100644 index 000000000..48893cdab --- /dev/null +++ b/test/responses/response_with_ds_namespace_at_the_root.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiDQp4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9Il84ZThkYzVmNjlhOThjYzRjMWZmMzQyN2U1Y2UzNDYwNmZkNjcyZjkxZTYiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTA3LTE3VDAxOjAxOjQ4WiIgRGVzdGluYXRpb249Imh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzRmZWUzYjA0NjM5NWM0ZTc1MTAxMWU5N2Y4OTAwYjUyNzNkNTY2ODUiPg0KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4NCiAgPC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkFzc2VydGlvbiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4bWxuczp4cz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIElEPSJwZng2YzViMTVjYy02YzY4LTljMWYtY2QzNy05ZTFmYTFkMmU3YTAiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTA3LTE3VDAxOjAxOjQ4WiI+DQogICAgPHNhbWw6SXNzdWVyPmh0dHA6Ly9pZHAuZXhhbXBsZS5jb20vbWV0YWRhdGEucGhwPC9zYW1sOklzc3Vlcj48ZHM6U2lnbmF0dXJlPg0KICA8ZHM6U2lnbmVkSW5mbz48ZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPg0KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjcnNhLXNoYTEiLz4NCiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNwZng2YzViMTVjYy02YzY4LTljMWYtY2QzNy05ZTFmYTFkMmU3YTAiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48L2RzOlRyYW5zZm9ybXM+PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIi8+PGRzOkRpZ2VzdFZhbHVlPmlHdENJcGpyZ09Hc2FDY0lFOEtwSWEzVzZnVT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+T0RIU2tzUkl1ZEc3b2s0KzRUZitPRnA0UFJ4Ukc3d3BERGdyVDZwQk5ZbUFKNUN2dFk5UlczRldHYlJtZ2NoSjdhYW9sQ3ViekQ5Qkk4Y01nb3V6ZGErdjcrZ1k4THloVUNmV2RSd0gvSzZlVmYvZUtldjk3aUQ1YVA1TTU1MCtCZ0NVaHVWNFg0MlpUbFhkdjBVKzRDQ1VYWnFuLzRjczBxNXhkeTBoM0JzPTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDYWpDQ0FkT2dBd0lCQWdJQkFEQU5CZ2txaGtpRzl3MEJBUTBGQURCU01Rc3dDUVlEVlFRR0V3SjFjekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFVk1CTUdBMVVFQ2d3TVQyNWxiRzluYVc0Z1NXNWpNUmN3RlFZRFZRUUREQTV6Y0M1bGVHRnRjR3hsTG1OdmJUQWVGdzB4TkRBM01UY3hOREV5TlRaYUZ3MHhOVEEzTVRjeE5ERXlOVFphTUZJeEN6QUpCZ05WQkFZVEFuVnpNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJVd0V3WURWUVFLREF4UGJtVnNiMmRwYmlCSmJtTXhGekFWQmdOVkJBTU1Ebk53TG1WNFlXMXdiR1V1WTI5dE1JR2ZNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0R05BRENCaVFLQmdRRFp4K09ONElVb0lXeGd1a1RiMXRPaVgzYk1ZellRaXdXUFVOTXArRnE4MnhvTm9nc28yYnlrWkcweWlKbTVvOHp2L3NkNnBHb3VheU1na3gvMkZTT2RjMzZUMGpHYkNIdVJTYnRpYTBQRXpOSVJ0bVZpTXJ0M0Flb1dCaWRSWG1ac3hDTkx3Z0lWNmRuMldwdUU1QXowYkhncFpuUXhUS0ZlazBCTUtVL2Q4d0lEQVFBQm8xQXdUakFkQmdOVkhRNEVGZ1FVR0h4WXFaWXlYN2NUeEtWT0RWZ1p3U1RkQ253d0h3WURWUjBqQkJnd0ZvQVVHSHhZcVpZeVg3Y1R4S1ZPRFZnWndTVGRDbnd3REFZRFZSMFRCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUTBGQUFPQmdRQnlGT2wraE1GSUNiZDNESmZucDJSZ2QvZHF0dHNaRy90eWhJTFd2RXJiaW8vREVlOThtWHBvd2hUa0MwNEVOcHJPeVhpN1piVXFpaWNGODl1QUd5dDFvcWdUVUNEMVZzTGFocUljbXJ6Z3VtTnlUd0xHV28xN1dEQWExL3VzRGhldFdBTWhnekYvQ25mNWVrMG5LMDBtMFlaR3ljNEx6Z0QwQ1JPTUFTVFdOZz09PC9kczpYNTA5Q2VydGlmaWNhdGU+PC9kczpYNTA5RGF0YT48L2RzOktleUluZm8+PC9kczpTaWduYXR1cmU+DQogICAgPHNhbWw6U3ViamVjdD4NCiAgICAgIDxzYW1sOk5hbWVJRCBTUE5hbWVRdWFsaWZpZXI9Imh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9tZXRhZGF0YS5waHAiIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6dHJhbnNpZW50Ij5fY2UzZDI5NDhiNGNmMjAxNDZkZWUwYTBiM2RkNmY2OWI2Y2Y4NmY2MmQ3PC9zYW1sOk5hbWVJRD4NCiAgICAgIDxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90T25PckFmdGVyPSIyMDI0LTAxLTE4VDA2OjIxOjQ4WiIgUmVjaXBpZW50PSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl80ZmVlM2IwNDYzOTVjNGU3NTEwMTFlOTdmODkwMGI1MjczZDU2Njg1Ii8+DQogICAgICA8L3NhbWw6U3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L3NhbWw6U3ViamVjdD4NCiAgICA8c2FtbDpDb25kaXRpb25zIE5vdEJlZm9yZT0iMjAxNC0wNy0xN1QwMTowMToxOFoiIE5vdE9uT3JBZnRlcj0iMjAyNC0wMS0xOFQwNjoyMTo0OFoiPg0KICAgICAgPHNhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPHNhbWw6QXVkaWVuY2U+aHR0cDovL3NwLmV4YW1wbGUuY29tL2RlbW8xL21ldGFkYXRhLnBocDwvc2FtbDpBdWRpZW5jZT4NCiAgICAgIDwvc2FtbDpBdWRpZW5jZVJlc3RyaWN0aW9uPg0KICAgIDwvc2FtbDpDb25kaXRpb25zPg0KICAgIDxzYW1sOkF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0OFoiIFNlc3Npb25Ob3RPbk9yQWZ0ZXI9IjIwMjQtMDctMTdUMDk6MDE6NDhaIiBTZXNzaW9uSW5kZXg9Il9iZTk5NjdhYmQ5MDRkZGNhZTNjMGViNDE4OWFkYmUzZjcxZTMyN2NmOTMiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KICAgICAgICA8c2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj51cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YWM6Y2xhc3NlczpQYXNzd29yZDwvc2FtbDpBdXRobkNvbnRleHRDbGFzc1JlZj4NCiAgICAgIDwvc2FtbDpBdXRobkNvbnRleHQ+DQogICAgPC9zYW1sOkF1dGhuU3RhdGVtZW50Pg0KICAgIDxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJ1aWQiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj50ZXN0PC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJtYWlsIiBOYW1lRm9ybWF0PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+dGVzdEBleGFtcGxlLmNvbTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0iZWR1UGVyc29uQWZmaWxpYXRpb24iIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPg0KICAgICAgICA8c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT0ieHM6c3RyaW5nIj51c2Vyczwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeHNpOnR5cGU9InhzOnN0cmluZyI+ZXhhbXBsZXJvbGUxPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICA8L3NhbWw6QXR0cmlidXRlU3RhdGVtZW50Pg0KICA8L3NhbWw6QXNzZXJ0aW9uPg0KPC9zYW1scDpSZXNwb25zZT4= \ No newline at end of file diff --git a/test/responses/response_with_multiple_attribute_statements.xml b/test/responses/response_with_multiple_attribute_statements.xml new file mode 100644 index 000000000..2ef97823d --- /dev/null +++ b/test/responses/response_with_multiple_attribute_statements.xml @@ -0,0 +1,72 @@ + + + + + https://app.onelogin.com/saml/metadata/13590 + + + + + + + + + + + pJQ7MS/ek4KRRWGmv/H43ReHYMs= + + + yiveKcPdDpuDNj6shrQ3ABwr/cA3CryD2phG/xLZszKWxU5/mlaKt8ewbZOdKKvtOs2pHBy5Dua3k94AF+zxGyel5gOowmoyXJr+AOr+kPO0vli1V8o3hPPUZwRgSX6Q9pS1CqQghKiEasRyylqqJUaPYzmOzOE8/XlMkwiWmO0= + + + MIIBrTCCAaGgAwIBAgIBATADBgEAMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9uZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMB4XDTEwMDMwOTA5NTg0NVoXDTE1MDMwOTA5NTg0NVowZzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFTATBgNVBAcMDFNhbnRhIE1vbmljYTERMA8GA1UECgwIT25lTG9naW4xGTAXBgNVBAMMEGFwcC5vbmVsb2dpbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOjSu1fjPy8d5w4QyL1+zd4hIw1Mkkff4WY/TLG8OZkU5YTSWmmHPD5kvYH5uoXS/6qQ81qXpR2wV8CTowZJULg09ddRdRn8Qsqj1FyOC5slE3y2bZ2oFua72of/49fpujnFT6KnQ61CBMqlDoTQqOT62vGJ8nP6MZWvA6sxqud5AgMBAAEwAwYBAAMBAA== + + + + + support@onelogin.com + + + + + + {audience} + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:Password + + + + + smith + + + value1 + value2 + + + role1 + + + + + bob + + + role2 + role3 + + + + + + + valuePresent + + + + + + diff --git a/test/responses/response_with_retrieval_method.xml b/test/responses/response_with_retrieval_method.xml new file mode 100644 index 000000000..d56cac575 --- /dev/null +++ b/test/responses/response_with_retrieval_method.xml @@ -0,0 +1,26 @@ +http://www.okta.com/exk4jkh2xxJIrTmGP0x748q4xnELjn82APp25kO4+wAdOCLN3BdNvZP7IGP2yW8=IisXGT9N0ewlZfvkrfNqqhXHnFkfXG8Z9zIybYIBrJ2WtkBh2gJiEmLGFwnaMyJmDhvrk42jlied6UrxpbZK9jwugJoVsj1CFJ7VHYkqX21yYXs0fwW7xKUvYyMDD0BS6FN96mdM8PD4RUNoxh6UymRs/akwPXCNYZqur2Sz/imEnRjP5SGA8RIpSYpva2vVqzwsDSRMRe2P18LH0fegDxOa81OEClYucSfvtZwgHopsSL3d+KsOH5zvnVngL7UxjxT2L8fEzy5LQl2kM28P6eZ/QccP8ACtUdVYZz0xGUF8I7IwV0HmouMjj1BYfiXVWLaiSZ5C1J4AA4KK1t2hGg==MIIDmjCCAoKgAwIBAgIGAVH33Q1VMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJVUzETMBEG +A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU +MBIGA1UECwwLU1NPUHJvdmlkZXIxDjAMBgNVBAMMBWZsYW50MRwwGgYJKoZIhvcNAQkBFg1pbmZv +QG9rdGEuY29tMB4XDTE1MTIzMTExNDYxN1oXDTI1MTIzMTExNDcxN1owgY0xCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKDARP +a3RhMRQwEgYDVQQLDAtTU09Qcm92aWRlcjEOMAwGA1UEAwwFZmxhbnQxHDAaBgkqhkiG9w0BCQEW +DWluZm9Ab2t0YS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCLjrnxEq1yqaEk +JPgqWeVxicFC9PnXCmrDtoOnhz/9AJbks5/cDRYFGMRYrS2a8EX8I4FAa2DPrKwjyk6YqzzR9jY/ +Il3x6PTSUEQh5cvCE/Vb9C7iJAInazZ4MTI5xdEwtNt3UD/aVaAfW8k64DquTZnWK4Wtg9igS2ne +pzYbmAma015O1oxggs4wv3JVgl5vCLdlMLj/kvBjx8XCXKOtVJVpHkJEI/pAE3s+XeVP3WBWbgEK +NAcjRnWJ3igXlNir3O2ee1+dcDkoVmpXILBmClMu/JEdQWvIfnXYZOYv9GUzS/EODQIbOYm8wsan ++ErfrVDAAr30zDa7pG823JwFAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAHTU2NZ3tgIcZNtx8QP8 +WMtJ0038nzGmttx+oBc3zFu1+z8XBGRGW27ISL3vncWSAxeuFFzFWjWpBmMU2piM6MFvOYciI3vu +iM6fpGCUKCojvKCfwZ/pQKP5RIk92vWlDl5USSr48NH4BUjC4qTIvQSaWrvZu+qlJ1h/JISPVy1J +ftZSshAYaZTChmnqp/KtKQq7DOPVc14HR3gBkyoakQKjtQ9TBHAENJV3N1FOPJ3/D/Y85EzecLdT +3fiT0RZZ7W/HsG2LLIQsTnsnBP018KEG2aqLFLxO67dVy2UchrxvwBjiyvoHVAdUdditoL5WKhAc +KhT/MUKMlzqzYa17eww=xgq5YG2k+6fI0+DeqrWlEHcxl3C+ujuFwMVD2th9d6H5bIvtAy52fFuKcYGAtGD9C/MqJkhA6L+EmpOhn1wUgA/lujmb9Od1ZkfMiI54WaX4gflahQ21YpXqvH5Ucp+XmG0AEiO++pSQ1tMf0L4SHMH60MXVGbKF4WPXNgHvGSvny5MTstoeYqM0jOpya9SrBjW29/DnjSw6VDIwZC89ZibIjLsxNY4aYGK2b0IwBRpo9YHtaD39CYQY8GMBtUNQFi6K8VPIGMihvrbyVNd1VMK3DD2iZTgF/wS2914K9WAZaGNCaeLuJD9rs4Sz7OIOYIrXQSOhfgI4O9azHpfUHRmrKor3AV6uL+8VkyMQQELXtrbWehs8O79qlZs2tfJuqSohkRDx0Z0MFvRw6YWqa4fF1AR7Ng/Z90FWNZJ0mzrVdztxdUnwa9t4bm07GNJtlhEELI1l0D1dzJLhdyVVVgJnm/Y8tv6EXz5lfUvfOj3XZcfuH51iYRaKD0LUxXwGIgIV7PoEanR0zSFC+wlYsYpqZuUSyFeAafKLEyE+Gmo7up/YXHSDhYyJx1UxUjyqr2lxVm36QWIdAhsQ7I/9Ew94IopuUydDD4lI0FezuRsphklv5wMxHon+49BEP8ZN3zK8E3csuxHXH0kESue0nkodTH9dJskpDUDtF68BxmVyLDTyq/9JOV++H5tHOXatWRfu74k+GUQvvRgRx/5dr8NO2QQ3r2pVtP19hScuZLVGpf9DV8RQRI/icyxJBz/OnmMGJoOU+t6tyvu5q2kSPZi/9Ipz4cFO6m11YFa5Kernuey6PGL6J0y9yFGtEs4nR0msYM6M8gBn1XRLvYCMVUcaUHXFiLZcvsOTNuLjiaes4gj6ONBfAcX98IZYPVLq1McQI+Izt0dNquU9yiMBG1GjJ0RUF4VR0naQezTixafugGNovgs2awDrRY5QJL2ffVzlxG3DZFRrcs2qGXQ7Gm9eja/j08xRfIs7wYqusCSFfmgqXcGQ41NkSszu91HOt49c5S35jvySU95aPcEhmN86O2NFZOpGKwnPbBAf2e1vglUcRZkljzw0mbUja26RULKog0IJlSe43yPBC4aof1Eher6x587YZ6vwmibN/aZLxRGe2ybm8HEDPt0VphG8Z17Kw9z5b1Uc0UjsWYYW9cEh/lRs3DdE40onwYgjggM6/cieSigEMw2kqoAYeVNd4O9nq1Uek9ejGEuuIpuiPdCnOufejmsIqVC3x6xHMdVkbojD/l7pESo48Qvqa1/bpzh58Law8VWByFnPpmM+dIK/FPy/0KjaXzcs8T4x/spH/hmNPO5tUTIt+dZIQtGrQQda7mnZ1SLOnNX3lLoQcaXDuXqG5OgXRC9zSXtFl6/0h7nHwP5vOCDKahlcu2Cm5L03WCGxcwqGb/rcZeFHjJhNkbqWvYAW+ouWz/aEQVbHc9Gf7OAxX6Xs/y5LTwW/TpySmGDZBlgmX6JE7yaQYORuhglXFiQkMAemzthrj8FWf+XejleSfbVZcM+rWd6sbzy2eXo665Q1gO+8S4jQfWwz/GP5N08ESdTbH2XhclWAYbEKP2JtO+rQAjFgWVraV2tnNKquVcEITDn3BqzHxkShfafpPpccYbnYFfSVRcy3laU1hRM+TbQz0KsmwjjaT8uJUj4A94eaxpPuUFxnh0TQjyIJKzcfa1KW8Hb155eUuYsjJG4fFliyFQweZEcn88RusCeXqQlmEkUNRl8qf2UwrdnCuMQpLSnMZvLClvJ5zTKQ2MevvCBTb8mXejyYfvdoF/9hjiXWOvARWiFHMhleaNV3ti2calnQh4XVO7PyogFoqp/ou98KRsyHdSp6ISTk08hOd2cinc1r8N0HUMrhl/14FItwky0MzRJeNomMRX7ggdpZr10Ytc1PrN7PYqslMRXqTe6U9mRteUYSxZqt1VwzPWDEe02sS+t2RIWwWvUU/TMId2mvNmBGUGNuBp7sIU0Y6OLSyPn9E3w/d2t8QcJcD1xFDehfOQshl+de/dx9VwwCJxQQ1px8fephOPvOatdW4FCPM9+gMKzCoiZvyc3uD7VOawV54+nMeL/5iTLLsVsCDSOKCh0RTZbg9aTOSK8E2UV9JVJ/BxtqR3I72Kl6B1CJFbQCPmjftCEK3oJpF6Hj7lcbYCRwF6dnhML7oJS92P8byznuKNqybAxGLqv4ijhuLvhN96Nr3TcH2rBaw0JXZlUVnSBrvHnqmfFnJBl/o5XrZux0sKHdaQBJXuFC3hquKTomCczlBMQcNaq1t79NYY2EiA/Q/zslUOcfIu9QEZCA8LpwTRAxIjjH3ebRn7rxyyqomnOXC4JI5WkJCwhFMEAexGLvqtBM7pWoNcEPn5J46El7TjRFuO7ypKpH388PkLGWrMPdD8I1EZM7pe0p4FIxLrIY9n5YH6iN0QPyZ2nCjl+ffCSwcxOBPxeFmkbZOZOGrSWiRUnBeK8Yf7+TVV4m7iOrLVazCqngcscFoax1O5kOitVbMO6biHtxNHTYccd6TsrnmGuOifpx2JMm4kiG562u259Bu4eld1MqmUt8vKk/3aVf3/8QqKqjDOFrcnF3JiXzEkbxstpiSG2ZW+a4GW+KKinFDq7AQsyTP74m2NonxHgt+ETLYjcPIiTL0iH5YaVAZfrDo+R1bXl0WYpo5Xd6yihKRZiR7mCt4zTFXpZLppTpJvngwT5qoyBgBmXFeh1zz75aEgibwQ8NimWSm3x4F+w7caiax/CUsBH8dxwGBFnuS3yxZ/8pNBgmZC5ZT2tLdSKydRXkWRcl4/BLmwuMQ9DiKaGbk9+3AbmoE3avFHcrtQZPDw+Xu0j7M93Lq6wPcku7KJnB9EboGcQZ0Iw4TPIFmNayrklXAMZRKRCFrQ/lYhftF5KgLq6/um8ult1ufodYMRnVKegR6I9q+RD4zmfEzmo7EPkY+47NzM+h0H1a9N64bUsYIHo170KO61zQlg1eqVTeiKqK227CK06Fqp8Hyuq3eZbac+6fST2sMVfgAzfNnFyQ7gOyo/7RMZzlYAcacPMNXgcqxCnBN+3dH8wmPstAB7lhNZRz9o/n2X4DI+o2RP/n4riy2TTOOrEuWxpSKcjtOj0PnDVBrsUcv+qXPO9oxbAfLCcieYwzWZOPKlVd4hqint4sHlrtX55Ga00fgVAHxbEEkDCRUgE09YVn4d590r/Pr5W3tyOBAc3qqgJMVqDT//huGuyfqCDvYqrMhcyKiLBNlzkUWTOqywrmp6CTGq6WZZ5lmK5ISrcyNrkPEWN7Uwm4XGUGYbfUicN1IEK1XcxWjRwo1Y8U4ooj7SHN8xu+6+3k+5zi5ZitgDWao+e7n/yHhADdJukdYGMe7H0IiKK8XpjYpcQ/DoEdqB6VV6Agu/4C8cBvzSi8/K/9tuyvPnLD1qMG6ajKpnP0885TxBEajx4/JZ+tNt39H0cOxPT7tAEBxMj0V4+IXaG3+35uxxUN7ZqqGZb9BWGNKF5xHfcM4G6mLSUHcdyJD1yfHT6sv/TpF9rhDxebirZYEkkUMZDu8v4Yy21lVg9GqRNJdqmn647xGT/8GF4fNV1uldQAXL9wi++hCCqNmfz24dMm2GfOr/0Zc1l9SCrbOQPi+YHgnWENzViqB4MZZzuinlHY8xu7CT/J5PjSCxzfMUHBEjKCd+/Wov3eSWiIX2tLA3W97OTzBpPS87/lLUzSY61Psh2pWUpmiaoiIGwer/YliigQEhNC2UVLj08aNiEkPge1a8HlrqyO/fO2Z1tv6eaMGoCdq7jAibgkPVeu9jKk0SQrQFlfkG8vRKhQ4s6pGVwNzdMFYUqZnccwz1rbiiKXGUfNdiZ5urtwXV3N2Ow3bt+EG/Qzu67njazYTVg6QVN1DwXw/DVNxnYLphwT+6CnO90J4dAu/OoiJGqVb6GaarAuedakEVKpMu8ZoifezZ9l3/xHbnHnck2ucvbeqAEaUPCG8f6/DD0Lga1ThG1Lbkfoqo4jXXARQZVF8nRWJtslZzQ39iAPS1dQS5kwd5GA/g0JFLDBQz+DNOERg5OqErMGcXmq8U68VAt5zhoo+m/GY44nqorILIkuS6H7vr8YSmd3QefPsptTjdKWXigU4+2U7Hgpj8LnG866ya0ZYLkybCGWlHt3QhrABsTQms9Yuw2GY/PALG08UTTYs+CHbjYTVAqz3evd+VMXN6iFeVOU3pwBCbGUY/WqtTmeBoeThWd/JfhbRss0cPLBiOwu2OFiXyKebiDbrW9FuoY9mpHcle9MeB2Ny7e3ht+PuKxlDsqqejEdRAszVrXAgz/ktstviLJRPGixxJfDqhJBOOSLEehRs9EmHEWZ2N+UQYRFGbzgk5uRALhX7M76twJR2APlkTqXB1UGaEjlKkWDuIu7NLV2ss9OhO7LmjXe6ETl76/ZeMDvA6V7MVXpI6nwwTRD649DJlkMtNeA8k39Jel3W9mWXlhlNGrlSUsfI/NeDqNW8DP8mmPWvoSrf6k4lu2TTAoxZ/esVN2xRhiRxMxpKPEn6AOOwL3RftcOOjRWdxsDBA4sjdiAJC1gADVJwIu8XOSLL5wc8HekHcgGzHoT60CJ+yhcI+ztQPDqtt7Pb9Oe1V9sKACQ1ymNzHLLnBawEN/VvL0WedGW0cTiO4Ydv8IfS/F8Clk6Bhyswg6hKQ8VHSwh8NrXK3FE3dM+gGQDAEJtWkL0XPGFNDdJvu/qE7MuHSqaad3uD3RUnkJB3KybnC3wBjJHSfC6cTf0z6vPQd3LKe8B4GdSZ2IQ0vGrwN1C9+qf63DQ45tpEoUp78JJaX/J1ag2QP2jYhbPMxGTxiAFo68NZaTjS+eR2FRTlbankQhs+u78oksenPo19d9Hz8GzAgU4Yk61VK2RqM0LsiSzjrFO5J3IkR78sSaai/93VFCPqw5rP8n0G40mpb8vYSFaFIy0MwudKrJRDBluNGVAmp9QAQDEt+ygzsifaO+SoY+AuL+LTXv53GAYB0W/C6yNDDuL0gvgp0VAnWw1sp66RLHKEy3L667NZzkSamxdxWaYu7+Lq8R2KiXSgiORo6FO3xFeXPbbNnDAzP25/4Lk0pY8buLWWctzgG8jfLikQRttj47AzAnfsxUDqes4/ln1D/HJZd8TpcjCbnqfXzkZRVezw5lP6k+R48CUHlZpVzS5o+BvCE585eS+CHPcQZ7G8qVlCryp17MCjWuvAm5oJFvH2QyAVe008tVgzn5LU11hjMsSXQ0xBHeSObIaVMKiyeKhETB1gLA/l7E9SOCoJ2e6Lo1WLOmy6l0k52WVIgC0alueeFBsYQ2n/7SWx25FIGUPlWea11mmcCwsmNeQvr4pgCSi7/n/cSFNKb/SdZgptfvXWdi3uJGNBclfktru4Nz8PxKrxyyP71XcLMN2gaEAFCLlpRGI+ow0s54BY8YxiqOHn6uUc8RZe8hjfLqKd5neWerFmswK0JH0JsmLfyrkFi7L8KeEjp8+UYOJD6wpR6ZCJETucI9pM37FP6zro4GD77Kt8QLoEvaUiIKve7dv9rMFzQxgvdyl7iyydNi09GxOnEaikasZ7xYMftCsKWOhnyUcwPjuStsQLME5vJbvsQh5eK44J+cHgw6lWk54r1ZyUxMPRbrBVojhTHBxXbz/MOgRipWdQo6UbPwBMNovD+MeWeORjZjbuDJVna29JEbaNMzkp60xV7BhKKTbus1yHItwKjYtAYQohFdAJJboMZnMqBZl62M1FnrqXODJMcW5amBXURHy56PZrkigCNeZXs4Uf+j0Bn2+rcoORgJmLS8+wt5NC/P+ug6xxxXpI3jzns9yp7rZpjeGQ6n6aCTcQbkMkhj2aeE7kZWntC9RoS6/52td7/h1VrUBY9o5GSD+Hiepd++ugyLssoCaafVbBw5vI7h4XaeFwfgugKTWXev2unFqaGW8zdi4b1d8L0Wx6BgdzOUBY6NBwVU2EN1JGmnEqyipTc8VIURJ/UjwepjWtuX4+Q6govcXvvJM3g3VRbO4Adsp6ozARg9smGx3bEHJqRsFpHfbFwJRqe51PN1lD3F5AjF37eYDViWm2NZVMUQqnekLL1HWhmSSFUzyxKrygdGO8CzrqmrxnSuXClGziCceHv6N0jiix6DwWYW+LsJkP7rdl1ttJFw+JlCthtu+oTQ2TBsDcGGnCU4fIoBCWyCsHh4sNfsIH9TJdeIovrEZQyxjK5YopbDxeGJ5RayfRdLQ428xNro1ZK9zrr3cneWvcgfVP+rScp+xRiRnxa+kH6I/FBZmpUNk2tUPTZKe3GQBEqGKco2noT+ukYFaBqDEc+1P6bhN6IGY8ZMKoLJVCYLYFnH/zUVu9bfj3MdYAhoyJS6hDYIgt4fdXAyIOlHlcPYk4qD9R0Cs/nM68UJQEK4eGVOjowMbeP2K1A==MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UE +CBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsT +A0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYD +VQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UE +CxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5 +PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl0+mhy6MP +dyjyA6G14jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA7DjNaUYQqwIDAQABMA0G +CSqGSIb3DQEBBQUAA4GBALM2vGCiQ/vm+a6v40+VX2zdqHA2Q/1vF1ibQzJ54MJCOVWvs+vQXfZF +hdm0OPM2IrDU7oqvKPqP6xOAeJK6H0yP7M4YL3fatSvIYmmfyXC9kt3Svz/NyrHzPhUnJ0ye/sUS +XxnzQxwcm/9PwAqrQaA3QpQkH57ybF/OoryPe+2huP8XSbkSQsgkyq9Gu52VifiVDD1z5Utof7Tytz36ObJNb6DQCaKMbKSq9aIVF9LUn0JMkTFnTaLSq749P76XBmiZRuD1OZOlUYt1vXAviiQ6+6fgIt7tpfDl8MxVPoMAV53c7mhIgxHSalONunVlKVnk0P2XaGHKK2EZloXK408= diff --git a/test/responses/response5.xml.base64 b/test/responses/response_with_saml2_namespace.xml.base64 similarity index 100% rename from test/responses/response5.xml.base64 rename to test/responses/response_with_saml2_namespace.xml.base64 diff --git a/test/responses/response3.xml.base64 b/test/responses/response_with_signed_assertion.xml.base64 similarity index 100% rename from test/responses/response3.xml.base64 rename to test/responses/response_with_signed_assertion.xml.base64 diff --git a/test/responses/r1_response6.xml.base64 b/test/responses/response_with_signed_assertion_2.xml.base64 similarity index 100% rename from test/responses/r1_response6.xml.base64 rename to test/responses/response_with_signed_assertion_2.xml.base64 diff --git a/test/responses/response_with_signed_assertion_3.xml b/test/responses/response_with_signed_assertion_3.xml new file mode 100644 index 000000000..00d9cdf59 --- /dev/null +++ b/test/responses/response_with_signed_assertion_3.xml @@ -0,0 +1,30 @@ + + + idp.example.com + + + + + idp.myexample.org + + + FA0AbR4w9oYdx7MFjERARVJAHps=GDH5jhCNX9PFxW+71SOJPyusAOwzECwmd57NDhvA/VKWHnV3PpvpNkOLyamoBNdZ4qxponnobg2zneLESrFnLJdJ1cgs51YvtBJTxKoA7oZMMNKReZFST8g7pDdrBC82n5rTdzxclaJkpwz1yjcho3K3TjxK+gU1svVrEKMUwyo= +MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl0+mhy6MPdyjyA6G14jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA7DjNaUYQqwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALM2vGCiQ/vm+a6v40+VX2zdqHA2Q/1vF1ibQzJ54MJCOVWvs+vQXfZFhdm0OPM2IrDU7oqvKPqP6xOAeJK6H0yP7M4YL3fatSvIYmmfyXC9kt3Svz/NyrHzPhUnJ0ye/sUSXxnzQxwcm/9PwAqrQaA3QpQkH57ybF/OoryPe+2h + + someone@example.org + + + + + + + example.com + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + + diff --git a/test/responses/response_with_signed_message_and_assertion.xml b/test/responses/response_with_signed_message_and_assertion.xml new file mode 100644 index 000000000..a5bca2633 --- /dev/null +++ b/test/responses/response_with_signed_message_and_assertion.xml @@ -0,0 +1,34 @@ + + + idp.example.com + + + hi2Ouec0ovl90Cz+OXAP6FD5X70=tJiaa5aZNzLFbBiIsyc0MBI4G1caG+gOW0joGlbMAyY86ERaDwDi1sz98+vykZOgjwkfZLT7K/AScdmp27PsaN4+NpLFRv/fUDyzKwjnDKMEzMBLi5nxDXVlYk1q5RCZbsV0W0He28Kl/+xwHP722CI/eWByU3rmR2H2wej8zZY= +MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl0+mhy6MPdyjyA6G14jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA7DjNaUYQqwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALM2vGCiQ/vm+a6v40+VX2zdqHA2Q/1vF1ibQzJ54MJCOVWvs+vQXfZFhdm0OPM2IrDU7oqvKPqP6xOAeJK6H0yP7M4YL3fatSvIYmmfyXC9kt3Svz/NyrHzPhUnJ0ye/sUSXxnzQxwcm/9PwAqrQaA3QpQkH57ybF/OoryPe+2h + + + + + idp.myexample.org + + + FA0AbR4w9oYdx7MFjERARVJAHps=GDH5jhCNX9PFxW+71SOJPyusAOwzECwmd57NDhvA/VKWHnV3PpvpNkOLyamoBNdZ4qxponnobg2zneLESrFnLJdJ1cgs51YvtBJTxKoA7oZMMNKReZFST8g7pDdrBC82n5rTdzxclaJkpwz1yjcho3K3TjxK+gU1svVrEKMUwyo= +MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl0+mhy6MPdyjyA6G14jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA7DjNaUYQqwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALM2vGCiQ/vm+a6v40+VX2zdqHA2Q/1vF1ibQzJ54MJCOVWvs+vQXfZFhdm0OPM2IrDU7oqvKPqP6xOAeJK6H0yP7M4YL3fatSvIYmmfyXC9kt3Svz/NyrHzPhUnJ0ye/sUSXxnzQxwcm/9PwAqrQaA3QpQkH57ybF/OoryPe+2h + + someone@example.org + + + + + + + example.com + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + + \ No newline at end of file diff --git a/test/responses/response1.xml.base64 b/test/responses/response_with_undefined_recipient.xml.base64 similarity index 100% rename from test/responses/response1.xml.base64 rename to test/responses/response_with_undefined_recipient.xml.base64 diff --git a/test/responses/response2.xml.base64 b/test/responses/response_without_attributes.xml.base64 similarity index 100% rename from test/responses/response2.xml.base64 rename to test/responses/response_without_attributes.xml.base64 diff --git a/test/responses/response_without_reference_uri.xml.base64 b/test/responses/response_without_reference_uri.xml.base64 new file mode 100644 index 000000000..603fceb5b --- /dev/null +++ b/test/responses/response_without_reference_uri.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9InBmeGQ1OTQzNDdkLTQ5NWYtYjhkMS0wZWUyLTQxY2ZkYTE0ZGQzNSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMDEtMDJUMjI6NDg6NDhaIiBEZXN0aW5hdGlvbj0iaHR0cDovL2xvY2FsaG9zdDo5MDAxL3YxL3VzZXJzL2F1dGhvcml6ZS9zYW1sIiBDb25zZW50PSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y29uc2VudDp1bnNwZWNpZmllZCIgSW5SZXNwb25zZVRvPSJfZWQ5MTVhNDAtNzRmYi0wMTMyLTViMTYtNDhlMGViMTRhMWM3Ij4NCiAgPElzc3VlciB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI+aHR0cDovL2V4YW1wbGUuY29tPC9Jc3N1ZXI+DQogIDxTaWduYXR1cmUgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPg0KCQk8U2lnbmVkSW5mbz4gPENhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy9UUi8yMDAxL1JFQy14bWwtYzE0bi0yMDAxMDMxNSIvPg0KCQkJPFNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KCQkJCTxSZWZlcmVuY2UgVVJJPSIiPg0KCQkJCQk8VHJhbnNmb3Jtcz4NCgkJCQkJCTxUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSIvPg0KCQkJCQk8L1RyYW5zZm9ybXM+DQoJCQkJCTxEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPg0KCQkJCQk8RGlnZXN0VmFsdWU+Wjl5SWFjZ2Q3dlBzR25LNlgvVVdDWFlYRWtvPTwvRGlnZXN0VmFsdWU+DQoJCQkJPC9SZWZlcmVuY2U+DQoJCTwvU2lnbmVkSW5mbz4NCgkJPFNpZ25hdHVyZVZhbHVlPldFL1U5MytzZ0Vna3lYTHIyNTRXQnRTM1BVTHR5cFd4MVlYYlRGSzRmQXRBT094SXA1bG9janIzdnZxVXUyV1YNCnJyeDFGNnV1Mjk4UVFXUmxXVHo2L3VJUVZ1ZGlNd2dIQjBSYkZWemlaenVkcjJaTUw1Si9qWTRoM2tGVWZQU2kNCjFVNlBkZ0xUcS9rZHVCMHlqYnpFUy85Y2gzVU4yMUdtQW1IRXJRK0ZLbjg9PC9TaWduYXR1cmVWYWx1ZT4NCgk8L1NpZ25hdHVyZT4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+DQogIDwvc2FtbHA6U3RhdHVzPg0KICA8QXNzZXJ0aW9uIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0iXzcwMGFjMzIwLTc0ZmYtMDEzMi01YjE0LTQ4ZTBlYjE0YTFjNyIgSXNzdWVJbnN0YW50PSIyMDE1LTAxLTAyVDIyOjQ4OjQ4WiIgVmVyc2lvbj0iMi4wIj4NCiAgICA8SXNzdWVyPmh0dHA6Ly9leGFtcGxlLmNvbTwvSXNzdWVyPg0KICAgIDxTdWJqZWN0Pg0KICAgICAgPE5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c2FtbEB1c2VyLmNvbTwvTmFtZUlEPg0KICAgICAgPFN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj4NCiAgICAgICAgPFN1YmplY3RDb25maXJtYXRpb25EYXRhIEluUmVzcG9uc2VUbz0iX2VkOTE1YTQwLTc0ZmItMDEzMi01YjE2LTQ4ZTBlYjE0YTFjNyIgTm90T25PckFmdGVyPSIyMDM4LTAxLTAyVDIyOjUxOjQ4WiIgUmVjaXBpZW50PSJodHRwOi8vbG9jYWxob3N0OjkwMDEvdjEvdXNlcnMvYXV0aG9yaXplL3NhbWwiLz4NCiAgICAgIDwvU3ViamVjdENvbmZpcm1hdGlvbj4NCiAgICA8L1N1YmplY3Q+DQogICAgPENvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDE1LTAxLTAyVDIyOjQ4OjQzWiIgTm90T25PckFmdGVyPSIyMDM4LTAxLTAyVDIzOjQ4OjQ4WiI+DQogICAgICA8QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICAgICAgPEF1ZGllbmNlPmh0dHA6Ly9sb2NhbGhvc3Q6OTAwMS88L0F1ZGllbmNlPg0KICAgICAgICA8QXVkaWVuY2U+ZmxhdF93b3JsZDwvQXVkaWVuY2U+DQogICAgICA8L0F1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgPC9Db25kaXRpb25zPg0KICAgIDxBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2VtYWlsYWRkcmVzcyI+DQogICAgICAgIDxBdHRyaWJ1dGVWYWx1ZT5zYW1sQHVzZXIuY29tPC9BdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvQXR0cmlidXRlPg0KICAgIDwvQXR0cmlidXRlU3RhdGVtZW50Pg0KICAgIDxBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTUtMDEtMDJUMjI6NDg6NDhaIiBTZXNzaW9uSW5kZXg9Il83MDBhYzMyMC03NGZmLTAxMzItNWIxNC00OGUwZWIxNGExYzciPg0KICAgICAgPEF1dGhuQ29udGV4dD4NCiAgICAgICAgPEF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpmZWRlcmF0aW9uOmF1dGhlbnRpY2F0aW9uOndpbmRvd3M8L0F1dGhuQ29udGV4dENsYXNzUmVmPg0KICAgICAgPC9BdXRobkNvbnRleHQ+DQogICAgPC9BdXRoblN0YXRlbWVudD4NCiAgPC9Bc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/wrapped_response_2.xml.base64 b/test/responses/response_wrapped.xml.base64 similarity index 100% rename from test/responses/wrapped_response_2.xml.base64 rename to test/responses/response_wrapped.xml.base64 diff --git a/test/responses/signed_message_encrypted_signed_assertion.xml.base64 b/test/responses/signed_message_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..102709939 --- /dev/null +++ b/test/responses/signed_message_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfYmZhYWE3NDEwMTQxZTUwZDNhNGMyMzY2MTBlY2U3YzdmYzYyZGE4YTU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNS0wMy0xOVQxNDowMjoxMloiIERlc3RpbmF0aW9uPSJodHRwOi8vcnVieXNhbWwuY29tOjMwMDAvc2FtbC9hY3MiIEluUmVzcG9uc2VUbz0iXzc3MDYyNDcwLWIwNmUtMDEzMi01YzNlLTAwOTBmNWRlZGQ3NyI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vaWRwLmV4YW1wbGUuY29tL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhNTEyIi8+CiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNfYmZhYWE3NDEwMTQxZTUwZDNhNGMyMzY2MTBlY2U3YzdmYzYyZGE4YTU2Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGE1MTIiLz48ZHM6RGlnZXN0VmFsdWU+S1c2bXlRZmtoR055c0JvMklHVWlickZqWEJYekd3YlNmUUdYYnlwOWxuQlRIcjZidEJyWlVZNHJJVkkzMjRVR1VwL3FjRnJMcXV6VTJuVk9IYTB1WVE9PTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5GbVpMR2tGL3FWaGN5QmcvKytqeFZpRGQwSlhURElMNDNqRjFJNEU1TmN2dUJMK1I2MTlhNFAvV21qZXp6ejZkUm5RY05VbTVYUzFTeGppU2lQNm5OWTM0Mi93Q3phUFNCeTFoM0M0WEczZUwzb1pERDFuSUJtclNKQTBTWTR1ZUF4V2dNeWc0bDZ3eDhCM0YxWDUyWUxDNGtXbDlKdnh4OXRBanJ2UHdBZlE9PC9kczpTaWduYXR1cmVWYWx1ZT4KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ2JEQ0NBZFdnQXdJQkFnSUJBREFOQmdrcWhraUc5dzBCQVEwRkFEQlRNUXN3Q1FZRFZRUUdFd0oxY3pFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVZNQk1HQTFVRUNnd01UMjVsYkc5bmFXNGdTVzVqTVJnd0ZnWURWUVFEREE5cFpIQXVaWGhoYlhCc1pTNWpiMjB3SGhjTk1UUXdPVEl6TVRJeU5EQTRXaGNOTkRJd01qQTRNVEl5TkRBNFdqQlRNUXN3Q1FZRFZRUUdFd0oxY3pFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVZNQk1HQTFVRUNnd01UMjVsYkc5bmFXNGdTVzVqTVJnd0ZnWURWUVFEREE5cFpIQXVaWGhoYlhCc1pTNWpiMjB3Z1o4d0RRWUpLb1pJaHZjTkFRRUJCUUFEZ1kwQU1JR0pBb0dCQU9XQStZSFU3Y3ZQT3JCT2Z4Q3Njc1lUSkIra0gzTWFBOUJGclNIRlMrS2NSNmN3N29QU2t0SUp4VWd2RHBRYnRmTmNPa0UvdHVPUEJEb2VjaDdBWGZ2SDZkN0J3N3h0VzhQUEoybUI1SG4vSEdXMnJvWWh4bWZoM3RSNVNkd042aTRFUlZGOGVMa3Z3Q0hzTlF5SzJSZWYwREFKdnBCTlpNSENwUzI0OTE2L0FnTUJBQUdqVURCT01CMEdBMVVkRGdRV0JCUTc3L3FWZWlpZ2ZoWURJVHBsQ050SktaVE04REFmQmdOVkhTTUVHREFXZ0JRNzcvcVZlaWlnZmhZRElUcGxDTnRKS1pUTThEQU1CZ05WSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkRRVUFBNEdCQUpPMmovMXVPODBFNUMyUE02Rms5bXplcnJia3hsN0FaL212bGJPbitzTlpFK1ZaMUFudFl1Rzhla2JKcEp0RzFZZlJmYzdFQTltRXRxdnY0ZGh2N3pCeTRuSzQ5T1IrS3BJQmpJdFdCNWtZdnJxTUxLQmEzMnNNYmdxcVVxZUYxRU5YS2pwdkxTdVBkZkdKWkEzZE5hLytEeWI4R0dxV2U3MDd6THljNUY4bTwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkVuY3J5cHRlZEFzc2VydGlvbj48eGVuYzpFbmNyeXB0ZWREYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIgeG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyIgVHlwZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjRWxlbWVudCI+PHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI2FlczEyOC1jYmMiLz48ZHNpZzpLZXlJbmZvIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjx4ZW5jOkVuY3J5cHRlZEtleT48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjcnNhLW9hZXAtbWdmMXAiLz48eGVuYzpDaXBoZXJEYXRhPjx4ZW5jOkNpcGhlclZhbHVlPnl1NDFNN2Y1NlQ5VndLb1hoWnBrcVZhKzJhSkhyaUJWQUtiVlpCdkxreGM3K2JtTnBqMHJOMTEyWE5kMmlmZ05VR0xhVUp1cVBZVC9EZW1vVzZvRjhkR0t5UktBdjJwVjNBbk9NcGFDNXFUbW03T1Q2Vm9pd2VWUWpIbitPRThuUmNiYmVreW9mQmR3VXUvSlFTMjJObkw0VEhwVGViNUFSZk5GWmNtTU93MD08L3hlbmM6Q2lwaGVyVmFsdWU+PC94ZW5jOkNpcGhlckRhdGE+PC94ZW5jOkVuY3J5cHRlZEtleT48L2RzaWc6S2V5SW5mbz4KICAgPHhlbmM6Q2lwaGVyRGF0YT4KICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+Y3Q1eHE1Q2E4dVR5aWVnZzBrQnZZRmg3REhjcUtCVURTVTRPVHAxQ2dkZDU1ZllmY1dZQ01vaGlRTnlpQnVtWEpLTjZjN3JTSllPRkFETVFYZ1p1ZGVabFR6Rnh2TzdwUlRBYmdMY0V0Q0JjNjRKclc4V0ZrN2hDbzd1d1Qvd1hXZGIzTXFVSTBWZzdQWHdkQnBlMjROdVNwa2hpenVIQVM0WHRjQ2lrZ0w0ZnRrWjd2Qk1NM2JkaU1wVEs0dktlYWwxMmJPaFZJVW9kaDhIUzM1SjJiSEs2aloyNkdGYnVOMU5xeG1vQ1ROZGpJN1Q3YU1yZTM2YURVQVo4NDNQc045aXViRU0rc3F5Um5sYXFTTHJrbW9YUmcwTG50MkR1dy9BK3JTRGlCVW0zbG1LL1JTMXlzalBoTWd4UFZGVGR2S3l5dDlsdVgvN0w1L3dSQzgzTTM0ZHBpMXNSazJxY3VWOFRzMmcvd2JBVVovT1loeTdLYk1mNE5KeVIrazlKVWpMMFIxNHNQc2ZlaTg3ZEo2YjhjSkYzSVRaaUVycnMwRHBKT2NBODFjcFB6T1BaMDk0WHMrTnBBQUY5c1gvWXZwYkR3RWEySTRwMXY1Ymw3eGZSY2FRekVnT3drZm9NMVUxbGZld1NCL2gxb2FaTXFodWtQTEdIMVl2THhUNkVKZDY1Um9ESGhYYmRCS0RKR0ljMmUrSFBQV3krbDBPaklLVXNHRzhNejBlbk56b3VIUjV3QVhtMHRwTGQrMHA3ZnRoL2FqN3hQcGd6bEpmaTJRa29uSFpDc2ZUd242UXV4MytVeUdpOW8wVzVreE4yQkk3cVhtdkxwdm5NM2FKYzk3bmI2YWw0dkg3NlRwVk1mSmlzSmhpcGpXMGRUS0FlMXJaaTNCM3JnWk4xU0tZUnlSOXRXeVZ4eGhYYTh6dmNvZmVUcFJNY3ZGeldpNyttSlFxU2pHL1Z3Y2hqM3d5RHBHbGF2dlNsUzRMaGxRMTVpS2IyRzMvR3hCVzFJMk5WcnFSREZuaTZlQ1BPTGd1eU13WUhuWVQ0ME9FdmFVOGFGOGtpaHlXbE1UbVZRUlVPZFQ4VjByM1BBRmQxMlQvNTRlMldISGxvUVFYVndhRHdnSjJJKzRZTW1VRnd4SUpjYW9OYm1IQVNySkhmTUdneU40K0czc3cybHhOZ3AycmUrUWJadEphMGNLUlg3bTh5UXlPYnF3L2lMYk9OMEhUQS8zUnNwUVNtNzdMRnFlVTF4Z0svS0RiVzRINkZuVldKVG5hNWhMdGhSb1JXdkNmdjFZWjMwUnZLakVmeFVhaURLQVBNcEdMS0M0RFpMSHduRUNhNlk2M0sxZGN4TCt6STJER1RUbWFtQXFKUlk0YnBaUE5LT3R6N001OXhWNnRYeEtpMS9IcnEraVE5dDRZeFQ0cXVXZFYyQitjTG1qUVYyQ0FzUjZSN2l2Mk9FTndBbjRoWDBhZDZnd2kxeVM4QUlZSFJiTmxzYlVOVVdTamR1RmRYYzliVHc2dERpWDVvQk40d0d4a2o4WThWRmFHUU1jd2xxZFprMTlyRjNQNndQMHFZNUN5L2RKM2RjRVIyM2tBcWFvdGlWWTBUSUFkb1V0a2lZOTZkVFRKNWVvVjdJOGc0YVhUajczblRCRFVPOTB0d043RHp1dWk0and5YlZKVloxZnhuRUZYL0R6SDI2RlM1K041bjV6djZLYTVYelE3Tit3Uzl4STVRYlRYdElWTXNKWmVBbnVIa3hQNWFmbFYzcXdyRnZpaGI1d0UvZVhqK09sZHlhenlBY0M5UzhGSXhEWktDN09HampxKzc1NXNLRmR6aWlnS21OSkJxbnVQVzI0VnlXckpNNGdwbmZqMzQyNkNFYUpkWnZ5Z0IrVC85d29INWNTY3NFcmtwaEZ3M0ttWFhSUzlCQmh5ZUNib3VsQXFROEdtMzdJdEZZN2tpbm9yeWd1VFN0STBDdDhLL2RrZFVTQll2NGJGek9TRGhScWJhMkk0ZHIwME1PWDJsVUloNHJjb1lha1BRSjk1K1RWOHcvbytXcHkzbmhTczJEOVprSklyZ0c3QTNpTkM5VTliY3ZjK3lpR1RqZ1hic1hvK1BlUm5SWVBQd1RIaXViRWF6QlR5cWlIZE9MODlUNmpaVTFSdzF0U0FmSmFSNnJ5TnZSdkFvVURlY1FGWmcyWFlid2E0SVoyYXgvNmZNRzliY3FtV0lXd2piamVJdkRGNUprTzd6ckQvT3lESmxTRDJmOC80Y1U4dnAyUm1paDdHaEMyclA1bkNtaEFza3lKYytKR0pML042eWVRbjFIRUxBYWpQVERVNzVsZFUrVUhmTlJNaFU2ejZaa2diUVpxMlQ4dlU2N2ZkMFJHTlhnT0RGZWZ6T29VUXU4VTQrZmJyZnM4emEvT1Vsb3lKdS9zZUplWWh3SEpLblRlVzNGOG5WZ1hrYm5PNmk5eDdsZEVaazIyOFRUSmp5VmhHbWFmT3hmZzVwN1RlTm5qUW1Vb1RpdDVMUHRuS0phTGpabHUzbWhMeGlucThwVUxkQzhwSWwvUit6L2ZoUWhWd2xJMEdFNEtkS3N4OXo5RWxtQytVVVF4OERoVHFhZUtZKzFObGdPOFJ6bjVBRUdxWEFMZk94OWFnMDNCbUlxSUtXM2RsQ0xuVVRmUDk4NUowTm9qYmRRUldVSDVpKzJGclJLR1V3NldnR01YNWdBVWJSYXZWK2RzVVpCMkxNdTlreEpsNjd6b3Y1cEtVTnZ5Tk1BeXNLM3BtSkUrVHlud0JuZzZwOUJnUWp6Yk1hdEl2ckw1dk5sNUovQzI0T0FQc2tqeUxHc1poVlBVd1IyY0FHMGhaQUdocWk2RDV1a0w1andoSUUzbWsrdTNYcS90L21ka1RtTEp1MW5KNGFYaFJaL2d6ZkJES25PZzBxemUxYUtFejFUbzNMWUN2TE1EZ0lMMlV3Um13RGFsWmNHMWQzWWYvcWRLajNoWEhWYjdTYjRwRm5ZeXJucjBKT3NPV3VseTVOeC90YmNXNFpuUVh6NU5HUDZ2RWFyOXRNMjU5Z2xKWW9jOWJ2TiswK1djTndkSlBtSUx5QUtJVUNHSzlMUXhCY2pYQ2hYYWpWMW5Bd2RZampPVzFxVENJOHhkNFFtekk5Sjg0MUVzVlFSaGZkRmkxK3NYQ0dmT0x1RkpoTndmd1lReU9jMTNicElOckx0MnNaVmxQaDg1YWhuWFpjU2JGSlF3LzB1TC9WQVc4NEl3U2x5Z2p1TXZheG9FWXhVZEhTT1krNjVLWElqWks5cHNvbkRlZGNvdFZBWXpzbk51SkoyYnFZblpqVGxWVnRFT3p0VStFZGI2WDhSQS9RKytTbVpqMmtJVFV5MHhPbVdsaFJwNzR0blgvMVFPc2xjdmRGUXN2c25lRXNaRyt2VWlKRkFGbHA2RlNJelNhakxOVnRLSC8vUEQyVWduV1VTenJ2S04wWm1ydWhHL0N0c3lIMiswR2E3ZGFFZXAyNURVd3B1N2JHenR6RWpvVXBLd2lnOWdJaTFtcnJKaFdadjdQMW04NDJTdzdzaGd3cjZWZ3VhTEE0THdiam5XNzdHbjNTTVgycFk4ak9yLzhNR05laFc0Y1hCUTVUNkQ2ZGdJM3FTK2YrTUNncWladzJ4ZUFzV2NoWnB5a2RoVy9URlYxd0pWV05yZWE3VTN6d1ZleE1YKzJXOERmSWt1QlVjb3JwZGJJcXNMODl5OGtNT3hjTzAzTC8xK295M0hLTjZXaEx5NDB2Nnl6bkliR09mVW1tNTl0VHFmQTlOOC9OTHR6VitLQzZpZVRqY3hGdWwxdGhtQVRuKzg1YVd1dkhvN0FZajVMNy90Z3p1aTE4OXBVNjYzV2hNamZZaGRobXZVYXp4dkltbDMwdnVublZVVDRwWU9aMlN2dXZRbjkzRVBiMmQ1cFhzYnN6aVFDeXVDV1FLRzJhcnFPcDF4K1ZIL1BnTXhDZk1SUTZGNzZEWk1vUjJhOTBramJkTnNIS2pxcVh6Zmxma0d1RWRGVTBqekZvZjd4Mk1mZ0UwZmZvR3UxQU5iR2hoU3BSNnE3aVQzZHVCeHhDbm9UTFJhQ3ptaDM5Vkg1NzYwbU5tMDM2VFcwZ1J4L0VQRGJidDNpeGFmZFNhSjE5bXM1NUQyNGoydVhtaFlFNWZKTVNvTkFSZHpvVjRXMUhvaForYU14by9sZFZBWGVMUkhHUHN1RnFSelc5ajZTSGIzOGhRSXVkUXdBQkNMR2ZrdmwzaWFlVzNKN1hPbFNmR3FQTkFvMVZKbnMvWlU4cUpuVTRBZFNXRDlWMGMzbDREVzdIMGdSdnM5OGxGQmozZzBCczRrMmFEbHFoTC9tcE9xTkZaeUxTQ3BsYmxaV3lNbHZUd2xOTWNQY25Bd1g3YnkzcDlORTlFbVNjbjE2dFRRV2o2NkFldjJGTGxGakpqL0VwRVNXTkVXZ2FzR1RZVVpxWGs2ZW84dWVSdHd1TzhRYUxHb2htOFcrSDdCanlhNGNnRllBQWFkc2hvc2NzS2dwOFBGdVQ4Qm9JQmYrS284ajR1MHdobnM4UWtpdXJGLzl1K3owWktDd0oxUGFwdFAyQXZPZlVEM3JUc00wYUZlYVgxRE9NU09yVzQ4M0xNVDdiMUE1Q1lidW5EWksvSS9keXRMU1piYjVOY1FPckN0RzQrd2NOOGNHRXhIOWlsMDBhTWNiTVZobXd1ZXYxSG5iYTBNQkZkNzNiK0NWY01oMFhrbkVRMXBMOWNnMklaRHViMDNqR1cybkhhRGlSV2YzN05LOTRjdmVlT2Y2eWlPVjlXbzNBZTNiREF6S1llVUhlWEdWTWtUUUtDdy9ZNUl2WkFMVlpBdzVMdGNOakJVa04vakRWdVVPS0JZMm9ORWovWHBFMkJyOCs2dWZoeVc2dG9yYStJbkc5RGtqbWZmRy84eHJ0eCs5YVBLaVV5Nm12encvTG9YcmM5LzFsRHNzZndjek92MFloSDdwRGdXb0kyM0NXbEU3MjVtNCs5UVZmRWRJY3Y1YjNpaFg0aE9kc3JrT3Blb0xxRXBtUXBnQjYzQmdLaThKYi9pV0N2Zm9nWEVTYzNKZUZkejJubTZMbXAwb0lzaWU5ZUJZMWFYYWpmaHBhdzd5N0Z4UFdwUmZsRm12d3RucUE1WHBZNXNDUzBMQTd2V0NmOU9LRzdGOTdhL0hiR1RGZGV2MkFUY2lKcGxYNWNzaG5DRDNjR05kUzRPSkgzNHBJTG05Y243NFl6MUt3TEhqOWRLOUFDNWp6d3lmSjNIVnpLVk9mN3NtelUvRVNxUUM1c1paY3FHR3pSandtWnpaYmxsTktnVTdmTGhqMEZ4bTlKbzg4US9BV0pjaEdmWW83MiswdmcxSEV0dlB4ZW1pOGxvOTFDYmdkVXk3UStxdXBlWGQxc3FrMzBId0dQME5WaDJHVU9WZlhPbmcrUW9HZDlQU2l6bjd4ZjRITGpLQ2h2RmovYm5QblZzZjRzdWNLTFI4S1JKWTF1U09zMEU0WjRXd0xoSmYzTlpPWVdzc0ZGbjR3VjdXNDdrLzhXSVlkMTBlb1dDdFNjTndYNVh2ZC9nZHAwL09IencybnBTVDFGdTBrSnJFeHJ0N295c1FmaFltVUp4b1F1dkI5TzhGOGFndHhjWDRyUDNXMklPcExqYmZibFFnTVpTK2xEZkNud1NVeWk0a0x5d3NmRURSVUJvejZiSkVBa3lXSzhTak9scEpabEJkb1dycDJCZjNOd1l3akovcnk4bVVSS3lOdndxWlRSYSs1V0xZa1R0VE50Uml4M3ZneDFKcEVPY0lSUnJlN2drODVyeFhPOENzMm5kU2ZITlowVDNrQ1E2d2VpVFQrU2FnYit2emt1QndjNVRoYm9ENElldHpOT24xcnZvbDB2Y3V2SWp6VElDTmUzaGNPT3pmSEhJNjFRQllKUDcvbkdickJsQkdSV1Z4bmZybkZZb2JMYXFidVFDRmExSGRNdFhZWnFMTGd5UWhzQ2dkdGRkTGp1eG1Ha0VQRGpaZDNRRnNSMjkraTArZE5Sb3U0dWFrTzk5ZXhZa1kveVN5NmsvenZDbWc5eklrQmt5MUtuL2E5Nno5MG9BaHZHemsyMnFienhQWmYrK0JlSkxHcEtwWjEwZFFKajJ3RG0yNU9VTVJhaUs1TTVaMmhqSFpkb1htWmk1S0NtRDN6Q2taYzBGYWlhT3Z4VVN0WnBtN0hmdU92ZEdWMzExZVBOMElIaUNsRW9wZVdwMEJHV0VpanhiY2IwUDcwd3c3bU5DVEEvczlmL3dmby93endITUxLYmtmVW5ST0hqbzB4SlF5a1lxTVhoVkQzMjBsU3h0b0grVExObXJhYzJoeUFtcVk1NFg2MFNnZGs2N3ZFUlQ5L2EzR0dEdGtXdEJnYlFBMzdIMlB5T3JTUnpHZjVBeWxEZUFhTWdyVUJiRHZEdzZnMlVtTHpZNUJFZnBnWWpEMWlNZ0VxSnBhQlZwWmM0VzlMZUdodGJpVHAxUGREVktPODNVS1g2UC9hOTh4cjd6eXU2bHJSd1BNNlp1LzJkRkVoSTNieFlXV2NsQ2l0SGVac290YVE5VXRvZXgvV0lmdXZNWGJLbmFNMjdiN3hCNXhORTZsSDFaSXlsMnNCUHpVeTFCL0pzUkk1M2NGdVlJVWhGSGpxV1FsK01xcnJvMEpmNzlrc09PRVFQNlpaalNUWG1aU0l2cmhjcitKejVlZXhyZW5TNmRjbG5Cc25pTTJLeGZTN3BSbDVUdG10Z0JpblBSK3YzMHdnKzJreTZnR0NMdDZHTi95WUd5N0JlZkc5QWFocWdxb21oelBETnNERThBU1Ivc2ZHdy91WS9uOERLZVRFclFwMlo3THppb0JsOWE1TEpGcy8yOXlIRkVybVA0a3pkVE9jcnBTVXJGSUxHSytvUzhqVytxalRBaXhydmpQZTBONTVURW9SNHJ6UUJtbGZ5dElna3AvL3FRbHhvUEhTTVRHTXIzMkJpOFNWNGtZMG5Cd1R4dmFuc1NEeksybmorTUxvR21KbzQ2T0g4Qm1qbCsvT3BpS3ZYZGNkbWtDekJucnlocThxUytaempBbDdOeHJqTi9TSVJiWEtIT3B2VEZzR1RNOEFCaDVXUHVmMmZUdkhLa05SQWtZK0xJSTAxM2dMUXQ3NFQvNmJkQktmQzJESjBrejVXcmxoSDZFSHpWcjdNNVl3MG9SVjJxekZXL09sSVhwYWdYUmhqTXZoUlRweE1ta2FNdjBZSlZuSGhTZHNLS1ZuYzkrTG95WnlsWjk4YnhzKzFBYkxlUllia0pxNjk4RGpWOE56WldMRjFhbTRRMjZMd1UvU2NxUllILzQzLzNSbjJnenJNSi9IY3QxQjVvbzI1aHVTaDY4ekRNd25lSXd0YXhqNW5yTG5oNUlhQkV6cWoybDd0NDNhVEZPRVFGM0MyYTlHWWVRRHFiRHRJblJzT3h0elRPOTBpUUxRS1ZNQjAwUTM0aDEzelVSWjVIR25aVkc2Qk14bHRieUxSaWMrYUltdEtmRktKTzFrTkx1bUljaThyOC9TaE1HaXpYUVpUb1JhbGlmTzNZSXdPVUJKK1FZaG9pRzVNVnhDUGFZQmxiQ1h6TnVZSEFXWE5qMVZOUDYxdDNnWStob3gzZ0tMYmoxVkkrMllrUHJ5UDVvNW83SHZWUlczaWxPR1JoZ2FlNlBtTFpEVjdBemVpbktOdm1JRkN1RE52NGtpR25RMklJYWhzWnRMU3JhU3VMbXVobktDVDlabUVOZDBZVjdHalJXS1o1MTBCd0p2MUdWNGVXQkptb3Y2eHRxR1M2cy82dEFub2NhV3pWSS9DNzFHWEZFbExVL0NOTFpYUnVmUGpMWmRjeTlhZFNUV2ozOGY4c244Mzdhc1NtYm9HdEVPMG9mNWp1cUhxSVVnNlNpQ2dVMW9nZWV5NUNNSGR5TitCdm9ZckkvbnV1Ym1Yei9qVFFzU2taZ0dmUjFUcm81WnBraUhwRURjUnFYY0haVWpzV0crU1FOcEdXeE5IUWJ1OEIyRlBEc3E5bUhlWmRWNXFYNlIyWWxyczhydTFwT2x3VCtkeG9HczFHNmp4SDI4azlaUHhMblQwTDFiZHBtTzVNQ1B1RkcyU05tdnZoTFBlQStVbEJEK0dNc0tPdnFpZ3drRjYyTEV1c2xlb2V1bGExZEFqRkdsenlmZFJ1aWJCT1pZOEFzc2JuaTMzb1BKUkhsTkI5RDg1alEvNHVoYWxtTlkvNmZuVk9LU2FXbGMxWkN3UWV3T25PaXRsUmI4RHlBcy85K1VQQ0F1aWY0STZVSEJOc0U0NWJlZGZraWxRdU9FV0xjTG9xNzhKM0h5ck02UjRocisxdWYvS1lidHlxUDd4cENWa3ZkbDQxelJSbGcvWnd3TFZZanhrREhRcUNJVDhoSk9MNXZFemFXR0o1Q2Y3N1AyN0xWRy92WnpYTm9ac2cvVkxTVHNaYlR5aHhNTG5zK2llUHZwUll2UGE3MEJDcS9SSVZ5bm1CUnFhY28xME1nTnlZY0hCa1o4UVFhZXZWbzdHRkI5OHJTYkRzOXI2WWdKZDQveXBXQklyUCtrVFFCczJlT0dzVFI4MU1KVnlwQXJFODArRlIvTVkrd0pvbWdLODFWL0lpTGM5c0xvVUlnTUF6NEZieFlSMG1UZHBKN2JNWDlBTE5ITVpiSEVweGNjNXRMKzNUMmRlQndWZHBIeUd1MlJhd0VUZnp1MkhuUzFIdjZQTEhNcDgzd2lYVXVYL0ZST2x4Szc0aFhYMWRvdWU1L0tyczhLSm90NmtNekRBMmtxcXlxQTVoeklFWkR1RCtsTU9yMGJOdTVVejA5eDdmRU01WHFmejNpTkNWQlJhVlQwRzdoMUJ0Z1hzcUU4cENMNDJiMWhacmNqak5GNzJGMWJ3WnNhR1VpbHBlelR5UWMvcXp4OFlnQzlnVHhKK0VqajRkME1vZGxaVmI5SU5NT3dUbStHV3M1ZElzbzdGSTJab01MbWdwNjFJc2kvUllVOSt6cmV2YmdBZ0NCNDgyS01HVVF4am1yd201Um1pNzI0YUdwamw3R3NSSVpJTVBJWnZTbC80MGRpS2lYdkRBbVZuSVo0a0NRYmszSGxHaWJJU3lDMkZNa0lqR3RtZ0lqSFE1YWRUMnV3M0M4bFZyYXQySU90bDJ2N3lRMjdUS3JhL0laVUxOVkErZmdqQ0crYWxzUysyTERaOXlVTkZSVnRxak1RUUtLQXhaNHhEdUZ5Q0dXd2l6Ty9BSlBJSEdPelA5emhoZVZSOGoxQnBJVllKK3MwczRka0RVSzR1c255elB2NWdBc1JTU0gzc0pzRTFTTXFzV3Q5NGU2V3JvdE42dlpad0lwNkFDR0ROc0xxZlNYdXpSNnhDSWw4cUVPTXQ1NnFyc0N0SXBuL2xhbnhySGplR205K0FBT0h0NXNyT1NWUUt3OXlPV05jNFRycDhIeW82QTBQY1JFWEMvNGpoNWcyMmt5bnpnVUc2VUtKeEVYTEVjRFYrd1ZBak1ZbGF2UGZXVHdxaXYvUlFXNkNtUUpLUGc3SURrVkN1RC9pZDJWQmpPOExOMUUwRzJmQURobUZMYVh3dUJGeDhzQXNObXRld0lvQ09kMWx2NEU1R0crb25aVklQMlNDSU1rNmErOEU3UjlLaVNPeUxWZXhyWlVsZ0p6UUZRS2RQU1IvUHhCUU1Ud0ZncGxXRHJML3dzRnRMZVJnTldmUCtwd1hYMnE5M3JtcTBYazNnZGFkS3AwenFiM290Znc3NlJjb3Fwd25ic0JiRHZTVzZPei9sdXBnTi9YRlVGdHBRV2dNa1Z5ektJWCtObVVXNFhHdG1xekwvWC96NExBckZOWDBFejM3OVlHY0Vka3hCZWNNcGtlYXJxanRIdHQzaUh4K1J2TjlmdWVpYTZ1WUo3NVJVUWdVRXJwVThHZWUvZFFaVG5TeDNISC85UVJ6aU40aGdDWUdvY1FmRWk5UkJhMlB1LzFocDV1aUZFNkVDZlZnRW9YYS82RWIyNk1JNjNYN2h2bjdERWFGemhoZDNHWERraFVZeTd2bUZzYVlHZXdqd0FXdXJDS0cxalR5STA3OURCb0RYVzlRcHlaKzZKTHlUS3Y3RFdoSzVta2wrdk41d0I2M1N5L1A0cjFoVG5UWmFrb0FqUTBlTVpRblROME9hOUorQWRxdEY5UzdJU2NUNTR0dk84NjNkdnJHWmc0VU1hUERodWQycC9IMTAwdkxpUmxuajNsOXlsQ0tqOWphTEE9PTwveGVuYzpDaXBoZXJWYWx1ZT4KICAgPC94ZW5jOkNpcGhlckRhdGE+CjwveGVuYzpFbmNyeXB0ZWREYXRhPjwvc2FtbDpFbmNyeXB0ZWRBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4= diff --git a/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 b/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 new file mode 100644 index 000000000..b1badfd28 --- /dev/null +++ b/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOThjNjA3ZjdlZDBhOGRhMzVhMTEyNDUxYTE5ZWE2ZDFiZmM5OThhY2ViIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNS0wMy0xOVQxNDowMTozN1oiIERlc3RpbmF0aW9uPSJodHRwOi8vcnVieXNhbWwuY29tOjMwMDAvc2FtbC9hY3MiIEluUmVzcG9uc2VUbz0iXzYyNjAzNWIwLWIwNmUtMDEzMi01YzNkLTAwOTBmNWRlZGQ3NyI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vaWRwLmV4YW1wbGUuY29tL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhNTEyIi8+CiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNfOThjNjA3ZjdlZDBhOGRhMzVhMTEyNDUxYTE5ZWE2ZDFiZmM5OThhY2ViIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGE1MTIiLz48ZHM6RGlnZXN0VmFsdWU+aHB2OGlYRnhlRlVmeFRHbE85ZVhaTGVZWjg1RHNacW4vZ1NWZnQ0anNUdS9aU2FrUittclRaVnJSa0daRDQvcjFlTUtyUjFOTWdDb1BTUTN0Q3E0NUE9PTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5hU01NOXg3NU9zMGpjN2h2Nzh3OFBXYW9OZGI3aVM3aEFoZTM2L1FDVWp1R2liTDVhQUNYWjVwRE5YeXVpbXMrNFJxYWlTdHRLc1VYRXhLOUVEMk44Vjc1M0crNlFNRmRIY2tnOEx5cG13MHA1cUxwNWJ3TGRPMjdMU2d0MXk5ZnVJeDArYWE5ajh5d1UyWFl6VzlqUWpsVmJNbFNGMGRYQ2NUelFuOXlMczA9PC9kczpTaWduYXR1cmVWYWx1ZT4KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ2JEQ0NBZFdnQXdJQkFnSUJBREFOQmdrcWhraUc5dzBCQVEwRkFEQlRNUXN3Q1FZRFZRUUdFd0oxY3pFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVZNQk1HQTFVRUNnd01UMjVsYkc5bmFXNGdTVzVqTVJnd0ZnWURWUVFEREE5cFpIQXVaWGhoYlhCc1pTNWpiMjB3SGhjTk1UUXdPVEl6TVRJeU5EQTRXaGNOTkRJd01qQTRNVEl5TkRBNFdqQlRNUXN3Q1FZRFZRUUdFd0oxY3pFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVZNQk1HQTFVRUNnd01UMjVsYkc5bmFXNGdTVzVqTVJnd0ZnWURWUVFEREE5cFpIQXVaWGhoYlhCc1pTNWpiMjB3Z1o4d0RRWUpLb1pJaHZjTkFRRUJCUUFEZ1kwQU1JR0pBb0dCQU9XQStZSFU3Y3ZQT3JCT2Z4Q3Njc1lUSkIra0gzTWFBOUJGclNIRlMrS2NSNmN3N29QU2t0SUp4VWd2RHBRYnRmTmNPa0UvdHVPUEJEb2VjaDdBWGZ2SDZkN0J3N3h0VzhQUEoybUI1SG4vSEdXMnJvWWh4bWZoM3RSNVNkd042aTRFUlZGOGVMa3Z3Q0hzTlF5SzJSZWYwREFKdnBCTlpNSENwUzI0OTE2L0FnTUJBQUdqVURCT01CMEdBMVVkRGdRV0JCUTc3L3FWZWlpZ2ZoWURJVHBsQ050SktaVE04REFmQmdOVkhTTUVHREFXZ0JRNzcvcVZlaWlnZmhZRElUcGxDTnRKS1pUTThEQU1CZ05WSFJNRUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkRRVUFBNEdCQUpPMmovMXVPODBFNUMyUE02Rms5bXplcnJia3hsN0FaL212bGJPbitzTlpFK1ZaMUFudFl1Rzhla2JKcEp0RzFZZlJmYzdFQTltRXRxdnY0ZGh2N3pCeTRuSzQ5T1IrS3BJQmpJdFdCNWtZdnJxTUxLQmEzMnNNYmdxcVVxZUYxRU5YS2pwdkxTdVBkZkdKWkEzZE5hLytEeWI4R0dxV2U3MDd6THljNUY4bTwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPjxzYW1scDpTdGF0dXM+PHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxzYW1sOkVuY3J5cHRlZEFzc2VydGlvbj48eGVuYzpFbmNyeXB0ZWREYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIgeG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyIgVHlwZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjRWxlbWVudCI+PHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI2FlczEyOC1jYmMiLz48ZHNpZzpLZXlJbmZvIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjx4ZW5jOkVuY3J5cHRlZEtleT48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjcnNhLW9hZXAtbWdmMXAiLz48eGVuYzpDaXBoZXJEYXRhPjx4ZW5jOkNpcGhlclZhbHVlPklVYXZOSWtRZlR5aWtDcWlOMVJ3aHF4eGQzbGJhSzN3MnlZOGpIeWQ2NVFBRFhUVmR3SFNxdnc0akFyanRSWHBuZzBQaFRXMlE1NFpUVGtjUWJsSmFZT2lEVFNrdTBQN2w0TGFWNlc4bkp4RFBMM1c1Q1pRaFJJT1E0TTUrTDNrakptZWpyTmhhOHcyV1J4cDlHSmZ3UXJpdkxRenlSUHhxdzRJKzBjeFpBWT08L3hlbmM6Q2lwaGVyVmFsdWU+PC94ZW5jOkNpcGhlckRhdGE+PC94ZW5jOkVuY3J5cHRlZEtleT48L2RzaWc6S2V5SW5mbz4KICAgPHhlbmM6Q2lwaGVyRGF0YT4KICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+NWdjT2JEdE5jRUZLWlN1NmdGVzl5K09nNkJlMHhDRHVzMEM3Zm4vTmgrYWs4YU5ya3ZvanJIbWNTdTJRcTV0K25nelNQTFgwV0RYNE1FMGkySmp6eUVQTW93NU9CRWcvY2lTR1dNRzJXWDk4L2RndlFPUndCc1QwQUhNWmo5czBsUWNoanRHeTcwM0VNNWFOemZzMmJyOTczaTR6M2tVcHJjbzBRL0I3T3FnQXNPZ2lNdndPWk95VHFRWUZSUEdOQ3dmLzhFY1puZmh6eWhHR0tvWWM4aklveFNRUWRWMUVmQ3N6R2ZXRzhEczREbG9RaElYbkhHK3B4Y2NTN1VuN0N4MmNEc2tlU2swayttSlJ1RnAzcnhXeHZWU3ZTOTFBWjRJZ2RHdnFJa1l3Qkt4cWd6SXNCZDV0TTRWblBWc2YzVHBIZE5lZVVjUmNJYjFrQVQ3ZGMyZFNsVjNoKzJkS1hyZEFlclVJeGdEejluc0g2ai9Xbno0YzZIOGp1Z01SaFlyaVNMS05KNTkrbHlKL1RjMGYwaEpvMDlldmw3aE5KUzFsVmMxVmUvbENUSW10R3I4WEtUNFdrcnozSVkyaTMrWWJTNmZEM0UxZ2JtTVY5TTFCRXI5cVk0YUcvcW9TakZlNjFvUU8wT1lKdFU4YU5TaHc0eDMrMDQwWHl2cDlRbVNpNVRWMzJtU05MS2xDTjk4Wk9YRS94UEMwek1hQTBSdW14Sm5zMmRMUG1VaDJvU3VqNjJXM0VoZGdFRnBEMUJ3THA3K3ZTVVdkUFBmVG4wWld5R29UUGh4dTZreWNpOXIzVmhYM242L2sxL2t5MEJjaU5Td042Nlc5K0lHOEFwRmxoK2owVXUranpNT0NhemI2cSt0dGZtMmFzR1huSFZya3ZkeXZ4WWJiU2FwT3I5ZTQ2bWd6QkZoM2lmODE0VkFPOWhPcDZxWnFOQmJHMFhOZGh1aElaSEZkVzA2RjVZc3hZWjNxU1B6MWJ3Ym13b0gvbUdnWlYxTHJWNUVrdmdrSHVFUDkxRURqL1VLUXVncCtSRjJ4Zmo5N2VybFAvWE5NRUhOY3FhRlFVODZMcVNSVy9Ka2kyaTIvWXRaU0dkMTNRNlNUUUVYOVNBN0hTYjhSV0x4WnNWU0QycDR0UEJ1ZXlweFJ2Qlh5d0ZjM2MwUWNuT1A1L2VSNGtGVTEyRkpqMXNUMU9qRFc0WkdCVm5QQ216OHBuUTBhTnpTWlVjQzhJMTZpa1lJdlVIM1pzMkJFS04yM0pGdkVTL2Z4TWhMQWdhSkRUajRWK2x0Y1l3UDVxTUw3NkxMeldSS3lyMTVWOE1keEREcEFQTDZKNUNuUkUwd1Z4S3pRcFJUUldNRTEzQnhNL0RRTHVOQkZjQXM2blB0ZURxekwxYWxpQmUvVGwyOXRmZURTa3lSOXpXNGs3V1M1Zm1UcHU3eE9CWWFnUXRyRWJ3elZqNFM3dkhpYTI3NmZLaDEwUTZzWFZ5Yy90ZzJ5L3ZBT29oSWJOUnZoT3UxdWdGcis3dmt3S3A2MnArTmErdDZEK0xRNEluUG9nZStUbEJreGNvcEtRdGJXM1ZlellTazJsSVc5YXZ4aFJiRmdwZ3kwd1NRUmoyRnlIOHpaUUo3NVB3SWg2aHRrckVISUpHdmV6RklTR0ViSGZHazY4WEFXQ1ovYmhVOXZTYlVXQzU4RHR5M2xkajVXUFo4bjBJS2JDTm5STFpxeE81K1pmN2tMMlgwTkh0Zzg1cW0vMWU3bE1COXh4eUpjS1FhblloamFybXM4dDlJZ3BTeHV0MlFNenNNZDdVRVMrcTE4dmpqZXFGeENNZWZQSmJCN29QWmEvbUEyb0Vid01MT3lkaVh6NUNxY09rQ2szaE04a0hWNDBiRWFwUytxQW01ZVYwalNoZEhVRVcrZ01mU2xQemcwTDhma2lITmpnVmp6OFVLOFBSOVFPQkE2d0lGZDdhZmNpYVFwbTdnNlREbVRDdGlmQ0Y4cnlEeWJxUU1hUlBGMnp3bTJaMWF5OUJJNXpPYklCUVlaeCt0clNmRUgyM2Q2VXBWaFdpd2dsbWxPMW5MZERwWTBGWi93UUJEMWU4VmtZNHRSZ3kyRURobkh4NWdnR21wMndRckt5WW11UlRSRWI0QVJOZ2U3TmVvT3M4NEt1M0c3RGtpZTArWFBrQ29XSXFkRUNweFpJaitRakwvN0Zqcm5sR3ZvSTROWUY2UVVMQmQ5ejY3cGZwN1lVMXhTcHVUZ2JxdWFOelR4UU9CMVkrTko1U09lQnRWczhIbFFvampwSWdRZ2cwQk1TcGEvVFNRMlU0eHd5c3Jpd21OcTkwTjJlbndIR1h3OEdObFh4aEZmRytYU3ZVaUw4MjVMTWNadDRYaERRN2FCamdSWFBYMUp6V0l4RjE5Q0JERUpIUWt4OS9mSjFCanVJMW82UHFpZDJldFNIVGpvOG5CKzNJSXZBcC9sU2NPc1Rtd21OWjZjeHZLOFlKUkVsZmYxVXhjbXVjV1k0Z1M3eTNjQkRXekVUUzNGVmdKdEJIaTk3YmUyRDMvVUFkcER4U3dBUzVLblNYVTlWYjZ0QnBJRU9jZ2IxNFBJZUJ5TnhkVDJrZEJHSXpERnNqT0c2VmZ0am9hdVIwMXZSQjFwVHUydEMxOHF5NVZBVHVMRW1LVlUyOW5KZ2tzQTlrSFFGQkxCb2xuK2ludS8zSW9EQyt1dXNzemd2cDJiZmljbTl2M3pQY2NRZlN0TGQ5SzlURm41Q1lkVmdVdEJra3l3VXVOMWhIRk5MUEpNUnFFSEVLSkRYandGb2ExODRHTC95UUw4OXFlazNkNEZkaHhqNVJCM0I0V2xkQkNsaEg4NnlsR3E1V1pVaXhjb1BtZ2xmYzhLZi8reUVXeFpTdUlUcXJyZU5sNzl1VkRxUzRwV1paQXV1ZUpvSG92SHBCYWMyQk9WQTdVNTB4TUpCbEd0WjFCWUNWZmNCZStjcUhqdlZCU3c4S3BuZGhjbXh5VW1URXFsNkRxazRNQW96eWdQODBFQnlWUTYvM0gyK05xeENKT2hJaTNndkFMV3QwMFJIeGUwbGdLdEFOOHdzWjJITlJDdzJyZ3NtVEtVUHVBTTZWZWdwbklLbGFlY29Gc1l1dHcvMEdpazR1RktXQ2dxSGpZbWprNmFDUnIrU0RrbUtCWk5sOTFuS2FTK2RCUWFPUFo4WFo0Wk1TcWJVSkY4QmxiOE9LM203N1RRRVVIbGN2ODE1RHpXQUdmRy9NSGRvZG05ODZyb1dTL1VHcEdhWmFCYVRRVCs1RFN0MkFYYndYSWpab2ZNUG9zUFhVdUtPTGw2UWRUNGNEZ0ZzOEtXbDNFaFdWN1JuREQzczUwa0lDSjdlNEthTmdKQXR6cWxkWEw4bndFelFxL1I1MnlZOW4zMGl6cUdpT2p4UGZjZnR3UFBNQ0IydDlYOXFwOUd6YnEzK21relFsamw1S0pxSVVuem5WODREUTFlU0FBT0FKVGYxcjJjblI4aDRVaytyb0RLaUVBSXliaWltcXZhMFY2QVA4QlZNZTRYeFgxV01oNWVkYUNBbjZmb0pZVWtXb24rU0wrOTl2N1pSUThmSUdpaGxkTEdlNXJTYTM4bUJGMFlEUWVqT2NlVWU4b0xvclFGK0gvL1RxZFBmc2dNYkZMczhpWExydnZxZXdYUTk3RVBxQjFKUFdCS0dVSVZReW1ka2l1VlhqY25UWXppN1gyTnNxcDRiU1lpL0lHbjhaSHNFQjZMM04vRkxMZ1lYdXhlYkVXZVU4a1RGVnd5eW0xNndRdkpLcEN5L2hIVmVqdUlWWVNSVml2enNqZmJzbi9wYklYQUlJMTJRdlVPbTZsOUU2THNqTC84RnhQUklhVXZGaTVGdUtUb1dMZlkxSXJ5cG81UlAzZ25wT2FyS0pQL2pmRXB5ZzYyWmh4OUJZVEJFYjhST3BKOXZvL1hMRlk1NDlJaWNoalNwelUrMS9mQ2hYU3M4VzIzMHRqc2kxdUJRa09EbXM2d21sRnUxQ29CZmhYRlVEdWhDNys2QXpEeUNENlNOa3RiYXhQWndMWnRROGsvc05ydUFEaHVjN3JFVWpJQ004RkRKWXVsV0Y0YWdhYXMxdzJZNjBjMm5RK2dRUXpSYzNUaENxQllYeVJ6cTNtMEIxSzUzSUhJcFlXRHVldHl1aXBMSWtFVFhIKzd6R1BQNHVpbW5mOVFCbEFEcEErVHhhalN0M2JxaUVFdDN5U3dBci9CMTlsQUZ0M2U2UWxYUm9HUTlaTk0yK3VSVFZTQjJ0bkhVN2UzN3FPV0EvdU55RWtmMXJoM0ErTUpiWExiM3NTeUs5WDJ5bGYwUm45RG5NcHhFTGVtYjl4Y283UFhpaEdRYXpaYmxpczhWMTRYWXM3RVU1SEZXTHpwOTJpMFBhaWZRS0pXWFQydW1BZmNqcmlNSEkya210ZDNrR1dXeGc5cmtheEhLanU5RDdxaXFaTWpYbEJJd2dYNG04TmF3dFZEaTA0cXkvelJReXBRWHRqVE1vQzUzTEN2YzZudWN2MjcwRTl2OUpCU3ZtUXdCS25IOE9ncEhqb0NKM2ltMG1vVnk0Zk1GSmpjbGdxMDRRQzVFMEZKQnBLWUcvaHdkZloxb3h6aVB3TDlmcjFJc2F3dHhwOGtPbHlLUWhHN2VWNk8zekYxeGNtUGJlSnc0a2hNY3JWM0VBVmN0ejQyNVZON05tWVh2dUI3WUZvTXhwQkhRWUFXZkI5M3FpdHZDTE5qaDFVUlpOS1JNNG1oWXg1RFJwUUJYU2lPdm5kUUtjZmtacTZIOVUvdUFzNHRZVmlMcjI0RDN6WWpVekZCaWxIMXlvSGdOTzBuVGx2YUVnS0pUM21nQnR0L3BZTXFhbjJ3ZjcyTHg5UnBKTUxsN1BZcUg2eU52ajgvTjZpQ1ZKRjRwdlRaK2ZYa3Y1bUZzcm1kdE45THd1VUxQQWF4b2lkMkxDTExWUXpUZThaOUVteWd4WWNCaU42WnBKd2RFNzVSYVlTbk5sV25SWDVnd3VvZU9MekcxdFgvNDEyeXNaZm1uNUhxaW5JSVMyR0lIK1VYR21VbXoyUGVpY1M4d2xJcUFJenN6NjRCK0FOMjRyb3NpVXpQbjhxalJNTEFWWXNXaS9UWW5VVW5JbHk2RFpqZXkzbjJxMElrZ3B3SkVtTG9sbGc4TTgxY2VZcm5hcnlZcDZhclN0RzBHeEZTc09UTUd3c29hS2R5NGkyaG5McFhqbkFMSlBFMnI5NnVtR01vMFNHMTlHQURsUTZlK1VvazJmN0JXWG1qellEZXJEZERFc0kzVEgvVUlFOUZQL2UvWHRGak1SdXBSNWlYVXQ3Q293SnJuLzVmUVpxbFZmQ1NBUlBJOGFsaXdWV1A1OEZ1QXJkWjROeW1qOWZ3dEZnM3lMZzRkeG01Y3JvZ0lVeWY0TzJFZnBhdUVlcDFNMjlZS2RSa3R4WGRMcWtuVWN0WXJTbllvbHNBd1UvN3ZJNy9XMG5xWmttalZmdUVNTGVFbE1YaFg0aWMwQ0Y0NGwyNlpDK05PQkxod0V2RXNzc1YxbTdTYm1odnlsd1ltUEk0a1dZbHJFRHROYk5kTXNWOGlnYWVlVk04K3JTNlhZeGVFbkxmS204Q1hhc0tLMDlOVGkxSGJWb3FjeU1ERG5aL0xtbERCL0hldllnU1YwT0I3a2JKZllrMmxnYzQydGhLVUNKbkdCWUFoczNwREVZeEZjVjdkV0RpVzYyN1NpUzNFaTREcnNodHd1WFBORWZ4dDVzcVdTRkRPWVlxcnlpQ3ZLaWJ1eGlGZkdQNkMvcXorS1dESHN5Q0FHNXR3bWQ0ZzYzM3pLWlNMdHU5QjVQem5MbnlaVkh3dlVBK1hIZXRMcjhXMTF4MWUrbWNmS2hVWDlFUlhySFFUYW85aTBpbDFEVzV4SGJnb3hkOE1ZQWJMY0gxSC9iQmhjeFpyeDFPcnJxZnZ1eEhyRmUzMEdmbXgrd09ONC9wUnpaaHoyeEhWbjlJREl6eXZoQ0xwaXlXc0NjRVpPZWkwcjdPVDhCUzFSdktnZkt5KzNLbjVkend2aUxIQzBpV21CWk5iMys4a2lVL1ZDd3NSYjI4QnlPNmdHdElBYnc4OEN4YXVLcHI4aTNmOVN2RW93SDZ3eHd6Vms1anJZSk5SaVFzVDErRXlRLzQvWXl1LzBjQVVBdTJOTDRJTno5UkdOMk92SkFmMDlBTllRYlE0RmNQL0VqRHVjYUNxODVLYVRvaGRxeFZTOC9BNExRa0IrM2paVHZRY1I1UkIwUFJ5cDZVQmJVQ283SnZ0SGhoMTE2UysybDJrMDFBQVdvbFlYejZBLzM4anBuN0hwdUlKRHRzdlJhY1hkV1NiaGQzMWZkM25ScWdVdHpyY2h0dlN1RW0wOXd5UTFDNmsxYVhtLzFmN0pFUFlncWRxemVPTG9icEhrVjJQVFB6ZS9NbnJFZFVPVjY0d3BSOXRZTVEwbnJ3N1JmZ21RUThqUHQxVk4wNk8yL0NEQXV1ZmgyeWlId0U5eHFtWUc5VEMzNmF1aHA4MkRmRDJqVTRtQU9GcXp2OWdyOFdvMkdtK3V6M0ZqQUNRaEN6Zm5SakV0aUhVWUEwQm5VSTlWM254RUZKYVRpV1EybndUK1RNSDRFUVhpbnVLZ1llQU5RZlVGYnVtMzBwUXcxR1FsWHZkcFMzb0p4amlnbDFPclFvN2wrTkZjYUZqVVhPcWJZdlpYOGlNaWhDeW1VSWxvMk44YjZYMTg1SG40NFNwdVhzWjVEeTNEYnl2YXJmNlFDM3N2ZUFFaTFJcVBmd2xmMlVUUFNsR2hBR0JjTTVLSWk0MGwzQXcwNldEUEVYWis2SXVPQVZ4aFRsNlBYeFc0ZWlrMG9xWm44RHBwZW50M3Jvdy9jYjRDWG9ONDdwdjNJNHRXRnljMmkwRDEwUE4zVUNnVE9mZmJQSmg0RzhyWlVONVlEUXUxNHpTa2hSS2c9PTwveGVuYzpDaXBoZXJWYWx1ZT4KICAgPC94ZW5jOkNpcGhlckRhdGE+CjwveGVuYzpFbmNyeXB0ZWREYXRhPjwvc2FtbDpFbmNyeXB0ZWRBc3NlcnRpb24+PC9zYW1scDpSZXNwb25zZT4= \ No newline at end of file diff --git a/test/responses/signed_nameid_in_atts.xml b/test/responses/signed_nameid_in_atts.xml new file mode 100644 index 000000000..5e7ec59aa --- /dev/null +++ b/test/responses/signed_nameid_in_atts.xml @@ -0,0 +1,47 @@ + + + http://idp.example.com/metadata.php + + + 7IMOicPUD8hBuAv8HeVDrUErclY=jPBUr/iVsSXvMeT6PhQxSTbi3j6M34OQiyAKLPyPQWypX0uJ04UbC2J4v1DqFqC3OJYyepQ6OBocSMH4r+uqv4hdoWeaj1Ieeo4HRB5UFTxhR4TV8tTPpEZjGA2vXz74BjtCi6PMWVe/28/TejkAosHfe62WktcznTvfWKePlFA= +MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl0+mhy6MPdyjyA6G14jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA7DjNaUYQqwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALM2vGCiQ/vm+a6v40+VX2zdqHA2Q/1vF1ibQzJ54MJCOVWvs+vQXfZFhdm0OPM2IrDU7oqvKPqP6xOAeJK6H0yP7M4YL3fatSvIYmmfyXC9kt3Svz/NyrHzPhUnJ0ye/sUSXxnzQxwcm/9PwAqrQaA3QpQkH57ybF/OoryPe+2h + + + + + http://idp.example.com/metadata.php + + _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 + + + + + + + http://sp.example.com/demo1/metadata.php + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:Password + + + + + test + + + test@example.com + + + users + examplerole1 + + + + ZdrjpwEdw22vKoxWAbZB78/gQ7s= + + + + + diff --git a/test/responses/signed_response_with_undefined_recipient.xml.base64 b/test/responses/signed_response_with_undefined_recipient.xml.base64 new file mode 100644 index 000000000..248cdad2e --- /dev/null +++ b/test/responses/signed_response_with_undefined_recipient.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJwZngyZGVmOWE4Yi1jMjNmLWQ2OWQtZDYwOS04NDhlNGI4YmY2MjQiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDEwLTExLTE4VDIxOjU3OjM3WiIgRGVzdGluYXRpb249IntyZWNpcGllbnR9Ij48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4NCiAgICA8ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIi8+DQogIDxkczpSZWZlcmVuY2UgVVJJPSIjcGZ4MmRlZjlhOGItYzIzZi1kNjlkLWQ2MDktODQ4ZTRiOGJmNjI0Ij48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIvPjxkczpEaWdlc3RWYWx1ZT4rYU5VZlM0NEgraUROLzFTRi9mTFFJK0RsT0E9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8+PGRzOlNpZ25hdHVyZVZhbHVlPkFTODFFbWF3LzJNVEI1bVVwVThzOTlDQXROSGpnVkFCVlNjYmtVdFlkU3BuMWVWdGhEY3NWbnFyVUkwSGJvV1g1emtoWHNqMTdnVDkxdkI5a29tOUZjbTJpSWJhU2hGWnVpRlM5dUN6SDI5ZFJSd2V2blFaZjdTT3kzNXVlMGdmQ0sraDhnTmV0V29DZFdvdVRZbGJsVWlLYXk4YXVnYndJZnA5Ukg4TWVFYz08L2RzOlNpZ25hdHVyZVZhbHVlPg0KPGRzOktleUluZm8+PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU+TUlJQ0d6Q0NBWVFDQ1FDTk5jUVhvbTMyVkRBTkJna3Foa2lHOXcwQkFRVUZBREJTTVFzd0NRWURWUVFHRXdKVlV6RUxNQWtHQTFVRUNCTUNTVTR4RlRBVEJnTlZCQWNUREVsdVpHbGhibUZ3YjJ4cGN6RVJNQThHQTFVRUNoTUlUMjVsVEc5bmFXNHhEREFLQmdOVkJBc1RBMFZ1WnpBZUZ3MHhOREEwTWpNeE9EUXhNREZhRncweE5UQTBNak14T0RReE1ERmFNRkl4Q3pBSkJnTlZCQVlUQWxWVE1Rc3dDUVlEVlFRSUV3SkpUakVWTUJNR0ExVUVCeE1NU1c1a2FXRnVZWEJ2Ykdsek1SRXdEd1lEVlFRS0V3aFBibVZNYjJkcGJqRU1NQW9HQTFVRUN4TURSVzVuTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FEbzZtK1FadllRL3hMMEVsTGd1cEsxUURjWUw0ZjVQY2t3c05nUzlwVXZWN2Z6VHFDSGs4VGhMeFRrNDJNUTJNY0pzT2VVSlZQNzI4S2h5bWpGQ3F4Z1A0VnV3Ums5cnBBbDArbWh5Nk1QZHlqeUE2RzE0anJEV1M2NXlzTGNoSzR0L3Z3cEVEejBTUWxFb0cxa016bGxTbTd6WlMzWHJlZ0E3RGpOYVVZUXF3SURBUUFCTUEwR0NTcUdTSWIzRFFFQkJRVUFBNEdCQUxNMnZHQ2lRL3ZtK2E2djQwK1ZYMnpkcUhBMlEvMXZGMWliUXpKNTRNSkNPVld2cyt2UVhmWkZoZG0wT1BNMklyRFU3b3F2S1BxUDZ4T0FlSks2SDB5UDdNNFlMM2ZhdFN2SVltbWZ5WEM5a3QzU3Z6L055ckh6UGhVbkoweWUvc1VTWHhuelF4d2NtLzlQd0FxclFhQTNRcFFrSDU3eWJGL09vcnlQZSsyaDwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWxwOlN0YXR1cz4NCiAgPHNhbWw6QXNzZXJ0aW9uIHhtbG5zOnhzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYSIgeG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgVmVyc2lvbj0iMi4wIiBJRD0icGZ4YTQ2NTc0ZGYtYjNiMC1hMDZhLTIzYzgtNjM2NDEzMTk4NzcyIiBJc3N1ZUluc3RhbnQ9IjIwMTAtMTEtMThUMjE6NTc6MzdaIj4NCjxzYW1sOklzc3Vlcj5odHRwczovL2FwcC5vbmVsb2dpbi5jb20vc2FtbC9tZXRhZGF0YS8xMzU5MDwvc2FtbDpJc3N1ZXI+DQogICAgPHNhbWw6U3ViamVjdD4NCiAgICAgIDxzYW1sOk5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjEuMTpuYW1laWQtZm9ybWF0OmVtYWlsQWRkcmVzcyI+c3VwcG9ydEBvbmVsb2dpbi5jb208L3NhbWw6TmFtZUlEPg0KICAgICAgPHNhbWw6U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPg0KICAgICAgICA8c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMThUMjI6MDI6MzdaIiBSZWNpcGllbnQ9IntyZWNpcGllbnR9Ii8+PC9zYW1sOlN1YmplY3RDb25maXJtYXRpb24+DQogICAgPC9zYW1sOlN1YmplY3Q+DQogICAgPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTAtMTEtMThUMjE6NTI6MzdaIiBOb3RPbk9yQWZ0ZXI9IjIwMTAtMTEtMThUMjI6MDI6MzdaIj4NCiAgICAgIDxzYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24+DQogICAgICAgIDxzYW1sOkF1ZGllbmNlPnthdWRpZW5jZX08L3NhbWw6QXVkaWVuY2U+DQogICAgICA8L3NhbWw6QXVkaWVuY2VSZXN0cmljdGlvbj4NCiAgICA8L3NhbWw6Q29uZGl0aW9ucz4NCiAgICA8c2FtbDpBdXRoblN0YXRlbWVudCBBdXRobkluc3RhbnQ9IjIwMTAtMTEtMThUMjE6NTc6MzdaIiBTZXNzaW9uTm90T25PckFmdGVyPSIyMDEwLTExLTE5VDIxOjU3OjM3WiIgU2Vzc2lvbkluZGV4PSJfNTMxYzMyZDI4M2JkZmY3ZTA0ZTQ4N2JjZGJjNGRkOGQiPg0KICAgICAgPHNhbWw6QXV0aG5Db250ZXh0Pg0KPHNhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+dXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFjOmNsYXNzZXM6UGFzc3dvcmQ8L3NhbWw6QXV0aG5Db250ZXh0Q2xhc3NSZWY+DQogICAgICA8L3NhbWw6QXV0aG5Db250ZXh0Pg0KICAgIDwvc2FtbDpBdXRoblN0YXRlbWVudD4NCiAgICA8c2FtbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+DQogICAgICA8c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj5kZW1vPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPg0KICAgICAgPC9zYW1sOkF0dHJpYnV0ZT4NCiAgICAgIDxzYW1sOkF0dHJpYnV0ZSBOYW1lPSJhbm90aGVyX3ZhbHVlIj4NCiAgICAgICAgPHNhbWw6QXR0cmlidXRlVmFsdWUgeG1sbnM6eHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6dHlwZT0ieHM6c3RyaW5nIj52YWx1ZTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT4NCiAgICAgIDwvc2FtbDpBdHRyaWJ1dGU+DQogICAgPC9zYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD4NCiAgPC9zYW1sOkFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/signed_unqual_nameid_in_atts.xml b/test/responses/signed_unqual_nameid_in_atts.xml new file mode 100644 index 000000000..66a0bebcd --- /dev/null +++ b/test/responses/signed_unqual_nameid_in_atts.xml @@ -0,0 +1,47 @@ + + + http://idp.example.com/metadata.php + + + LQa/IrEdbLE1BHEP3B7KfvOABpg=fWg7OauBy7cExPo+GbNVb1e1OopaYc0ke+BYF1N4bpoej86o7U75xOttDN7oz58LhpDZYWcgEvsqX/Jth7Adc3v0bsUEw1h3rFVhGCOnJLS21o1r9cEdkUSbGcJ7jk6EnlxE9s3/puAxkanqzC50hWsIm19Xzu9Li1MN3eDgB9M= +MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMIT25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMxODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5kaWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl0+mhy6MPdyjyA6G14jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA7DjNaUYQqwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALM2vGCiQ/vm+a6v40+VX2zdqHA2Q/1vF1ibQzJ54MJCOVWvs+vQXfZFhdm0OPM2IrDU7oqvKPqP6xOAeJK6H0yP7M4YL3fatSvIYmmfyXC9kt3Svz/NyrHzPhUnJ0ye/sUSXxnzQxwcm/9PwAqrQaA3QpQkH57ybF/OoryPe+2h + + + + + http://idp.example.com/metadata.php + + _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 + + + + + + + http://sp.example.com/demo1/metadata.php + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:Password + + + + + test + + + test@example.com + + + users + examplerole1 + + + + ZdrjpwEdw22vKoxWAbZB78/gQ7s= + + + + + diff --git a/test/responses/unsigned_encrypted_adfs.xml b/test/responses/unsigned_encrypted_adfs.xml new file mode 100644 index 000000000..2711bc988 --- /dev/null +++ b/test/responses/unsigned_encrypted_adfs.xml @@ -0,0 +1,23 @@ + + + https://idp.example.com/simplesaml/saml2/idp/metadata.php + + + + + + + + + + + xkJn7LQHrTZzLdrNec33hMSC5dc8B0uM4fw8HKhwpkWINwC9deaj0QVz4fZ82Zv2QdP3v6r0wJ/8VrUIF8puJTHSU0u2EUU+XqBJxvivGp2UhUPCyWmirSi6efBnWqMKhOz5YJoaLEu17yw+kOnavLZPv2bB8b5bo2mZPiaPG6s= + + + + + ERiTkdJzJN841qIRwT79+laTQZWz83osoKohRBIaBHNLasGuBApR6iJqsSrQb2/6wWddDaNOqbnBEY+6Ze1mSzDjYomrWtVDD2psrzk1T29G56LuTQiOtTtOUlOPOVUkS9Tp1RgCXmi7No49lJJHxM8b53Y6hNjJQiSwrEdxhrv83MSuqYahuT5n2yB9jGg/ZnF6IP2YXLT7cd4kYRj/0g0mEBGAIHV3ZGRLVSKg0CMVPnQUCPLXqVCKoRbF2nt5R8Q6gGmKm/2zsyEfDw/vW076fVg6EoK+XVZ/M/gHKccA+iyeHKMN2k/yWeHn7FB32jY9oR8Ct6mW9JlgBbliM1SCDMnshuJXlKnyXuvOfJQd12NUrt9lWACwnKQzSZ3y/b5UthalAYTgHPVtL871hXnC9MTeTfAk+HqV7nfdPJTk/jVDIi0Wsbqlf+ppC1nvRab8ovWmfpUKFYhHhiQ6K6XQGo7xT/dKqV7+2wvhHtvYPt8iW76RkWtHVEjZaHjgCF/XGpMrj5CuX8KMQ3b67CIxb28V5MxnUXrJV1q5TnEF56bvlG4NNqQTYWMcu+bhcA5w1E/rJZxSKfpg7VdT4RMA3s0ZCXs9DR7RnjQP9szrytarMRwF+AI07Axt2/z71t551rNzCedNLdQFnFbFh9IgDSmYin7y1mxsa7IiLUZcJwtJTh33tKf0ObwMfj68vWgEJ0Aq8ZjnhVB7C7sRK4vh7NOgldS/CXZ2L5E5kYh2hVzqh0EGJrkOs3Rb3/nbZCwO4zdAYvv74qrPUkEUxG3/p9wjcg+bSFulcJCpk7i0+NnsocQ3OpDQGUFhtv6Q925h2LDAYQSkQgIvPnqbt+A6VRCng6g+mMwbbwP6o/agY2CyduED0QINAvZz85Cpts3uf4uxJEnaztDGT1RBPFwhMuJVt7Qe4nqurRftCh+T6KiVvwE6CqfuRFQm+SlWFyYq5u7GdRNFsuT3YKji3mF+UdqgNJa++R4bpOtZm1QQzv1qSS6UCtrJHbjBXhYDig3dEz1erkMWCwwjcicpeSrjBFc0lc1VzYfm1mOYWBHTE2RfDnEUztOzBUmiBVD3GXmS4xrUErIqz3RHe1erG8HwSZbfsXfOpfR5K8RsqL5A8l+vjSN6wfQGB1uIf07OPplzLL/UlZWcPQq+j0GGQAd/4Kbl5giWSSbwueaW95/dUJQGcOw7gIw9LIRasAWb6JQ0/TRgwU+a7Hoj+cFAdWxkeV63V9g4BvwOW3Zr00pSo2G9g+KUsAuFQxk9h9cDEqdEM2vPtfeSkjpXOZnGOrRNVUG23lgJHEQsTNb+VQb4ROFJBVjjdpE7jHMU9YaK8EfQokUBfr+8OtIdp9+LAUnfZ3JKGMKOROuQ2rtjb3NCZ8sy5u6usom0mbcxHiVqA+XbFYoNfer4JyqI+vanSC+dXnUEHnWxF9s6OqmSLUNCQ0TlKhXA/6SEBTC5Lb+Nust80tYHSgBm2S49aFD5sR2sI+8SCd3fftcyZESZCH7zH/t0c6gr7AEIPHaro0jmJPX/2Sxbof0TDmd1VCP1vEQIKo/jXgiPplIPMzk+ZTKLCn5TGjlBXQMLkEgXQuFcxf6kwnWgvV+4wfkV0dSweMF4RIq7oUBMuRcKLZzsI1xK1+Og1oros/oMgmkfAvR+LHn/q2vl73HgTMKyti5tBwWJBDGQ1uKP4UDzlO3L3SU72xT2SeAkavU6WJ0fz6XXRVIYnfkfzJ6nUpXYxEA7G+9X2zIBe6x9oyG1rOB8tNG3IyHJXfVujz5TaMVycP5hFqBy+EN2WF1Vu0qMUgi7ZY9u2xDnrI9XkYD+d0hXhC6Ow/EzMi+THV6w2MsOhy5E0FMQPbiipRSirzxs0bQkKLmgxkxaulaWPuCATc2lNZOb1Xp3y277TX5IkJQdbo1h772cUTf/tfW6lb/my34VbFEJEusMXSPxwXa2LkNoqDvIBPjsMYnogagriVWd+WDIjM/Sqh+RhRo0Owp/V4w8KhrCU0tFI/g1pn59i4mrhnMUyeZdYSPk0TFdNBMekMB5Kq3Cc9kHSpe4TCAkWkknZ9qKF0sH7kQnDzfvWsrLDHdv9ijX/Kz+J4wwopeSKcLbjOORpnNd0sAuF4FhwuOh9t9bHgxIVNpipcfiMUKzxI1IcLQjFC7aNuc/K+SdW4mw1L9rI8JihNH21vid4h9BZUaY18fedWvdtH+0W2TPsHgMKM5KbADS6ixcxaK9qBAELDaRrIwY209pDgDIj6TU/IrhB5SKGsrEoEnvvxDXIds7vZPfbn6Q5KphxcU66zLhI1AtGmYkO1uIRRrjX3A1zchhHpE2OmTFhCDLYMIuGPhY55I71Nnzn6ysF2HHnxRBWEIEV8aEFFA87UzqhdzNWI3sMcy7yVsyLxWpdSB+GpsCM9vU1+BRZh10KeNROSQryY3lUp/uYpWCsuTPMXuYvqS6RSebFgVu38bgGfaBvLILfb/YkcHWGP/5Yo/ndEgsTI2pDTqdpDArNaxoHJJ+uC1DhPKsQgc1OLtdcIR81eHn9JHdUKBRWcHU+8GBEcMmFjHrbr7V8eVPOEuTodO1gIjQuUC0OjRD5O7gadPNwMG5JV3Q878V8stkrQFPyFdgsCSjrPPgeXifmsZDeVzVw+bjoACqbTHcYGYOEYUkSfYe4G43YP/we0wr4ehJUo9nj4taP8IN8nnn8ZTvuai8LHVgRZ+uXZI7qJvOToj1s8qBl7KWZTHRuVCvACvZp7V6mzzBxKdQwB8A5/mnp3jnVhjFkDTzczBDaPqyhurN3pYVdGhV62TTrECf04u0uvJY/84v56xHsnDOw0PtKZ3KbRdaQT4rI3YfS7FlI4jk/uIcG5OijKxzeK8v9UCDn7tAoDATkSQvi9Tn9cJfUdLMYqKXN+3CLKOsJH974b+78ZhFplD6v++6PHzZaeelPkP/9a9vYUbDPX3Rx3bxVfcVgO3Mvm/1mB/D71q33zEUreS0qBQzLqWLSOmAca9uXAsTMrvUBjl2Ir5NXcLvpm5kUMu3B3SazJiDT212fPu03xT3PIYlGMXyc10KmFByXevKAcRTsS8ov7IHnctxo6As2/UsiQU0S2Sa2fx0w7T9+yLOPkUPtplCLHjqTq3ZDsmpRpDV3XYFMVgBLM6LqKjDep3uJB3Q1PX1pewH3HklWyV7WyIWsoD6hFAD3tRinrIOZ2GYvU+FfTfZuJ2Bl6UGpJV3vYa4MnVtQlpyNKsOoCfxAO4xi0cWotlmJpyOz7PlE+mKkYCKDl7i3g1nbtk2vC6l1Z7rJaPkYLBsjW4WuifOs9h/DyrmaeSk2BGcT1w8vgiG9zsHsREpcO7IHFmPtViNdwzRSd+0ufubp1g42v7//WgNU/swT+xeUk5Ew/dnnnVjWF7ihKG+LjA6ZG5WvCYqeyT1KffCJC2awSL8mgHTpaBHfO6DbA+Znh+5mNiHNdD4KdCqUNuaCW3OL3ftj/fgyXLm4JQl88s3ixPkHWDLQkkvNHJf4ZMD0Zgzw0GhZ9dEElcrGrTSq2KcX0wmAC2bO41FEfvIi4YGTMi44q1UH7sKiOam4bDrnWf9r+ecUhutCivqAY5g+9OPt3VLgdEHv9HyUk2PjYBAQJA+nOSHnBGxLJQXD0uXYoX3Rr8c4wHCyyC5pnRtWX6fLiaXrrtm6mi4BxzUG0FGBWNjvaCvbgOxFN7i8xsode8FElDUFYXp8MG3yFlyDp7FHEZsWwUX58E6Zj+HjBUETcYQXUjX3gBC2RxjZcz80ZsSaFEIhsphIAzREr/JcfRgpJH7b0FvbG/EIQBGMwSZ1LK7ZM0oXJY+W6BGShKtp8eOr6Ej8fxcxQoDxID1H/jgKwzltKq6v1fpYSbCjd+lEuz3OBRLa+bVr8rzSRezJ1XbQCvCDk/KeQS2au3FOoGIhaYTzDec8GvEx/xe1gQjuumCKsfDOAdTk2RNCw7zvsPGk+IMteMUNJya+bEK8j1zkb/Elxc6smwAScotZ2meKe/lhh0TwX8nVBHjTP/5gPFGl8fl36aJ1DqRij9O6iv2h9dckT1xbCWY/pW8gF7/TyOQoDje/JLZg97xhZ0PxoQyWHopXlNaKivfDXF5sV72lcOzazSP32mSelJuh4kES332dpjQLsiT2TGK7tsN3r77RM2iKpA3HE/XyhjyFqkwI4c6E7tCQmY9CPXeNWZyLWXdL/4Fgvs7elmQjhVvhx+9K5CLv5O/DYTeB31L9oF0zLV6N/K+vzCOFaFT6tI6B+icXUtccuKGcggIDF4N/z+AKCNK1m2TX0P0Y7ZPESdVzvSoYQqYbCRx+yGTACEHd6c72y3UlgecifscdJ983F+QDFjeZBRf8C4NhWxALyrwqAg1tKJPX8RXEpQfJDHoGNdIFFveZhku/4hYZF6B2GJ8l/fgItQDcwSnqctLNBlwVJ0P5cgbU3SCAYQF/+ldB0NJiJ8gzYOQCp2N8kJEEl4cc5gq28v5BtmUGhA7GilS4ZSHs6tJIuqego8ZWKir4KIKZg5QiiTfK/sME4u/ZadKJnJ5mhCTE+M7HG+Tyrmn6rkgsHHn6sTc4px23xxIH3PIM5YCQwK0DxopaflXDXUBwYYUr7mO8zv+NLzI3o/nkQzwX2+Wp3cb9qES/8N7q6h3653y8jgKYi+1Tv+ODLTFax9QkAGAKNsbeW9dOWCGovDSlcA+jizBmO+ag2Dy1AoZEI/ioy9rfmNWe7rNCvR/ulVNsyeC7VO0Y3o6g7JCttjc5+Beh4/HCTg/Cn6l1//Lhu1jVecSCZQOTv5wLQKqITF4/NtneGytWkUsroJwMu1PjCFBCp0I938tBk8mCpzf57i3C50I+vIEFNDNq3zmoSkcMae8U6Kvp1/e7p4wtwjwHAvy7PwewtYzndhT5CwqEq1uN/SrkDlXaCAMZGt9jHTzONviXjox7l0fr/l8HJ7y/GLno7wdE00a0LxzlxNGNdcKuqrDjndE3QmLUXrzTICSH7+2P5bqhoeSjj44HAu/QkDHpBqQl0k92DiKy4USbULnrAlRn8ix0u9uAZL+aMwrERyFtIoetNJMrWpkEHELg2V744qR3QY8QnjvL9jmG5J1rAXNCk2qbFI4SLYO+bLIAZaxIcXrP5GMf/ojYUZEnBiny2E+JWR9TFRjh+8yxXk9/PwJWI3RDJFdV6rY8rsKm1M2+01qNwjAYKkWTdyBeg3Jlvb84BoqOwPPoJHXKB0fJsY82R/FoVQK26aL6bA5xcAvfVszwSYs+4yTJA2roA== + + + + diff --git a/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 b/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..026e25684 --- /dev/null +++ b/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9Il84ZThkYzVmNjlhOThjYzRjMWZmMzQyN2U1Y2UzNDYwNmZkNjcyZjkxZTYiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTA3LTE3VDAxOjAxOjQ4WiIgRGVzdGluYXRpb249Imh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzRmZWUzYjA0NjM5NWM0ZTc1MTAxMWU5N2Y4OTAwYjUyNzNkNTY2ODUiPg0KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4NCiAgPC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkVuY3J5cHRlZEFzc2VydGlvbj4NCiAgPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij4NCiA8eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTI4LWNiYyIvPg0KIDxkczpLZXlJbmZvIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgPHhlbmM6RW5jcnlwdGVkS2V5Pg0KICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+DQogICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgIDx4ZW5jOkNpcGhlclZhbHVlPkViWU5vanhlSS9Yc2VYYU9LTGlEakVkdkZWUXlhajg2cjNkeWRhT25TZEhsK0NmVmRpMUpKSzNIN2REb0xHczkNCmFZbHdlL21xa0E2ZUl2dTFtNWM3eFQ2c2NUek5sbFlqcWplaUlDVEtaaUdzb2hjTE0xbUt5Zm1mUUpJeXQ4ZzANCkk3UjIxOVdKR0hOd252MGl1OTc5b3lvSkQweG43M0hpNlZnMXdDZE1hS1E9PC94ZW5jOkNpcGhlclZhbHVlPg0KICAgPC94ZW5jOkNpcGhlckRhdGE+DQogIDwveGVuYzpFbmNyeXB0ZWRLZXk+DQogPC9kczpLZXlJbmZvPg0KIDx4ZW5jOkNpcGhlckRhdGE+DQogIDx4ZW5jOkNpcGhlclZhbHVlPmp2US9lMXpCWTB5aXViSU9rQlpCY1FSSXBVVUpYWVlTd0JHZ1MyYTRZMzZ3ZXJKczMyc256bzNhanpscDY1eUcNClFKd2hwRHIwdVZ1MXRjdlJ6ZEt6NWltWUF0WnBGVWRva1h6L3E1YS9vNnN5bTNnOGJJeEFnZXdHZEkzamE4TkgNCmtBbUhxNmZSUFVJKzVUTGI3bXUrYkxnR0FOTU1HZ2htb2NUKzh0MXIrSDBNQjRVQVdIeU8xYWYxNTJvOVJMaFANClcrNCszdXpLU053YmtOT09UZGNyY0FuWHhjTHMwa0hGSDdkNC9makNSZUdNMTdydjZFTFVzT1d0dlVKdUtCV3UNCklVdnhOaU1BSnRmdC9scHVnanZ6Q2w5a1RKVTh3SWx5YTk0T0FyMWN0cElGKy9PMWYzNUFlTGF5SERtb004QmwNCk0xdHBxamRaeDBET3JxVUtWMzR4NU8ybnhBN3dQZEJQSjF3TmVaNnhiampRWXA3Y1NyQTBRWXlLcVpwZ2kzYTINCm1EOGhINTc2U1dsNkN5TkpHSkFFNXA0VlBQZkVUZjBHOVgweW5mbVpuUERFQVg1dGVCQmYycmVUQkJtRW9xNE8NClpIODBYT1dkMCtLdlExb1ZqUVBBU3JPU2M1T3g3MEdheWJWcEhOaEplbzFuTHZmOWtxQThBUXRmTHhLSTJnRkoNCkw1UjhZL1JRc2pDOUJ5bHdTTTJyUENkSEZoSGdBMXZId3ZZWmZLV0JLVVIwdUt1TDV4dEJ4STlVNzJwYVovY3UNCjFTbXNzUjNPNmNBSkhGbFArOTJJZDkzSENwanNrTVBtc3hzbWJlUFJYWlJhOUc0YlBlY2lOZ3JPSzY3VzFReU8NClVtWEZjVHNzTkJWTFJibEs0VnhMWTlGWENvWWZtMFB2QmVtSzFVSFhYd1lSZkk4Nkc0VnIwemRFSGc5NUY5QTENCmJFMFNHaEkwaURlak41elZDTkh2dVY0T09CMUorZEtkdUMzSythbkJ2TTdnT2tZblpYY3VPb3lNejJ4Y1p5NFYNCld6cGNkYTdtNXc1NThJSzVXallBUFVXamcrS055OWM5VzQ1SWJQMTdFcXVrKzhuaGtWWVFWUjBOSHU0SHRPWjINCm5jNURRZDhNcC9rQTRZMFVLSjRVQ2hRcUpYUmVHT1dFbi9tcnJKVCs0OHMycGVyYTVvUUowTU5yZGJvK1hLUWMNCnJsNFV0V0tocVRCZGhnSXQ3d2dDL2F5ZEQ3YXRhS0xoOE1Yb1dQMGtMd0ptSnRGUi9NTHErTXJ3VFE5SDI1QWkNCmU5M1ova01ab0Rkdlg4enFkN2xDNXh4eHVtcU03SkVveXdoS1BkdFFXR3V1RUFLUUxCYkxLRlpoYWFRRktKZlcNCmxoTCtWMEpNenpSZkhZN2F3a1JBNzljRUtEMnZKZGlaYjlZMksvTXBQN2xLdjNxMlFqbEl2K3c1cDFQZ2Zqa2sNCmRJcUoxZlZnaWhIdHVIWHN1cDh1R1VtNnQxUVpNSlo1L2tMclpyQnhWOE1pR0M2REEyak9uQjZVZjhKV3UwTFYNCnlPL1YxSHpGZEZ4b25lcStzL2FsRklmVjBYR2FEZytmV1RWTTdPdmZqVzZRdzc5bTQzRWRpa212YUU0N1hLNzENCkNFRDVlUHBZUFQ0MnFKRklRbElIbm9UeEVmWnNIbDVNeElVWERhaVNmY0NjMVJHcGdaVTJpRnhHSmZidk5RQmMNClh3S2hJNFFqSTN5TjNkcS8rVjM2MFpTVUFmR0xveTFkb1hsWlRoN0ZRVXhKaDNMVkN2TmJneWxIcXhObWNWRGcNCmxGY3dKODhPY0tjdEI3cjZEVEFFMHlxTHJlTGNBZU40V1FLeDJMZExNOGNoQ000QS9aYlhlYWF2bzN6cU1VTS8NCmR3emZ4QWNzVTVMdXdxSE9xemhyY1ZqaUwycTIwdlNBZER0SkRwa282ZnpjUWtyNkhsb2pTSFVCYldNSzg2MWINCnNUNzBjQmFtZlFoNnJvcFN1SEN3enlQZnBzODVBU0ZJVVpUU2FPcXEvOHBkSEtLcUxoaVBSajlOMVFBaDRDN3QNCklmeWdnZDdaeFpwYXpDa1BQS1RoSHh1WXVOMFNEZW9TbDRncjRCMHdqV252VFAyR2pYMGFiZmhyM2VvWUxiR3oNClBMVnEwR2I2aUdvNVRMOCtjZ3RmK2tVYlVQOWtOam1oMFlSN2VITTR4Wm42bkRSYmRjZzN3Q25vK2tDU09LNEUNCkNqNHZZTVdhVXBScTl5WGJEZ0FkWjlUOGZ5WnQzVGc1T0ZsWmRuNHNnUCszNmRqZWwybG5hOEJXU3d3dEgxMm4NCkdZRy96TXZqTnZSUVFjdkkxWHo5OERRbEhKV3d6bmMyaWp1NFE1MHBIM1h2K24xVC9HQkRib3B4SUVKZnRzem8NCm9XRW1ad3BlK2pLS2p2dWtVYTBRQnlWSDJsbkI3WjJpbCs0b3BMeFE3YW9vNEpCbDVoUzc0WnM0SVdhRStFZXYNCkU1Umt6bjVyWnZXMyttczhtUDQyWUhQekFwbGNWRFlXRGQvM21XSy9RYm1QMmJQL090V1lFWExld01yaHhZQmwNCnk3MEYwQnhkelpKYzBuZ09pNDlDbE9CL2NTemowQWd5L1BleUhIRE5xalpMU0VZUXJXM3JUQ0U4cUV4ZU1ZQmYNClN1WkZnZEdSanBSUmZlK3JUZEVmd0o4WlhpTTBMbXV4L1NHcjJOWkl6NTVMV0lDVk1KSVdwdmxXVFdrZ0swc0MNCjlwdTVXZVRXbnpNZEpDTnlnZXRnMEtNdFBUcXIvT0huS1lQVU93ZjZlZkU4bjF2QmFpeXVodncveVN3OFRqNmgNClhQdHRJN0djcWtWbUt1dTI4WThmeFdSL0huYjFLeWlzQUZLN1RGV3ZFY2FzUVlCR1JKSkZQdzIvbCtnWmZka3kNCjFhdTVpcWs2WXBCbFJ6d0NsKzlDLzFZS2lSZlQ0dU8xZG5LWm4zVkd3MitoeG1OS0hOUHBwY1hvQWdLa3YzWXMNClQ2M0ZtWXJVcTl3Z0dhblVuVDAyVFFMdDJmbjl4bDJhZGhSM01pUThCaVk1b1hyaktva2lkZS9yT2xKOURnNnQNCmZXQ3k0L0RONC9zcng2UXIraGNhcHIwdmhCaFZJUHFqOEJ1c2xCUXV1WlFXaFNiSkVzeXZ5Q3d3REJmSjdMdDMNCjlhTFAvL0N5UVVqd3lFczdNNFJWb3haaXRSTTdaYWI5NTc2aGFIRzQ3RDU0NlZBNXUxU0puMUs2d0s2SFpIcFoNClNnblVCZFljOVJNVSt4N2FZQW1Mam5NZG5aSzNjSGhwN2VNbWJBcEE3aGZkbnhMVzNKemt0cUY0aFBiclBPVncNCjJtdENuU0RXenVJT3FEcTdUQU9xOXhrN0pyQk5VcmEzc3VydnFGbElzeVQ2SWdhNUpPT1BwL2p6RTJUcFd0WmMNCkFxdUFhYU5QY3ZRT0wzcHp0ZU15Q2ZuYmNIRXZOaXFObDVqbE5Ua3N6MG9Jd3BvTHFEaTFRTDArYUhJQlRHTmoNCkMrM1JnMGl5SzdxT2hQc0ZndkFIVXBVc2g3NDJIV0NJQ3B2cXVlM0kydi9VRWtPVTQvdUVaMmFHdG91blkyQk4NClRzV1NNRGJkWHNGMC9mbVpoaWdPaFdKMXRkRXFxQjVSNklGUnV6Wm5Hd1lkamxQM3ZQUnZmdEtuZWVqY2h1VHoNClJXNGR6WVVyUmxDaTNGVU9tT21OMVhvK2tJNlVsb2xIY3hEbGlTeWVKNlhsSkxUYkJzYktGUUdnMEdWc01MN3MNCmRZblNMZU55aEtKVjlydzFwVlp5cUpLb284NDcxcHFHRnlZMEZlbWJubVFqcTJtVk85Qzl5c3MvQ2lsTXpyeGoNCmFPZFlzK3EzUEhPRmFNTS9wV1BjcXdtOEtmQnNPMENSVW9RdFZ0TmJDNFB4WjVKa1pyM1ZFa0FJOUgwQnRsMWUNCnpySERTTFpUSGZZMm5yQk9JMWtKQmlucFJweWtyUVE5Vmp4OWVNek4vRFlzWVFuTTJUR2RuUlVYYW80NTNCZ1QNCkdxK1NCVkhVRGExOFVLL2tEdGNONmJqNUp2RlNSalc2N0RQYUp5ZUZzVFhJay9wMHIwRHQ5WkpseTUzQnNtQ3ANClBTbXZab0REbHB0b0hLdmcyRjEwR1I4cVdDeEFMYkhNTWJOZU5xNm11ZGVmRTh5dUFsdFoxZW9ic1BXSmYvZ2MNCmNkemhyNHJ0Vm4rc0dUTHRFWE03NmhUaEVaVEN0YjkyZHFNNFVzK3FUT3VpWklwMGxHNGVxblZsaVYrZ2JvdmoNCkQ2VHFTNnlYVVVZMGt4MDh5U3NvRndBTG9pRk1ZRXQ0eE9IcHNtaDc4cEJOTGNkSm54ZDkzSTFsb2tpd3puNlgNCm9DeVNmR3pMZmNFN1lPcVRaVmJBekFYaWlBY2s0VWZHY0h0TmJ0M3E0VnFBWkdRcW1ha2VLY2QwbUVjbnRTSUoNCmhyb0ZZajRza1ZCY1BnT0hxdFYzTGQ4L29jNnhCY2FyUTE0U2F0TzQ4OXhZbDVCYXFGVGgxTnkvbDNVelVkejANCnlNdGlHQWVqYTVNNEh4SkJodWFMWnhNSDhyL0xYQWthdXA5TFI0MEJyTDF2YTJwWXZYNlFoekFIZi8xT0MzcE0NCnE5MGEyTkdaNGg1VGdnMkRIQU1oMHV3Z0NFd2JxSkthMUpqMHlyaVRtbVlJS0c3dmV4MzF6Z2pGNWFhK1RTM2UNCnJKRDBJNW03cFZTS2hGME1WbHRYQUtIQkNPUnNPTmM1SjU2bDFsenJmRW40U0xjQ2k4MVdXbzFxd0VTenpBWngNCmU3c0hwSHdQUnljWDRWMHlKSnpmNmM3dDlkZlpqSFFJMW5lYzNsNStONWZ5TzhwUWtjUTduK01NdzN4SnNKRC8NCllVb2x6TjU5cjJEYlhnQ2MydFNJQXE2dFpKUnZlb2k2c2NpeGozSE53TFl0eUpwUEhJeGNsMjBXYVRoaFpQbHcNCllRUjQ0U1l3NE5NZ29yQzArSXl1RFZTckdFNHk4ekJFOE1yNGlwcjBLYzF3V2NUSkxobHB0MXlQYXpWU2xCRmkNCmVqckxFbHNjVlR5NDBnMTlhQnA1N2tMSGl2KzkyZnFDY1ZoSVRvOFFtK0QzOEJ3UU90UFJwY2REb1ptb0tLVXgNCmprbGpBc01vOERmMlViVzQ1dkJ4TnBaVm0yQWhSaldzcHFNVFBwb2ZaUThVUVJwaWlHNTAzeFhUeThZbWR3TWENCmFJZ05jdXpUV1phbXN3ZUtFT0ZCWG1VR3NnWElMWjJsVk1RMXI4cUdDc205ZWxWekhCZVMrVkJzUHltdUdBRHoNCndOUC91cHcyckZSa1lnellyRUFpend4b0RvWUJHRXhrSXAzRGRuc0ZPd1NXSGh4YzdkMkhjbkhBK1pKTEUzN2MNCmRDTUQ2Z2NnNEgwQSttd2tibkNBc0p3alFPTUk3bGU4akRaWEJENjZjM1V3NGVEZUlyc25CTTl6Wi9lUmhlSXQNCmdxOWluam1jMFNRU1R0UE5jdzM0QmlkcEFwY3RwUnpYOVlTVmFicFoyaEIyc2JUTG9weG1ZeGVZM00wNlJBcGsNCnRZT3gzWGQ3aGFLdStRYkZ0YnhPMHJhSEQyYkgwd2ZUSVRtSytzd1IyTGVOY29sMXgxMGt5bVBlb2tlMGw3dm0NCjgrWEd1cC96WU5LVnc2SW96eGtuUFllL1JRZm5LMTNlNnp5QitrUW1XUmJ4VEFyZHlpbWJLSUZxQWVVVmh4L1oNCjBZK1FqQlh0MnJuYnlFM2VjcXRQdkVwRUZXQlpnZUxXV1VSbkVZa2lnN1RpSVNoZnVYT1JPYllpVUViTlAxQnQNCklWQ1c1dFBvMGREZmtGTWc0b3k1NHQxWHNCU3AyOEpHNVUvMjRJUU83NXNyZ2J4eTlrTjRQM1hRNWQ0Y3h4S3QNCkZHQWJvZmVBOEsxMFdYOFpPT1JsdGxMTE1oUjVmcmxFMVc4aDI0S3hTczM5TTErbm1IRHJTT3c1MzhYNDg3bSsNCm9jZFNnOFpwWVhtaDBHTm0wRGM1d0M5aXpRdERxakR4ZTJCc0RCSDk4THhPcW9TYjc3dTdneTVqQ09mdXh5MncNCkhXL3FIWXpIWkdnRzEzWDc1akswVmJUUzFNaERJWm5MTjRnWUxKalI3Lzd4bHcxVEdqajhIMHFHRks5eGw3L2MNCmxGblZTampwc0lyaWF2SXJ6TUdJaFF6YnJQK1hhNDdVRFc4bHBEZXBwbjZlbmRaa0RlVjNvZG5HcGUrbGVYd1MNCjhQbTE0TmNIYmVLSHViT0ppdzhWSlZtUS9BY3JrblBXVlBMdDlxY1loMmpSRUtpbVMwVlFlTEg1OVNQZmQyM0cNCk9jTnJ2dFFyVHdXd1hPM0x5cnBhTUIvbXowNU1EMzYvalBWSnFoeCtYS1c2M1JmUWhCQ2lIdVhCVUR6R2NTdWMNCjhjQ1NqRDhXWmJhdmlYbVVQSFNuZFptUG9BdVo1NDJhNVUrTjZ4azRueVpuT3M1emdWUTdDUkU4eU5lcmZKQWINCnM3N1BkZCtoUDdSeGIyNWd4d08rS1AwN1pxNDgrSTkvYUlwSVBmZFhWNkI4ay9VV3VBeERJQUZsTzFjYUR5VloNClZxN0RsN1RaRmpnRUN5cWNQWklqaUdNbWpUNDRHY3V6cXR4MTdTVEZhRlFYeHBjQXZhQlhOYUxrQWdwYVBod2oNCmVjdFQ5cTJFb095SVJpTEI1WTJ0OVY4Z1BKdE9XRy9CSjliRXIrV0wxRHU4SmdiMk9RSFlWVnpDbkdQSkVIZXENClN3azRUL3FwL3cvT1RTNlJuRWFwNWlyaEw5bVlIVUhXYjI0K1liYUo2YS9KTVJBelRpbWlwWEQrR2xjRnBYTlYNCmUzL0l1dW1Cc0R3UWo3SlFqUHB1NUl1NUZFdjYrUlB1eDQwa3JRTk9Vd0QyMWJYWnVGV2ZnNW1YSzBJSDBsMkUNCnVJcDJWbGFtNDZLSG11eDQwVTlFOWg1dERRUVZwbStucTFrdE9Yc3gvcmRZcXpsUHNhZkpYbUdrTEJ5L3prMU8NCk9DSmYrNGdQYUthN0ZCRXdSaUJtY2UxN0JvQkkwaEpRUGJjVlpOZC9uUzZSazE5OXNPcytTc0E4TUZEbG11OGQNCk1odmlqUVBERE5uaVVBWDhvKzE5WXlESFN0N29kM3pqQkJ4UHdwa1ZaZWZGSXhvY0l1NFp2ejBwTExsL1FGZGcNCi9MbjdSdjhuYzNMQStsZGdidjV6YTNpTTZ3YVlzOUhud0JJRGQ1ZUk2VDVvYU8rN3o3b0cxaHZZd1ArMzFDc1MNCkcwRWZIWWNQU3lyNlVBNkdFZURuWWc9PTwveGVuYzpDaXBoZXJWYWx1ZT4NCiA8L3hlbmM6Q2lwaGVyRGF0YT4NCjwveGVuYzpFbmNyeXB0ZWREYXRhPg0KPC9zYW1sOkVuY3J5cHRlZEFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/unsigned_message_aes128gcm_encrypted_signed_assertion.xml.base64 b/test/responses/unsigned_message_aes128gcm_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..d42f624c8 --- /dev/null +++ b/test/responses/unsigned_message_aes128gcm_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzYW1scDpSZXNwb25zZSB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIgogIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0YzFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl80ZmVlM2IwNDYzOTVjNGU3NTEwMTFlOTdmODkwMGI1MjczZDU2Njg1Ij4KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPgogIDxzYW1scDpTdGF0dXM+CiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+CiAgPC9zYW1scDpTdGF0dXM+CiAgPHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPgogICAgPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiCiAgICAgIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij4KICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDkveG1sZW5jMTEjYWVzMTI4LWdjbSIvPgogICAgICA8ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgICAgICAgPHhlbmM6RW5jcnlwdGVkS2V5PgogICAgICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+CiAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPgogICAgICAgICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5FYllOb2p4ZUkvWHNlWGFPS0xpRGpFZHZGVlF5YWo4NnIzZHlkYU9uU2RIbCtDZlZkaTFKSkszSDdkRG9MR3M5IGFZbHdlL21xa0E2ZUl2dTFtNWM3eFQ2c2NUek5sbFlqcWplaUlDVEtaaUdzb2hjTE0xbUt5Zm1mUUpJeXQ4ZzAgSTdSMjE5V0pHSE53bnYwaXU5NzlveW9KRDB4bjczSGk2Vmcxd0NkTWFLUT08L3hlbmM6Q2lwaGVyVmFsdWU+CiAgICAgICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4KICAgICAgICA8L3hlbmM6RW5jcnlwdGVkS2V5PgogICAgICA8L2RzOktleUluZm8+CiAgICAgIDx4ZW5jOkNpcGhlckRhdGE+CiAgICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+NFM4SVFhWXZOczlEL3FIb3o0RytMTHhkUVpnWGVURDdESzVLR2Q2dWd4OFhMcThsZTdaRXlGdEdCSHZHSmZjcktSNy9aMm03UC8zZHpCOHFka0E5bUVsT0Q2RlpHQnVST3RteE51WXRDU1B0ZXczUlh3Yk05cGRjby9ZMVJ0NjU2T1ZMM3pYMVovclZ1b0F3QXNvNDlBV0x1SUUwQytiVFRiTnhONGI5TzFtbXFaZWxTTnQ3M2pWd1BGaThmT3A1ZW00SlNVeGFVNTUrc0VTems5blorL21wK0E4aFVzUzdCZUduQzVoMnFrL3grZkkxam42ckRUUTA2djR6K2RGSDgvc2Vtb3YyTVJnNmJwV3dtVmc3b2plSi9WM1RlTTJCRUQ5Z0FLcG1xK3Uwb20zaGd0UEp3K3F1cVVzL0pucUNQN3ZJV1V3RzkrM1BuUEREQkJqM3BRaGdZbkdqbitNVTRrVzZ3Uk1mam5SSWV6WW80K1BuY01BdjhJRmQrNUdMQ0hFMTZxQXI0bG1pT0FIeFoyVFEyU3lFV0V4OVdHUkh6bVBIODM3bDlIeGk1c3U3UTJFdkZ6K2U5Qy9Hd0VDanpqaDdNNWtSTlE5TmlMekZWK08xUml4bmhJeHlGQlZlczgvbjI4WHZmajVVMzZKQzlJZ3FlNlozOWYrYVg0MjV3alFGSTgwcWdDK3pwUFZSTU9sMnhaNThVY1FHcVJreEJra0lmN0N0RDZNNUtKRnQzNXlib2M1blhETUZjQnFMNC9YOWdlcXZPU3FqbXNhd3h3SDc3ZEJGQ0lNVlN5OFFUYkFEQ0ZFMXJHcEUwRTFSTzhsSzNjaFNhelgxd3h4ZTd5ajhTTFIvVm5hTWpzdGlmMDhPR2tQayt5bDFsZDloSTBtYVk1OEQ3VDgva3IvNVcxQVN1bkE3dGFOVEwwa3R5NFNoblp0ZWpScWR0VURkNTRFYUdjazdOZENqMHQ3Tm5sY3VaQU02MkdqVVlqalIvRG5ENklpeFF2alp0eDBYck9GK0xxR2NRUWwySUZPWVhOVWFlbkxoV3FZMXJBNWVWWUlISXNCU1NTYVhCOGQ0Z2svWXhnY3lYRm41eUVqcHFSYzF1QkZJY3hVVjZCWG10TjZiVnpTditqWGZsckJ4SmxsY3hKNVpHbUtsN1FxN2oxZUs4MGtiUkFZc0NjMGNWNnF2ak5aSXk4aklTdEFPemV1OWcrOUYwRWxBSXBsajZtME00RzZDQUFiMHdzcWIvRU92c3V5UTFuWWR2SW52QVQ4L2YwVThvTWRjNmN1dVdGTnRvdi9OZjAvZk1rMk5yTGJDQ2hSalpMOTgwN2NlVGZubHE0VjByNGd0Z3JCQWNFLzlNNE0zUHNNdlQrKzFEVkwvMGMyU0syUVFCeEdzRXhCeVlqdGw5RWREOU5rcFVRVVRrK2FjaGdJWGVTbDM4UjZwaU5RWHlmSVJ2UDJpR2pnOHRVSDhSdTY1OE1FY3RtblBlWXF2LzUxZXFwNVRMVUU4ZzF2alZPd090RHJVc0FMeDNZT3ZCTXI1TkNoTVVsZWVUNHFzSmVNOG5YWkNvVlM1RHppZHpmM3pPWVRxWDNmQkVzVDFFLzNkQzBUNEFldzFjK3ZyT3NZTkQ5bndneUpkcVVNZ3NmK01PVTdYcjIvWXFteFp5ZzcwOXNDUGZURjJRQi9teVVWWTdEem1YQ2gweFVDelVJMS9yczBnNGs3b0RWeWNaQ0pEQ2lzYlU2QWVrNW1pSmFHQ29BRDgyRzZValRIbTV2RjArcDJVT2dCQkY5ME8rRW9aNXREV0FzZHk3KzlZTnZpMEFhU1g4d1h4UElaUWRmcmNucFlybERxS1luVnVWVnhicUlMVTA0UEZqNXF6RVhEYllEWk45YmswYUZpRjNMSlhEZFprTFh5RjA0YVp4NCtJZDA5Qm1CeWRiRlhxWlFsSU1oTU1xYjJ0d3RwM1pmWTZEU2kzeUZBS0R3QVVvR1ovMEJ3bVlyS2pTZFVEcWh0Z3NVVW1VK2xybFZ1bTdhY2RtYktVa2lmc1NHMGlQTDcvQmJrcmV3M2p2TTlwR1FMNzlQTExNT3UrdUo4cVdsYWlPSFFNSHNHVHpQVEhpUGxZVVR2YkxickFCT3FnbzZDblJ6R3BDaXFaT3B1ZjI0WW9hQjFlalR4YU5RcVE5RzZsQlVYMlJQcTEzd3ZuZFFnTGM4QmhjRmhCV3oxcGJ5OTFsRUpMSE9yQjRnamlIZ1d0eFFnVWNITWcxcEY5YXZZNnB0d3d4VEhkR3V3RjlSU0piRVUwUG9RUXJTSVJKem91dHgrdUNMRExaTU81cE8rcmZRQzVyVVZ2Q1ZKZUE0cHpNeHNYRkVQaVprd3V1U1B5SmIvd1lXMzNxSERlb1NNOTY0aXVyb2gxRkhUUW03cGx2VUJYQ3VqYWxWZXNLUUh1bnFvTy9kUVRUcGdUMkpUQXlhazN0ZG1zQVpKcUNqNXlwUTgrVjNHOEU3bEZIY0xVR2p3eVg4VU54eGc3cXhobmVQYXJ4NEhYOVo5MTZVUys5RnpnZzR2djRxeWlqSXdaSHBweWVmbjlyY3lrcTVVUGxyTHhvM0hQazZPaDVaT3FvWEFNRHNOUUVsTjJjdHRSS1ROSjFOcEFwK1o2MlkvVmxBWm1lVzRZTE1ER3Y4THZhN0ZQSllZMldMMSsxekxVbUFJYnRpUnFiVThIaXc3UUcwbW5MQlJlZ3A4Q3dobG9UNEJQSWlCdGFWdWNwOG5WKzhDbENBR21ZczUzak9rQlNsQVdSc2QvNmFTZEpYbFRNT3hFOHdxcXZvL3haWmlKTXNaSyt1MlZnbHpMU3NsSDEwa0FyWTRoTU5LNjlYbndUNi9UQlJUVGtrSVVoQXFpaDVEYnN5bGpLcjNRKzBBbTRsQnVQL1V5VTUvRktFcVBkd0RuOFZGQjdJMWpFUGhWczJrR3grTGM3WHZyK1J4T1FHUWVqblBZVHI5azZ4NFI2OHEzeUM5WHE2NlZIUlBSR0tJMFVhSFI0ZGZmQys4OFFkWnZBMFVkMStuaE5vSzhPazJHYXVyZUo3OXB2QytSbG5QaWFRUHpiS0treWtxREg1ZExpdWR4U0lZY3ZXY2NvcmpJa0h1ZHJORmpWM1AzQXlKY1VucGMwNHRwc09QWldZckFtTjZ3ZWdKMnYzay9tWVJiS2tVOVdua0Y5MWQzTzJMR2htUzBZMUR6Ni9nTlR1aUUvbk1pMDhFYk4vSU5DMkMxeGNXQkZrUVRUWGRVM1lvUnFxOGRqdXFSUG1SRUhMVWRoU1NKaTFiVTE0ZDVYcWNUMFhLR2NZekdtR29QMkFGT3VWTTZiWlI5VXNvcXFlQnFWZVVHN282eWI5eGxxNXV6VkhHWnFSU0NUUUNKRjgrNHJNSkw5TEYwUFZEL3p4U1Jpc2xnWk9EbHdEcjFxL2ZOMituMzI3S2w3ZDF1SncrOS9FcExIaFNzWm90ZzhsemNGenpwMUZ3SzJjNEdlZ2MvYjBZb3l1aGd1ekRLYy9sRU1QdmNuVFR5NUhGZ2VWL2JkMmxkOXJBRWRGeTdwR3lEamlERU1nV3BLdmRneHF3cWhzaTVJWjN1QXRBK0VxVTk5bmZBeTVMQXArZytaWHZuY0N0OStmb01ldEFrWmxsZGNyUm9iZytPejZuaWtzcTBLd3V5ajR4L253ZUdlTnl1dnpWSnBEZklya25xanB1VFVVVzZ0L2p5dXU1U1l2VTA0NFFoMHVzdjVpQWJyR202SklOZStYNWQrMjNHNkgrbGswUjZEYVU3ZXREUXc4ckxaMHg4dGZVV2xHengrRnhmempISUpxRStadm5YTm1HY1F0Umordm4vN054bVJHNlQ1dlIxQ0oyUEdoOHhBRk8rRWlaekRQcXBTbWp6NWIxRGhLNWIrdlVwcWVZMUxMU3lFU2NxaUR2eWxaWjB6VFFoZmdwNW1xZHI1OXo5NnJ2QjNjeUVuM0FrUzVqbGcyN0tOZE1rV2pNZnh0T1ZUVXRzZGdrdzA0cm12dzE4SWxrQUluZGwyMkltMTVNUFU0UWJEU0Zra2VoR1FPclQ5a1pUTjJuVjlIaXRBSHVRMUxGdjhEa1Q3RVhrZ00xYUVoUkcxRmdPb3FsYUdTbytlUnNYZkRQc2hVYkxRMHRZTk5BeUJjckFhUGxCVkhucGIvODQzNmJuVlpyRURGdENERjhuNjJTNkdQMnNjckh1bW5wQWd5TUsvMFFJSi9NYzdrK2tZN3dUb0hzSkJqUWFEeU9nZU51c0NZck9ZVFdENU1MTVlvcXRGb2xCM1lERkhIS1JjRk5WOUp0M3NsZjRMeVBPZ2FYV011bENpRlk4WmVOQy8xNzlpRUtFbkY4SUJMZ2lOYnRxY2ZPbzdBQU9yaVpiOXhVQWhROTZHRUlXeEprMHp3U1pzQ1BYS0Y5Mk9HODlkNUpKbWk3OHVkUnZic2hUbmExaFlQd2owVEZVRUMyNWF3YVZYaWQxK2xFdVBRZ0toYU1VTENITW5VcUhVeStiRWM5R1ZSMHNMN2ZGZGpiQk4xRlRDNFA1TmV4V1NQT2VLd3g2OWNQbjd2KzVKeWI1TzFmMURyNGtZeFpzODBITlFsT0hOb0Jhb2c4REFuT09naEVEQ2RjenRqelAyd0VoRHhjVXN2dy9QaDhUOUJrcmFDa1h1dEhPU2dDTFBIeDJrdExxWW1HTDFvUmdWNHg4b29EUTNySmZ6UXlZRHpWL2ZWQXpQMEVQeU54TmpxK05VREN2bXlCbjNQOWw0cUs2eVNHTURYRm5COWZYdDVlcENKTW1nZWhZaXpETTJMTS9OVnRvM0NCYm85akUvUTJJcm9ma2w4V2VVQjZMVmtEYTRzQWIzcUI2czdzQ3hUSURHbWUwWEw4Q3FXNG42QUdRTzRyMmV0ajJwTDVpLzlWbGV4eFZuOFVHZkcvamZ3WjltNUlIYmdacTF0SVpBL2F4ZUgxQnQ0T1hRR2QvQmM3Yjd3bGJZY3V5ZUlqVVhuZ0hSdzNjZVhwZGhJY2tvbnpVRTBzMEJhaFNPbWovN1ZzMWx6M3d5Ulp2YS9BNDZ4azQzNTdBVVJRVGcxK3hmVkd3dHJzVHhRSGZyWlhydFdYLzBMY1N3SDF1aFM0cXNHaHZuZ3hhWGtFc05NZUlUa1RjVmxpRWxxb3NLKy9SaWFmNE4vRXB5RjdDVFBUeHJTQm1lSzFRTmYwejhycFBXTzYycG5nWjFkcjJjekh3T0dFQkM3U29CTHIwbDBVYmU4TnRxTC9YZnlwUHQ3MVFoVG42bkRjMEtnUlBpZC9zei9vU21Cd3NrVTlxTjhmZ0RMSGRuMVJLWU9tMkdyc3MyTDN6QmZGeUM1alV4STBtMG5pZG53anZudy9tR1FFa2xOSzhpaTRZT2ZYdDlYL3ZNSlg0MHZvTFV3UmZhOVovcWZydGN1dlZVWTdScmc2aFRnc2hpVzNDTk54dExBWUd1YkMxTUZCSnd0REVzL2FhbjN3elRQOEtnYVdNb2YrZ2JkdTVpZUdkcDlheHhrWmx3SXQ2dlRvUm9BTGlYRkY4NVlkRHgwVWlQS2lmcXRBNFV6SGxoblZIdG1Dek43QTBKMENSS1FSUG9yVWpGNGMvVmFOYWh4UjJmUGxoQ3NsVUJqZFhQQ1FCQjA0VkhKdFpHeFoxSXIwaG90cnF3YVh1MGtKS0dKQUVWZnRhV1F3NkRhaUg0eXlOMzVybVFCWENlZ215UEZRekdpYXFSR3J2TkIxekVWby9VTzlmQ1FPbDdVWVlMZldBYXBIalJNUnlWZG1VWGhFR09OdzhIMjYyZVFxTVBIVEY3ckxXVVNmeGtoQWR0ZmQ3N001VzU5bjRnTHRLWXV5Wm42cTBoOXA5Ny94QXA1c3hzZlJvejc2RWlmYVJQUGxtTW5yK1laSlhGRmc0Tkt1TzE2YzFBalFreHlObWJNVEJ3T2taVWtSUndZUWMyUFpvVFBGejJ2Y1hMVUVIOXA0ajIreFh0c3Q1cXZPZ1cxRlZISHMyZ1A2WHg1bTBFQjJyZys3R0tBUlM4VW5aVDg3ZXNVcjdreXVlV3JBODJmck5nWUpkNWFhYnVjUFVTOU1LUlp5SnA3bEJZRUthNU5UMDNqejBuNi9MWkZzM3JXcWlNUWhhZmNSNW5wQzBjREtvblVVY0tGWnVsOU9ONmc5TGN4QWJHRkJCS1J2L01VMnRrT3QvRHRlNTMranF1QklYWG02d0hTYzVNLzBodVl3U0F0S2lIUDE4OGd3SDllSnNEUEJ2di9KSzE2MjU2UWNFWGk0V0RDMFZqbW1rZjB1eDN2U1RlUkFtd0g4RzhXSnVuVWJpaGRKckpFWTBUSG9temVOWUFSb2tTaUYva3MwajlySS9Za0oxbWQyNXZadGR3QXh0L0tqcVptUjYxb2NDYVdYNlRPSHpFUzMyNVdHRzJvdE5aZitXTTNaUThsNEhJanh6SVRXOCtnWEg5T2FrRXprYXBqcGtyakp3MGZNdVArMkxVTW1XVmlxL0dRTDVRbVFPaWkvTDNvQW5kZEdBRUZBNGRySUZldktLdE1maFZrVVQvRXRIQnFGV2dkbVV2V0tyOXBTa1ZPaVJFaXdid2xBaGFlMFFNaDdKKzJPTUdCQ0RUUzNMcGRLUEYxR3ZwZ1pXSDN3OHpsSU9Ock1mbSs4MjQvTnNFYVZxUU80VjFWSDFhNE5FaVI1SEtvNENZTVBtM0xuNGpuMU0reFpXVGE5cHU3VW1qRi9mbVFLSFh4UDl0UmZrR1h6S0VZajloSWtKb2xDK0tsOWw2aWtWcHNyQytRbmh4QUFXZlY1YWRvNzBWb2xSUEtsQjlmOGVFcTNPMjB1REQ2ZTZyNloySVhBb0grbXhwWVArZkhpUzVIcHpyTUE1YXlkL2YzV3krNloxWTE4elVsSjI2QVBQcEdPTzZlWUsyeWVzaVdZS20yRERvcUd2S2haaFhBM0o1NjdCTFg3eXprb25KVWRqKzJkd2VkMGY0ZDVvaE1RVU8ra3BBT04yTXRtMHV3NnM0U044NzZzWGtyNzdwLzQyVWc3SGh2UzdjTUVIVnpOTGtWdDg4V2RyVmpVTkxuRG5wREVSTTdWVEZJTUNkUVNjaVJUSm5ZK1RUbHJROVdVb2NNVG1Pck5NLzdqMWlzUFB1RFlaSmxXRjdFTld3Y3pwMWVFVXZTTXNFVytOMDQ5STJmUWI2bk1wdFRyRHh2T2o1cmFNSXdaMXYzYlIxWXd6MHJXcjZwTkZMd0E9PC94ZW5jOkNpcGhlclZhbHVlPgogICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4KICAgIDwveGVuYzpFbmNyeXB0ZWREYXRhPgogIDwvc2FtbDpFbmNyeXB0ZWRBc3NlcnRpb24+Cjwvc2FtbHA6UmVzcG9uc2U+ diff --git a/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 b/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..eafb5bd9c --- /dev/null +++ b/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9Il84ZThkYzVmNjlhOThjYzRjMWZmMzQyN2U1Y2UzNDYwNmZkNjcyZjkxZTYiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTA3LTE3VDAxOjAxOjQ4WiIgRGVzdGluYXRpb249Imh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzRmZWUzYjA0NjM5NWM0ZTc1MTAxMWU5N2Y4OTAwYjUyNzNkNTY2ODUiPg0KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4NCiAgPC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkVuY3J5cHRlZEFzc2VydGlvbj4NCiAgPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij4NCiA8eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTkyLWNiYyIvPg0KIDxkczpLZXlJbmZvIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgPHhlbmM6RW5jcnlwdGVkS2V5IHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI+DQogICA8eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjcnNhLW9hZXAtbWdmMXAiLz4NCiAgIDxkczpLZXlJbmZvIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgICA8ZHM6S2V5TmFtZS8+DQogICA8L2RzOktleUluZm8+DQogICA8eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI+DQogICAgPHhlbmM6Q2lwaGVyVmFsdWU+M2JJdnZsUDhvTDZNL3N1ZjdmTThHaFVhbjFhU2hMRHE4VDBpZE9hQXlLRlkvKy9iSlhiT2JIeEp6d0FVbG9jUw0KVTRrL1crc0tsL3J5cCtEaW11L0ZPQi82dm5sNnErNlJaMjZjVXVUNWFQQ0xWS3A4Q3RpL2R6SlNyT3lYQkR0dw0KQlQweXZJWVBsZ1hKSS95c1JyY0drRDVwVXlPRGsyVjl2bTY5eTN2azcxND08L3hlbmM6Q2lwaGVyVmFsdWU+DQogICA8L3hlbmM6Q2lwaGVyRGF0YT4NCiAgPC94ZW5jOkVuY3J5cHRlZEtleT4NCiA8L2RzOktleUluZm8+DQogPHhlbmM6Q2lwaGVyRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiPg0KICA8eGVuYzpDaXBoZXJWYWx1ZT5uL3FZZ1c4cjVzdnA2TGszditDWjg0NFVqMUF2VGxjMHM0VnlFOFQxSnRDYWVxV0xJUWxISHAxNGxjcUdta0VvDQpTRmFQSVlZNFdqNW5YZHJzL3RFUEtqbStiN3RibWlzelBBQ1V3U2dtWDJSSm1taVJ1VWlDTXdsU1BhZEwzQzZhDQpqZXNYRVVyTzZXb1NNaDFaeDA3Z2hDelZuTG8xQlFrMGJwVXlsYTFoQ1BOSjNCNlZWb0NVVlpiTHBlaGRaaGwyDQpCYTlkbHZ3RDQ1U3RxZXFZQ25mZlBKZGp0ZVo2NVhhNEhjbnVDb0JLbVdDZjVYVUhVNVd5alRQQzBacnFUU2p4DQpBYkgzNTVFVnhzSGdNMGdiNHd0c2czRzMvaVQ0YnBZZUJhQUtzZzgwMExuMG9rQW5jY20rMXhYRUpqYW94bFoxDQpVTURVTVY4Zm1JaEh0alJzRjVnQmxWVDFSSWJhYUxyeFh0aVVFN0tvbTFwd25DNURoSk0rRHFOV0ozMUJDemYyDQpBeUtyQkgrbFZqZ2JLMlhFQ0dLVnE0K010aWhvMHZwK0ZFd2xUWUdhcGFsV3c3U3JnSWE5MUltc2RIWXA2d1luDQpLeTVpYnVqQkRpQzl1OUtHMENEUk5Mb0dFM1Rrc0E3OHNQZ3l5NnJNTHljZHFlMjdudlNsWko5cUZaQU1RdWZVDQpFYnM1SmxvRVlDcDdUY0lqMENCT3lKMW12NTNmZzZxbmd2MUQ5OVBWdkZrMmNSa0UvZDZkQWdXaVh2WEU1VTliDQpncVhwR0JWUndITkRCOGVnYUhlUGVEWCttRTF4V0lsWWtZQ3FIVkw0cm9BZHgvNjdYdDhqSWJJcldPZzMxVkpUDQppTk1pK2ZZaVN4RGxXZmpBdkdoNTVnWjNyaE5tQnRYdjZ6MUJDcnowYVZzZDQ3UG9UbXVzVkwvbDNHbzJZTXpqDQp6QlRsWFRrK0VYSUJLL1NSUnl6UFVXaEV6ODlFMGlVMHNZYW5CSld3ZGdKZDlqQXBzK2cwem9oS1IybHJUVTh2DQpNMEl6Qi9XcTRSZTg1TDNjMDFnTXlvSE9YWXpXTFlmcEMybzE3TkRERENLUW4zNGgyWkxZalFESWhpQWRhSVJQDQpJelp3NUJTbGo1NUl0WVU1Mjg1c1l6Wk15NjdjWnRyNlcybUdDNkFObXdaRFo1N2dYb1BVOVp0NDI2dVFZRUhjDQpURjg4Y0FIamcyNG9tZkNybzlsMjdIK09PYTNmYnRrU2hGRTRlcVlSSkp0anRteUdxNWhhMlpFeldMSmNUNlcrDQpSQUdXY1l5Yld1Mm1YU3FWS2VJZ29XM00zV2VGUzY3Slp5WVdUeDl5TTE2REJ1bGZ2c2xjazBEaFJxek5vYW11DQpFYW5adW5iT0V5Wk9USC9QckVPbUI1d0tzRng4aUgzUEZzaXZ2UE90R3hhcy9wbmxib05CeTlwRllVbmpaM0tEDQpPdGV6bmx5WGdOWTVKeHUzeFRrNTdaWTh6YnU2L0o4eG1jTjAxNnhCeEphWlkzSWlYSHJnUHIvQVNnZnErcjlEDQpidjZ6OUF3eEV6ZlFpK3RDN0RrM3FBMFZuT2kwT1Uzb0pEZFk2YXBoYUN3UVNkQXRPamNsaDh0bmo5SEJ0aGJODQpxZWxhSi9Td1Bub015UGk4RzZzb09oNkZjN0dHNk5nYTB2bWxBK0p5bFB3bmphS0hwUUtDYXdPSFZUSGdPaHhKDQpYbVhacDdUN1c1M2I3ZzlqeVc0QWF5ZXllUXdzM3lQTSsveDZ0b3VYYmc0TTQzL2prWllnbnFxcFlqdEppN1JzDQprdWhyZ0VGY2x0RFFTWVhicG02T3ZwOEdPdDlvQ1hyTWQ3dzJzOVR3MDRndWdVeEcrUmZ6bGhaMmswTDJPSWRMDQptSmFUSU9ZRXJmZGZsd0pDdTFMQ2hyQ2xKZHpIVUw0L2h1T0djeG5iZUpSUlBvMVFPMlFFVlpHc2RGRkQzdkJLDQovSDI5b1BSOEJxNHlMY2FFZkRva2lnSnZSbW5qck1KVkxMRmtaMkx0Ym9BelVvRFM4Q3hUcWFDNktacjhqMTJNDQptYitKbHh2VlJJbUFvemlEYWpyM2dOOThjTEZPTDBsbzh3QnJZSUM1Y1JqYmFJR3Ard1krQVBBNVBaSUhJZzJmDQorczlydkZHUjVsazFZQTlvYVkwTHM2RHEwbEh4aTMxRmR2dkcyMEcrcktRdnl6aGtIYVIraDI3Sy9yaDA3S21HDQpPSTgzSlgwamw5WnJieno5eTlONVkyaTEva2V4U3hMS2lXZmpjM3JyOHdPODU2Z2tmd0dmTHBFb3FiUi9sSmlPDQpVdGRUZmtqYW52VjcxclJEQlU5VVFibXpXV2Q4R1MvY0JnR1NKVk14R1Y5OThob1prRTdOTDFaWUlUSUsyb1lZDQp5czZCSG1YYkZ1VGNrbVozZy9ETS9NUmg2MWFwZS9VUzRMSXZRNm9PY08rRjRENmp6emVGeXFsdmlXcEVNbW5wDQpuSUFLU0ppOGVWSStqN3hJYjhzZ0VrU1VRVEt3M3Z1NEx6MW1aSy8wSWFDZCsxY3Z6N0hrUWRzSHZVNmZFSjFQDQpXazBKTHd1akxvT3djRlRXSzVTNVpPSjN6Q3BZU1YyTWxSMldvd3M3UzYxb3AxTVpRRzZUYTRXVldXRm8rR2xIDQpnek5xTFpNb1VZbGk5NzF1ckV2cHVmalFUYm45N1NxSXlXc1F2OHp5WVlPdVUvYk15ci9BVVJyNHF1aFE4WnpCDQo2S0Vacm1ZZzI0dFdTMFhpN2RTdWl4Z1ROenZwd2cxaU1oMnBoSkUzdFMyU0RRNzNqdGZmaVBmck9tcDhUeU9KDQpRMWpWMitWWVVqMzdnSllBbmd1bE8yZVYvUzAzUkdOUDU3YkJjeUFhdHpxdU9vemduMjcyL1FZRG1tWE5JWmFvDQpoVVBxK1U3blNUWXpIMWFzVVdGZXFDYW0wM1gzVWVDWWNYb2JhMlc2b0hVMEtwbld5MVo5TVI0emdGSllqbk13DQowVVZZaFMvWHNqQUQxNEZMNFM0VzBoNk5rdUhhRnJHWDVRKzlGRDVseGNOV1l6emhIU3VHbWRhYk41Z3dSazFUDQo0NWY4SUxwYktxSHowZU5hc1ZiS3hpVytqQUpqRzAxNHhkeC83Q01VUzZvSzlqWVJWdlZCWjR0S2FpakZFQmhpDQpiWW5nL0JPdEo1S0xIOWh0ZEkvZUJScjYzQWVreWNXSVV4T0sxdWZ0QTdGTUdnaW83TnRrSFF4eWxlMTVKUzh6DQpCT0ZrQ0Zqbks1TkdKQ1FhakNaM0VRV2VVQ0dIL1JiajJTZnYvS0dXQUJIM2x4c2FSZTIrdDJ6YmlETFlkNEhWDQprNEF6dnZwWHI1N1F1by9aMkJDR0VwKzlDTTIvYnZLajgyK2I5MDlOMDN6cldCVGlCZnVlaitXNG10ZjhWdDJKDQp4WXpSb05Yb0NCcFFDb3RLNzgybm5TR1ZHNU9SQXB1NmN3bGNHUzdhNDUwTHRoOVFVNlA3ME5BcjJIcDJ0VlZmDQpBa2hscyswQmtVQmxCOCtIMWhqbmpwNVNlTHRZMGt0Qm0xakx4c3laWnlaWGdJMXd0d0JaRTdVSFFjMUhvY2RoDQpkNmJFekVKNURvUG1Zc3U0U3cwS3NKMHk4emVLMEVjWElUS0J5WVlLMll5L0I2YlZxRHh0QXFRc0g0VXE5TWRWDQptMzd4NUxwTHFzM3hOMEpmWVZONDVMS0FYeXBsMGFiTFdBR3JTaHFBY29LMDRMN093RFFhTHdDNEF0MkFsSVAwDQptZEpjc2dkS3lmeTBjR0FrS3lqc29Hc0c3eDJ0TXZQQVhjV1NSNHFKakIvaE1aQ3Q4aGlpbENrUWNZeDNsSDJoDQpHN1hPVUU3THRuNllvOEllaGxwK0VxemU5cVlLQTJVZHBkbysyRWlBRlphUjZSd1k3dDZQWERiNkd6MHJsQXBrDQpaSlZ1ckJ6OFpmUmJLUDY2WWVyblk1bXNzYnY1eC9FZDR0cTJEdHViZmNIejhRdzBEZ0lHbFFyZ0JVUGRIMVhTDQprV2FhUUF4cDVaTHAwVVFRWmVMd3VJOHdDKzhmaThKS0ppLzRZdTBzYnk4ZGhJMTl1QXR2aktxUm9FRjhmdGwrDQoyZ1dBaWhiWUUySUg0bHF4cTlBQVQxaWhvcEwvUjhkK2tKQ1ZRdjh5ckw5ZGNyWHFtbDFFZFFJMXR0dTJIWE5IDQpTakNuUlNZVTl1dnNTRzVWaVdkVkRxQU5yWjhkS29GbE9wZnFXRjd6WGdFVFpnZFlOdU1kcEtvRlRUMEs0dFV1DQplZGpvdXNPcFlacWRsaDcyelZEQndIM2E4OEJTZ1krVkNGN2s1SEhGK3M2SnV0YjNNOEdzYTdrdWZJaUpCMWRODQo1aWhJMU5TOEFmUjJ6Yko3aXEyRWJDQk5UNHBMR2pqL1owTXNJdGt3SmlxWStuQnZ0N2VyZmE1bitpZWZIamt0DQptcDVzKzEvKzFKNXJTZkVFL1R2TnpFRjFBUlNrZy9PTlNTcVNsVGVuQ01lWDl4TElzRGo1Ymk3M0QyT3hCN01VDQpoTEpxS0YwMEl6aU5pVnpTVEU4bWZ6em41OWVDOUluOWRDM1FxQmg4MjQrT3ZxSC9WWHpSbSszZ1ZjUytCMG9HDQplN21GNGZuWXRsNlhGeEphazdmSWNoRE1NTGIwSEVsbHFKQ3FOMDRyRHhITjhwZnlKbGlHamlxeE03TGY2OG1TDQpkU200ZFBmQzFhbUY5TWxPaVVnbk1mQitPSnc0OEhRM3U2YUpTd20xTFFwRHB4MHpKRy9OMzBOaCs2RklTQ1pDDQptNEdGdWl1c2VjYjNZSVNsNEw5angxOU04RWdHZk9EREJma3dJSW0vdXF5dzN4OGhNRjM3eXlMK3ZCdlFpMGE1DQpZRDU5RThIdnVEUzgwL3d3dnZnNGhGbUtvckt0V3Z0WjA3MHJJOThQcG1OTG1EZ2RabUxqZTFra2hSWDhpY2pkDQpvdHpZTFJsSDRKTTNOR3dQenlvS3dMREdiUVBaRFNSYXFTeHpQR0YxRVk1THVVUE5rcTVHSnpIS3hKM1hzNmIvDQpLNlZFY2kxbURuWE9od3NiTTgrV3oxWGZnZU44YzlWZyswNXFmMk9kOXNmYllNYWJ6dU5QZkhxU3gxTU5rU3lDDQpxdFVsR2poRVBWemE5TDdUTkdqdFFmdTFjdGZmQWpUODlocjYxYk9jeGtvcHpEWWdqeXNwc3N1dGxFYXpoRE5sDQp4b1FXNmRZcW5qak54WnBMRjR1c3htZkRJaWg4VHd2T0NEZm5yOWFTVlEzUHMwbWt1VzA3czRJRHJ6d3VGQ2EwDQo2RXBMNmFJaGdOYUNrMmdMYjVyYUJ0blJVcm9QNnVxZXdOaUZJdU14Zmp4TGtLWGJtdjM0K0Jxazl5N3VMOWNtDQpnSUNwSi9DWXVsaWZJSXNGYk1CSnU0T2lLTUNhYytZa2ZuQUJHdkFjYTljUllOVXMzamxCUE5FYmJiVUkyeHRKDQp3YmN6aDljUjdPY1ZzV0JHZkdLSjN1cnZLanVKZzZQR0FXRkM3cER0TmRNb1RNTVF1SytHc2Z5blBhU1N5RmF6DQpleTJPeG56WmxGd01yOS9lWHlsNWJNTGtva0ZBN0Y1L2Z1VzNlVjJPOWhobWg0dUhwTFU0Y29zR1dYNi9KaVFuDQp2S281Qmx3cTM0L3hlOGJwU082N013Y1RzbkpnR3FtbUxoaXBPZXlCdk9Ca0lYUTU1QkZlMTVDNmxDd1VEdUQwDQpsNkh1VnpIcFFnZEJCRW16Um40VWplYWF5cWFJSG5pSVlsVUVzWHRiUVNWUWw2N1N6aWFoYmFtWkVuM3BmenUvDQp4a2VrQUorZG9UNG96NEVsNXlNVDZvQ1AveW1zUkM1WlV0MDFuNmg5VVFMK3A5WXh0czdrZURSUXZtZlltcGVkDQp6VEwvSHBkNEtqZkk5U0lyVjFXVXYyWkljRk5sQU9IckREc213M3E1V1R0K3dFc2kveDQ0dkk2RDFrbTZJOHFjDQpnSndIN0QzdXRMMGEwU3ltN3JQUVpJV1piazdpMU1JeEJsRnEzbEI0OUF2M2o5MzdRT1o3MWRxQjdsK05oV3doDQpvbWR4enpzTU1aVWtudnI4TEt1bEF5NzUzK1FCWWZTSVU3eVFIU2xxeVcrcTI3NXJUM3lidFVCMG9xdkdqZDAzDQpGNzd1QzVqT2tldjhGQktxRFJJK21hT1kwQkVaMTd4NTV1Sk1uN1FpbHh2UnB5cSs3MCtITnQ5TEhOeEVQVkFuDQpDcHlhNENFUjJoellDaG9JM0w5UlIwZVFTSUMzSVlNV3c5SzJHK21xdnJIdnlURnBXREVYQk9LSWJtWXhsYzdhDQpUUnF5dzVGY2dWR0hoK2NkOXlPVWFUR1Vma2ordU1JVWxicjBnVWdPdG52THRJOGQxQmpUcjFpUWN1ZlhIUkYxDQovM3VqVzRpTFJpSHkrdlpiZE1wbVV6VzhRMm9QU3Zlb3lzbFZXNDdYVGFZYkMwQkY4bUNzN1ZPMU5EN2hPZjBIDQo4M3BJd0xsM3g1ZWhyM3FNZFZjU0lmTGNieVFhaXgwOVNvMkI1MDZiZUxHQzNSYUxIVG5Pc0liZVV1ZE9WNjA4DQpwNDY2SjdBUGZxNmhUV08wZGMxVlBucGtFeERteEcxNzlYT2VacVJUcm0wSDVKSklJL3RmY0RSdEQwMkJLMGljDQpnNGM2Nk42bzgvQXA1bFFVQXNhMXlYT2FSNXhqbldsbEszUXZuZWRWK2lWWTRKK09vQldCUlJhSHFZWGpWM2tFDQoxdlpGVC8waVhXcFpBN3lNaVRuYTZHVVA2Nmtmb3FVVlI4bzhJem9acFBpUm9JY1pDR0xZMG9rYlhENnhjYTFqDQpTZm1GWjF0NWZxZC91T2NXVUJFSFBHK0FQaHNWbXdRWS9MT1RlK2J4TS9uZFJLbkd6VnRjcHpPVTd1cmdpSE1JDQppNHdKWmNtRDE0c00yeFBNTk9IZlBGU3ZaeHltYjNvVWEwL3NhdGNKdkVQN3lUcExadGNhSkhKTURnU2dTbWgzDQowc1NVZkthci9OZXdHTVVFNG13TmFCVmtLN1RiKzRqaWFWT0Nua216T3NEUjVuTHVaUXhlaDNxWDJBcXRBR2R5DQpEMy9vUEE2VEp3THlUS0RINkd5bXZUNXViTzlkWDJnKzRoNUprWnZROEFpT3Z6UjZta0dIeUJ1YmdJNThGOWF0DQo5RmIwaExNYzh6TVlzY0NCbmJNSE9sOVorc3JLRXpGNXRST1Y0L21SaThRc3l3dGlLMTNnUWl1bktxYVVHTWxODQpUZWoybDZ6VFNCZ3NJUWdRbWJRb3JRPT08L3hlbmM6Q2lwaGVyVmFsdWU+DQogPC94ZW5jOkNpcGhlckRhdGE+DQo8L3hlbmM6RW5jcnlwdGVkRGF0YT4NCjwvc2FtbDpFbmNyeXB0ZWRBc3NlcnRpb24+DQo8L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64 b/test/responses/unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..5fb9a7033 --- /dev/null +++ b/test/responses/unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzYW1scDpSZXNwb25zZSB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIgogIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0YzFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl80ZmVlM2IwNDYzOTVjNGU3NTEwMTFlOTdmODkwMGI1MjczZDU2Njg1Ij4KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPgogIDxzYW1scDpTdGF0dXM+CiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+CiAgPC9zYW1scDpTdGF0dXM+CiAgPHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPgogICAgPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiCiAgICAgIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij4KICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDkveG1sZW5jMTEjYWVzMTkyLWdjbSIvPgogICAgICA8ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgICAgICAgPHhlbmM6RW5jcnlwdGVkS2V5PgogICAgICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+CiAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI+CiAgICAgICAgICAgIDx4ZW5jOkNpcGhlclZhbHVlPjNiSXZ2bFA4b0w2TS9zdWY3Zk04R2hVYW4xYVNoTERxOFQwaWRPYUF5S0ZZLysvYkpYYk9iSHhKendBVWxvY1MgVTRrL1crc0tsL3J5cCtEaW11L0ZPQi82dm5sNnErNlJaMjZjVXVUNWFQQ0xWS3A4Q3RpL2R6SlNyT3lYQkR0dyBCVDB5dklZUGxnWEpJL3lzUnJjR2tENXBVeU9EazJWOXZtNjl5M3ZrNzE0PTwveGVuYzpDaXBoZXJWYWx1ZT4KICAgICAgICAgIDwveGVuYzpDaXBoZXJEYXRhPgogICAgICAgIDwveGVuYzpFbmNyeXB0ZWRLZXk+CiAgICAgIDwvZHM6S2V5SW5mbz4KICAgICAgPHhlbmM6Q2lwaGVyRGF0YT4KICAgICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5lcjVEazdjWkx4bVJKR3JLTWlpZm1SdjNZTXRlUGZ4MWQrcEM4Wmw1NFBMeUlkSmMxY3JZcU9Mc0hNa1lSVTVnMkZVM0VQcTBGZ0YwNjlYNUFHckQ2WUxhZW5rS1h5RlNrTkpoQStVcjBGMTlwUTBZNU1SSFJoblVLRUZvK0ErT0lwV21qc3I0cEJrOGxycno3TmlUL1UzNGoxM3BiSWJ0eHN6OW5ha054YXNLYXN4bEozb0VxZ2R0REF6RmhtRUVHek9WeGVBM3dCVUVQcWFQRGpZT1dLVEZ3YlJSYlBoZHB1eE1EU1lob2lUWGluRDR3QUd0QXdjeGo5QXhLU3MxYm8zRGFRaWJKeVQ3M3UwM1E2amQ1ZWxRQnRLeklwcUxqWDc0RXdDOXN2eU1uenJ0cEFYK0tBM1lLdnM4K1hmZG43R2s1emw4em43UXY2aFFBdUs3eW5TdlQxRSsxcFBEU2pxdWMwUEhCNm0vS1ptbERjZldhQllXYVE1UWtMMkVTS3JzKzFqb2RkYnpoYXBRU2dvaUJoeGZiT1BtM3lrd2dMSUkyZ0ExUmNBSSs2amtoSXNZM0xmNjZqM0pUeTRSTWM3VVdwRGI5NVZVaFZYN2pBa05nYWZVNzZGUW15bGpGY2RaT2VEL0pSc0tGWWljREdlMERsMUczRyt4OE5tTnhMVndXS1V1SUJSM0RXeElwZk1kdHRkaUlpbGRNa3I2K1U1Q0cybjVURW9VNGNuVWJ0dkI2VTRBdTNVQVNuU3YyRCt5V0JIemNycGp5SXNxSkdvOVhCaUhrUlN5bEphS3B3M1hIc0RJelJWMHZSVUs3K0c4djNaQVdNYkpqUDFhcStYckNYT29EYk5RbzF1bGJxQXQxRUFhT29OTVRLRVBsNmFKMllvSGpSUUcramJoeHhlRXFpK2Raa29RcWErQ3JTbFpSVXI1V1lkWE5aU1g0ak8yRHdTY0lNMnhWSnA0SDU5eWVIaDluVVg5S2hjWVlxUmtIQVNzY3lZT0M4Y1RWbXdSWGpmOHdHb1JsWVNtY2dpYWhHbkRZN2l5eFlVSUR5UUZrYUhWdlgwWktpKzd6YlcrMjNSYUZObUJvVGdCYU5RSmprN0RsVVpTM0lJQ2tsQ0dIWTVWSlFmN3lGRmdITXBJVTBFUVRtOXIwN2NGekRaR25idjU5dmR0Uyt5aElvcDFUSFRsOU9SOVRINDhxQ2RpV29NSWtjV0lENFVEZEZoNXF3OEFneXNHdnVuTElMK0RHbHgwb1RsUDUvOU9zUlBlRXovTzhmTDN6OTJTUmQ3UHRVRFBMbnZ0TjVMc3hOSGJlOEphazY0dG1TZXNhYnFvS05qZEpIcno1bVlqODVrQlR3ZlRzdmxMOVpmS3FYM3oyQUlkakdoUjB1bHFyQ2IzYU5EZkRYRzE2eFNiSjFRRUwranMwZFlRMEtGc0VWYkNKRjVyNDNkcldRME04RjRkTnYrRW9VQXVSQlVGbXFjYjR0eXlhajRZTDVGNEdsV2dzaERrNkpWa3lnYmZPZHliVWRKSUlFTDUzWGRIdEc2M2JDZHV6SHR2YVFLTENaN0Z1Zjdud1pPRnRzZ1dXQUcrTVpsQmdoNzYrWHZYbFI2clZ5RmN3LzI1R0hsaFZFa1JvTVUxUzQ2bmpQRlBXS1cwamdjYWlGTkZ3QXBnc2VSWEFwanRGTjg3R0pNSS8zMjNGQ1dkaVkwR1FXS292Uk5jSU9LNjJYQkQxTmlhVDJKM0lQdFN6UkZRL09zYUlOU1J2OXVURitZRGFKUlA5L0hOUkdKMWVtbXIvaTg3WlFYU0k0amtsY0NTZmx3MVc3cG14RGhuWTlXa3NpdzErUEFJajZSTVgyejZVS2w2RVFTcThmalN6REV2dUoxYWZCOHhEZ2VkaDlGSVcvZTh4VXBXN2dCR2hReE5taW1qNU9mQmROSzYyTzhDT3RJMThDcU9jU0k4UENhaGZOSEdMS0hlYm8yMlYrcXMrQUk4SHV5S202RnN1S2V5aVZPOVo1UlVMR2YzUW5sY0ZvU2ZuWFJ2TXc4ZzVPL3ZRTGdvcUtzaXlJTkE0V3JKc1V1OUFaMjJOcUFhTWxac0h3Sk5IZk1Fa2RYQ2JIWVFKWFdhRVNJMm9wbDZSNi91RE5yUklWREk3VHVCNjE2bHh2VlM0LzBkZ2IwSThPaU9PUHhuQ2Q2blhmT3lTRVFBeVBka3pMaTVseWVFOExYWkhjV3BPdkRrY3FDWXRscmp0UmtkQVoxTEhiRDNsOEQxU3gwTlB2YStLeERURk00N2N0WXhOZVVRQm9EZGlOcVdEYStHOUZaZ1FpNmtVbUhqZDl5d0V6OWFELzllRFVUVkhBQmNoZWJ1azdaaUEzcG1xb1laa09hdzVGSW5JUlJYYVlMZkVENWFGN05sMWJJY3djMW43amJQOEplU2dFNEI0ZzZ2dklZdzE4K0NiR3hVcUV1bDVBTWhKdEMrR1RBWjk2aEJlQjNiQjI1Q1lwMXBvZ01HMldXMjRiNkFjVHdUQWhwOXhtYzYvOTdvbHZiTFJtMGdtTVkrNG84NnI3cWZLY0F4dUZYQUYyaHYzWExaN2cwZUdhWkJRbUdqNTE1d3pjZ1JMTGFmQ1JNRVo1STdVNUdTMFRjc2I3aG9zMzNzMnZxMnd5QnZWUmEyNHhwbzFsTW1DRlJqbS8ya3VqUlZtZ1MxZ0F1N2JQMEtValdRK1ZPZHU2OE8xRXo5TDlOSFB4NjQ1RnFpU0dLTnI5QnNwd08xMzFiNUp6OTNaQkRJNnVjdnpvNGU5anhQWFNZbmRsa1RWR0diUmRoOHE1bm1lQWxEZTNMYjVqM3RrZkVSQ1BPcklnT3M2SW11bXllRG5wNU16bStjVTVXNDRSM2tKSlB1cWRYdFZPZWVkTVFza0d5ZkxnckxvcTU3Ukh4OWNsVnlSQnpXaExkOEpYYW83WHhMbWNwVmFSSEJSYXNEaVpSc0NLSFcyV2NkcXJqWlFZOGFWaS9oL1NBVy9vUTUzRWtDSTJnZFhXSkhlN1JWVHZwWnZCcVJMRlNvbFhBQzZNbXFISG9xUmFBMEtRRGJUMm8zYzZjUVlGNk43VkV0NnZZNnhlMXNtR2JpU1pXcXFpSk1lbTM1UWtxUzNwN2hQTWVNRUJCb1VCcEEzeGZScFg1MVM0N0NmOTl5N2l5UWtjcWV4NjgrTUZabXlCVzRsOFZrWiswRXlRRXlydEtKSDAzcFZyNUQxa29QMExyMWZ2cUIyNVo4TzFBQ1duWDladkFYbUhocG1YTUlPNWU0ci9QYUNlakVyZGRqYTlKb1BsMEtrVDZhOFk0R1hMZXJKUURwTHdDTzQzSCtIa0hGSERyVFUyRHMreCtteUpkSkFOSkJKZGtraytMTERvVlVpVk1UUi9RS0tCUnVRR2Q3QU5IY1Y2OGJwUnp6eGZOa2czWHgvN0o0NEY0ZHlLQ0RnazFYdUR0U080Q2dsNDZNR2NCcUFzeURpRTlyZG5ZRmsxTFRac0dTWStQbUloT3huVzBUK1dxRVRBVE9IVk5FRTFBb1Q4UkJVSnRRcXdWTXR3cUx4MVB4bWZnWHVKU1RIUHhFZCttRkpTWFMvdFlZUmJUTGFDdmJxL3lwTkxSSlZVcEY2K2ZUck5GdUM5M2RZKzkvOEVLaklyL1NMR0twai96bXFIVjdXYmgwUlVXMG5tbzdZM2RIdXdlaDJrdlNiQjA4MXkxOEFmaXM3dUhnTFdsdW1UdTV1M3crNEZidmMzYUpDQVFMdjFCV3VuZy9iQVZ3U1g5dGtuWmpGSUgrNnRMY1QrSGVjVW9DanVBcXlRaWdpdFZSQzFrZEl3SFVPZThxVzd2UXBNbVdOV2k0aUQ4R2U4RmpMbXdNNWpvZWtra0xWNnphMjlKc3U2WHc1aTNjNWh0ZjR6eGp0VWFOekNXTDY3cFBjVEFETlhxL2tCMTRCRmFSYlVWRVNVa3BCcHJZTXFOZnBkeTZMcXl4Nm5TMHZJeGhyWlREdkpCRENQekE0NHJxeGFNTkZUSklOYXRoMUhkMm56ZDZwdjdYQ1FtRzAxbW9FMHIxNWFmRVkvcE9CcG9KSHlKZzNOZWpBMVlPSVRJNy8yWU11VG5MRUw4MXE3MHpicDM3L25Vd3ZwcytBUUZRdmdEclVINkxWQnk2alhWeFhnOFJIV2pDeDVTa2JHNjFDc2lqREROZXR6djBUNUNuallDc0pPZnAzR0xheFRkQzBEZ2NKYTlqdHpzUnJhSml6UklPbTZDdXJ0ZU45b3dKejBMVjZWMVJQTGdiQjlIL2liVVVLeXZjc1p1aTA1alBLMWlFVlZKSGJBNURLVFZkY1c5Q3Rzdjh4WTIrRXN1RmFnY3QzVFhDRmVtR0QwU1VsZ29uOFJZYmZPMWdRK3JjblBjMHZ3eUcyQk1GL0VwaEVlZG9iaHNVWmwyeis4QW9kc1dxRnJiOTh0ZEFTMnNXQ2liM2NLMmFZcGo1VWlRbnR3Nzd6THJaNVhzcHdjOTIzajJJdzVVejNyVm94UldhM0dUN2ZPdG5uKzlnOFpwUXRJdUhrUG1SS3BWNWVZczI3cUZLT09xZ29iN3VVNHlQRVh0c0UyK2dFRkdKeEpuZVUwbUpsOUtaZW05WUR5bTJSSkIxREMzc2J4M2szTWxXN1F2Wmx3bGJQek1GQ1lUYm84ejBWQTEwVUtMS3RsT0x4NlhPRCtRclFVU2hJMEVUMkhIb0MyME92aFYxaUlIclREL2w2ZkErT1YrcGRPVENCNVNQMWpldTlHbC91T3FNUXlOVTFJOGJ2ZjhJT3k5bTNSYW9DWnpYT0NpZ3kvKzM2aFlpMFFiQWtzWWtuWk94bThMQ2pZaU5HQVEraXpmMzBaR2p6ZWlEd2kvS3hwTzQybU9uZml5ZXMwS08zeDRDWWF0VllvYklYUkkrcDN1Z3FCNGxya1NKUnN1blNvRjc0eVhSYmF2Y1dPT2cyOTEySzNMaE1JUU4zMFhmditYTFRXVzkva2VSVlUwdE1lWU42MWM2eW53OGpFd04yQzVNYzk4NC9WcCsyZVpYY0lMd3FkNXJQQ3p3NStSdnFJU25JOFlBb05FL1Bvalh6RGpmbTlSZVVYanduQi9hcHIzOGZqMDFNRVM4NGhUY1p6U2swQWFWdXUxOThjQ1RlYzVxTEp3SjhiK3kxM3dQNFErWXlXWXFZMXpnZHdBQlB0QXo2ZTJ4L085Tis4M05pa1J2RmZDUVlScjZaVG9iMDgxVmxISCtKUmdOWHBFNTVCakhsVzZ0M2lQUXhUNkVYZDd6MlBBcG5hdGpOd0wzS0R5bGpMY2RKL0xuTmU5NWw1UjN1M3FmUmlHd1FXb1J2MittQUxGVFUrOHB6azJHUDNCRm9pU3RLK1I0SFoySjJSUEhDQWVWaUw5ZE1FZ0lyczArMllHL0EybVNUNWkvSkFWamxMSHVzU2N3dVhDS3ZoNC90ck9HeGJPUDVNb21DTTdIc1I3ZjRhLzVveXJyMkJ2VHhJbXFSeERLR1diSU9PR2xaMytUaTFQd1pjVUVYNC9oQTRaUEVSOWFza3BEYlpwOHEzWUN5NE1wbjdzN1JrZUxsTmFUN25JZHJkTnRuWnF0aUxCZHJaaXhCZWlUNXdCb056K3FkenBTdXJtTUxEMXdhQnlGb0I2Qmx3K1hEMWxkUWtlVVRZaFBkM0M1aVlER3ozbjR4M1Y2NmkzQ1p0UWlhQkc2ajA1dVZTbU0wYVhESHMxZXY0QlNlekZUdi80dkl2Rzl0WVB3aDM2TGl2Vjk0YVJGQmRqVHRpcjR0L3NUTVNMUHNBMFVuMVduNVZuUnl5MGN5U0d5QTNISHFicnYzSTMwSjhpSGV3bmNtek5qQUh6MlExeDZ0YVhyL1c4VlZLNWtuVUtwTnVuSzRPMktGaDZUVk54WVdUcUlUQXRZYXowN1ZydnRoK3p1bmYzZkx2RXJUWXY2WE1oUXo5eGcvd1FxMzZHZ1AxSE10Q2ZSY2VjU0xTZkswYStTRWI0WmtuRFpiQ3h6ci9lUDJxcmcxcHUxOExHYXpTZUJ3OUZKU2M0VTc5S3RpaFlTN2lyV010ZVh4MzFNMWtRNnpXUlFUQ0pNeWVUeEczdnZWVm96MFNXYXdnQWRnQVBkY2c4TWU1dlNleWN6WkxINW1QM0E3eW03RCtYV3JPT05qVktMTVZKU0hxdkhnbXZMeEdOSHB4aDRIVFJNdTgyanVSamhtRStHY0lHSE4yeEE5c0F2N0szQWo0OWhsZm5RL2owQzBkL3NqLzQ1SVRKUU9oSXlhUEY4dDVxOWd4QVhNeWJpWnlCOUpMUmtDdzV5RnpEaWxEWnE0bTl1Rk1aUTNyOG96MHp3WWVSYjcwZFlQNEU4WEZyZXdqSlNTd3NhR2FDckFqNzZrcE1haU16SXdLK2tEU3NEbCtjU250bTNhbGNtSVRSU01tTmFjdDFiOThTeTVPSU5sTDVhVEZ5WjhKYU1GS0FFQTdoWUF1V3I0cDYySUhUNTFQZlZyRDN4cTc0VHpCWWZsdzJoOStyOXZNUlFKbHdYaWNqK2dUbW1BeHpid1RUdkkrMTMvMTFITFowNzNjNTI3dW5iYXBpcW1NSlBjbHlsandSQzhaZS9ST2hZbDA0S01MT3dLNjY5SHdYNUdCQld0SXVPVTlYcXBPNUtxQlV5WXJqZ1NsTnJKMXlER1RDN3kzNEtiVHgybzRlbGhSYkR2S3c1L0VQK3d1d3QxSERsbkpLUzdpTEppU0hONmJDS0EwVU1GU2tIRXB5c3lpcDNwUWJuL3hqdjNZR2VwV0J3WTNNZUZ3MHROQUdkeUtlcHhKbDFNMkRZQ0R4QkRGazJwWG4xODVDWEN0SjRxY3NXREhmLzY4RngxZVhTZk9GUlk1MTYzVXJHanZvQ1Z3R2pGU1hNbENCMU5ySUlaaTRHRW5SVVoybDFVV3Jtb3FrM0p5KzdoSWFHRFJzZ1poa3BLcG5yL1d6Y1lsSnpKcHF5b0RQeWhzTUhoSFlCcXhKLzNjdGY2cUJ0SjgxTUkyZDBRZktVbHNNanVWMGJxNUpBcTBKYWdKTVdxbldwK1h1YjdPTUR0bkVhdjNEblZxNUNKSzNDRHVUTnNKRnBsSzFRSHVubjVlTXJ2STF0dEdkaVd5SlkrRGxCT2puRnQ3RnRtY0lGcDhSQkc1RG9VYktkNjU3Z3VPTFYvT3cwMGN5a0ZPSDdWQnNWV3ZuZzUzOGtaVmVHTkExL3k4WlhRUHIyZXhYRDZUTT08L3hlbmM6Q2lwaGVyVmFsdWU+CiAgICAgIDwveGVuYzpDaXBoZXJEYXRhPgogICAgPC94ZW5jOkVuY3J5cHRlZERhdGE+CiAgPC9zYW1sOkVuY3J5cHRlZEFzc2VydGlvbj4KPC9zYW1scDpSZXNwb25zZT4= diff --git a/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 b/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..6308fd1ac --- /dev/null +++ b/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9Il84ZThkYzVmNjlhOThjYzRjMWZmMzQyN2U1Y2UzNDYwNmZkNjcyZjkxZTYiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTA3LTE3VDAxOjAxOjQ4WiIgRGVzdGluYXRpb249Imh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzRmZWUzYjA0NjM5NWM0ZTc1MTAxMWU5N2Y4OTAwYjUyNzNkNTY2ODUiPg0KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4NCiAgPC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkVuY3J5cHRlZEFzc2VydGlvbj4NCiAgPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij4NCiA8eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMjU2LWNiYyIvPg0KIDxkczpLZXlJbmZvIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgPHhlbmM6RW5jcnlwdGVkS2V5Pg0KICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+DQogICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgIDx4ZW5jOkNpcGhlclZhbHVlPmcyaE1tVWhNVTQwV0V6a1lNbVErVDhYTU9KWGIxc1YwYVdLTXlxNXEvOFR2QVY1UUNncEdGbEhRUExBNkRUQk8NCmpRRGFINkZrK0c0cWNmcVZ0VFI5Zy90WC9iZWNyMWduaVRzQkI1Zmkwd1JPRmVzejVpUUZ0bHdyMVpkdkdrd3kNCmk1WDZkc0dQeVVWMiswQy9WNnVNR2V4VzVCUlQwVjQ0RHhKR3VaZGNycVE9PC94ZW5jOkNpcGhlclZhbHVlPg0KICAgPC94ZW5jOkNpcGhlckRhdGE+DQogIDwveGVuYzpFbmNyeXB0ZWRLZXk+DQogPC9kczpLZXlJbmZvPg0KIDx4ZW5jOkNpcGhlckRhdGE+DQogIDx4ZW5jOkNpcGhlclZhbHVlPkJzL0dsQmtST3dtQVR6TkhyRnY5T3hna1p1YjgvamJ5MmQ4eDZCZ2Zrc1JycHFiMkhnM1lDdjNvQkZjMThOZFYNClFMNG5GTUlhcmtYVXR3TGJJMHNBNHc2ckJMa3luSVd5WkRqVm9oRWRKa2JtZUg2Wlc2Wmk4VDJLa2lmUUFxYWINCk5wOXBhOXVEOGdKWXhPWWpheE9FT0FzNVA4MDNXcjJ3djh5QnZCNzNwVlFXR0xrY28yK2hHL0NuK2syS1VkQWQNCnNnZDlMQkdjU2RjQW9xL1ducStGMXR1cE5XazJlRWt5cUR5Y0ZMZjdzaFhKb1d4UzNUb29KOUc5QURscS9xb3kNCjNRa1FxRFRPY3hFdU9ONTF6K0hTT043Nm9wK3RHZTJkVHlmM0JZTDFPWi8wcXhjNGNPTE56SDdnZ3VWTlJFZmUNCkVXTHN0eDhCWHlUTzM1aEtIbmVGRjBjUTlGSVVMMGVROHBSNGZGVzJiRWxtbmovM2ozdXlBRUhsQ0pGb0J4a24NCnNrckcyMDQ3THhpcXRScm5pMEU1TXppajI5UTVaTnFOT1d0SzZvdkNRVTRBdkRzMWQrcHFqdFRFcGJIeGRMSkUNCk9wcWxvT05LSGNEa0JuY1I5RVlXN0FIMzNzcGV2MVIzSFJoVGx6UHUyREwzd25RMjhSaFdjYzV2QXl6STFxRncNCktKdmVzdmlHa2FLaE9KbEZMQklVcmZrcGM5R2Raa0JSeS9KN2tWeC95b1JiRW0rUW1Ud0xWNDE4ekc5R2VEVzMNCjhGTXlhQjJyMUdKRThuejd6Wk9IWkpXek4wUHpxSEV0UFRHZDE1Z2ZucGVGaTgxZDRiTE9XK2g3TWREQjFTVGsNCk1NaDJrUjIrSklzYjFVV0tXM1dmZENiU0FndlBMY0g5Y1RoOFFHMUw4WGlGNFlUWjZzREk2dEVMM3ZYMlZVeWwNCmc2TUVEdTdOc0FFK095d004K1JXOVcrUXNzcTNra20rWVEzeVpFYjlaVVFpeHBOVUhadnoxRzdYcG4zZzRPbnENClpwZDJxNGI1cDVOUkRQSXY1dksrNnFBTnU2SExGRlVjRFBJTGJhcWpnWHN4b0hwWXk4Y1daZmlIc2Q3MFFXOSsNCmRhSi9YZzJpN0tiTm9vNEQ3OGYrTTYrdEpLOC9PZ3FOd3YyYXpjaER3SStCN1FNN0JEclJrSFlOOXRaUGZncXANCjVQK1R3YjB4QTUwV1pkc25ValYxYTlCeUhRQURGUUw3Wm43ZE5QRUxZYzdaZXpxRnJxZDJoSlo5NUxRbE5MSDENCjlBMDh3Q011a1ZzaVBjYktYa0JpTXJOUDFrY2tZYjVwalh1UWJqTldLVXhWMzU1RGUxMVIyYm5zT2hsMUlFenUNClpWUjJvWWZ3bXJwNG5OMlFkbURDL2J1bzNsYnQ0UGM3SHM0diszbW0ybXRHM0ZsbnpNRXVMZHBUcUFLS1J1NkENCkVTYkJZMmttdlJ1cGpOWWNDclIxeGNhSEUwWlhqcitWT21NMDRQZWxhSWVCQXNTVi9XVDZrbFVveWdERDhxVEENCmkyTDMwZXREbDE3VlFZcTZCSTNvbGZRYXl1TXNSYmVvcWlLYXZ6dHNCdlRVNE8yaFhFTm53MER1Rk1sYy90VGcNCitXWm5SZDdmUkp3YndnSHRwdWNiZWtNdW1hWlJOVk9QNGxKd3Qxdk9XRG9QamFMWmhUaEZZV2dQRXVIdkRIUEsNCmxVM2hvNjRobkxDaUFtdENLcGJxUDBvNFV5SXUvN3hwaGY2cTJRelBydUlma20xSHh6VVo3TUludDkwU3MrUEENCkFBV0lhTEp0T1NCUTVQRmVsWXA4V2pncjd0eUQxN1YzUmNYVUU3Q0lZSUVQZ090dHdYcHkyd01KczBWRXpiOUkNClFTZDA3dG9GVHlMUzlKZklkelZWbGU0eVBQV0w0aWNkWjFFMGIydDNNME1scDdjZUF5OEJscWVHc0RwVSsxMUkNCm5FeTdkNlVPZWdaK2c0ektMNFpUaDJaSWkyL1JJL3pKV0tCRjRpVVk3WTFNbVV3cFlpeG4wZ2J1eVJUSGtiZEUNCkdvV3VhNmhqcXJFdzVqNVNObEFDbmd2TGt3MGRGUWVteHdHdWZHRG82Mk9BYmdTNkh2VXJsSUVaWVJZV2xKdGcNCmlac2FsTjJva0xVWW83VU9JRWVxejd3WllSNzdxMUFrNTVpd0JrNXlCcVpyZ1ErSzRXWkg4REFQK3d6QnNybksNCktyYkNTMUVpd2pNRnlPVDZYZHdDY3FPMEI0S013alpZV2k1emhSRHRjWnM4ZDRmbWlnL3Yvby8zRCtpVlVXWWwNClFqSExRY2wycFFsd0I2ZW5OYU53dy9xYmliTGl3UGVRcHpEWVZnMlRyQ0IrMUNyZ1NvZnoxLzNWZWFoV3J1cGgNCmo0YmUyVEFNcU1sNTZrN0ZGV0VuUzBmd3hNaDgrbGhDajNDdW14d2JNaFYvYnlncW51OEdnMzUvbHJtMUt3MWQNCmhuWDJTem9LdVdSbHFkOGZWaXphQUNOYlNaeVl0UTNZTmtrQWhUNDdGcVptNWF0c1VWWG1veFlxci81TTdtVlcNCjBWU0djOVR1aTJHSThqb0IvT3h2VmVUK1F5bjdWakpVaDZ0ZWtJK2JYVi9uRENvVHNDMUgvaUxBc25VZmRydW0NCisyeCtZSjU5MHVlWUJhaHBMWWJZQVdZTHVaalcrSlpTbHc2Sko2d1NocjM1VFNGd0JIMkpNNmljUGdnQXEvMkwNCnhVTWZPQmFDcVQxWEV6c25iSG1RQkxXcUpuUWpZb3QvdVRFSjJEYUVyL2p4MDhnWHZVRDJSSitBYTg2Zks0ZW0NCktyRVNMdURvY1pzOHZLS2k2QnFVS1ZnYm05STZQTCtObUk0dWhSeDlyWURtWjQxSUZuK0JTSDN1cDJucE1QVmQNCkR6K3A5VTdjTnd5OHVDSXZBcUdONnVnamJSaEoyVkN4WDdQU0ZYa0tUcUExUXdDZWNDekRPaVM3VmFaMWIyZHINCkVRNXRBVnBsakdydERnYWFNOHFWa2ZPb2JZeDRrS2JReCs3VjQrd0RPWEowKzVpOVdjWVM0YlUxZUJPU3h2NEoNCkk4QlFGSU9ON3hzejNvWnY1TTZ4NnNzc1dUQ1FpYklhS3ZzRWhQQ0RBMHk3UmlYcXd6SkZEMGI1cHcvSEhoUDkNCmZrZGU3VU1ydDQ4MUY2RWtodXJqd2RnK1pjeU03THJZblJFeTlFT1cwRGVRS1c5UjBPSk9mbXhLcU9qZ2NyQjQNCkd2a0FBUHB2S29SY2k5enpCaUZMaCs1R1N3Tlp6c3RKcnhaM0NXSnhWWWNETkRwV0x3VkFJT1A0bkg1VWVuU3YNCklNaE9ZSE9VdTVwQ3NvMFh6enJlVUN4K0w3dnduQ1g3L0FEZjdzenQ5TlN4RTVZU1NEcExrMytnRzNLZ1lEY3MNClU3MWtiekhlaUk3bmdWWWJhQStQZmtsTE9sY0VHRDdRMzVsRHdja0U1WDNQU3ZNc2dBVm4wTnB3RUp3cmcvMVgNClViVXpXb1c1N2ZzZEhRNWw5THlmOERJNXFtNVJuMjkzMnZFWE51MmtLTXRtdlZGdHRrVnptYW1EaXV1WjlTWjENCko0RXRSVlRwRkFQL1RDRmR1N0orSWtQUDdtaWZjUFdtcjhwSGdRZGhjYVcwMUs4QWl6VnhER1QzYkdjZExGTHcNCmM0aWp5WmNGakNhN3dOZEViZC90RElndEFhOXc5ZFZSVW9qR2VCQ05wOU0vZlUyMXFiYTgzallFNG9MTnplVHINCjU5a2ZvZkJ2dDZ3aVlBS2NtMWdlQ0hoRmpsNHloTy9vSkJCZ0t1dk9KZmU1UUR4Vkl3cWhyWGtWU1IwTnZDZWwNCldJb0pPQkVJVkRqODFIVjdjbjhDR096RlhCY1U1L2pVSGdsR3RsdnlJcVVJZVROK3pCVUMweHhoa05LTUFYaWENClRzS2xkOXZkd0JEK0FDQnVSSHZxbmk1SUxtOFhIK2lIMFlYdGJSZUtrL0p0djhxcEVTTGNOWC8ySmt3dTE4WTENCmg3VnhZUWxxZlhHb0V6d0N5VSs2cFhhZnkxc3VQOXlkcFQ0a2FMNjJ5NFJQL0hsWndicGNGK0NoUHpPTzZxUU0NClZRMmN4SlU0WHJFeUQyQ3M0L0hxbVY4dzdCZHBhZ3BMVXM0SkFpR1FaV3lrSW94MlE0azkwSGpEd1dGeEFyMkINCjJWeE1iSENXbmp0Qytrd0hIZUFtT3NkbHhjSHhabFZKTFhqQ2orVWR6M3o5RDdLT3FoQy80ZHZTaURMUW9XaTANCk5wL0dMS1BpNWdwVkhCbGJPaU1YZzBZMFVJMXRjbVFueFNYUmpOb0RFekdCa1FVWXgrTzVHSWZpUWg3N3pEb2oNCjg4b1hCeEd2WDB6alJ0dUVuSXprNS90cmloS3pSeHVsTFBhdkl6UVE2QVRnZEo2UzBwUlpEYUJNVkdDMEFRcW0NCk92Q01wUy91dDJiUllGaTJBRFdSN1JNaUR5K1lpM1FISzJyTDJKTGc3M1hHcEN6MjFlbmFKUE0wQ2lKbWZHWWYNCkFQYnlpWjVrZlNIWDRxTlUrZXRYQVhiRFptSkoySDdtYVByWC9LVE1zVFBrU0oza1QzS2NSNHVSYWFYQWVtby8NCktZVTB6YUtaVDZwbWlkK3FmenA0emdpZS9WelNyMGFmb1o1eEEzUVFVV21tc2VvdEpBbGd0UVFKVDNIV29TYVoNCnZ6Z0gxcnJHZzNKWjgwYlFMS29jQWs2c2tTRHAzSEs1VVlXZUJVZkVKbXUwVkh6cytpUCswSmd3ME5XNlJ2ZWQNCkpiZTExYnZTY1V0Z1lKaUhqVENhdlJDaVB0UzlQVmxreDgzVEZVZkFGTWZNRUhYUzRGWE5adWRzTm1RTzA5bkoNCk9GaDBpUzhxd2FEWXhYNkFabGo1dm5jZmhqNUJxK05PVDNOY1hhVWlFdmxxQUhTRXNENUwxN3gzTTQzc1lRMEINCmVrRklUdTJnWlRnU3QzaGNPdVBSUmZJV3dFQmk2Z3g1MnROTnJFQ3dXWnZ2SUZGQWZVS1lMUTlZZzR1aGVzRDENCmVXcXdYU3BUNWpmazAvRHRaeGJQc3FXQk9CRWlmZk01OUNMK0FReE9SLzRYS1ZYNm5uWFZpQTJpY0tLUGprY3QNCis2c3EvcVAxNnNpWDlCS2tROVYzY0E0K1Z2QjUzbFR6R0FUSnB5SEIzRkxvZ2M3WFFkdkdBczM3b1N2R3pEclANCjRBd3I3V1VYMzB2VDN1LzE1dnZCS0I5T3Rmb2l2MUFRZVphNEhCWUdwbWQ4anVNeVN1SUVnWElJNUhVcDRUZnMNCkwwc3pCRnhQc1NGTDdKNFNtSVF1UXpGVzBRNjRBRVNiSnp1cUc1WldHNGZSVmlYb3M0S0x5RWNpelVhWHFScVYNCk5BL3IxcWpRRXF0dkdTaGw5NXBib085d1d3bjZIWi8yQjl5UkFNd0hndU1keXVhTURLMW1OMlB2d094TThJZGMNCkJqYTlHQXNwampJcTFqNDQxeXlZNjdkdDlnYVVLK3p3Y1NNNzlNNTFUS1dlaEdmSFJnb0dHVFZhbTJjMWIxL3INCkkrdUtKVUpCN2NpeXF4RXA3QWtzSTNhNmxyRisxM2lmd0FTNUlLeUY5WVltQkQxLzRuMVpvOWIveXF3cUdiaXANCk5XbjZxWmVmZ3kvOS8zNUJORk52VndxWTkwaE9Od3JoaUV3UGJEZDdrVmZubEIvSjFjMUZjZkh6cFR2aWltT1oNCkptb2FWN2xHMUd5R3ZJZjlPMHU5dmFLQ1FscFN2V0NtTm9QeUlOdklxT3U3YjdzemtoM0Y5d1lUazdKWDArenYNCmh5Nk1RenNRRFdmbjRaL1dXK3pWY1BwMS9ybGRFcUQ2cXgrQmFGQzRLczJJUFF4Z3hhNUwwZUlDa2kvbHhyYUMNCkR4bVlId25LNG1zbjFRcFJlY29XMTd2dTJrbE4wcmtiK29jZ3Vxa1g4REtYT2xBMnlYZUVFUDB5L2JOY0swb0gNCmtidk9USnFKZlE3MzJFSHYzZDRDOVhQUi9NY3o4c2JvVkZleGZ6ZXh6Z3c0TTNibm15SXNrVUthS29jU3hIWDkNCkxwMWRBT01KaU1uaE9yY0NJRGs3TjhCakhGYnV6RHNxc05pSG13aXhyWW1rZUg3cUZ3a1Z4MEdjeTcwdUM4SEYNCmV4bDYwc09KTC9xNFQyeWJBZFVWODZZZUJLdDMxK3hJdCtRaHZVY2NyMWhNSTZlRHZ3RVc4UngxMCt2RFY3Ry8NCnF6V1JkV3lRRHdvRHdrZm1KaWxUaWhmRUV3a0syeEtlY3RrdDZPcnQ5ZDZhVE0reXZKUjZkalZwTllHRVE0eTINCm9rQktJNjVReG41MERxSC9HZFdMWXZXUUU0K1cwMndJdkNCUjBobTJrVUdaU0lqbWl4NzJKQWN1Z3V3T2pzb3oNCmRnR3VCSGdRaGx5cWJjcUc2MTYwREIzN0JMM1BONUordFprOXlZaTQ4S1dGb2YrdG1SdFJDTWxRUTlsYU9LS2INCmRqNitiWGlUMXdpTkdrWlJoRFd6RmFrMXBzNUFmb29DSTZuODlSU240ZzVPeENKaVZHQ1gwN2NXZU16aE5aaGsNCjBPR01Rd3pMVnUvK1pYVTdPaFZHMHg0M254TDhjcVRUakZialU0aUpuQThVeWZaT21oWGw0NE91TWVxNmVDY28NCmNVVFlxb3FXRHBqeUFUOW1xOGorMitEcERWNFFGeE5GckNySEJNS2RlcEpLNjBYTnRBOWEzbnR6WVpsQ3ZnelcNCjBncDVZMVpMVktNU1lJcEZKWDR0b1I1bmxIV1NvR3d3ZzNEZ0RHbVNjN1VqaVJDMHU4RmwzcmQ0WitzdU1HVDINCk5tTXVnS1FFK3JNemlLeExpYUl4YkthTERWeDR5MjdlYWNLS1NtM3V5ZGtnK3N6eHdDVmx2WVlqclg3L2d6ZzkNCmo2SllYdm1SOTNrS1dBandhN0FOb09VcWdPSHRHbDM3SGpuUGd2TGMzZkNHd0xYRkl6d24zR0lXcmpUT0w4Y1INCjV5eG41YzhrM3VDVkx6bXVOcm82UlRiMVpwcWZYZFh6S2FYQUtONHNOZW8zb2w5dHJOc0xFOTBVOEhkd3RtQXoNCmd4RER5NHIySWlOWDRnWVVqc1VaKzhIUkRpNlFKU01IVDZNb3M1bFZrakZEZEluLzIwTWdwem9SU1FuR3RHeTkNCmNZUlpSK0hlSHpVQlJyWWFJeHFqTS9Zb09LSGVDQThLRUVndWVtb1lGMTZrQXhlaDNGRVZrWENBZHhEbEdKQm8NCkQwaktxMWxTVllMSno3NWlpVjY4S1E9PTwveGVuYzpDaXBoZXJWYWx1ZT4NCiA8L3hlbmM6Q2lwaGVyRGF0YT4NCjwveGVuYzpFbmNyeXB0ZWREYXRhPg0KPC9zYW1sOkVuY3J5cHRlZEFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64 b/test/responses/unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..cfcb85dc6 --- /dev/null +++ b/test/responses/unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzYW1scDpSZXNwb25zZSB4bWxuczpzYW1scD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIgogIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0YzFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl80ZmVlM2IwNDYzOTVjNGU3NTEwMTFlOTdmODkwMGI1MjczZDU2Njg1Ij4KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPgogIDxzYW1scDpTdGF0dXM+CiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+CiAgPC9zYW1scDpTdGF0dXM+CiAgPHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPgogICAgPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiCiAgICAgIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij4KICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDkveG1sZW5jMTEjYWVzMjU2LWdjbSIvPgogICAgICA8ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgICAgICAgPHhlbmM6RW5jcnlwdGVkS2V5PgogICAgICAgICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+CiAgICAgICAgICA8eGVuYzpDaXBoZXJEYXRhPgogICAgICAgICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5nMmhNbVVoTVU0MFdFemtZTW1RK1Q4WE1PSlhiMXNWMGFXS015cTVxLzhUdkFWNVFDZ3BHRmxIUVBMQTZEVEJPIGpRRGFINkZrK0c0cWNmcVZ0VFI5Zy90WC9iZWNyMWduaVRzQkI1Zmkwd1JPRmVzejVpUUZ0bHdyMVpkdkdrd3kgaTVYNmRzR1B5VVYyKzBDL1Y2dU1HZXhXNUJSVDBWNDREeEpHdVpkY3JxUT08L3hlbmM6Q2lwaGVyVmFsdWU+CiAgICAgICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4KICAgICAgICA8L3hlbmM6RW5jcnlwdGVkS2V5PgogICAgICA8L2RzOktleUluZm8+CiAgICAgIDx4ZW5jOkNpcGhlckRhdGE+CiAgICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+cDJWc0RDSWFjYzB5Ry83ME02aHhpSGp2dzUveFFqb0JDT1ovZm1xcEQ5WnlwejlrVldMU0UzM3lvUkJOc2g0bGZqSHFpYVdSRU9oSDhuQVpMV21QY3gvWjNVTUxQWU1mdWZrWDFCcjFSTGp0VitITDEvRjBwa3pNTnc1VEZjY1dHcVpMbUVaRCszS0FpQ2NMY0lVTnJDeTBBaDVVMWlzTjdxd2llT3ZsRlU2eFZsUGZqN3JuY1o0S29qWDFDQkplaEFpS1ZIVmtWb25LeUxEOW9ocDl0NHZHbzBJbWlFZTRLTEF5ZTI3R3FEL2grQnJZMGdneXJ2UjdFYVJ0Ym5MYzVvdFRBQ1BpMmVua1ZJYS90SjlrUitCcEhNTnI0Qk91eEhKYXRCMS8zSUhCWmZvcTAwc3NNdVRGdXFETk9RT0tUYkxsYmpGb1Vsa3ZscmRrRVlZRzFtcERUVmppRFRhTW40UFdYc1hFb1pueks1R3ZFWHE4Z3ROTXZIRk9SbVBmVmx0dm9ZL1JpMlpYTU1yYThEdVFNOEUxQTIzY3ZvQzZTbmE3dCtYdmRJdmtETE5TNlE5dEY3cFJWL0VDVVp1QWVQdXRNVCtCSEFBZmY5YkY2ekYzOTZBUmE4UHdlZVdZMHdEM1B3dVRuaVlNeFZYV1hIWHl0SytqLytqT3FvcXF0aTZOSHdxaXRuWWNNcHJQT2hNbytDWXRVNS9VUE1SbkN5UStoYXhaaEFUS2pNcTY2WmhMZ1NibEtmVjljK1NYbUhCaHRiNy9pemdRcnN0SGNjZFdOSUIrRDdVQ3JtQjJhMXpna2t1U1FlaEJEZ1lyeUJ0cHkrdk1rN042SDFDYXpkNXFqb2lSL0JFOHlrSGVRVFhNWU1mWklUZUpWYUY5QjlqYzBMeEpLN3ZpSXNsSDZzeUxsdGpOeGhyeTlDcHR6UFM4ZGJOYU5ReTRMVDVqeEdFeWtSTEg5eFhZTUFQOGFIWldtV2FxUlMwczRleGdKVjExRUlmU01TZmNPTjZzb2MzNVM3MGlTWjJVZE9TV2dNM0xiRlQvdmltUHU3S3o2TER5VTlobGVZT0xTbE0zUjl3dm83eDRtdFhjeWthR2VkeGU2TjFENEV3Wk1TaGR2bEMydVpUK2VJWEkzM0RHRVRVcVhyb0dBWkwweUl3QVpEemRDZE00S0VQTndIZVQwTFFVVUZTTHRibE96VFh6Zkw0VHZ6cHRqWGF0d1JvVWI0cmdxSkE2VUwxeWRvY1dKcUFyMkFsVS9hV2hTc2lWVm5ONS8yMmRpVHRnQTVxWTMrTGl3ZWJZMWl2MlB5YTUvTTNhcFQ2VkZtRWpUdEo4aGIyTDUyWksyZlhqVThvUHM5YmQzQmtQR25zWWt1cWxBYUJaaW90MWFVcWVnRVlLdTFVSmtSVXdVK21Qcys0TDNWemFqTW1LazNFMk42U2xOL0NvdlFpS0FnOCtIUWlYVzVmczZQd0M1KzRQYnZuZjFDcDN6ZjVKQmRzT21JYjZxeVlkek5YOUFxaFJORnlZOWlXZWlZTHB0eXdEUnluVitXcm5VSlNUcis1Y3Y1UUZDcTRMbm5FaUhFek0vMW9CWjZPTXh4WE5qelFMQUJDdDZuMzV5TEFCVllFSlhKcHpVWHU5QTduaW1GSmExaWxmS20rL1lMRitXaFJJeWpEbW5oVHM3NnBkY0UwWFR1eTFjSGJ6TGlrcWpaRGtGbytFUkdBM1JnYmFPZmc5VmZiZGNIRUdSTkxSRENIbU1ZS211UjVCbWUwT0RFazJjV3dpQlBHWm5YNXcwSjNqRDJWV2hOU1k3UzVJREwxNzRvVkR3T2J2RXo3VWE2Q2wvc0RKaHlWRWdjaDQ2VCtRbkNXd0hMaEo0TkJsWkEwUWFrSzBPNWxTeit6cE50aE9zWTIxZG80cHZQd1NPNXJuMGt1M0VZNkhscjJGVWgxaXdxdEU0cDF5c0I0RVVTcXNGRjlQenQxN0xHNHFyZ2FqU2lBVkVRUDZiUmNLNFp4WTJab1RDQTBHU3JYVEZLVGUwY0tzRDFoQVlyN08rN0VzVENqRFB5YTg0bVlOQUplS0hFNnFwVitCQk1aUW9rYlJFU3VpU3pyVEp6Si9ubDRMZnNxdFBteWhVdmcxL2ZudXBESlVETEE1VDIyMjE3c1FYZmtmeHdoVXJzSTk3dEpvZ1JDRkU4dSs3VlRrbUVCY0RLRUh6WWJNaEdwanA1U1dyS24rUlZHaWxHaUdOSFpkcnJOZzlZT0xWajk5RkdhQ2I5V1F3TVZsMXoxS2l5ZGJFS3pnKzI4QmdZRlJsc0ZZb1NRQUJhejEzbHhGQU9JVm1hVnNLYXRWdDYwQ25TOEdGeUw2MkdDN1kzY1FzazlybndHL2xqQlYxNS9LMU9WY1YyWnFybDV3clkwQ21pNW01eWR0UC9IVTZPaEdBSlZ0RjJoYzVRYTI0dWJVZ2JsemFyeDhmbG5XVEUyb1NLOXo0Z2xSTjQ2K2hzaDBiUXV3ekdLcXZON3dhWFlXRFhGRG5aSGRZOHlDTlhTdWVDUy82eFoxakFWNG10aDlyRWpJZUNDNFNXKzVrN0ZWUmFYanpEOUJOSjRwZHRDNzRSZGdFWmhlVFlOSThPZnU0SS9ER2tob2dJVHVnOU13RmkxdmFBTm9vSmJEcHNnSGJYL2lIYjNkZklMQXVxM2pidm5xRWswdHg0a1BicVI2SWtKSVdVU2pSQmh3SlJRLzRoRDFTbGNXNXBObDVsRW1BRFRZZFhVUk0xeGdlM2Y1czB2d0FBRzUrRDc0a09FWVJOQmNNaVRWaGttbFkzTVZQUWdVOVJZMFBUV1ppa0IwUDA0UGVkRDhkMnRTQXVmd01zTWZoRVpheTVwUXpTaUg0Zjd4T1lMSmt2aFJkZFRiM1BYdUJXdkQxcDZSeWxMMVFmN0h0RVh0Z2pvTzdLUVo3SUVJNFgxOW85cDdDU01JYU5qR040VUNlNUczYkJkQWhIbXFndnFBSXplOTh2cjRuTWZUZ01FUFY4RWpTRnpVWExNM0RyKzZScEpMckFnRUZ0bGhEREpoNVRMZUtncGFybFN4Q0RMazhJWEdPQ2ptZ0o5TDIzMWphV0MzK1NzY2cvWURkTWFLVUtaSG9nUDBWUlB3MitCeERiRXovaW1kMTdEaXprMU5FRFg5M2FDSGNWMWE1b3M3cnR5ampsVGRUTmhueVFhb2RYK2tyQ3NUdnFnaUR5ZGN4bjR6NW1VdlUwQUpvWW1DU0N1QnhnSTJlMmV0UmREMTBUU2lUNklGWFlEWmVYM0N4SHMxN1NmYXhjSUV6TGdtMGdsRVQ0VUxUZEdDblVlY2xoVHhxeTd0OGF0ZytGcUdDQlNEZWpaNTloODN5M0lnRE0rNjFwVTRFZzQ1R2JqK0RveFY5OGlSSzVPelpBNCtSTTl3eFlZaDBmQ2h6bitKczBQakFrd0ZGZ0VqOWpLaDhNWWJwd2hGeDM0cnUycGNUSGt2dmlVdjYrWDJIZmQzQisyeTVCUzZ5Z2UxWWVDYkZpamlJTkhQQS9kNHlzSlVLZnZTM0duSEYyaG1wWTF2UEZLRmVlRUh3VFJtVkdPUExKN0hWMGZKQVNVemtQWmVxWDIwcnZDdmdnOEdRbm4yTDM3dXdCd0xvYlM1T2xSamUxOEdLQStTYlBSZ2U3Q25rUjZoVzhDSEl6SStwVlJOTkNzT2VTV3lUUTY0Q1p6b3lTeHJxdFNVcnMzUWVhMWZCOG9JRURyWWNIU05ja3dHMmt4WUJLdmRqbzhxa0ZaVlNZWlhjV0IvRnhSSkxObU96OXN0TFg0ZnJiMnJGTzMwd0w5R2FaOHFFeHl2clJzb0lTeWExei91ZE8zR0wxU3k2a2V6WGxpK1ZNRGRRV2pyYkRXVnczT0dmSXFKUWRjTlhnN2pYa1JmeVFLaGYxODlxTWdtL3dxcmpUUUR6aGNhREsvN0tvSnZVOFJ6blFHb2FIMmJTOURmREdObDBqc2ZBQmVDVUpPZTBtVlA3aUZNa0xmeG16L3RyQ0ZMMElRbHFBdXlDM3h1Wjl2VWtKRDE1S1REcW5kQlQ3TEdMMWxpbGpCU3BQYXZLQzArTUVuaHMzUTJRM0lKcDdoaE1wdGcxd3NQY3o2VUNOMTBTb1pqV202cXJGRGk1SVkxcXlGMFlKT29tU2J0VGo4SXZVZEJtQ1ArRmxUQzR5eldOY3FQQm13MHJlbDdqd2xHZFNhN1Z0dVpwazhFK0t3TFBTRE1hUUR4S0w4Tm5XZXNuMFF3dVEreHYvaWU0ck5qVlovSXoyWHNrbG1Tc1VTclg4Q1h1V05YNmxTWFlpVzdnMFIwTXg4ZnFZS1RsUWZ2M2Y1U3VpenZsUGx0UzhCMlVnUlRPUE1PTEc2Z3h6dElpMXdBSFAxT2hkVmtlZGRmNTdiVlN4SDZvQlh0V3gzZEpFT2FVQ0lXOXNCQ2FhN3Z0RGFWS2pUUUQyWWlZc1BIMlFuTWFjZlZqR3hIZi9XTWZHRWlJODRwaTQrMU5jQVE5eTFFc2hZRll4YVB1S3h2L0dNRUpqbGlmejFYL3I1czNxbC9PenpGTXNkdDFlcTZGVWx1Wmt1NklFNGVkUldrZXRDc1F5RHRxVS9qNzNiZWthSGhpaGwrcjVISzB1QkVDZDdIVGE1M3VOaXVRNEpKVzd2U3ZFK1JmbStiREdrcjFmZHBOQkVjSHZaL1VxWnhwV3VmUDBPZ3B6SGdmVS9mSS9GQ3haTXFpemFjZnhzUTJoZi81TCtnMXB5eVJnMXNWamxFTFByOHpObHFTWldhOE9FK2JmbFVjWnAyamFsVkZKYzlpR3gvQ0dJVnlacEJ2Y3dqc29DakpFekVnS1I5Y1REbE92aVlFNzBEQmVJVFJub1ZDeUlaS2p3YiszUHk3WTRkQ2gwSmR4c2Fmc1Rrc1UrRFh2cHhoNHpjVXpKTUM1L1hZa1hXWjU3SGZnWllFSkI5WnNjU3pVYTZWY2lxVmI4SElSR2RINlNhSjluK0pJZWVQV3ExMFZCVGdvT2Jma2RtYUcwT1VITDVTNjN1Q2NyK2dBOWxXRHQva1p0V2gvWmNyK0NhMkl1cGtGZHFYTWpWNXNka3NJdndDTHZKenlYVjFZeExaU3Z4OFFtN0JVVmxFZ0doSUxpNEphbkRVLzlTeXRURytCMElRTUdKWTM2T2d1QllqN21lL2UxZXkyeHlSbW8xU1U5bnpCU1FHV1lLTWhrZlYzajhlclZxaFA2bFJaT0FxM21uaHNBRUpCZ1FnQVRmQ0VsMk1GRFRlSTdTbFZ6aTIyRXZSc2Zvc2ttVFlhWHNoZG5uY0N3alp1WUlieDR3TWtwL3RtRVBzMCtTWndJNyt1NDE5aVpWelVmUkV0Y3JMVXEwUHFKWVd0RCtHOEJnZy95NTZMWUtHM1Q0QXNxUHM5SjZhWDltd04zaHRnSDdEWEpkb0R2dzhxMzluWjdBNm4wV1ZxYkZDMU5oWDdsczZsT0Z1NmsyRm5YSkxXUEwwbmxGeGZPb1JNTi9zN3h4Qk1KMzlJck9DL2htdktmY3RZRHRSeGpHZS9nZ3hibWoxMmJDbGozSjk5c0VzZktWb0VkMUNHbDRDSVl3dWcxQkN1SG9DMngwbXd4VCthUGljV2oxVUV2YUxwM1kzLzFTdVlxSjRxN0xWMlY3N213Q0szNEhpbDZPUXFhdUVjdVdBS0lybEs4MGJ3aG5ibEMrSTZPajh2ZGlMem9lTS9UOFhNWDVVU3lmbDNNeFcxV3EwbVdaN0luRGU1TElnRmZueHNzRXdZcCtYcldPVzdIemZpWnVWSmpTNDBaWnZTd1EzTGlEVTNVQmVVWjdrb0l0Ulp3ZEN4M00wN1c4TXFVT3dqa3JCZUZSVEhRSWZ5dUQ4UkFlVEpZQ2NaUFJtQzRYcDVyU2tOa1dlUmpoVEFZajAvc3NQSUdPMkZsN3FkVVRkYUxtMFJCZ0pMMGRjWnVjZk94djhJZHVtSndETUNaSlllMjlMY2NZbENYUTBFNklhNmpqWDFueFVjaGVNMkloTVNhZEY5WnNNL0pTSzlPNTFHVk1PYU82WUlrb2lrMTBiUjNtbkVhUlRiMldUMDlaRzdCSVZFTG1oYkdGTkppcWZVNEdxdklkakJzSEJVNjhqdkd1dDBSVTI1ZHdHSVk4azFQM0RPdEZjbTZqcTkvcGZZK21BWFVISTlBeGxJUE9YVmpySTZQUkJTY01HVVZSNUJ1L0VJOHNJSlMxNm5mRDkrQWdnbCtPUXAwUXVuQk5jcWNoTUorN2NlWDAzb0l6cVVnRHZvZCtqOWJYN2hnZ1UxL1VySW9Dc3JZYUphcWZjaEJXUFd0QkgzUS9VMVhFOWhrbUdreWxKamJqRU11MzBxQ3ZMbm1Fc2pEMlpJTllSWmVnaENPRCtvR0IzTXdaWXZuQmJtckNZRVZEbGV5S29oQ3cxSmtrZHgxU21JdVVIdC9tMXlXME8zb0RJdzdPdWpDWGM3RTU3U0MrckErcjB0eGFJNDV4aTh3YWkyTTN0VVl2NStObGJDWllhNkNoeVhSRVZkWXJnSnY0cVY0YzZ4UXlUSGpmY2xCZENsazVxWGtOUDlHcmMzQnI5V2N0WFVpZVBiM3A2OTVPS1NpMDUyZ1p2WFMrclFXakljekk0eG56NXphcTBuTmNMRktOWWR2MHF5cG05YlA2YVp1dEczRWxEQU9aYWdGS3lnMEFIdHdaeXpma09UVTUxQkp5ZTdneU9WR2hQaXcvSHhya21iRDMwQnFjendtKzJXOFgvU1V6Zk01NWhVcjVuNHJDNVQxOXAwY3RpQjAzMzJPU0UrbUdWbGh2c3hvQjY2bG8xVG5zZGFqZ2tKakJnRzk4Vnd2VE5UN0JOUDhCSWlNaS93enBiQUh5MndoQWtBZWFRS25HUGdNL096OWppVmI4Tm9ia2J3bm8vTEd4Y1o3L2w3Nno0OElHMWxlZXJBL28rYVR2TmN0ZldNbXowaW4raGhYOE9FVnZkdzMvbFk5UUJXNWxqWWgwVWZpdmEvWG1oQVo4TjJlWGtCcHNXZGFSc1dLdys2TE12bnJnVWdmaWZyN1V4TDJKTS9UclBqRktPR3IzK0NiR2RXMkF0WVFPQ1FFcHRObFJKOStmVklQVFZVVGZZZzZwb2V1NjlMMndYbEZmeGpxeFlQRVBPZjlTU2tNZ050NWN2NzEvSi9rbU9LMGZGWWlDVElzWUhKYWIwNXFqNXFSaTJKUndmTWZqTWNRTVQrOHN0R0U9PC94ZW5jOkNpcGhlclZhbHVlPgogICAgICA8L3hlbmM6Q2lwaGVyRGF0YT4KICAgIDwveGVuYzpFbmNyeXB0ZWREYXRhPgogIDwvc2FtbDpFbmNyeXB0ZWRBc3NlcnRpb24+Cjwvc2FtbHA6UmVzcG9uc2U+ diff --git a/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 b/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..903e823ad --- /dev/null +++ b/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0YzFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl80ZmVlM2IwNDYzOTVjNGU3NTEwMTFlOTdmODkwMGI1MjczZDU2Njg1Ij4NCiAgPHNhbWw6SXNzdWVyPmh0dHA6Ly9pZHAuZXhhbXBsZS5jb20vbWV0YWRhdGEucGhwPC9zYW1sOklzc3Vlcj4NCiAgPHNhbWxwOlN0YXR1cz4NCiAgICA8c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+DQogIDwvc2FtbHA6U3RhdHVzPg0KICANCjxzYW1sOkVuY3J5cHRlZEFzc2VydGlvbj48eGVuYzpFbmNyeXB0ZWREYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIgeG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyIgVHlwZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjRWxlbWVudCI+PHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3RyaXBsZWRlcy1jYmMiLz48ZHNpZzpLZXlJbmZvIHhtbG5zOmRzaWc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjx4ZW5jOkVuY3J5cHRlZEtleT48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjcnNhLTFfNSIvPjx4ZW5jOkNpcGhlckRhdGE+PHhlbmM6Q2lwaGVyVmFsdWU+VkVQYzBwa0o5NEs1RXFTVnkrSTBjdWVSMWVURDZMdW5Bbm5ETFd4eit2Q1ovTEdrdms5MXM1cktlRjFWTXE1d3V2S3AwZFFFUmJ3di9KU01vTGMwMVBiOVY1azZJVy9udUljeEZ5ZnYwbnRTckVibldobjVPbGJqYlBiZ3JGN3hxaEdPSXdENWZWOGJ4dzUyMWVYdmwyVU1iNVFiVCtNRjdSdGhieTROcTNBPTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHNpZzpLZXlJbmZvPg0KICAgPHhlbmM6Q2lwaGVyRGF0YT4NCiAgICAgIDx4ZW5jOkNpcGhlclZhbHVlPmpuYlBmMEtvMUlUdXJJM1lTKzc3cVdWbmwzajRpaU9RcWl3RVdsT2tmRmwyZ3JJenhxS3JURCt0ZDJmWkZvOXdvbVV6TUdaZkUxNzVST1lvb0NXdm9MbS96elNTZUpiUTJmWjExbnlZNjdNL3pBNitpWDBVQStyVDM2aFdxRytRZjY0V2dsekNKaWhPNk51bmdZTzFFYy85dXNIVFJvQ1FPTm4rK1kwTEFUcityTndUbXEySUVNS2F6YVFFL1IxWHVxWU5HZnNGL3pHYkJYY0VVM0d0WS9LdlJkSVlTcVU4VzdSTnJZTlFwZGJFOWtLZ0Q2UzJxRUZqSUcrbmlKa05DU1JQWEpSR3pxNHJlZ2ZEcktaSGpOWXdadVlnWDBKTEsyczc2dU9BMWw3R29EY1JrUnNRRnp0Slc0enp1T1M2S1pjNVRBSzVHQ0tlbFRQZU92YWdJcC8yMmpMZ1VJN2dDUFJManNhelZoOFVtRUd5UVNmamxGbkROVnNWTUJCWFMzaXNZaTBPeFRmeStDS0hpbmlMOXNXb1BpT3FCMCtIZURLcVlrVUREMVByT0hlbkRKdDVjd0s5N1lObGZHL3didU5XSjk0UDFabHZVOXBVMCtzd2Y5RmNHV1dIQUNhcXRRMWU4SUcwU3hiZWdDNG9Kb1Z4eVNTQ3BFVUhWajVWWkdhRkpGOHI4OGxwZDE4eExOZ0NZeGg0cmx0MTB6cVVianp4WjRIVDZXSUxsVHBaVWVBRG4xMDlCWmNVbnNwSm54TFpxN2tFZlBlWnVacWpTcVZSOFZBVk53QUM5c1lFNzB1TllTRGRQbGxVbTJJZ1hDZDhqT05SckJvNHc4SUlUR1BTUGNabmJTbUlSRnVzY3dnSERVQ3IveEsyZFprK29uOFNKV0dCTUJTWDAvWkhxRkw3WnFEdWV2QU8zZ1hGSkgzRERscm9VVW9CcDlmMmFjMm90MCs5bE9CcFpFcjRZbmJyaENpa2lFYmI2ZThPQkJlQlNNemhuOENSVVpFTjRReGJxVlpIeEZ3bnRjUjhpSCtjRm42Uk9UL1l3SUQ4OTV2YS9wZEhGb3NHWndyZ1lOazdEbGIyYmo1bFhOYjU5UHN1aVZUZUdiV1d2QTZmRWMrVzRFZjl4emFUNzg5Q1lIRVdtMjdGSC9qdEw3VDlGdTBXcFZoZzU0WlUycC9Balg1dEljbkZIWjJrSFB2WVNUemVBTWNacTlDMVFEelhjZUxaSWhPUUc0Um1JVW9YM01JbXIyVzlRY2tOVVRKcnJ3VHBGNnR0NW83YU5EOXg0bmt6Qnpkb2ExajhHaVVIOFNwTEJpV0VJWWEvSnVOTDNWazEyQS92WnBndGpBUS9PMVZiSm1pZGg5bnhrUHY0RHFNcUVnV2M3bFRBV2JqWmowM1U0K1dKMnlrbmk0U1pXdjdZUUowY0d5VlZkUnNXS2hJM3VTcmZHZnNSNXR5eS9ZTFkwY3lGcVdGM3dYWEp6bFJ6WUI4N01CcGlWOEVZc1UxclpsK2thaXcrZWpiYThFWWVITFBLSi9HUldCREdxc2h5VGFJWkVqc2J1cllpN1VRVGQzRGRBdjUwd0tFUGhMaG5kTHJEWUtTbkRLQlRkdE9sSnBZYmlmWnBublBIZjM0eHFiZks1M0M1WUFEdXNrSWdPRnppMTZhNEsvakt5TXdkQ00zNE9yOXdlbGJtb1JiaFZTUVNMdDd6NS96eEVPSWV1bloyelZpRThMaHp6V01NQ1dhcU5HMVdhbHVjZWc5MmRwaG12U2VVTzhzSjM5dUNtRkNZalAvenA4YnRMemNFQ2tIUjVyZ1NjUDVNRFhwZFdmOFpIaVIrOUp3RGoxbHlRbXZJaXI1bHBHcEVaQjRMTUtuaDNtZ1oyVzF3TFhzZVZYMEVsemxlbW9ua2Frc0lFYWYvcElZVlZheHJVblBJdGhuWnl5SWI4aEk4VHRLbFh6blAxSjNnVVdzb1lnVHdmaUlDOGRTaEdoQ2ZCRWt2aEFLNnBNOGFkUGxsaHJoTTlUclgvVHdtbnpyODNrZm9OcXBBTVRUdk04QzRvaGNaZEJLRmpRT3A3b3dodDFXdzd3bHlvRXo2Z3IwWEVIOUdhR2k0NjFhVDN4YWQrWDVsdFJKRmliZ0JlNjgwYnRyTW5zZWhNL3QyazZtS0N1dDY5Sk9DT3kzaTl4cTZLa2R1RnZvN3JNQUFtRE56ZzdHMU9aUWwzT3VRaldKWFVIRi9TK21Yc0JpNEQ4NGJPL2NLaHFlc3MzRlRRL0VDYjdNcGpKV0FuZVY2bmd1aXcwWlZ0aUd4N3JkWWRQbkRGK0dQOVk3YWgvNnNiUFUzMWFuN3pBa1ZZamFKai9oaWNZOTRoUVVMQ0dYRnVwWUtuTHp0R1dEY05xbmY0cCtBLzhLTWEraVJDRVFkNVFwNTg5QzdLOVl1eXhFb3pILzNFcUhCK3hGbFBKb2c2c0lEY3ZBSWI3RHduSVdkOEQ2Ty9ZOHV4Vk15QjZjdk14R1lPZXF5LzZhSzFNODFrR2x0amlyWXZyY01NMEVqaGxybGwrejFuWmxLRU9UYzB5QmNERDlCbExZbVBtV0szaTFmeXpOU3o1OVJlOG80RlEzV25pSmQyQjN3bHdyRmplOGVKbkNHdEM2dm9pSlJDaHNMb0tKc2hGTmREWWRvSkRUR3RPbHpRZWRjZ1NzRXhFVEcvM3MrV0k5UE5ZNU5OUUlhZzdBWlhmQ0RseVJwemIzenRSNyszTFQxZnZPMERBb1J5NFMwV3doQ2h5K2t0RGtDSU5LMFhlRW8xaUNnK0ovaVlNam5FZmVQYVZmcllvVkJzSzg1bnZoVXUrQXB4NktBUGQyZXpGcDVNRVcweWQ2QXRCRmNyWW5DbjVZc01RSHJxcHMrMDNDYWRZdkF1YlVSOEJMSDJZdEVXRWF4bmlkN3hGbWRoZnNWZE16Ly9jem14NHJudGlSTjJNK3RzUDdScFBteGpDL1E0cEVCTGNNOUswUHFDSTBKdGpkVUt5YnFvaHRhWEMzNndWUHNrUUJRZTB1UFJ1L0o5SVZ5K3dUeVo2RVAxSTUvOHdLdDFycWxSODczTFRqenp1UVFUSXdnMThzWmQ4RkpuQ2Q3NjFWd05MMkRNRUNEaWN5UjV4NHR3ejZDblhQeFdMTXFHakpCNlVkRGU2ekxEYlc3VThGQzhPZTVBSkJ2V21pRW9oU1BDWHVaWi94UFMyY0hESDd2NzliS3JaSitaMHhValpCdHZRK1BtY0ZZbHNneTBBcGpWZ2kwTjludWFObUFrZ0tGaUtFWFhGMUZGUTcvdUdiTDdTY1FGKzR2R3l5UzNwdUkvcUhrU2hTSWI1S09TTzkwcDBvY2JIdlRvSWRsYTBONC90SGIwYnFuaHpFY1FhSGRnY1oweGtpNVFmSVRUam9PeWhFd3doajc2UytEK2R1ejdpRldEZUNVb0ozNk9BK2pPODNrdHJDUW53bEJ1OEo5V1NYeVdnRjFvWVBtKzNxNnJGZEd4blpNb28yeUNheDI5K0JHQ1pHT3lnbi9IOUVhWUF5VU41akFqMnQxUDlOdEFmV0U4QnltaXBiV2NVVFZTYUM2Q2NBQVpDbXpYcWd3VU9BMUpOc2tlZ2MydTFoTHhnWjBCL2w2cm01bXY1a0U1akpPdkU3NHJPbUFvc2tlSDI3Y0xiUXBJZjdqN1Q3QXRpRkNqQjhpeTNvZm5RVU5zckpUODNSVk56T1hPaWkvdUpHY2NORlVSbm4zdEUwK0ltK0xGY0RPbTdKVnZwbVdYZUIwVDRDWXRSMG5rbHFCTnBxM3FZN20raHpFcnE5WUFNQ2UwZ0tjdFdLNW1ZcXBTVkpZN2t0QXlMN3V5cW9DVVVCRHJyWjIzYmxGNU4zWGdmRnQ4STIvN29IQ1lkWEU4Nk9ScGhGOGRGMXMzS1gyOVZtMHVjMkZsa0FrUUZFWVdDU0JvUVJwcmVoaHhnN29qTVRBYkU0cXY0RHZheWFab3VVZTlvSk55WTF3OFhza2ZSVVhnK1Z1UVo3SjNkaVFveVdaS2RwUmFIWnl5UXJFdkJmRzRUdzVidFowMUQzU3MyK1Z5N1QwSHRFVXkzUG1kcHVQenluNW56YnFBOVdGU28zeGN0WiswblNYYW8vaExGblN1dUptN1JBZytRY1hRMWxuUHk5WWw2MlRlOG9nL3pYaHQxRmd3dDJSdmJJc0hnWmJNUWlJNVpYbjAzYkFJYW8vcVQ0QmkwbEdPdUFLS2taZjM0YXkraEcrQURmU0hQNkRjV0o4SkZxMVRVMG9TeFNzWkpQMW5Ra3c2SXJnNG94WWpjV2hOME9FRVNFTkE5RTkvZ2F1RGhwTDRvMHZsbGx1QlJRWkRaNWRieWJ3K1FSRU9VU2dkTjdwUm53WERkM3dkZFZRVzhjd3pVRVYxWGUzRzMzY1JUMVAwQS9HeU4zN0pKSDNvSzhHRnJWM2MrS1B5Qkl6T2ltVUpaVjVlbHBaSDh0QkZmTmxVSCsxZklxY0ZQaVpVdC94V0pPYnh2bGUrc2R6cjdQdkVyTmVlSmVLQWw0TmVta011SzcvcDVXWUI5K0Z4cU5aN0hray9jQm0zdXBoZitnM3dzYUI1QUZodUZJOHBhbk16SkN0dU92ajhsOTRtTXViMGFOcE5LaG05OVEvTmM5eDVkS0VVVTVJWjVqS3liTkJIbVd0R3hGVkxTcURpOXpyL3NFTkZXT0ExUUx4b1I5NUViVlJYSSsxZ0hhMkVLdUhHdkVNUXFxc2pRMXUxbnAvU2pqbUdpTnNaSUFjWnJJcTVHTEo5N2JDd0dqODlWYWRCZGdoaytEWGpXVENaOUZmUE9lbjNtK01LVHZ4MVI0SFJjOFJXcitRbXZoQzdJNG5UbjVaaDVwUjNOa2t0Zkc2UlBEWEJvcXFPcHBhVldTcTEwNldVOTNid2x2Z2VEY0JaeHFhZUJkNjdVN1RjY2IvbG5yRDc3ekNaK3k3S0xNQjlPVC8yKytyZTRNZCtCT3NmeG5RRzVUZSttVCtaNFJKT3h5RWp5UHkwL0dNd1dtYng3SEs4V01FU0NjR3dLekZzajhKcExzTndIOEYzaUlkMkZsRUo3Ym8wbXM5QXpSL05GZzhvZnpKVWd5VEMrVVRBZmovV1M0RFRGemg4enVVWklYd2pTZGZmakZtRG5DMS9vZ3ErOGt0TVN2S1RUeENwdEJpdERNYmpxNWVDeVc2bEQzTjRxd3oxS0k3clc1WDZocWV0bTlyUm5pMDh3SkJNT0U0VWQ5ZzY2V0NPNHdRdG0ySmk1VHU2WHBiU1hvNGN3bjdkbVNUNE05NFRwL2ROdld4OUl5WDV4ZURPM3BtSzF2VFF3Ly93ZU01WmliNlFjbnNsUy9hT0c2TFJvd1M5bE1INCt4aHdwdWxRbUJVU3ZDM2IyeG9UMjQ2YzZVbWViRDZHNmVxVDNydDZVZVRMOEpQWlA2cmU5b2prVW83emlXYVNNK2pHaTl6NDFJY0JEc3JuZnRZYXdLQUJVQmxKZWJVcFJWWWkycVIxZ2lPcVA4N0FBYSttYlYzZUp0SzdOT0JxUUZlcUN2WWVkbFNYc25jUXUxYTlSdm5UME9SQ3JnZU5IcWRsWEJZQmE3MTM1LzZNZ3FHTkhQNkJyRWpLUC95TTJ3OExCRTFJWEtYTEI3VFlJd2tqa05XVDVSUFA4K2cwbDJOUlFHMTIrd3EvTFozbWZSVk9wMHBMMnQwVWI0RHp1S3cyekNERXc5OEpja0VDL25KZWtETzFsN3VkeUJOUlZZZi8rbTRPWno0LzFVQjRuZm4rcXptekpaZVJqd1psek5HOGNtckhaNERvMnlwNTRmM3FJdHdJSWEzZ3ZWMUZVQ0hDeWR6Q2FjV0o5Qk02ME1MNW95TFZIalpCdUFRNWdpQm1UejhtZ2VrOUh5dTF6ZEkydmFHTFhTb2g5WXpKWUpJUmZlV1hpWk9IbHdVbXFFcmw0RmErTWdneUlCMXhQSTFQbHNVUE1KZGcreW9ZZW1WZXk3R0lCc3ArWTJTaGZzMnh3MUlkT1p5VW1qOHNEd3lZTngvUGVsc1dhUkpjTFBLWFJheEpDcWRDSHMvVXduNnFQcUlhTUphSXM1ZHV3MXA4VGpHZUpTQUFpNU1pL2N3MEdpVERhZXBKeldwL3UwUEE0K2tRWm9vcDEzTGRSMlJpZGpVaGFJdFowYUpMdHI1bE9WNTBhVy9KbkRCOXJOd3RDNXk4K2NWR2N0T0tWNW10SXhNMkNtcGg5QjZCZW9ENG9Iei9XdkJmYnREY0RETE9vTkYzdGcxYVNtTnRPR3krSUJJUmw3bzU4MCt0RjFwRlovbjh4OU5JR256QVpaZXY2RFBYRE1UOXhhS0dVcHZzb2Vaa2QvV09LakJrR1MrcDJsWG4xME5SMk45U0dVaCttY3ZxUXZTaGtIUG54Sit6Y3lBeWo1TWMrZHUvVlU3b2hJM0h1WENvYXBDTDRMUkFuMTczVk1jUktwUkNGOHRWbHVnYXcyQ20wd095cXJRVy83RHBZbFk5dU5yeG00VHRGdnA5NTc5QkFCNTdPT3Jhc0oxMXE3WWtLd3QrNUdNbmlRaUtsU2RET2hnQVowdHJ6dEhwTVJ2Znh1RkZBSVVDNDVnN2swWHFYbUE4UjhabEhtcTVxTmFyRVZpZkw5UWF5eWxqaFNxYXpqenR1MVZiS0JvWnd1MXVOeHVxenhlTGEvOG5GUkkzTXZ6dXA0OFc0aWROeWtoOUlYeXdMOGk2V3VPNWE3KzIza2xiNEpHZjNjdFcxR1YyM2FBbEhSWlkzZzdseFlER2MwKzhzSjZGREpUdm5tcTlDd3dhRkdYWWFTNFJhc3Y3UjRoaWE5bWRoRzAyVmVYOG5aM2UzcnZvNUlVOHd2UjVHSllkdGxPZlhFUGlZYkZsUi9QU0NwZTViR1hzbDkyQUJqOTkyaUJoZUZmZkZvSEJya3RIeEMxL2V5TVRqZEVseFVIcSt0TWtHTEVKQVZKUlJva2ZxQ0xIaTJBcmNQWU9EaklSNStnNmd0aFd5OWNGdmtidDFTdDBCTXVSZGJiVTVsNFo0ams0aWdJbGRpUVloRTdFMlNhNXRaTDJMOVhUY1QvcUYzaW92dHlIcHJZbU5vUzwveGVuYzpDaXBoZXJWYWx1ZT4NCiAgIDwveGVuYzpDaXBoZXJEYXRhPg0KPC94ZW5jOkVuY3J5cHRlZERhdGE+PC9zYW1sOkVuY3J5cHRlZEFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 b/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 new file mode 100644 index 000000000..75b65d0ca --- /dev/null +++ b/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 @@ -0,0 +1 @@ +PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9Il84ZThkYzVmNjlhOThjYzRjMWZmMzQyN2U1Y2UzNDYwNmZkNjcyZjkxZTYiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE0LTA3LTE3VDAxOjAxOjQ4WiIgRGVzdGluYXRpb249Imh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9pbmRleC5waHA/YWNzIiBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzRmZWUzYjA0NjM5NWM0ZTc1MTAxMWU5N2Y4OTAwYjUyNzNkNTY2ODUiPg0KICA8c2FtbDpJc3N1ZXI+aHR0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4NCiAgPC9zYW1scDpTdGF0dXM+DQogIDxzYW1sOkVuY3J5cHRlZEFzc2VydGlvbj4NCiAgPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij4NCiA8eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTI4LWNiYyIvPg0KIDxkczpLZXlJbmZvIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4NCiAgPHhlbmM6RW5jcnlwdGVkS2V5Pg0KICAgPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIi8+DQogICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgIDx4ZW5jOkNpcGhlclZhbHVlPmQwN0x3NnFDbGlva08rR2ZMTnJuT2lOaUFhQmpReWoxRnZtRVNSbFN5aUFpYXc0cE5HcUpqeWU0R1h3R0FXdFoNCjgzNm9OdTlQbzZLbngwdERWZ1BDdlNNMHBvQndINkRML3NRQ0FWV3p0MisvNWMyLzVTN04vQnp3eTJnOEFhNFANCnpwZmplN0NSUmU1OUo4WTM2Rm1OVUdMT2hDVmJQZERCVmNZczcwMDZvK2c9PC94ZW5jOkNpcGhlclZhbHVlPg0KICAgPC94ZW5jOkNpcGhlckRhdGE+DQogIDwveGVuYzpFbmNyeXB0ZWRLZXk+DQogPC9kczpLZXlJbmZvPg0KIDx4ZW5jOkNpcGhlckRhdGE+DQogIDx4ZW5jOkNpcGhlclZhbHVlPjFxbWZNL3BWOC9HcVkwL3MxVnR4SUx0VEZ2bHZjK0xhUTVKUW9pSmtqZEU0RkUrNmNOT2FacnkwMmFiMHlTUWYNCldySmpMczlGWXBnZjhaU2NFd2N1aTBvbVhVdTBuS1Q0dTlrdFIxQXJKemhnQ2VmSkR1Tnd2akVyUnJKSDJpU2MNCmxZcDIraU9GRytMLzlzcEpqV3VTRVk2U01SS2h3ekRrMnZxOEdPY2dFbTh3VGVhRFlpVEpZVVdoVWJ0UFZ1S1INCjBpUnBBTjhkQzhIakdvL2t2V0xaS2xTeUFDVzFjbktsVGhZWWowdUJMTnppRXVtRFV0UU9keG92dFJZVE40bUwNCjNuTWVjYWlpOWx1TlNPeXROQ2t4V3NMR0JhOGoydEtGS2NOSGVoWVF0T3c2ZXVxeHdZU3ZlT1o0WVlTd2FGL3gNCjJhcWdYMWhXY053SVJBMEcwQkNpTDh2KzIyUVpuenAyd055SVBaYmdRYkVuK0xIMlJxeGUxTlNvMEdEa24vY3gNCkdOckhpZjFqN1krbzRkaHAzN1l1WFBJc0VYQjViWlJCUWtkaHVRTjJ2SWw4YmdLdlJ4WVJDOU9uSXoyb2E1S1YNCkdLcjMrQnE3L3ZuY3FMOWpta1pZZ3VhUm1kQTZaZ3N5elE4Mm42eG5tTkJjRXM5SmIzM1JBaG9yR1pOYWVFRm0NClQraFVxb2xBemxuMW5sQkpCNDNHV1hVRjRKK1FYdXhVUTlVNnVlN3oyU25XL0l4SHRZcDNjSnA3Z3lxbVpkaGQNCjQ3bExJaVhQVnNTZEdKR3BLczQrRWkxWUZHTzRFQWZPRjJlZitXdGpuZ1B5TnFLdkNTdTExMzlUSURmUTZoZWwNCjNIcXphN05vKzdpVGVIb25CQkF2YTYvOU9BT3JtWXVidWZoSjBhRWQ0bGgxVWwvU1BCTjhTeUJxUVQrcEFJRlgNCmVub0pVYUQxUTlZaXZid3FlTjlDM2s5alBGdnBkUkxPNTBNSjF6YVMxc29RcENEck1xb3ZjSjQ5ai91REJ4WFENClkrU2Z1dmxxTW40SzIvL3ViTUx0N1Rra3I1R0hZRy9yaUhjSks2dExDMS9JOTVNSFlMeDlORGpjVzlmNDQreEINCmFicnBaZEkraFBIdFFpdlM3MTRXemZGNTBJSElJT3p4M3RyU1FyV0NVWkJ4MHp5blRXNzYrSVViZXVLKy8xWEYNCkR5RXNTdUZjdUZmWmcvL2E4YldKSGN0RXNUQm5XOU43NFlxUDN4MVNkYmE3VEVJSU9YZkV2dnN4QzZmeHIwMGENCjlYK1VWYk5lZmV2WmxXeGVkUnRFWGRkMEVmUG1OS3dMY2tsSDNwRzZJVkUyZlFqejFXS0xaQWM5NXdNU2FMR0ENClVOU3hJeGlFSmZsZXJsNHVFMWxPWm85Qnlhcm9makRvK3pnUHoremxsYUVJNGQ4MlZwV2hEeG1oVEV1Mi9uSWoNCllvcnhMMFJVcnBBVnlkSVoyV1hmYi83eU1ibEgxR3Z3RGo3WXFTQmJZZHM3YmtqaVdZWWVIdzdqVGdBU1NrWHENClIzRDhNYXEzLythWnZtcnZQWlBQQkdTUWpVSy90elBmN2pSYURkemVRQm9VTmlQS3RTREJ3UmF6SDVTU2E5WUMNCm55amlJd3o0aTFWT1c1eUFjV0prYVNmaldVRXNoajNKaUFVUGxnWUV2UVNHdi9SLzl0WG5uamtmTDdpUGUrMWMNCnM2R0lONEFPSlB1OTgxZ2FmN3FEMG9JdVROVEtxSXBIZmR5SlFsSURpTGszdkszTXN5dE51SEtQSi9vcWF3anMNCmFERGRJT2d1S3E5d0NUck96akJ2cjhvZ3krMTZpNkVyM0V6eU9xemE0M05IQWlHVms1S1dCMWFCbzZYM1BVZnENCmt1am16alNsS2RKZ1A3eitQRFhFTVU3UzZzK2VCcXVTRnI4citia1NTeVd5UE83cjVPbTNtZjVFN00wQmdVTEENCkw0YXBIM3JCd0grb0lmL282MHduMU55RnRaSC9Dd3lsRDdzbXVQN1JxcVJkd2k1V0cyVEdiYkdaUm9mdG9zREYNCjlHUE1VbVBsTXVaV21MdkZ0YSs0ckp6SkpRMVdYTkFYVm5raEZ1MDFsNFR0dHo5ZDM0SklKVXgrckJHNkRaTmENCk8xTWdBeGg3ek0rRGZHdE5SWllXZE1XbTBNcHlVSEt0WDhrWEp0YmtGSjZNb2Q0b1ZjWHJFNU1la0xmTFZMSm0NCk5sZ2JCT2lodkh6dWI4b25RaGZpeEZQc2VmMEdOM0ZlV3c3WW8rOVZUOEtHckFLN1BzNXVlVktUMURqSUxWTG8NCkFUOWREQ1VVL21lQjBkaFIycHVVRmlRVDFIeXNrUnVQK29MNFlCWktVUDJFQmQ1RGV5aUs1TTdVYTVQT1dBdlgNCmh4UUNSMGZGSFlEQXdUWkNqZ2FZeklDVVBvYnFLRHJZOG1xNjNwZnlDQjhBWnJkUVFreU9mb25WTzF3U1NZMnYNCjJHVnBzZ09uZGJSVVhxUVlBempoUU9hRzJ3VFBFWVhkY0dGQUNJTkEreUhrdmNLZFVMMVlXSkU2WUtpZ3U0QzkNCjI0RnpIL0wxL3ZDeTVHSXRMUkJJdGVROHk4RkNZNkNVNGx4dExKbHhDeFB1OUxiVnAwZ3cvd012WHgxMDM3NFgNCkhZdXc2YytjOGZiRXBtTjhHaUMvNk1NNjgwSEdla3FzWDFIakx2Ymt0Z0MrWEs1SmEzeDYwUDh2eHV5NVJRamQNCm1QOVRCU2NrVUtheUJMc2R4clA3STJzWkFIa3p3dTcrbEsxNHovZTRJUFZ5WHExRVZSK1BndWZCSnhGak9QUDcNCmpQWDNFR29maEtNaXVnN0RPOGNObTRLMmVULzF5cWs3dllEM0loSTNZc0QxUkpCeFJscXFhZjIrTGRNTldsdmYNCnMrbWZWSUJUUDh1bFpHV0MyWlE4Yk9yKzlOU3AxNDVmK1FHRnRSSWRMdHRsYU5kUEZpaHd0UlZYTjRmVG4xbGYNCjRmQnpaMURFVzhKUXYvMU81RlZxREdPdVliOHU1UWxrMVhBTktRSHh0ZWRsNzlzNEljRzRhMmNvQm8zcmJYQkINCjNCRWFsZWhTbmlMa0QzUzVEVlBTOFYzSnV6bnBjVmlUb2MrY3NyQ0VTNnJNODRvN2R4MjF3SGNmTU5nTlQzL1ANCjVaQkhOanllemJRVGVEVnFEcUo4VFd1YWc1LytJdlUxVTJCUTVwZmo2eG1OVTRMcXFDZUpsYnBTNS9STUhIVHoNCmpTTHBacC9tYldodWo2alFzekdubWZ2Tjl1OFpMMUlGUDQ1b3B6QnhLMlc2c3hqNThBTmZ5Sk02Rk4vcmlkQTMNCmd5K1piMUlibTluVUszOXhGbnJEemFDUjZyTU40RkpuZVFRSkpIVlQvNll1QlIzUlRZQy9qK3FJZzhiblk2Z0wNCjZsd3lxdkhRWk9saHFvbjJ3SkZLL3JTdWRudHl5d2NIb2dzNjUwV1JmM2xPeEJEVTdaamY4STl0N21rR0M4THcNClQ1Yk5IMGNMUVVXanVFT2MydEpFUy9lVFQzQnpBMFJMRTIxcUJvWmU2UzZUdkJ6aE9LSDZDa2hjNnBQQVN2TmQNCm1vVU1JckdxWmV2dkhTc21KbnRRRzREcE54UUNyMGxQdnVDYzgzK2Fua0dzZXhDUHVjaGNrTnBhcDM5Zk5KRDkNCmNMdFZXclRjYURMdlQxRHBISzkvUXkrMTlXV2ttbzMrMFVNRDk5NkF1bnMzc3hIRk4wb2tpNU5mWEQ0RkJJNVkNCloxYnoySVIxNmFBNHFYNHc5ci80RVZqVnJ2Y3NlcTE2d1JrdXFXejF1T2I5RDJwR3RGeHIzUWREcUhENTV4SE8NClBaRWlhVmV0cWx5WlQyUWFlWENxVHJVN1V4M29zS1VlQ1h1VHBnSU1IcGZodXh4dmt0UE5kemlBMmtzbTVvVWoNClZXN1dVeDA5QVBTMVo5dEN3N3pxMGJDcEg5dXpkdTdHcG9zeGc3akwvYlVOMDJ2K2pnbjJiZkVhaEQxYkRHRFANCmZIVG53aVRvTStDd2I5a3JkbkROWGFXSk8rUXdicnBBWFdIUXpRL0s0WnB2Mnp5bjdMdFA1OVR1QnpwMHVSYUwNClZ0dkgrS3REcVhZYWtlbXhLR3h2cjQ5NDBPN3RNMXFLYTJyUEpwUERObi96cFl1QStmQzN1TTFwOUcweHFvVkwNCmdxTXJRQjZDZlNCODUvVzg3M1h5UjZ0eERQOVN4VVF0N0d4d1gzR2psQ21ZTjBhK0xFLzNETTNEajhwcTcrMkcNCml3VE55NGN2S0NzODdUMlFiQUpGRTl6NDh4UWV4WnpzOGNycTAwQkVKWnZ2Wm82Nm52dE9SVVBwNzJxMkc3a3MNCkxmMUd6TTNsN1FjdjN2TmU5b3N0bUhjSFNCNHpIUmRWenE2Mml0Z21HYWVhSVl3ZDN5ajhXQmxZZlIxQ1VUeFoNCnQ1T1J5OTNOR0pnWmFJdTFibHU4d1JXajhBMmw0L28zdTZSQ0U4S2ZIOEhBWWZOemRpaU1TVnJrNVpWNWo4Q0wNCkdrTm1Ua0lNanUyekl3d3BMTHZadDZQbWMrRVdnMVc2R01TeWJnR0IwZUhkTld2ZmNQS2RwSkxZaGxGUUlUWVgNCmlEOUNjb1RiYmQ1b1l5TU5NOS92THYyRjZlR0hUVFM5SmttU3k0Vy9uUEg0c2NKRnVyYWg3cEZsQWluVktGRy8NCmxrRVFMTWkwbkVGSnpkT0UvblNxRGZVeFFxMFk1d21TV0VDWndHUjQwL0c5NG9wQWlxRXRWM3JwMlVWUXh1Q20NCmZTSGgzaTVKT3A2dS9hR0c0eUMraFVOOUJvMVRMdGdiazJXdURmQU55VVVrWWNWUlllb25xZTFmRmI1Sy9RYTYNCk8zekl3V2RCQjBNY3BocE1iV284Y1JNRWNIUHRINFEwbE1GbWNjTDgrSEJRUDg3d1VpSURydGQ0WkxsWDBlSVINCldDT1VoWUNsazVySWdTeHVTTGk4aWtsL0tYWUFGaFBqczd5U1FOTmg0dklOMlVkSW8xbG1GZ1kwY1B3dlVyQS8NCmN0VDB3ampWcHZsSWx3Z0tXY3MrL0pRR29SRjNmbURXMURJT01Vc0ZwS3B6WWlCZTJNbkZBdTJSZzNNSFFrTjINCkhUM0VpTkZ3NENuRU50dWhZVFJ5dW9paDVFajZxTk1nSFRNLys4dGdFTWR2Wi9VYWJzYU55UjdONi9VVXFIb0gNCmdISG4rZVE4aEI3YUkraCtCY3E1VWwrV2Rzek9vckNZTDY1bWYzQmtTcTJ2WElHMCtTMHoyaFVjalYyY0hROGUNCmNxOHNUdHB6Sjh3ZWZzeGZhYjdrcmUvMVg1dnFraEVTb2huUFhLTHpZSWJ2Zm9BVWhqd0NqbHA5dUlvMTZZbnYNCkVZRkluM2VCdllyVG15bmRJdGZCeUR4WllGY3hqbjVid3NBQjZScW8vekpsS0VYUU4xNS9XUklyeUp6QmpmVnYNCnpTeXdqL0V2cTVtVngyaFhuZjRZVHZ5emNJK0xsbDRqVmhPZ0hUcE1qUXU1OHc5UEFQVjRwcHdIbTFUb0JkY0UNCnZjTGlTbHlWeFlLVVk4cGdocU9Ec2R6dnp1akFOWC9zdzV6amtSTFJXeEViUit4R2VQbnY3aHRseC9mQ2pybk4NCkdaY0RmRTA2b3FWYnlhcENNdWV2OG5JZlQwZUNBUmRvR08zYjRDVmYvL1B5eEoxY2tSYmorcE5UOUxtRkQzcnQNCi95c0E1b1VnL2VvVG93NGFycnlhUi9pcVZMMjZ5NzNEbC85cE9sR0ZtZTJZUXhkMm91ZjRxUGFYRTlGcWlzUjINCkRMck9nL0hQcThQQTAzTlJ5WWRGRm00NGkwWWJwVkU2ZmtTVnN1eXZtYitUME9jdiszUkJuZVc1QWkwOE16MGcNCkduTTBhdlFiK3lTcHlDckZVN0ZCRmVsYkw3a1hyZDNBcnhBejR6enJRcUZYUWlLUGlsSk12QWNYZzVtcEZQcHcNCmNscWs4ajJoMzVVb2NpcEdSUDJpTDJUMi9rZFQrMUp3RHFZVHRFK0hSRkJneTE3UGNHdXVKZTB4b2JGTW42YUENCmkzdDZBcnMvV1Btd0tPTzFHMlo2U2pFcTZXcy90VlhJOTljTG1nQy9PdTZxRk96ajY2bFZLWDAzT1JhWW1LanUNCmJqQXhyRmVMeWpLSDVTVmVRV0FqMGF0clY5UFFpOFQ0VE5Ja2dOcENsM0NCTDREajRZWU5GZ2ZGVmlYOUExdnUNCmlKU2hYR1ZJcU50MnFHcXFoczNYck1WdU5CeDRrMi9LSUduSzVPUU40WWNDVHE2T01yVEVySUxIODQwVzZISFUNCkRzNHIrK3FIeTR1eDVSeGpMdDVoT1doU094RC9mQ2V0dFd0NXg0ajlEUDZWMU4xTXJJQlMxeGszVVNLK08vSjgNCkh0RkF2S0NLUVBqb3NBMlpKcnF6Sy9QOG5QVzRCamR4KzlRRjI0R1lFU2srUjZZRnBzNTVhMVFBZm9BbVFVZ3ANCjVvbVBzQWRQVFVPaGNBVWt5M0NxY1RLQTY1ZjVPRjU5N2ZwQ3RmMDFZaU5XM2F6ekRNTWxsdFNWTW9ZdnQ0K0UNCmk1clA2RkN0Szc1eVorZjVsSkJuVGpiaDgzOXlTUjdBd1lsV3o1TTBaU0JQVDVsQmtkdXdQTkdYeVZJZk5acHANCnp6TFlVRlhyWDc4MW45TXZua0xkc0hHVXZYUGN6QXNjUG1qUTZOR1hUbFdHMEdHdmZMckxWOWg4UmxtS2JONDENCmFBMG1JZ0FsZWwrSXQyVlRqY0tmTm5CQjBiNThpZG9TR2pSMStYTE5EUzRZVlFvSnFTRWppOWZTRGxmWWdhSi8NCndhSmEvZ2pHTEVzalo3a253QjZFc0thY1p0Wks5bXRzakhYUjBCbndNT3JsV2tvYzdNSkVyQVU3S3JwQzhkdnENCkxpUmxZbDlvUzJzMEQ3WUpxVkFxTmhtam9SdkhSWnJGTlBPZzQzZmljcEV4Y0lrY0cya1NZWVRZY3hRYnNqYUENCllsYmkwR3hBT0lQMzdKdnlhelJNcU9XajBxcXZ5b0U3ZVdiVDRoeXY2S05jcWNqTUZOdGVmZ0ZsckZ3YXRrOEUNCk9KRTEvdFdJSUlmLzNpTGtLMVhoK2trUkllSUhZZThGWEVwWkhueHgrVDRzUDEzSE5McFBtZlhNUzRjVEFwS0gNCnFkZUs5dnIyeHJZYk9Tcmpwd3NNbXc9PTwveGVuYzpDaXBoZXJWYWx1ZT4NCiA8L3hlbmM6Q2lwaGVyRGF0YT4NCjwveGVuYzpFbmNyeXB0ZWREYXRhPg0KPC9zYW1sOkVuY3J5cHRlZEFzc2VydGlvbj4NCjwvc2FtbHA6UmVzcG9uc2U+ \ No newline at end of file diff --git a/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 b/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 new file mode 100644 index 000000000..bf127f4d2 --- /dev/null +++ b/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfZTU3YTRiZmMxMmMxMjZlNWQxZTE3MjY5MGJiMzNjYTVjZjRlNTQ1YTE3IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNS0wMy0xOVQxNDowMTowOFoiIERlc3RpbmF0aW9uPSJodHRwOi8vcnVieXNhbWwuY29tOjMwMDAvc2FtbC9hY3MiIEluUmVzcG9uc2VUbz0iXzUwZjEzZGUwLWIwNmUtMDEzMi01YzNjLTAwOTBmNWRlZGQ3NyI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vaWRwLmV4YW1wbGUuY29tL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPjx4ZW5jOkVuY3J5cHRlZERhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIiB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTI4LWNiYyIvPjxkc2lnOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PHhlbmM6RW5jcnlwdGVkS2V5Pjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNyc2Etb2FlcC1tZ2YxcCIvPjx4ZW5jOkNpcGhlckRhdGE+PHhlbmM6Q2lwaGVyVmFsdWU+V0J6Nkt3bEtZRzBKcXM4cGwvK1pqeVRCT1lGaFlsRjdDdldqYXdITERqMUU0V0JLVnc1QmM1cVR3MVAvZnlSSU1DQXNpblA5TCtsZTRlR3ZRTEFxbTJXejlBUHZZaFJ5MlY2bFJLU04zY21VUm9QRGtOSFp5b0lYWmQ4TlZ0ei82SEk5akZvaWFYRmRENDhiUTVhNkFZV2lLaUdOZEdpT055WG9tc0dPUzNzPTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHNpZzpLZXlJbmZvPgogICA8eGVuYzpDaXBoZXJEYXRhPgogICAgICA8eGVuYzpDaXBoZXJWYWx1ZT4rb2R4dGhoWFM1YzA3RHRIeUFTeEtGUEhmc3M4TE1mWW5vckp1eVdLWHRuLzBKR2prT1dvQzFSK0VQR0o5ZEpZR2IrekZmSzd1akF0Si9BMkwyM3NEMGV3TVh5RHVkQ28zTkplRm5LYUNJaTBVbmdTTDZCMzQ5cUtXVDlHZ0I3b2MzOHFSYzhtSHVsaHpYdnQ0YmFUVjZmZXE4cnlIb0tiVXkxMEVsRXZXZ2t1RjBweWw1R1dLempJSnZzakZZOFJZNHhRRUNmRFkxcGRYQ2ZxR2RhQzd3dGFOdE1DbWFpdzVKM21FZnBXSnpNMlREY3dUUUJDYnJLRDJVNktEZzZNYkdjUzJWeGtoMEJkVWc5eWtaT2NocXVWYmJJd0w1dFdjR2dpWS9zbnBuU2dOWEx0ZjBSSURvdExlendtOG9kL2hmaGwrQVA3eklkVVVVWHNDYXBWUlV6Uk01Y1ZHN0NOaHBlQ25XcTMxem1hckxRblBtbWdqdTI3QlE3aTN2aWRud25hV2s2U2lmL29JUVFPMExWTXBBUXcyUWNpUU9SRk1lWml0TnUvTTljNkFwaXlvalV6a1Q4UjJlSitlcTcydmpQRHpybDJrYWdMVU1zMXJ0dFNmVVFLM0Zac0Z3bVYyKzJIM0FqaWF4bmFMYzVLUE9qMEJGakNyRzNSQy9TTmpEazZpU202L0h5aUh4Qmd2VHJyNEE1Y2tid0ZudEgxb1lKYlltY1JiQUI0cUVqd0IrK0FFZjlPVWVCVmNDVUM0cDluZEdMQzZNTkcrMzIzM1N0Mkc5ZEQ1dk54SUk2WU9MUGhwYlQ0Y1ZpYkZmRVRTdlRDYVNLVDN5THNOTEVlbUZxWUhoR0dNSFFjSFYvRFkvNmxhbFJSRkZVQkVZNWJnNWp5MzF3VGI4bTJMQncwQTN5OCswWHlaUFhCNEdZMlFIVWU5UkdUYVZHVkFoZXlFaTZ4RTI2bks1NU5ocnRnbDl4b0l2aFpMVmEvdjNCQ2ZFbnBZTCtYMVhQdzZwQnoxbEFZWjErN05abGtqcGw0L0FUL3AyMVNkZVVpaDB3OGZsMzNTVEpjWDYxdWNtcW9TS3FOL2tWUllZejY2SHFjTlpNb2dVYmx3bVNnQ3hGYU51THBydy84WlZjSlk4cHkrWkhSZ0JFNFJUclpoNld0TkwyaEg2bzdpUy9XSEgzYlRiYkEweXowMi9idGFZd1JZaGpLOUNTaDRPYjZ2MkZuSU1rMzkvSzV5VmJtZHExMFVFclo4SFllTGl0N2IvT3pmUVJ2Wnp5TnZHaWhoc3FsYlhPNUZSL3ROOG8xZFVuaVkxeURlcHZzS29LNmFUN3FGYjZYazd2RHQzNlR4MkY4NDFobHM3dVh5OVdWWUhKN2lxUnBkRU1taHBOOEFBWG5nZ3RVS1V4QTU2MTkxOGcyNjVtTXZERGgyckVUK2xBVjVIdk1WUXVYR2Zxd29ST0REaU1WRjJTZW83eGd2VWM3bHgzZCtkVU1SNFFKeHROd3hMVTJTWW1xL0thbnlXWTRYVGxZNmJzOE1pRExTdFlhYjFGN3hQK1J5S0JuSTBxdHc3UUJ3YytBNTZOUkxjOVNkaE1vYlRkYnlwMVpRdEpJL2F6dzBsaFB3Q09mZHhFekl3bHJtUVJZRld2dXZqVXpNcE51dkxPTlhvejdkbW9hQUpsZDYyZm0rZHcrUW1lYVRtZjg3K1luMHU1U0FBdW5keGZsOUNwemJLRU9JTFU5NWNRN3BKYkNTcGlkZWRoYm9YWithU1VKdUpub0ZPWHppcStpc3F3Z0M3UkR5bm1pQ0oyalppTDBxVzNJa0Y2Y1lKcHloOWMwMEthSGJTd3NkUTRrMm9Nbk91VHVvbVFHY28zR0NpSzJHMjUvRC8rU0FFTzJIODQ1UncyQ3d4RzdQRDFwRVU1Ylp4Y0VuLzlaVkR3Q1hxaEE5UW15UzZQaGZHWTRvUTYvSEJyVEZsSnN3OWRJbkdFMzhSZlZ2SkVWU0hGcklmVlZ4TVRzbUR5V0hFb0ROeTR0MU1QYkl5VGRuSldqWk1LN0ZZL0k4cndoQ2FHLzFNWG9wWEUza0Fmd1h5bkI0ZEtBUnA3T1lwcFBnS2F0NVV5Y0Via1pBWlpIMmhmMVIveFRBeExidzdKaGRaQytQU2ExWUVNUjYxczB1ZGZsN1BOVHdhUDF2Z0VSY3R3NmdDaTMxM0kxNDkzeUExS1FwMTNaekFQRUk0RkZnTVcvUmdSVEppWVhrZTkzaVNmeUxTTk9GRGNPaGxMaXpSWldqWWoxRm9RbE9vN2IxbGZsZ1pUT05mQzJzbWFtSDFZNGVUUlVaVjdxQXgzSWkxTHh3azFSL1phNm9VNHA0OGdHVUowTENCVE9WOU82Z0JSUzYxdFdSZld1aGhrUWFiTVphS3NxdEx2aW1PSW5NcFl6WGhsSEptN0Z1c0s4ajBsUGpqdjBuZUtDY2t5L0c2Zm5LTERaQzgyVER2UE5vQmVLZkRJWmNvUm9vRDhyejU4bDd3TWFkSzBUZzFqdnZKYmZLU0xrcmpidUpHeGp2L2hmTzd5OWFYNW43U1NhZ2orYkV3OVNIdlpSaWx1Qmtjb0Y1bVFOTGhIcjRtempnNlRRT0VkMmYybXhJNjJkL09mVE94Y2VFQU1Ua1VWWUpQRVUyQ01KWlJka2h5TXFiVGFJZGJVSkRzRmY1SlNBaTNuMVlWbGtWWDEwWkwvVE1TUTBUVjVWdnozOXprS29IcFBQY2ZHY1Y0Q0o1Ym1pYkh2RVdrTnN0ZXovV09QQ2RMLzFLNnhUeFB2NkNWWHNBMHJqQ2RJc0tGd0FsMGFTMmtLQkVKUmdhcXVLL2t5Vk5oR0ZWaHEwUG12RUFZZWwvbklpKzRiejZRQjlyNVI5Z3JNaUQ5dUdGMG5HbHlWRHV5ZHRLSlZYS3VEajVwVHpvSU4vM3FGQWFvY3lkWmJSMHN1RUN4Sloxc3pIUVJVTWNMcFYvdVFmVDRsMkpIajdIeVlqQUc2ZnBFbzdQKy9XelNCSjd1dkdxQXJOeGJHbzljV3JTQ1RpaWw5ampYd3N6eG8zQzlGc1RmUFU4a2FnSHJsalBhWEduMS92SFdYVTlTTVkwVjl4T1p6bUdXUlI2QmNKSW5qNXJaOFBmaHNGVVY3YmY3emFpcDdxZkJJWWRVMlRvekQ2OElnTHQxM1ZFM0ZDT1lHR21mM0hUVDMrSDhCSTBjbGtJMkt0TzZVdWQ2Q2ZVcjVmR0lGWlhLQysyalNyNlBpWG9kSkRBMU92WHM4VlZpOFltSm5QMTMxeHBMU2g5QWRVUGxPR2gxODJ5WENPNlN5Y0hYc1ZuN0IrOVUxU3Y0NXZlSWxhSWFSRVMwU0ZENVpxU215WkgxMWNHSEVwTUtZZWZwMnU1TFFxVVVsTEtReDNsN09WNTlYWG5yUmFnS0tRdGFrT29JSVdwcGFCeUJIdENCWmxGdk5MNTV0NlMvcEdwSUMzMzA1QWdubTRhdlJzMjZ1UlV2ZnhralNRZ3ZSeGltM2pkQTBzbUFVV2FEWk9KekE4STFjaG9CZnBQazF2WHBQWEhQc2hRcGgwdEVXeHpaS3dnTjZBQklZSGM3SXcvUXJMMUFtQ1c3VmpVZXpnQU1hSDNRaGNndE1QdExVbmZCRG1DZWF0ZEdUYjBBMm9iNlZFV3JyY0VnR2FOTlh5M3JrNXFGWXdaTjZieEwxVHVnTDdnRjh1NERLN2U3TTdtUzltU0pFbEI2S2NmZ2lhank4TWFlaVlRSFBWeGI1ZkZhNjhPcU96S0J2MkVKTTNmKy9WQ2xibHE4Q1Y1NUIrckFuQlVYM01Ray9aZEprU2tBWHpHL2hWQXpzb0s4RnNSQmJubWEyaFJYV0lIY0M0eUk2Z0xKV1ZYQzFxZnNXSkZzMmQvSGN0ZEVXQVNkS0d0NnFranVpMitHZFFLbnpjRUpBSkptUXdGVklObUNUc1JJTWxkajNMLy81RkpBL05CN0VpREFZeUJMcmhiblAwUkNLblBJWXVyMEpLR3pzSUwvSFBqRkNtRXA0L0w3eEE0ZFQ1bXRBbUVKeURoUzN5d0hoZ3QzQlV2bmJDa0lxdmhEZ2R2Uk9LZ2Z5RlJkNTlpTFlZQUx2TTlPUnpySzM1Zm9VeEpxMmhwSUlqLzRabyt2blhjaTQxVlZsdUpiM2dqZ1VuMFVSbDZhN1ZBQ2RkQ2UxR1A1ZmI0NE91YlJBbzdHYVpySmFkWnpWN3RQVlhnZ0xFVmZiaVovWGZVMjR6YzR0aFIrV3NtMEJTSmlYOUxFQ1doM1UzWjlidHZQQmx6b1hvSVhxZmhFQ1V6eGIxRUtOQ1dUUjJBNDVyNWJwRzJoVysvTmVNK2dqNUN0bGhubGQ5QXFHZGlXMmQwLzljMFdxNjJSM3RJVklScTZ1WFk0cVBjcUlWcFNaeWtPcHVnM3dkeUJBZFYyUXp0MUVTTVFaenVXWEJTSnBMM0NIQ2YvemNDRnZwWEo3RFM2QzNLeStteEU0L1ZJUlNmYWJYWXFJcWpMQXg2eUNLREdLa1N2c0k2QWFxTTRNN1hVU2tHV2JiVTN1SmlDRm1CMDRKS1U0OGc4VG4yMlh4ZXNNMWpobnY1N2I2Wnl1VVVneDBJTWJacW1iNUtBN3BWcitCdTFhaUR3L2VLU3k4TGplbjRBd0xGS0xiZGRTd2luSFNzWGpIYm03NDV1VEVZSm5NV1NYalRhNG5PMjhmRGZLeVcyM1BwWVNuZmREMjlEN3NMdkt2VFpEVWowdjZmWVhFZTNQeGk0czRQVVJNYVVPSU1jTFNmYkN3ZStHUm5FWXhRRUUrQkF3K25PcHVrNnVjMWUycHZHMWJGRHJ6UWJUQThtTDFmOVVacER1MlpiUDZUREFZdTBWNVVGVFpUWWs4cWNDZFBxcy9PdlZwMy9wTWJ4eWNlQ21UNTdEeWpLNHhnNEdFOERWSk5GRHlxeEw1S0dUeENMeUVpdGtDa3JubVhGdTNKV1NZdVNOa2RhbUxFUitIMVExamQxNE94SWdoYXdKRksvMTJYZElKSjNzUUlxNHdWV1NSV1VEV1h2eHhyRlBOSktuNUdRT0NJUEVRRjYzL0NNaDdlZGU5WnhTTGVQZW5qb0N5VjZIbFFQSUpwOHZXTFFnYjR6dC9EdXNReHh0VmJpR1FNVTdVUVZPTEJFeGRGZUFteDdiTTNuditla280SVVKYWRnYmVFa2txOXhPZW10dEh1RnhmUWFiNWU4WFl4ZG52bDM3NnR6bXB5Tm5jenB0T3ZEdHkxWnJBampubWhlTkYxcHU0d3ZDZElWS1Y1YmZxb1BaWTJVbnhURHlZemR0NmRKNnJEOG1POGNwdGlmRUp2cTRGQXdNWU1kTlVveVNtdGZTWmQ0MFdXNzlBa2dPU1NwMXIyZ2NPN2h2TjF1YTRBU2trVjRPeEltYlRhSllVWTFnZVFScTJTTVp1UlZhU08wNEMrRmg0N01ZblhncGh2bU80RkxYenY1bWVlQUNRWko3bHZldjFIOHZSd2JjTGZxejNtRFl2ZWFoajVlMjkrSGtEVEFDYXI2d2UvaWxrZGttdXhnWmpoQXY0UUlSQ1ROejE5WERzcGdiQ0JHZGZWT2k5ck03aTk4dVpVY1RzcDlPK0doTit5MG5Ca0R6STlxdnRSK1JFSHZVYnhsUEhzNTZyYzNXMFZ0bFNRYkF0SVA3M2t0bkV1azIvVTFNUElTcm1YbWs0WXp5RHNIRTFNUHJaVFZHTzJDQ21BS2NCQm84V21TZm1jd3RHNGVmYzI2Mis3UE41SUtZMmp6eWRNNHRDVk9ObHlFaEJPTUVJNHdNUjFlZGtLZ2tBR25sbHRtMndoUTd3L1lpUkJFNURXN29BOGsra2ZFaTdGbXQzR2Y4Ly9qa3hOUmQ1YzRlZ1JrVWJaVFZDRXRyWDJUQTVzV3FNTXBNSWxha2hYZFdoZ3p0UEtZQ1kybWFrYmwreFkwVkMxSEhpSEdMSFVjeEd3WnJPblRicVJSUkEwYXNjYWkzcDh6VVc1TW1aWGJOaXJUY0tGY2JpcW9KZHc0T05PVGhFV2JCeDc0TVgvQXozZnlpRG5talpSVlZ0RjlWQ09GU0JpK2lWaW5lU0JGMm1HL3d2YzdDdnRITEcydC9BMUNnL2RieklvWnJZVElreWg2LzNBc0RDMS83T3RjZEwxNitNSXM1RXBDbSt3aU9pOEppRlRvejRVc1FRREhNVm9iMndlWnFpR29ITHJLVngyM05zZWd3ZW5hZjZ2cVhESXExa2QwUXNnbTl0YXpNSTNyTVplU1FxU1BIWG5DV1Y0RlRmdDNQNTd4OGNId2ZtUmUwU1RNRUE1QjlFMFZqVDBWVWZtUDloOXBKaDNKb0RaY3gyTmEyTytqRDVUTDF6SnRJcy83TlB3dkl4MDVlWVByelliNWQvOGtGemF4V1ExRzUrb0VLNm1KckdaYkxXZzJaZ0dmNGFXLzRGaXVySTdxNFZzMDc2UDYvTGlsKzlxRFdIWFRJWGZIOExITkRhWTh4eFpoazFhT0hZMzdRQjNQd3dqMTljYkJWMHhRcHIwQzdlb3J1NnZnWmpEZHN4cTRralZQdmFUWWhjM1lPQk9qczhRbC9xaTVxOGNKTlZGSXUvZk4yU0kxb2hWSWU0QXdYT3FmV0kzOHdpY2l3ZVpmaG9EWSsvb0xrM1ZKMGl3aEdnTW43dTQ4VWxhajZwN24rL1BlUEMrcWNhNVR5WUtNTkNxUHUrSXQ1TWxIK1M1S2N1eXU3WHFWeURRdmJPTGZhdE5JWFZ4RVZjQWcvQXpjQnpmUVVVMnU4VElUWUl4TVIyRHBrY1JidG1FMER3enRIRlRPWCt0NGQwem1neFlqR3EvcmYvenZYUnRyM3BsT05ON2RyM1RaTWpTWHB5WUljSHRBSGZsR1FBUlpRdVZDamR5QUc1VmszZ1dkZE9qYXM5Z1V1djRrUnptYjJQNk9sZTJlKy9WRjA0a0tWd0dXNGhtK0s2dlhDV0k1bnArRDNKaDQyUERtL2VjcnowWC92NXgweUFieFQxbWJBMnBoeG1uVUxPS0NuUkhzNHBoMk1iVUhCdTBXNlpqUi9EdVpwYnF0SDBzR1NBRHJDSWVzcDZuQ3VjelpmNE1ISVhCbUQ5azNYdDczSE4rbllCVDBlVG82L2cyZDl3TVNwVTVXU1p2cXVkcVBuM2t5eDZPRURueE1ad0FjTk1YMG95ZEJYY1ZodE9VM2ZYUmp3YlpsUjF3eXFBa0tETk8vRzFONHNQZEJBa3FhR1RHVGR3c3JzNXhqcnB6bWEzd0RycHY3Wm9CMWlkUjNtSGRKdnNtdnY0blZmU2lYaG5wN3ZoakI5L3B5MWdJZ01ScTVIalpmaTl4Yk1XUjJYdllhV2ZlRTJTbkQxRFhoR1pZekh3djREU2w4NU5CRUw5bFlSaTdpbEYwMnpRU2NDcnplRkR0Z3ZtQlVSNzQ5UHA4U0M2am4xVkswSWVLbUpmaHpqWDFNbURzV2duTVJUYVRTbVRzekVqU2RacVFWZVhXTzVjR3RRY3NYV2tZSHNEd09rUEE0U3NrUXAxbmVkam9BUWRhQ2xQcTRIN2Q5LzFEM2JueHEvdm5xb1ExNDBlVTIvV1RLWUplRVo1dlhNREMxUFVyRkdKbVUxeUJCR0VmMnp4ME42NG85YkxCczJORk9rNTQ3aU5CalhDOW1WcnpNaUkwWDZ5WWttczk4b3Z2M2Y5SDlnUGdtbGxEaFJkSzdadUgvWGsvMnVRZXkvclRwYkdKd1lOckpoMXNOb3cwL1dEYU4vNldMV1NvU0F6eEZwZXhKcjV6NEFNS0JBSWFrUHFFdjBTZ1RzOFFvZGtjR2ljSGFNdFg4NWVpUkdLZHlsN0pLV0krYjU5MWZMZlJJMVVsOGxjeVkwRXh6MkxhZTk3VVVwT1FuSHBpSys5dnNqU2prbEFDcndaMHZVRGFoNllHL0Q4OGMwbnZDU3NoKzNNYUJsc25Pc2VyT01aQWF0OTk2emRKRkpYRkhFOWt6dytrekw4N0VJV294Nk1ObXdKWlZ3dUxHRkgzZkY1bU0xRk8rdFdCdDZ3UFh2b0R5dGVnY1BGOCtpZ1pJYzN3VkpDZm1iTWlnaDZRY0E5d0YvU2p3SWYyZ3VpdVNORDUwOUFVN0U1aTZYK0FCSGs0OUdSSEdlQmZLbkMrQzc3T1pVT3JpeFErUk1aekhVdUZ0SmZKWWJJd2VxYkpZdHhoVWREVDNGK2pYV3krcWcwOW9ONldPNmRFYk55MHN0Y3BtSUFHLzFxSmwvd3o3b3R1aDY2bnV0QW9TaHJ3eElyWjVEd0ZOUGdsUnF3bzFTbENMV1EyNGc1QzAzS21Fb29NSHdFTURtYStac1E2aVJlbllFVkVLR09ET1lqbnZyK2psZktLQm1tWVFDSTA5WjNEMzZWWEdkM0JNV1NmZXVlMElLcUtsb2w3bS9YM29wdGlpY2R5Y3hQbmVUZlNxMVdpVzE5NWVscTlPMysxQTFabEZhVmlpdmpjcHovZzQ0ZTJBM0tJKzE0bWMxYzF2ZFM3cDUvUjJ3aEUydVcvSFN2enREdlZqQi9PUUVOdTBia01SZVVwTlNYeE8xNjVyTkViU2p3SjAxQld3QkxXSmZsTGp0ay9UdUVyb3N1cXVScGFHZGI0ZEhrTHZWWDM3Zjk4ZGpqeFdoU0hrb0NlakVaWGtVSmc5a1l5cE1hMnhVd214ZFRGNGMyaVZsREhkNllVcjRGbGRzNHNsTFdOVTNHRWs2RFprSytaaFk3emRCRVloMUFCMThmdVhacU9qWGMrczZrZk9GV1ZzbXQ2aCszNGd0anl4YXB6STJBMUR2TDlnTFV6ekRLR1ZyZ0VJNlA5QkJPVnhLWXdFakNZY0NZT1JJYUdxVTdUZ2Z6bzNtRE4vQTU3MllXSnNxc3VoRDVJRHZiblhVU3RCdHd5TithYjF5dzZjRlBEMzkyZUlFY2t1RklhWnlrQ3NZMlNvbERqWU1HMjRiRFo4bzBOL1Y0N05xancydlNjQlFkQkQzczRCVVp2SGhoa1NNYU1YRjhUY2I1NWMrSGhva05IVUNSOEVDT0lKSUZWSGdoTzVaNzliNDlOQkhrNHRVdXFHMUxBVFJnOVlia0c5dCtnTlUraG9hQm1QRVpPQWtQVkljaFN5RTg2YkZlbkxyb0VSeTM4R3NIM1Z4Y2hOK2F6OEtNMzg0SGFIUHdhQ0NOUDdLZjh6cjBZWkJuWDJtWDlVWkZHSHpYYlBtSUhBVFV1Nmtac0NwY3QyVElOaHdlNDJGWXpsS29LV2RMakFiRlN0K2dsb0JGbS9Dc2RiYm1zNjI5dDE1YTJzbEFnblMySXB0Z0oxQ0lONlo1cDFwcEw1TUl2TUhIbGRLUk4wODMwWlIzMnlZMHEvcWhPdnFzTHhpdTVxczdPUzV2emhVQlVNcURmc0ZWd3grSm5FS1RBOCswV3YvbWZSYnp5UE5EcTBGZW1RUWkrN0lBMFIzNkR5OVlubnVpd2RpM3BvU3VXcW5WajBCMG0zazVHMXRYTDliVUdQMnRjMTlWcSsrYWNyMDFSbytmWkRnSGhOVU9oTi9ZU2F4cWJDVHlGV2ZGcFBHSUEyTk84OXJOYk5adzBxbFB0OS9PR201c3ZOdmJ1V0pTb3ozZzhMVzJxZENYV1FVZDB2M0NjTGhnUmZiWjlPT21BUkJzUDlYejRLZzBUYXFld2tYd3JGQVFLTk5teHYzMnM0eWRyLy82QXRqalBWTzRhV2FEVmVSVzFFSFlsQXVqSkw4WWRBSU8zYTBzcTdGQW93UTFkbkpCK1YvVFl2VmRMRkVpRlJEUVJmcmlHSTZsVUluUHZMUXVLaXk3WUlrOExlUDU2L1VEQ0srRWlJcE5CNnpId3p3bU0vTU53dEtLendrTXorQmRZUFVMbVZ3R0ZoSW94MGJuY0ZXNzFJczhzaWwvYTFiZnEwVkk5TnMwSHpEVVFYamFTaVJxY0hXQlgzQkpDbFU5bXl1Z1dTb2VZeFdTd3ROVTJISzJ6b1JzQUVyMjZDNVVDazBJaXRzT1gwN0doaU5zOU1ZejFBc0ZWUVRLSjBtWDRFR2I3eURXU2czZC9UM3EzK3p1RjNqSVkzNHBqbjl1eWFBSkV6UnRnWmVCOXNZOS9qd2o5ZHhwVDBlb1pqazFFSHBhdkw3cVJkOUx2Y0U2ZllMTysxRWdsZkxFT3lzRm5ONkZjMHQ3UGkxVXdHU01NbXhuTEl2V2pBV0VLcFd1RGUrRUJZWW1CUjJhclhRZ2ZPRU9xbDNEeTR3N2NXbjA1Zz09PC94ZW5jOkNpcGhlclZhbHVlPgogICA8L3hlbmM6Q2lwaGVyRGF0YT4KPC94ZW5jOkVuY3J5cHRlZERhdGE+PC9zYW1sOkVuY3J5cHRlZEFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 b/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 new file mode 100644 index 000000000..96b55d787 --- /dev/null +++ b/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 @@ -0,0 +1 @@ +PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOTM2NWZhYTNhNmY2YTIzYjVjZDc1YTc0Y2IwNzg2ZGMxOWU4NDUyMDBlIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNS0wMy0xOVQxNDowMDozMVoiIERlc3RpbmF0aW9uPSJodHRwOi8vcnVieXNhbWwuY29tOjMwMDAvc2FtbC9hY3MiIEluUmVzcG9uc2VUbz0iXzNhYjdhZGIwLWIwNmUtMDEzMi01YzNiLTAwOTBmNWRlZGQ3NyI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vaWRwLmV4YW1wbGUuY29tL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPjx4ZW5jOkVuY3J5cHRlZERhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIiB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTI4LWNiYyIvPjxkc2lnOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PHhlbmM6RW5jcnlwdGVkS2V5Pjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNyc2Etb2FlcC1tZ2YxcCIvPjx4ZW5jOkNpcGhlckRhdGE+PHhlbmM6Q2lwaGVyVmFsdWU+eGtKbjdMUUhyVFp6TGRyTmVjMzNoTVNDNWRjOEIwdU00Znc4SEtod3BrV0lOd0M5ZGVhajBRVno0Zlo4Mlp2MlFkUDN2NnIwd0ovOFZyVUlGOHB1SlRIU1UwdTJFVVUrWHFCSnh2aXZHcDJVaFVQQ3lXbWlyU2k2ZWZCbldxTUtoT3o1WUpvYUxFdTE3eXcra09uYXZMWlB2MmJCOGI1Ym8ybVpQaWFQRzZzPTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHNpZzpLZXlJbmZvPgogICA8eGVuYzpDaXBoZXJEYXRhPgogICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5FUmlUa2RKekpOODQxcUlSd1Q3OStsYVRRWld6ODNvc29Lb2hSQklhQkhOTGFzR3VCQXBSNmlKcXNTclFiMi82d1dkZERhTk9xYm5CRVkrNlplMW1TekRqWW9tcld0VkREMnBzcnprMVQyOUc1Nkx1VFFpT3RUdE9VbE9QT1ZVa1M5VHAxUmdDWG1pN05vNDlsSkpIeE04YjUzWTZoTmpKUWlTd3JFZHhocnY4M01TdXFZYWh1VDVuMnlCOWpHZy9abkY2SVAyWVhMVDdjZDRrWVJqLzBnMG1FQkdBSUhWM1pHUkxWU0tnMENNVlBuUVVDUExYcVZDS29SYkYybnQ1UjhRNmdHbUttLzJ6c3lFZkR3L3ZXMDc2ZlZnNkVvSytYVlovTS9nSEtjY0EraXllSEtNTjJrL3lXZUhuN0ZCMzJqWTlvUjhDdDZtVzlKbGdCYmxpTTFTQ0RNbnNodUpYbEtueVh1dk9mSlFkMTJOVXJ0OWxXQUN3bktRelNaM3kvYjVVdGhhbEFZVGdIUFZ0TDg3MWhYbkM5TVRlVGZBaytIcVY3bmZkUEpUay9qVkRJaTBXc2JxbGYrcHBDMW52UmFiOG92V21mcFVLRlloSGhpUTZLNlhRR283eFQvZEtxVjcrMnd2aEh0dllQdDhpVzc2UmtXdEhWRWpaYUhqZ0NGL1hHcE1yajVDdVg4S01RM2I2N0NJeGIyOFY1TXhuVVhySlYxcTVUbkVGNTZidmxHNE5OcVFUWVdNY3UrYmhjQTV3MUUvckpaeFNLZnBnN1ZkVDRSTUEzczBaQ1hzOURSN1JualFQOXN6cnl0YXJNUndGK0FJMDdBeHQyL3o3MXQ1NTFyTnpDZWROTGRRRm5GYkZoOUlnRFNtWWluN3kxbXhzYTdJaUxVWmNKd3RKVGgzM3RLZjBPYndNZmo2OHZXZ0VKMEFxOFpqbmhWQjdDN3NSSzR2aDdOT2dsZFMvQ1haMkw1RTVrWWgyaFZ6cWgwRUdKcmtPczNSYjMvbmJaQ3dPNHpkQVl2djc0cXJQVWtFVXhHMy9wOXdqY2crYlNGdWxjSkNwazdpMCtObnNvY1EzT3BEUUdVRmh0djZROTI1aDJMREFZUVNrUWdJdlBucWJ0K0E2VlJDbmc2ZyttTXdiYndQNm8vYWdZMkN5ZHVFRDBRSU5Bdlp6ODVDcHRzM3VmNHV4SkVuYXp0REdUMVJCUEZ3aE11SlZ0N1FlNG5xdXJSZnRDaCtUNktpVnZ3RTZDcWZ1UkZRbStTbFdGeVlxNXU3R2RSTkZzdVQzWUtqaTNtRitVZHFnTkphKytSNGJwT3RabTFRUXp2MXFTUzZVQ3RySkhiakJYaFlEaWczZEV6MWVya01XQ3d3amNpY3BlU3JqQkZjMGxjMVZ6WWZtMW1PWVdCSFRFMlJmRG5FVXp0T3pCVW1pQlZEM0dYbVM0eHJVRXJJcXozUkhlMWVyRzhId1NaYmZzWGZPcGZSNUs4UnNxTDVBOGwrdmpTTjZ3ZlFHQjF1SWYwN09QcGx6TEwvVWxaV2NQUXErajBHR1FBZC80S2JsNWdpV1NTYnd1ZWFXOTUvZFVKUUdjT3c3Z0l3OUxJUmFzQVdiNkpRMC9UUmd3VSthN0hvaitjRkFkV3hrZVY2M1Y5ZzRCdndPVzNacjAwcFNvMkc5ZytLVXNBdUZReGs5aDljREVxZEVNMnZQdGZlU2tqcFhPWm5HT3JSTlZVRzIzbGdKSEVRc1ROYitWUWI0Uk9GSkJWampkcEU3akhNVTlZYUs4RWZRb2tVQmZyKzhPdElkcDkrTEFVbmZaM0pLR01LT1JPdVEycnRqYjNOQ1o4c3k1dTZ1c29tMG1iY3hIaVZxQStYYkZZb05mZXI0SnlxSSt2YW5TQytkWG5VRUhuV3hGOXM2T3FtU0xVTkNRMFRsS2hYQS82U0VCVEM1TGIrTnVzdDgwdFlIU2dCbTJTNDlhRkQ1c1Iyc0krOFNDZDNmZnRjeVpFU1pDSDd6SC90MGM2Z3I3QUVJUEhhcm8wam1KUFgvMlN4Ym9mMFREbWQxVkNQMXZFUUlLby9qWGdpUHBsSVBNemsrWlRLTENuNVRHamxCWFFNTGtFZ1hRdUZjeGY2a3duV2d2Vis0d2ZrVjBkU3dlTUY0UklxN29VQk11UmNLTFp6c0kxeEsxK09nMW9yb3Mvb01nbWtmQXZSK0xIbi9xMnZsNzNIZ1RNS3l0aTV0QndXSkJER1ExdUtQNFVEemxPM0wzU1U3MnhUMlNlQWthdlU2V0owZno2WFhSVklZbmZrZnpKNm5VcFhZeEVBN0crOVgyeklCZTZ4OW95RzFyT0I4dE5HM0l5SEpYZlZ1ano1VGFNVnljUDVoRnFCeStFTjJXRjFWdTBxTVVnaTdaWTl1MnhEbnJJOVhrWUQrZDBoWGhDNk93L0V6TWkrVEhWNncyTXNPaHk1RTBGTVFQYmlpcFJTaXJ6eHMwYlFrS0xtZ3hreGF1bGFXUHVDQVRjMmxOWk9iMVhwM3kyNzdUWDVJa0pRZGJvMWg3NzJjVVRmL3RmVzZsYi9teTM0VmJGRUpFdXNNWFNQeHdYYTJMa05vcUR2SUJQanNNWW5vZ2FncmlWV2QrV0RJak0vU3FoK1JoUm8wT3dwL1Y0dzhLaHJDVTB0RkkvZzFwbjU5aTRtcmhuTVV5ZVpkWVNQazBURmROQk1la01CNUtxM0NjOWtIU3BlNFRDQWtXa2tuWjlxS0Ywc0g3a1FuRHpmdldzckxESGR2OWlqWC9LeitKNHd3b3BlU0tjTGJqT09ScG5OZDBzQXVGNEZod3VPaDl0OWJIZ3hJVk5waXBjZmlNVUt6eEkxSWNMUWpGQzdhTnVjL0srU2RXNG13MUw5ckk4SmloTkgyMXZpZDRoOUJaVWFZMThmZWRXdmR0SCswVzJUUHNIZ01LTTVLYkFEUzZpeGN4YUs5cUJBRUxEYVJySXdZMjA5cERnRElqNlRVL0lyaEI1U0tHc3JFb0VudnZ4RFhJZHM3dlpQZmJuNlE1S3BoeGNVNjZ6TGhJMUF0R21Za08xdUlSUnJqWDNBMXpjaGhIcEUyT21URmhDRExZTUl1R1BoWTU1STcxTm56bjZ5c0YySEhueFJCV0VJRVY4YUVGRkE4N1V6cWhkek5XSTNzTWN5N3lWc3lMeFdwZFNCK0dwc0NNOXZVMStCUlpoMTBLZU5ST1NRcnlZM2xVcC91WXBXQ3N1VFBNWHVZdnFTNlJTZWJGZ1Z1MzhiZ0dmYUJ2TElMZmIvWWtjSFdHUC81WW8vbmRFZ3NUSTJwRFRxZHBEQXJOYXhvSEpKK3VDMURoUEtzUWdjMU9MdGRjSVI4MWVIbjlKSGRVS0JSV2NIVSs4R0JFY01tRmpIcmJyN1Y4ZVZQT0V1VG9kTzFnSWpRdVVDME9qUkQ1TzdnYWRQTndNRzVKVjNRODc4VjhzdGtyUUZQeUZkZ3NDU2pyUFBnZVhpZm1zWkRlVnpWdytiam9BQ3FiVEhjWUdZT0VZVWtTZlllNEc0M1lQL3dlMHdyNGVoSlVvOW5qNHRhUDhJTjhubm44WlR2dWFpOExIVmdSWit1WFpJN3FKdk9Ub2oxczhxQmw3S1daVEhSdVZDdkFDdlpwN1Y2bXp6QnhLZFF3QjhBNS9tbnAzam5WaGpGa0RUemN6QkRhUHF5aHVyTjNwWVZkR2hWNjJUVHJFQ2YwNHUwdXZKWS84NHY1NnhIc25ET3cwUHRLWjNLYlJkYVFUNHJJM1lmUzdGbEk0amsvdUljRzVPaWpLeHplSzh2OVVDRG43dEFvREFUa1NRdmk5VG45Y0pmVWRMTVlxS1hOKzNDTEtPc0pIOTc0Yis3OFpoRnBsRDZ2Kys2UEh6WmFlZWxQa1AvOWE5dllVYkRQWDNSeDNieFZmY1ZnTzNNdm0vMW1CL0Q3MXEzM3pFVXJlUzBxQlF6THFXTFNPbUFjYTl1WEFzVE1ydlVCamwySXI1TlhjTHZwbTVrVU11M0IzU2F6SmlEVDIxMmZQdTAzeFQzUElZbEdNWHljMTBLbUZCeVhldktBY1JUc1M4b3Y3SUhuY3R4bzZBczIvVXNpUVUwUzJTYTJmeDB3N1Q5K3lMT1BrVVB0cGxDTEhqcVRxM1pEc21wUnBEVjNYWUZNVmdCTE02THFLakRlcDN1SkIzUTFQWDFwZXdIM0hrbFd5VjdXeUlXc29ENmhGQUQzdFJpbnJJT1oyR1l2VStGZlRmWnVKMkJsNlVHcEpWM3ZZYTRNblZ0UWxweU5Lc09vQ2Z4QU80eGkwY1dvdGxtSnB5T3o3UGxFK21La1lDS0RsN2kzZzFuYnRrMnZDNmwxWjdySmFQa1lMQnNqVzRXdWlmT3M5aC9EeXJtYWVTazJCR2NUMXc4dmdpRzl6c0hzUkVwY083SUhGbVB0VmlOZHd6UlNkKzB1ZnVicDFnNDJ2Ny8vV2dOVS9zd1QreGVVazVFdy9kbm5uVmpXRjdpaEtHK0xqQTZaRzVXdkNZcWV5VDFLZmZDSkMyYXdTTDhtZ0hUcGFCSGZPNkRiQStabmgrNW1OaUhOZEQ0S2RDcVVOdWFDVzNPTDNmdGovZmd5WExtNEpRbDg4czNpeFBrSFdETFFra3ZOSEpmNFpNRDBaZ3p3MEdoWjlkRUVsY3JHclRTcTJLY1gwd21BQzJiTzQxRkVmdklpNFlHVE1pNDRxMVVIN3NLaU9hbTRiRHJuV2Y5citlY1VodXRDaXZxQVk1Zys5T1B0M1ZMZ2RFSHY5SHlVazJQallCQVFKQStuT1NIbkJHeExKUVhEMHVYWW9YM1JyOGM0d0hDeXlDNXBuUnRXWDZmTGlhWHJydG02bWk0Qnh6VUcwRkdCV05qdmFDdmJnT3hGTjdpOHhzb2RlOEZFbERVRllYcDhNRzN5Rmx5RHA3RkhFWnNXd1VYNThFNlpqK0hqQlVFVGNZUVhValgzZ0JDMlJ4alpjejgwWnNTYUZFSWhzcGhJQXpSRXIvSmNmUmdwSkg3YjBGdmJHL0VJUUJHTXdTWjFMSzdaTTBvWEpZK1c2QkdTaEt0cDhlT3I2RWo4ZnhjeFFvRHhJRDFIL2pnS3d6bHRLcTZ2MWZwWVNiQ2pkK2xFdXozT0JSTGErYlZyOHJ6U1JlekoxWGJRQ3ZDRGsvS2VRUzJhdTNGT29HSWhhWVR6RGVjOEd2RXgveGUxZ1FqdXVtQ0tzZkRPQWRUazJSTkN3N3p2c1BHaytJTXRlTVVOSnlhK2JFSzhqMXprYi9FbHhjNnNtd0FTY290WjJtZUtlL2xoaDBUd1g4blZCSGpUUC81Z1BGR2w4ZmwzNmFKMURxUmlqOU82aXYyaDlkY2tUMXhiQ1dZL3BXOGdGNy9UeU9Rb0RqZS9KTFpnOTd4aFowUHhvUXlXSG9wWGxOYUtpdmZEWEY1c1Y3MmxjT3phelNQMzJtU2VsSnVoNGtFUzMzMmRwalFMc2lUMlRHSzd0c04zcjc3Uk0yaUtwQTNIRS9YeWhqeUZxa3dJNGM2RTd0Q1FtWTlDUFhlTldaeUxXWGRMLzRGZ3ZzN2VsbVFqaFZ2aHgrOUs1Q0x2NU8vRFlUZUIzMUw5b0YwekxWNk4vSyt2ekNPRmFGVDZ0STZCK2ljWFV0Y2N1S0djZ2dJREY0Ti96K0FLQ05LMW0yVFgwUDBZN1pQRVNkVnp2U29ZUXFZYkNSeCt5R1RBQ0VIZDZjNzJ5M1VsZ2VjaWZzY2RKOTgzRitRREZqZVpCUmY4QzROaFd4QUx5cndxQWcxdEtKUFg4UlhFcFFmSkRIb0dOZElGRnZlWmhrdS80aFlaRjZCMkdKOGwvZmdJdFFEY3dTbnFjdExOQmx3VkowUDVjZ2JVM1NDQVlRRi8rbGRCME5KaUo4Z3pZT1FDcDJOOGtKRUVsNGNjNWdxMjh2NUJ0bVVHaEE3R2lsUzRaU0hzNnRKSXVxZWdvOFpXS2lyNEtJS1pnNVFpaVRmSy9zTUU0dS9aYWRLSm5KNW1oQ1RFK003SEcrVHlybW42cmtnc0hIbjZzVGM0cHgyM3h4SUgzUElNNVlDUXdLMER4b3BhZmxYRFhVQndZWVVyN21POHp2K05Mekkzby9ua1F6d1gyK1dwM2NiOXFFUy84TjdxNmgzNjUzeThqZ0tZaSsxVHYrT0RMVEZheDlRa0FHQUtOc2JlVzlkT1dDR292RFNsY0Eraml6Qm1PK2FnMkR5MUFvWkVJL2lveTlyZm1OV2U3ck5DdlIvdWxWTnN5ZUM3Vk8wWTNvNmc3SkN0dGpjNStCZWg0L0hDVGcvQ242bDEvL0xodTFqVmVjU0NaUU9UdjV3TFFLcUlURjQvTnRuZUd5dFdrVXNyb0p3TXUxUGpDRkJDcDBJOTM4dEJrOG1DcHpmNTdpM0M1MEkrdklFRk5ETnEzem1vU2tjTWFlOFU2S3ZwMS9lN3A0d3R3andIQXZ5N1B3ZXd0WXpuZGhUNUN3cUVxMXVOL1Nya0RsWGFDQU1aR3Q5akhUek9OdmlYam94N2wwZnIvbDhISjd5L0dMbm83d2RFMDBhMEx4emx4TkdOZGNLdXFyRGpuZEUzUW1MVVhyelRJQ1NINysyUDVicWhvZVNqajQ0SEF1L1FrREhwQnFRbDBrOTJEaUt5NFVTYlVMbnJBbFJuOGl4MHU5dUFaTCthTXdyRVJ5RnRJb2V0TkpNcldwa0VIRUxnMlY3NDRxUjNRWThRbmp2TDlqbUc1SjFyQVhOQ2sycWJGSTRTTFlPK2JMSUFaYXhJY1hyUDVHTWYvb2pZVVpFbkJpbnkyRStKV1I5VEZSamgrOHl4WGs5L1B3SldJM1JESkZkVjZyWThyc0ttMU0yKzAxcU53akFZS2tXVGR5QmVnM0psdmI4NEJvcU93UFBvSkhYS0IwZkpzWTgyUi9Gb1ZRSzI2YUw2YkE1eGNBdmZWc3p3U1lzKzR5VEpBMnJvQT09PC94ZW5jOkNpcGhlclZhbHVlPgogICA8L3hlbmM6Q2lwaGVyRGF0YT4KPC94ZW5jOkVuY3J5cHRlZERhdGE+PC9zYW1sOkVuY3J5cHRlZEFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg== \ No newline at end of file diff --git a/test/responses/valid_response.xml.base64 b/test/responses/valid_response.xml.base64 new file mode 100644 index 000000000..6f289feb0 --- /dev/null +++ b/test/responses/valid_response.xml.base64 @@ -0,0 +1 @@ +pVdbd6LKEn4/a+3/4HIeswjNXV1J9mlBDSoqQY3m5awGGkG5SaOgv363eJkkk5k9e58nreqqr76q6kvx8GcZhbU9zkiQxI917h7U/3z64z8PBEVh2nrBJE1igmvUKCatk/KxvsviVoJIQFoxijBp5U7Lgsawxd+DFiIEZzmFqr9zSX/tk2ZJnjhJWK/p2mM99UpXQZyA5QYjKU2ZQZwoMQpny4zckOWG4ng2Bo16bX7lTDGoKyE7rMckR3FOVYATGSAzQJwCvsXzLcC/1WsaJnkQo7zy8vM8bbEsStP7aOei+zhhCUlYh6a7izAFjK/JT5PH+v88R0SCaANGwZ7NAI7HjIMQZpQGbzs2J9i20Kg/VWVrVVyyp1MEcgmRxDhMVkF87yQRezLiH9j3tg8uaVnBipLbZddqu+TGsiiK+0K4T7IVywMAWNBkqY1LgtW3Ou1WrXb1x64ee0kFp6I4iQMHhcGxStnAuZ+4NRiukizI/egn4BzLgRM4g0uHcTgx/lZnqxDfg1QkfxPuA9eMIIb4iLsgnvBesIczHDu4NnvRH+vffrf9VYrTDMXES7KIfBT/GSsc72lzUuwy5JocJfjPAL+s2gP7I0ctWNFd+G+Kdyncd5A5Cnf4yTSCbKGRRTcT7J22Edb93kxZEeF5AR4rAu+NK8Wt5Gfx07a5NfjsMXcLCGab2WHeD0cjLd8tem3be5baqTDmhyNV6q4jfqBvdSk6mJOyLePpXbnDLDviouex7/fWLihJrBZjGzXjgemvUbOBZmFmBX3LM+3xejvaimXHBfpLNN3Y3sEB3mD7miwSXV7aeRjMmwobHtN096pk8Hki2sHsyAf5myKsZcnxxjbXSWy1OzIeb+m840+vMqoc4MMtwYUEmhrK0U1QTzeWR09Kjp8MXVd7R1WFS1NVTXU0csxFEgn8XIOj9mqz9TdBr1mANjRnXai1LcMkhWoutblp9jpFfz47doYG3PQgN+uobUO1ZmLZncJpezWat6Ez1Trh7q0X+nbULWy+TJ1j58WAjbO9b+hTXgqnvWaMXsVS0+Dg7EemEMx3b0eIuwUoRxoExtoox5pZGloXVbrpR53R1Uv1CPtn/+UUhvPpO6465dqfrjtzo21UsdulYViv0ga9dnfLRXtv98Kj8dIptKKyH3QKf2JHc8Pm3dRedwwDJmfONNrLqxQbes8zIOip1rZn6bagmZ1TjSAUeyOoqe3ApKmYWiJHd+bbfmmy5RB0wuFqlw44U3OWQ9GTJs6mIKOV1Uxn+7niHadb9XnTmPrDcroRecPkDadPxnjWn08UvjHwD9G6q27L1USc74qXTTNLYQjuIv8gGxP3sD5AuceJ60x7tWTpQIaOPxBzdl+kHe0ILDPsJD1uYxzD0IqU45slLDK8goq2HqHZ0twWugZN2P6cU/ucUxsODX7fUwOT3Ud3SN6L4G6+4I/u9hnyJsvtu1xgm8e+JBp9dTx/3ZO7vbnw3rq+G4HxxOD1TJspyXY/mGwncjmGuD+Qn8Fhohjicih4KLf2+jKKvMNCbW5ywdof2dEhez5O/FncBwfMkpm1KOOjWRZOxDYnBdxmJoKCmZqbZ0k52F12nGSHCb7j/epYfN7oN+X5KLDvD8mHY/R0GQWsnIrko6QmLq5V5+zXLzyprFvWznEwIdXN+CNoC15nh8v7V/7s/ePYhTG0HB9HqH6zDf7emAmq+cDBPwwP57mjKXGyDTyBESVBZjjgyYwje4hpuq7EC5gTmw3ntyaN/2MOqARrZ6+xk1+kEa2nrtW69A1B+c8Lzd1zlSZwGa8ybdGUgxC6bnYq+lNOX4H/vo9/iXyG/xhZTWIvOGGcunF+rH7dYSdq2RhlOKv/HOi002qjJB/H4wx6Oc5O1RPAu+op5zntBTtBGuBTebPr39um+Qr509KFArVwg9MyOUVtY1oVXDWM+x6Su4T8DVrXXbpzg9PjSafDPAucc/gPK7eGkyTCtOD3uERRGuKq5+hidGF88/kkf0BnP6Vzi5f78ekE4YgWqFaJfzMBW3QnUISfZCt9YarHLi7p/MvJnqQAz3aAwEkAKAgIkuR6GMm24IiyU39PilLNcZl/oVJD+oVAB5CnX35EOC3nZEfVE/pTJJk7od8ItLPYrcapNMnyW8G+AP9i7YPuVrSb9nr13G6m6/D/9Bc= \ No newline at end of file diff --git a/test/responses/valid_response_with_formatted_x509certificate.xml.base64 b/test/responses/valid_response_with_formatted_x509certificate.xml.base64 new file mode 100644 index 000000000..f5fd5306a --- /dev/null +++ b/test/responses/valid_response_with_formatted_x509certificate.xml.base64 @@ -0,0 +1 @@ +pVdZk6LKEn43wv9gOI+GzSqo0d3nluAuKg3a6ssNKApB2aRQ0F9/SlzG7umeM+feJ80k88svM2vJev4r873SAcXYDYOXMvNEl/96LRaeseF7UfMN4SgMMCoRowA3z8qX8j4OmqGBXdwMDB/hZgKbGlBGTfaJbhoYozghUOUHl+j3PlEcJiEMvXKpL7+UIzszYZ0VDNuqooaNqhZnm1WrLvJVyIs0DTlk07BeLs1vnAkGccV4j/oBTowgISqa4au0UKV5nWabLNuk2VW5JCOcuIGR5F5OkkRNijKi6MnfW8ZTEFIYhxQk6e59RACDW/J6+FL+rw15g+NNuioiQodmWFSFhoGqYp01oclwpsnVy6952Zo5l/j1HAFfQ4QB8sK1GzzB0KfORuwz9Wj7bOGm5q4JuX18q7aF7yzTNH1KuacwXlMsTdMU3aCIjYXd9Y8y6VapdPNHVj+wwxxOMoIwcKHhuac8ZQUlTmiVgLcOYzdx/G/AGYqhz+BVlMEqZPjgR5nKQ/wMkpP8Q7gPXGNsVLFjMFfEM94bslGMAohKs7f+S/nHn7Y/T1GPjQDbYezjj+K/Y4WCA2lOhKwqviVHCP47wC+r9kz9ylF212QV/i/FuxbuJ8jc8PbotX/iV6qHOTBYySaH6wc4W1DcG6x01JecwKNxrriX/CJ+Wjb3Bl88VGc48MPgiKfMzg8MUaugNjOCbDwEm43ETrqxOllrm2mkh3Dvq4Kz7G1PuraKxYTqU0N9oh16krqTmdjhPY3eyFOqve+NgZRsG0vR89jZcb0X2VHF7CS7FduQg0Y82mpWN3YrfbHnZCNOEC0baJojVPBRfEeHtrVV7BTJKgsWZBMJrVZ3fRL19ss9nQf+5CgjyiE63hNc1OiGbCTGXZDOJ5ZNdkqCXpV+X+qeJAksVUlSpfEYqovQ59i5DMat9XbnbN1uI6VbQJ11gNzSFBWnkrqU56rabaeDebEwO7VHCth2ATNrSy1F0mZ81tGB3lqP5y0Adbnt7VddzzH9TmqyWQRP7TcF1C/2jtIvFnS25undRmC885ksg+HFE+uAnu9XJ4A6KZ2NZUArGyWbyGqmyB0j1+kXXbFw0yqdfiadwOCCsNSBN9cfGPcJ44G+ac+VlpLHb2WKor3XtsWC8d7ZLxetg9n1TspbO5XT3GPYTp2p6c8Vk7Uic9NWFBBemJN4b++1QOl3bQXQ3WJB0nZdrW9ysto+VwsAvjsGstRyVZKQKoeCX1FXh6VKZSO67Y3W+2jIqDJcjni7NoXbtFjA47XWiGaHuWif9J3U29Z1Z5TpW55VVFaBAzxBs8F8KrL1oXP0Nx1pl62n/Hyfvm0bcQS8YoGu+M5RUKbWcXMEQpfhN7H8rgm1Ix5BZ8gn1CGN2vKJ1lSvHXaZrXLyPM0XTyuNW8RoDYoFUd6MjdlS3aV9Gaigdc7tMbPWJbMWGCnsoSu5KnXwK4Zw4OnKfMGerGJh1wOsSjGHDuOa6mlQ45WBNJm/H3DloC7sVcexfHoyVdh+LM/EcHcYTndTIZsANBgKPfo4JRwUfjnibCPRDv2l79vHhdTYJpx2OFHjY9w7TZ1ZMKCPiMIzbZEFJzVLoU81pinYxaoBuGJBjdRtryYezQ41CePjFFVYJ98sn5f/XXnZINTj1vmwuV6vA4KWEBF/lKTQQqV89/3+3se5dVPbQ4gwzs/LX0Gb4DZRXG/F7LtbkaEWykiDDvKN8t3W/WfjqptPDRD9MlJcppFGjRFM2uaqfI0TqgxtC1Uo2Ea1YVk1lkMM36jDP5o//o/pIBe0vblBMLlKY1LPvlzqkJvFSL4vNPPE5BrXqtq5aZOk7HrAsuJz0V8Tcjf85zH+NfIF/mNkKQxs94xx7sblCvt9h6HfNJERo7j8PdB5pZXGYTIJJjGwExSfq8fRD9UTL9PbG4Ju5KJzeePb3/ui+Qr506crBWJhuefP+By1hUhVUN4w5mdI5hryD2jdVunecs9XKpkZk9iFl/AfvtwbjkMfkYI/oczwIw/lPTeuRlfGd59P8gd06lM693iJE5x3EPJJgUq5+A9zsUZWAkH4JtvaF6b9wEIZmYoZwa6JtG1CmmNqNC0aNFerWTYyBJODvADLj6QI1QRlyRcqySPvBjKWvP72aQGb8GxH1FPyk4axNSUvB9JZZOVDVhTGyb1gX4B/8e2D7l60u/Z29NxPptuT4PVv \ No newline at end of file diff --git a/test/responses/valid_response_without_x509certificate.xml.base64 b/test/responses/valid_response_without_x509certificate.xml.base64 new file mode 100644 index 000000000..55f513925 --- /dev/null +++ b/test/responses/valid_response_without_x509certificate.xml.base64 @@ -0,0 +1 @@ +pVZdd9o4EH3fc/Y/+LiPOcayDTb4BLoU0oSWfBDTbpuXPbI0Bie25Egi0Pz6lQ04kJI03X2CGY/u3LkjjXT8fpVnxgMImXLWNZ0GMt/3/vzjWOI8K8JrkAVnEgwdxGRYOrvmQrCQY5nKkOEcZKhIGPXPx6HbQCGWEoTSUObOkuL1NYXgihOemcZo2DWLZBWTtuvjhFrQScCiXhJbtB00LdIMECIeJIi0TePrlrPG0EulXMCISYWZ0i7kNC3kW6g5RW7ouiFyb0xjCFKlDKtq1VypIrRtXBSNfEFxg3FbSm4TXe4iBw3ItsVPedf8JyFN7DVjZAWg6SDHBYtgDFbQdmMSO14ce22zV8kWVlxEr8wgNyk4g4zPUtYgPLfLIPfY3o09pjKM0pkmtxBbtamsWS6Xy8bSa3Axs12EkI06to6hMp29M3W3DGO7HuiIJbyCG2DGWUpwlj5WJZ+DmnNq9LMZF6ma5y+AO7aDSnALVsQiTpO9M+0qxVOSiuQb4fa4CoktOcfOBrHEu4YEBDACxpfrUdd899b2VyVOBWYy4SKX++bvsQL2oJtTALXktjhN8PcAD6p2bP/McZjO9C78L+JthHsC+YqzBfRGj82bSSa9/qebYezJ9gP58s32rsnRx0m3IrAbXDlqydfms21TN3i9YjL//Cnn7Ie8cu5zhoPoCE6cMXHF5/7t7cC9PBWTy1l0e1VMOVnkE3/+/ezucRrdiEDZI/vz9DJ6OBtM7oeOmDezCN0Or+yTxdlFf6DuOt+DLHO//JgtAnd8FH9U9zduZ8g6YnwX0VORHo2Cs/lq7PkBTfpRNPeP5I/gb3g4oXfnyRKGE7f/TR8i/8OH09ljMD3p1uXs8NejbM/b20y2SGlT7lsDTsGolr0+sGQVHUYLQkDKqtE/g4b97SjcHOfVS8fZsb+djyMyhxybdWz662ArrcYdgZ9m4XqMdlqOH6PEs5otz7cclPgW8RNsdShtuR44zU6bvGlw/o+xVhnRIr4FojbWhdZzNDQ+6iOB1ctCOw2n8qTUSqrQUJecZn1KRSl6T+lN/ddu/k3mNfx+5gFnSVpilN1Yn73XO0zyMAYsQJgvAw2xwsYFV5fsUvQTBaJUz0M76gXra+caSFqkUMortn/rTXMI+dmnDQUdQdPysyyzfgCtClQNc55SOpuUb6C13aULmpazQF92SqRknX7vS91wyXPQgjdghfMig6rneBO0YVyveWbvodvPyqnzqTkrTxDkWiCjMn9xoUd6J2iEF6ptHQgdMQorfZ07ftIKUBIT5DkthAKMvFaLJoD92CNNn5i7pDRVBSt1wDXI9INHz9Peq28iEpIyTruv9M+SC3qlnzy6s0Cr26HgQtWCHQA/8G3PV4tWe7ejp55M27dM718= \ No newline at end of file diff --git a/test/saml_message_test.rb b/test/saml_message_test.rb new file mode 100644 index 000000000..f8e5be4fd --- /dev/null +++ b/test/saml_message_test.rb @@ -0,0 +1,99 @@ +require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) + +class RubySamlTest < Minitest::Test + + describe "SamlMessage" do + + let(:settings) { OneLogin::RubySaml::Settings.new } + let(:saml_message) { OneLogin::RubySaml::SamlMessage.new } + let(:response_document) { read_response("response_unsigned_xml_base64") } + let(:response_document_xml) { read_response("adfs_response_xmlns.xml") } + + it "return decoded raw saml" do + decoded_raw = saml_message.send(:decode_raw_saml, logout_request_deflated_base64) + assert logout_request_document, decoded_raw + end + + it "return encoded raw saml" do + settings.compress_request = true + encoded_raw = saml_message.send(:encode_raw_saml, logout_request_document, settings) + assert logout_request_deflated_base64, encoded_raw + + settings.compress_request = false + deflated = saml_message.send(:deflate, logout_request_deflated_base64) + encoded_raw = saml_message.send(:encode_raw_saml, deflated, settings) + assert logout_request_deflated_base64, encoded_raw + end + + it "return decoded string" do + decoded = saml_message.send(:decode, response_document) + assert response_document_xml, decoded + + decoded = saml_message.send(:decode, logout_request_base64) + assert logout_request_document, decoded + end + + it "return encoded string" do + encoded = saml_message.send(:encode, response_document_xml) + assert response_document, encoded + + encoded = saml_message.send(:encode, logout_request_document) + assert logout_request_base64, encoded + end + + it "return deflated string" do + deflated = saml_message.send(:deflate, logout_request_document) + encoded_deflated = saml_message.send(:encode, deflated) + assert logout_request_deflated_base64, encoded_deflated + end + + it "return inflated string" do + decoded = saml_message.send(:decode, logout_request_deflated_base64) + decoded_inflated = saml_message.send(:inflate, decoded) + assert response_document_xml, decoded_inflated + end + + describe "Prevent Zlib bomb attack" do + let(:bomb) { Base64.encode64(Zlib::Deflate.deflate(bomb_data, 9)[2..-5]) } + let(:bomb_data) { bomb_prefix + "A" * (200000 * 1024) + bomb_suffix } + let(:bomb_prefix) { """ + + """ } + let(:bomb_suffix) { """ + ONELOGIN_f92cc1834efc0f73e9c09f482fce80037a6251e7 + """ } + + it "raises error when SAML Message exceed the allowed bytes" do + assert_raises(OneLogin::RubySaml::ValidationError, "Encoded SAML Message exceeds #{OneLogin::RubySaml::Settings::DEFAULTS[:message_max_bytesize]} bytes, so was rejected") do + saml_message = OneLogin::RubySaml::SamlMessage.new + saml_message.send(:decode_raw_saml, bomb) + end + end + + describe 'with a custom setting for message_max_bytesize' do + let(:message_max_bytesize) { 100_00 } + let(:settings) { OneLogin::RubySaml::Settings.new({:message_max_bytesize => message_max_bytesize}) } + + it 'uses the custom setting' do + assert_raises(OneLogin::RubySaml::ValidationError, "Encoded SAML Message exceeds #{message_max_bytesize} bytes, so was rejected") do + saml_message = OneLogin::RubySaml::SamlMessage.new + saml_message.send(:decode_raw_saml, bomb, settings) + end + end + end + end + + describe "Prevent DOS attack via base64_encoded? validation" do + let(:large_saml_message) { "A" * (OneLogin::RubySaml::Settings::DEFAULTS[:message_max_bytesize] + 100) } + + it "rejects oversized payloads before attempting Base64 validation" do + assert_raises(OneLogin::RubySaml::ValidationError, "Encoded SAML Message exceeds #{OneLogin::RubySaml::Settings::DEFAULTS[:message_max_bytesize]} bytes, so was rejected") do + saml_message = OneLogin::RubySaml::SamlMessage.new + saml_message.expects(:base64_encoded?).never + + saml_message.send(:decode_raw_saml, large_saml_message) + end + end + end + end +end diff --git a/test/settings_test.rb b/test/settings_test.rb index ad02fd2ac..8e7cb9f65 100644 --- a/test/settings_test.rb +++ b/test/settings_test.rb @@ -1,6 +1,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) require 'onelogin/ruby-saml/settings' +require 'onelogin/ruby-saml/validation_error' class SettingsTest < Minitest::Test @@ -11,35 +12,85 @@ class SettingsTest < Minitest::Test it "should provide getters and settings" do accessors = [ - :idp_entity_id, :idp_sso_target_url, :idp_slo_target_url, :idp_cert, :idp_cert_fingerprint, - :issuer, :assertion_consumer_service_url, :assertion_consumer_service_binding, - :single_logout_service_url, :single_logout_service_binding, - :sp_name_qualifier, :name_identifier_format, :name_identifier_value, + :idp_entity_id, :idp_sso_target_url, :idp_sso_service_url, :idp_slo_target_url, :idp_slo_service_url, :valid_until, + :idp_cert, :idp_cert_fingerprint, :idp_cert_fingerprint_algorithm, :idp_cert_multi, + :idp_attribute_names, :issuer, :assertion_consumer_service_url, :single_logout_service_url, + :sp_name_qualifier, :name_identifier_format, :name_identifier_value, :name_identifier_value_requested, :sessionindex, :attributes_index, :passive, :force_authn, - :compress_request, :double_quote_xml_attribute_values, :protocol_binding, - :security, :certificate, :private_key, + :compress_request, :double_quote_xml_attribute_values, :message_max_bytesize, + :security, :certificate, :private_key, :certificate_new, :sp_cert_multi, :authn_context, :authn_context_comparison, :authn_context_decl_ref, - :assertion_consumer_logout_service_url, - :assertion_consumer_logout_service_binding + :assertion_consumer_logout_service_url ] accessors.each do |accessor| value = Kernel.rand @settings.send("#{accessor}=".to_sym, value) assert_equal value, @settings.send(accessor) + + @settings.send("#{accessor}=".to_sym, nil) + assert_nil @settings.send(accessor) end + end + + it "should provide getters and settings for binding parameters" do + accessors = [ + :protocol_binding, :assertion_consumer_service_binding, + :single_logout_service_binding, :assertion_consumer_logout_service_binding + ] + + accessors.each do |accessor| + value = Kernel.rand.to_s + @settings.send("#{accessor}=".to_sym, value) + assert_equal value, @settings.send(accessor) + + @settings.send("#{accessor}=".to_sym, :redirect) + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.send(accessor) + + @settings.send("#{accessor}=".to_sym, :post) + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", @settings.send(accessor) + @settings.send("#{accessor}=".to_sym, nil) + assert_nil @settings.send(accessor) + end end - it "create settings from hash" do + it "idp_sso/slo_service_binding should fallback to :embed_sign inferred value" do + accessors = [:idp_sso_service_binding, :idp_slo_service_binding] + + accessors.each do |accessor| + @settings.security[:embed_sign] = true + + value = Kernel.rand.to_s + @settings.send("#{accessor}=".to_sym, value) + assert_equal value, @settings.send(accessor) + + @settings.send("#{accessor}=".to_sym, :redirect) + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.send(accessor) + + @settings.send("#{accessor}=".to_sym, :post) + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", @settings.send(accessor) + + @settings.send("#{accessor}=".to_sym, nil) + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", @settings.send(accessor) + @settings.security[:embed_sign] = false + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.send(accessor) + end + end + + it "create settings from hash" do config = { :assertion_consumer_service_url => "http://app.muda.no/sso", :issuer => "http://muda.no", :sp_name_qualifier => "http://sso.muda.no", - :idp_sso_target_url => "http://sso.muda.no/sso", - :idp_slo_target_url => "http://sso.muda.no/slo", + :idp_sso_service_url => "http://sso.muda.no/sso", + :idp_sso_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + :idp_slo_service_url => "http://sso.muda.no/slo", + :idp_slo_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", :idp_cert_fingerprint => "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00", + :message_max_bytesize => 750000, + :valid_until => '2029-04-16T03:35:08.277Z', :name_identifier_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", :attributes_index => 30, :passive => true, @@ -53,7 +104,6 @@ class SettingsTest < Minitest::Test end it "configure attribute service attributes correctly" do - @settings = OneLogin::RubySaml::Settings.new @settings.attribute_consuming_service.configure do service_name "Test Service" add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name" @@ -77,5 +127,613 @@ class SettingsTest < Minitest::Test assert_equal new_settings.security[:digest_method], XMLSecurity::Document::SHA1 assert_equal new_settings.security[:signature_method], XMLSecurity::Document::RSA_SHA1 end + + it "overrides only provided security attributes passing a second parameter" do + config = { + :security => { + :metadata_signed => true + } + } + + @default_attributes = OneLogin::RubySaml::Settings::DEFAULTS + + @settings = OneLogin::RubySaml::Settings.new(config, true) + assert_equal @settings.security[:metadata_signed], true + assert_equal @settings.security[:digest_method], @default_attributes[:security][:digest_method] + end + + it "doesn't override only provided security attributes without passing a second parameter" do + config = { + :security => { + :metadata_signed => true + } + } + + @default_attributes = OneLogin::RubySaml::Settings::DEFAULTS + + @settings = OneLogin::RubySaml::Settings.new(config) + assert_equal @settings.security[:metadata_signed], true + assert_nil @settings.security[:digest_method] + end + + describe "#single_logout_service_url" do + it "when single_logout_service_url is nil but assertion_consumer_logout_service_url returns its value" do + @settings.single_logout_service_url = nil + @settings.assertion_consumer_logout_service_url = "http://app.muda.no/sls" + + assert_equal "http://app.muda.no/sls", @settings.single_logout_service_url + end + end + + describe "#single_logout_service_binding" do + it "when single_logout_service_binding is nil but assertion_consumer_logout_service_binding returns its value" do + @settings.single_logout_service_binding = nil + @settings.assertion_consumer_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.single_logout_service_binding + end + end + + describe "#idp_sso_service_url" do + it "when idp_sso_service_url is nil but idp_sso_target_url returns its value" do + @settings.idp_sso_service_url = nil + @settings.idp_sso_target_url = "https://idp.example.com/sso" + + assert_equal "https://idp.example.com/sso", @settings.idp_sso_service_url + end + end + + describe "#idp_slo_service_url" do + it "when idp_slo_service_url is nil but idp_slo_target_url returns its value" do + @settings.idp_slo_service_url = nil + @settings.idp_slo_target_url = "https://idp.example.com/slo" + + assert_equal "https://idp.example.com/slo", @settings.idp_slo_service_url + end + end + + describe "#get_idp_cert" do + it "returns nil when the cert is an empty string" do + @settings.idp_cert = "" + assert_nil @settings.get_idp_cert + end + + it "returns nil when the cert is nil" do + @settings.idp_cert = nil + assert_nil @settings.get_idp_cert + end + + it "returns the certificate when it is valid" do + @settings.idp_cert = ruby_saml_cert_text + assert @settings.get_idp_cert.kind_of? OpenSSL::X509::Certificate + end + + it "raises when the certificate is not valid" do + # formatted but invalid cert + @settings.idp_cert = read_certificate("formatted_certificate") + assert_raises(OpenSSL::X509::CertificateError) { + @settings.get_idp_cert + } + end + end + + describe "#get_idp_cert_multi" do + it "returns nil when the value is empty" do + @settings.idp_cert = {} + assert_nil @settings.get_idp_cert_multi + end + + it "returns nil when the idp_cert_multi is nil or empty" do + @settings.idp_cert_multi = nil + assert_nil @settings.get_idp_cert_multi + end + + it "returns partial hash when contains some values" do + empty_multi = { + :signing => [], + :encryption => [] + } + + @settings.idp_cert_multi = { + :signing => [] + } + assert_equal empty_multi, @settings.get_idp_cert_multi + + @settings.idp_cert_multi = { + :encryption => [] + } + assert_equal empty_multi, @settings.get_idp_cert_multi + + @settings.idp_cert_multi = { + :signing => [], + :encryption => [] + } + assert_equal empty_multi, @settings.get_idp_cert_multi + + @settings.idp_cert_multi = { + :yyy => [], + :zzz => [] + } + assert_equal empty_multi, @settings.get_idp_cert_multi + end + + it "returns partial hash when contains some values with string keys" do + empty_multi = { + :signing => [], + :encryption => [] + } + + @settings.idp_cert_multi = { + "signing" => [] + } + assert_equal empty_multi, @settings.get_idp_cert_multi + + @settings.idp_cert_multi = { + "encryption" => [] + } + assert_equal empty_multi, @settings.get_idp_cert_multi + + @settings.idp_cert_multi = { + "signing" => [], + "encryption" => [] + } + assert_equal empty_multi, @settings.get_idp_cert_multi + + @settings.idp_cert_multi = { + "yyy" => [], + "zzz" => [] + } + assert_equal empty_multi, @settings.get_idp_cert_multi + end + + it "returns the hash with certificates when values were valid" do + certificates = [ruby_saml_cert_text] + @settings.idp_cert_multi = { + :signing => certificates, + :encryption => certificates, + } + + assert @settings.get_idp_cert_multi.kind_of? Hash + assert @settings.get_idp_cert_multi[:signing].kind_of? Array + assert @settings.get_idp_cert_multi[:encryption].kind_of? Array + assert @settings.get_idp_cert_multi[:signing][0].kind_of? OpenSSL::X509::Certificate + assert @settings.get_idp_cert_multi[:encryption][0].kind_of? OpenSSL::X509::Certificate + end + + it "returns the hash with certificates when values were valid and with string keys" do + certificates = [ruby_saml_cert_text] + @settings.idp_cert_multi = { + "signing" => certificates, + "encryption" => certificates, + } + + assert @settings.get_idp_cert_multi.kind_of? Hash + assert @settings.get_idp_cert_multi[:signing].kind_of? Array + assert @settings.get_idp_cert_multi[:encryption].kind_of? Array + assert @settings.get_idp_cert_multi[:signing][0].kind_of? OpenSSL::X509::Certificate + assert @settings.get_idp_cert_multi[:encryption][0].kind_of? OpenSSL::X509::Certificate + end + + it "raises when there is a cert in idp_cert_multi not valid" do + certificate = read_certificate("formatted_certificate") + + @settings.idp_cert_multi = { + :signing => [], + :encryption => [] + } + @settings.idp_cert_multi[:signing].push(certificate) + @settings.idp_cert_multi[:encryption].push(certificate) + + assert_raises(OpenSSL::X509::CertificateError) { + @settings.get_idp_cert_multi + } + end + end + + describe "#get_sp_cert" do + it "returns nil when the cert is an empty string" do + @settings.certificate = "" + assert_nil @settings.get_sp_cert + end + + it "returns nil when the cert is nil" do + @settings.certificate = nil + assert_nil @settings.get_sp_cert + end + + it "returns the certificate when it is valid" do + @settings.certificate = ruby_saml_cert_text + assert @settings.get_sp_cert.kind_of? OpenSSL::X509::Certificate + end + + it "raises when the certificate is not valid" do + # formatted but invalid cert + @settings.certificate = read_certificate("formatted_certificate") + assert_raises(OpenSSL::X509::CertificateError) { @settings.get_sp_cert } + end + + it "raises an error if SP certificate expired and check_sp_cert_expiration enabled" do + @settings.certificate = ruby_saml_cert_text + @settings.security[:check_sp_cert_expiration] = true + assert_raises(OneLogin::RubySaml::ValidationError) { @settings.get_sp_cert } + end + end + + describe "#get_sp_cert_new" do + it "returns nil when the cert is an empty string" do + @settings.certificate_new = "" + assert_nil @settings.get_sp_cert_new + end + + it "returns nil when the cert is nil" do + @settings.certificate_new = nil + assert_nil @settings.get_sp_cert_new + end + + it "returns the certificate when it is valid" do + @settings.certificate_new = ruby_saml_cert_text + assert @settings.get_sp_cert_new.kind_of? OpenSSL::X509::Certificate + end + + it "raises when the certificate is not valid" do + # formatted but invalid cert + @settings.certificate_new = read_certificate("formatted_certificate") + assert_raises(OpenSSL::X509::CertificateError) { + @settings.get_sp_cert_new + } + end + end + + describe "#get_sp_key" do + it "returns nil when the private key is an empty string" do + @settings.private_key = "" + assert_nil @settings.get_sp_key + end + + it "returns nil when the private key is nil" do + @settings.private_key = nil + assert_nil @settings.get_sp_key + end + + it "returns the private key when it is valid" do + @settings.private_key = ruby_saml_key_text + assert @settings.get_sp_key.kind_of? OpenSSL::PKey::RSA + end + + it "raises when the private key is not valid" do + # formatted but invalid rsa private key + @settings.private_key = read_certificate("formatted_rsa_private_key") + assert_raises(OpenSSL::PKey::RSAError) { + @settings.get_sp_key + } + end + end + + describe "#get_fingerprint" do + it "get the fingerprint value when cert and fingerprint in settings are nil" do + @settings.idp_cert_fingerprint = nil + @settings.idp_cert = nil + fingerprint = @settings.get_fingerprint + assert_nil fingerprint + end + + it "get the fingerprint value when there is a cert at the settings" do + @settings.idp_cert_fingerprint = nil + @settings.idp_cert = ruby_saml_cert_text + fingerprint = @settings.get_fingerprint + assert fingerprint.downcase == ruby_saml_cert_fingerprint.downcase + end + + it "get the fingerprint value when there is a fingerprint at the settings" do + @settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + @settings.idp_cert = nil + fingerprint = @settings.get_fingerprint + assert fingerprint.downcase == ruby_saml_cert_fingerprint.downcase + end + + it "get the fingerprint value when there are cert and fingerprint at the settings" do + @settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + @settings.idp_cert = ruby_saml_cert_text + fingerprint = @settings.get_fingerprint + assert fingerprint.downcase == ruby_saml_cert_fingerprint.downcase + end + end + + describe "#get_sp_certs (base cases)" do + let(:cert_text1) { ruby_saml_cert_text } + let(:cert_text2) { ruby_saml_cert2.to_pem } + let(:cert_text3) { CertificateHelper.generate_cert.to_pem } + let(:key_text1) { ruby_saml_key_text } + let(:key_text2) { CertificateHelper.generate_key.to_pem } + + it "returns certs for single case" do + @settings.certificate = cert_text1 + @settings.private_key = key_text1 + + actual = @settings.get_sp_certs + expected = [[cert_text1, key_text1]] + assert_equal [:signing, :encryption], actual.keys + assert_equal expected, actual[:signing].map {|ary| ary.map(&:to_pem) } + assert_equal expected, actual[:encryption].map {|ary| ary.map(&:to_pem) } + end + + it "returns certs for single case with new cert" do + @settings.certificate = cert_text1 + @settings.certificate_new = cert_text2 + @settings.private_key = key_text1 + + actual = @settings.get_sp_certs + expected = [[cert_text1, key_text1], [cert_text2, key_text1]] + assert_equal [:signing, :encryption], actual.keys + assert_equal expected, actual[:signing].map {|ary| ary.map(&:to_pem) } + assert_equal expected, actual[:encryption].map {|ary| ary.map(&:to_pem) } + end + + it "returns certs for multi case" do + @settings.sp_cert_multi = { + signing: [{ certificate: cert_text1, private_key: key_text1 }, + { certificate: cert_text2, private_key: key_text1 }], + encryption: [{ certificate: cert_text2, private_key: key_text1 }, + { certificate: cert_text3, private_key: key_text2 }] + } + + actual = @settings.get_sp_certs + expected_signing = [[cert_text1, key_text1], [cert_text2, key_text1]] + expected_encryption = [[cert_text2, key_text1], [cert_text3, key_text2]] + assert_equal [:signing, :encryption], actual.keys + assert_equal expected_signing, actual[:signing].map {|ary| ary.map(&:to_pem) } + assert_equal expected_encryption, actual[:encryption].map {|ary| ary.map(&:to_pem) } + end + + it "sp_cert_multi allows sending only signing" do + @settings.sp_cert_multi = { + signing: [{ certificate: cert_text1, private_key: key_text1 }, + { certificate: cert_text2, private_key: key_text1 }] + } + + actual = @settings.get_sp_certs + expected_signing = [[cert_text1, key_text1], [cert_text2, key_text1]] + assert_equal [:signing, :encryption], actual.keys + assert_equal expected_signing, actual[:signing].map {|ary| ary.map(&:to_pem) } + assert_equal [], actual[:encryption] + end + + it "raises error when sp_cert_multi is not a Hash" do + @settings.sp_cert_multi = 'invalid_type' + + error_message = 'sp_cert_multi must be a Hash' + assert_raises ArgumentError, error_message do + @settings.get_sp_certs + end + end + + it "raises error when sp_cert_multi does not contain an Array of Hashes" do + @settings.sp_cert_multi = { signing: 'invalid_type' } + + error_message = 'sp_cert_multi :signing node must be an Array of Hashes' + assert_raises ArgumentError, error_message do + @settings.get_sp_certs + end + end + + it "raises error when sp_cert_multi inner node missing :certificate" do + @settings.sp_cert_multi = { signing: [{ private_key: key_text1 }] } + + error_message = 'sp_cert_multi :signing node Hashes must specify keys :certificate and :private_key' + assert_raises ArgumentError, error_message do + @settings.get_sp_certs + end + end + + it "raises error when sp_cert_multi inner node missing :private_key" do + @settings.sp_cert_multi = { signing: [{ certificate: cert_text1 }] } + + error_message = 'sp_cert_multi :signing node Hashes must specify keys :certificate and :private_key' + assert_raises ArgumentError, error_message do + @settings.get_sp_certs + end + end + + it "handles sp_cert_multi with string keys" do + @settings.sp_cert_multi = { + 'signing' => [{ 'certificate' => cert_text1, 'private_key' => key_text1 }], + 'encryption' => [{ 'certificate' => cert_text2, 'private_key' => key_text1 }] + } + + actual = @settings.get_sp_certs + expected_signing = [[cert_text1, key_text1]] + expected_encryption = [[cert_text2, key_text1]] + assert_equal [:signing, :encryption], actual.keys + assert_equal expected_signing, actual[:signing].map {|ary| ary.map(&:to_pem) } + assert_equal expected_encryption, actual[:encryption].map {|ary| ary.map(&:to_pem) } + end + + it "handles sp_cert_multi with alternate inner keys :cert and :key" do + @settings.sp_cert_multi = { + signing: [{ cert: cert_text1, key: key_text1 }], + encryption: [{ 'cert' => cert_text2, 'key' => key_text1 }] + } + + actual = @settings.get_sp_certs + expected_signing = [[cert_text1, key_text1]] + expected_encryption = [[cert_text2, key_text1]] + assert_equal [:signing, :encryption], actual.keys + assert_equal expected_signing, actual[:signing].map {|ary| ary.map(&:to_pem) } + assert_equal expected_encryption, actual[:encryption].map {|ary| ary.map(&:to_pem) } + end + + it "raises error when both sp_cert_multi and certificate are specified" do + @settings.sp_cert_multi = { signing: [{ certificate: cert_text1, private_key: key_text1 }] } + @settings.certificate = cert_text1 + + error_message = 'Cannot specify both sp_cert_multi and certificate, certificate_new, private_key parameters' + assert_raises ArgumentError, error_message do + @settings.get_sp_certs + end + end + + it "raises error when both sp_cert_multi and certificate_new are specified" do + @settings.sp_cert_multi = { signing: [{ certificate: cert_text1, private_key: key_text1 }] } + @settings.certificate_new = cert_text2 + + error_message = 'Cannot specify both sp_cert_multi and certificate, certificate_new, private_key parameters' + assert_raises ArgumentError, error_message do + @settings.get_sp_certs + end + end + + it "raises error when both sp_cert_multi and private_key are specified" do + @settings.sp_cert_multi = { signing: [{ certificate: cert_text1, private_key: key_text1 }] } + @settings.private_key = key_text1 + + error_message = 'Cannot specify both sp_cert_multi and certificate, certificate_new, private_key parameters' + assert_raises ArgumentError, error_message do + @settings.get_sp_certs + end + end + end + + describe "#get_sp_certs" do + let(:valid_pair) { CertificateHelper.generate_pair_hash } + let(:early_pair) { CertificateHelper.generate_pair_hash(not_before: Time.now + 60) } + let(:expired_pair) { CertificateHelper.generate_pair_hash(not_after: Time.now - 60) } + + it "returns all certs when check_sp_cert_expiration is false" do + @settings.security = { check_sp_cert_expiration: false } + @settings.sp_cert_multi = { signing: [valid_pair, expired_pair], encryption: [valid_pair, early_pair] } + + actual = @settings.get_sp_certs + expected_signing = [valid_pair, expired_pair].map(&:values) + expected_encryption = [valid_pair, early_pair].map(&:values) + assert_equal expected_signing, actual[:signing].map {|ary| ary.map(&:to_pem) } + assert_equal expected_encryption, actual[:encryption].map {|ary| ary.map(&:to_pem) } + end + + it "returns only active certs when check_sp_cert_expiration is true" do + @settings.security = { check_sp_cert_expiration: true } + @settings.sp_cert_multi = { signing: [valid_pair, expired_pair], encryption: [valid_pair, early_pair] } + + actual = @settings.get_sp_certs + expected_active = [valid_pair].map(&:values) + assert_equal expected_active, actual[:signing].map {|ary| ary.map(&:to_pem) } + assert_equal expected_active, actual[:encryption].map {|ary| ary.map(&:to_pem) } + end + + it "raises error when all certificates are expired in signing and check_sp_cert_expiration is true" do + @settings.security = { check_sp_cert_expiration: true } + @settings.sp_cert_multi = { signing: [expired_pair], encryption: [valid_pair] } + + assert_raises OneLogin::RubySaml::ValidationError do + @settings.get_sp_certs + end + end + + it "raises error when all certificates are expired in encryption and check_sp_cert_expiration is true" do + @settings.security = { check_sp_cert_expiration: true } + @settings.sp_cert_multi = { signing: [valid_pair], encryption: [expired_pair] } + + assert_raises OneLogin::RubySaml::ValidationError do + @settings.get_sp_certs + end + end + + it "returns empty arrays for signing and encryption if no pairs are present" do + @settings.sp_cert_multi = { signing: [], encryption: [] } + + actual = @settings.get_sp_certs + assert_empty actual[:signing] + assert_empty actual[:encryption] + end + end + + describe "#get_sp_signing_pair and #get_sp_signing_key" do + let(:valid_pair) { CertificateHelper.generate_pair_hash } + let(:early_pair) { CertificateHelper.generate_pair_hash(not_before: Time.now + 60) } + let(:expired) { CertificateHelper.generate_pair_hash(not_after: Time.now - 60) } + + it "returns nil when no signing pairs are present" do + @settings.sp_cert_multi = { signing: [] } + + assert_nil @settings.get_sp_signing_pair + assert_nil @settings.get_sp_signing_key + end + + it "returns the first pair if check_sp_cert_expiration is false" do + @settings.security = { check_sp_cert_expiration: false } + @settings.sp_cert_multi = { signing: [early_pair, expired, valid_pair] } + + assert_equal early_pair.values, @settings.get_sp_signing_pair.map(&:to_pem) + assert_equal early_pair[:private_key], @settings.get_sp_signing_key.to_pem + end + + it "returns the first active pair when check_sp_cert_expiration is true" do + @settings.security = { check_sp_cert_expiration: true } + @settings.sp_cert_multi = { signing: [early_pair, expired, valid_pair] } + + assert_equal valid_pair.values, @settings.get_sp_signing_pair.map(&:to_pem) + assert_equal valid_pair[:private_key], @settings.get_sp_signing_key.to_pem + end + + it "raises error when all certificates are expired and check_sp_cert_expiration is true" do + @settings.security = { check_sp_cert_expiration: true } + @settings.sp_cert_multi = { signing: [early_pair, expired] } + + assert_raises OneLogin::RubySaml::ValidationError do + @settings.get_sp_signing_pair + end + + assert_raises OneLogin::RubySaml::ValidationError do + @settings.get_sp_signing_key + end + end + end + + describe "#get_sp_decryption_keys" do + let(:valid_pair) { CertificateHelper.generate_pair_hash } + let(:early_pair) { CertificateHelper.generate_pair_hash(not_before: Time.now + 60) } + let(:expired_pair) { CertificateHelper.generate_pair_hash(not_after: Time.now - 60) } + + it "returns an empty array when no decryption pairs are present" do + @settings.sp_cert_multi = { encryption: [] } + + assert_empty @settings.get_sp_decryption_keys + end + + it "returns all keys when check_sp_cert_expiration is false" do + @settings.security = { check_sp_cert_expiration: false } + @settings.sp_cert_multi = { encryption: [early_pair, expired_pair, valid_pair] } + + expected_keys = [early_pair, expired_pair, valid_pair].map { |pair| pair[:private_key] } + actual_keys = @settings.get_sp_decryption_keys.map(&:to_pem) + assert_equal expected_keys, actual_keys + end + + it "returns only keys of active certificates when check_sp_cert_expiration is true" do + @settings.security = { check_sp_cert_expiration: true } + @settings.sp_cert_multi = { encryption: [early_pair, expired_pair, valid_pair] } + + expected_keys = [valid_pair[:private_key]] + actual_keys = @settings.get_sp_decryption_keys.map(&:to_pem) + assert_equal expected_keys, actual_keys + end + + it "raises error when all certificates are expired and check_sp_cert_expiration is true" do + @settings.security = { check_sp_cert_expiration: true } + @settings.sp_cert_multi = { encryption: [early_pair, expired_pair] } + + assert_raises OneLogin::RubySaml::ValidationError do + @settings.get_sp_decryption_keys + end + end + + it "removes duplicates" do + @settings.sp_cert_multi = { encryption: [early_pair, valid_pair, early_pair, valid_pair] } + + expected_keys = [early_pair, valid_pair].map { |pair| pair[:private_key] } + actual_keys = @settings.get_sp_decryption_keys.map(&:to_pem) + + assert_equal expected_keys, actual_keys + end + end end end diff --git a/test/slo_logoutrequest_test.rb b/test/slo_logoutrequest_test.rb index 4d78b512b..cd2b5f128 100644 --- a/test/slo_logoutrequest_test.rb +++ b/test/slo_logoutrequest_test.rb @@ -1,63 +1,559 @@ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) -require 'responses/logoutresponse_fixtures' +require 'logout_responses/logoutresponse_fixtures' require 'onelogin/ruby-saml/slo_logoutrequest' +require 'timecop' class RubySamlTest < Minitest::Test describe "SloLogoutrequest" do - it "raise an exception when response is initialized with nil" do - assert_raises(ArgumentError) { OneLogin::RubySaml::SloLogoutrequest.new(nil) } + + let(:settings) { OneLogin::RubySaml::Settings.new } + let(:logout_request) { OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) } + let(:logout_request_encrypted_nameid) { OneLogin::RubySaml::SloLogoutrequest.new(logout_request_encrypted_nameid_document) } + let(:invalid_logout_request) { OneLogin::RubySaml::SloLogoutrequest.new(invalid_logout_request_document) } + + before do + settings.idp_entity_id = 'https://app.onelogin.com/saml/metadata/SOMEACCOUNT' + settings.soft = true + logout_request.settings = settings + invalid_logout_request.settings = settings + end + + describe "initiator" do + it "raise an exception when logout request is initialized with nil" do + assert_raises(ArgumentError) { OneLogin::RubySaml::SloLogoutrequest.new(nil) } + end end describe "#is_valid?" do - it "return false when response is initialized with blank data" do - request = OneLogin::RubySaml::SloLogoutrequest.new('') - assert !request.is_valid? + it "return false when logout request is initialized with blank data" do + logout_request_blank = OneLogin::RubySaml::SloLogoutrequest.new('') + assert !logout_request_blank.is_valid? + assert_includes logout_request_blank.errors, 'Blank logout request' + end + + it "return true when the logout request is initialized with valid data" do + assert logout_request.is_valid? + assert_empty logout_request.errors + assert_equal 'someone@example.org', logout_request.nameid end - it "return true when the request is initialized with valid data" do - request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) - assert request.is_valid? - assert_equal 'someone@example.org', request.name_id + it "should be idempotent when the logout request is initialized with invalid data" do + assert !invalid_logout_request.is_valid? + assert_equal ['Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd'], invalid_logout_request.errors + assert !invalid_logout_request.is_valid? + assert_equal ['Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd'], invalid_logout_request.errors end - it "should be idempotent when the response is initialized with invalid data" do - request = OneLogin::RubySaml::SloLogoutrequest.new(invalid_xml_response) - assert !request.is_valid? - assert !request.is_valid? + it "should be idempotent when the logout request is initialized with valid data" do + assert logout_request.is_valid? + assert_empty logout_request.errors + assert logout_request.is_valid? + assert_empty logout_request.errors end - it "should be idempotent when the response is initialized with valid data" do - request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) - assert request.is_valid? - assert request.is_valid? + it "collect errors when collect_errors=true" do + settings.idp_entity_id = 'http://idp.example.com/invalid' + settings.security[:logout_requests_signed] = true + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + settings.idp_cert = ruby_saml_cert_text + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + params = {} + params['SAMLRequest'] = logout_request_deflated_base64 + params['RelayState'] = 'http://invalid.example.com' + params['Signature'] = 'invalid_signature' + params['SigAlg'] = XMLSecurity::Document::RSA_SHA1 + options = {} + options[:get_params] = params + + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + logout_request_sign_test.settings = settings + + collect_errors = true + assert !logout_request_sign_test.is_valid?(collect_errors) + assert_includes logout_request_sign_test.errors, "Invalid Signature on Logout Request" + assert_includes logout_request_sign_test.errors, "Doesn't match the issuer, expected: , but was: " end it "raise error for invalid xml" do - logout_request = OneLogin::RubySaml::SloLogoutrequest.new(invalid_xml_response) - assert_raises(OneLogin::RubySaml::ValidationError) { logout_request.validate! } + invalid_logout_request.soft = false + assert_raises(OneLogin::RubySaml::ValidationError) { invalid_logout_request.is_valid? } end + end - describe "#name_id" do + describe "#nameid" do it "extract the value of the name id element" do - request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) - assert_equal "someone@example.org", request.name_id + assert_equal "someone@example.org", logout_request.nameid + end + + it 'is not possible when encryptID but no private key' do + assert_raises(OneLogin::RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do + assert_equal "someone@example.org", logout_request_encrypted_nameid.nameid + end + end + + it "extract the value of the name id element inside an EncryptedId" do + settings.private_key = ruby_saml_key_text + logout_request_encrypted_nameid.settings = settings + assert_equal "someone@example.org", logout_request_encrypted_nameid.nameid + end + end + + describe "#nameid_format" do + let(:logout_request) { OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document_with_name_id_format) } + + it "extract the format attribute of the name id element" do + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", logout_request.nameid_format + end + + it 'is not possible when encryptID but no private key' do + assert_raises(OneLogin::RubySaml::ValidationError, "An EncryptedID found and no SP private key found on the settings to decrypt it") do + assert_equal "someone@example.org", logout_request_encrypted_nameid.nameid + end + end + + it "extract the format attribute of the name id element" do + settings.private_key = ruby_saml_key_text + logout_request_encrypted_nameid.settings = settings + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", logout_request_encrypted_nameid.nameid_format end end describe "#issuer" do - it "return the issuer inside the request" do - request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) - assert_equal "https://app.onelogin.com/saml/metadata/SOMEACCOUNT", request.issuer + it "return the issuer inside the logout request" do + assert_equal "https://app.onelogin.com/saml/metadata/SOMEACCOUNT", logout_request.issuer end end describe "#id" do it "extract the value of the ID attribute" do - request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) - assert_equal "_c0348950-935b-0131-1060-782bcb56fcaa", request.id + assert_equal "_c0348950-935b-0131-1060-782bcb56fcaa", logout_request.id + end + end + + describe "#not_on_or_after" do + it "extract the value of the NotOnOrAfter attribute" do + time_value = '2014-07-17T01:01:48Z' + assert_nil logout_request.not_on_or_after + logout_request.document.root.attributes['NotOnOrAfter'] = time_value + assert_equal Time.parse(time_value), logout_request.not_on_or_after + end + end + + describe '#session_indexes' do + it "return empty array when no SessionIndex" do + assert_equal [], logout_request.session_indexes + end + + it "return an Array with one SessionIndex" do + logout_request_with_session_index = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_xml_with_session_index) + assert_equal ['_ea853497-c58a-408a-bc23-c849752d9741'], logout_request_with_session_index.session_indexes + end + end + + describe "#validate_id" do + it "return true when there is a valid ID in the logout request" do + assert logout_request.send(:validate_id) + assert_empty logout_request.errors + end + + it "return false when there is an invalid ID in the logout request" do + logout_request_blank = OneLogin::RubySaml::SloLogoutrequest.new('') + assert !logout_request_blank.send(:validate_id) + assert_includes logout_request_blank.errors, "Missing ID attribute on Logout Request" + end + end + + describe "#validate_version" do + it "return true when the logout request is SAML 2.0 Version" do + assert logout_request.send(:validate_version) + end + + it "return false when the logout request is not SAML 2.0 Version" do + logout_request_blank = OneLogin::RubySaml::SloLogoutrequest.new('') + assert !logout_request_blank.send(:validate_version) + assert_includes logout_request_blank.errors, "Unsupported SAML version" + end + end + + describe "#validate_not_on_or_after" do + it "return true when the logout request has a valid NotOnOrAfter or does not contain any" do + assert logout_request.send(:validate_not_on_or_after) + assert_empty logout_request.errors + + Timecop.freeze Time.parse('2014-07-17T01:01:47Z') do + logout_request.document.root.attributes['NotOnOrAfter'] = '2014-07-17T01:01:48Z' + assert logout_request.send(:validate_not_on_or_after) + assert_empty logout_request.errors + end + end + + it "return false when the logout request has an invalid NotOnOrAfter" do + Timecop.freeze Time.parse('2014-07-17T01:01:49Z') do + logout_request.document.root.attributes['NotOnOrAfter'] = '2014-07-17T01:01:48Z' + assert !logout_request.send(:validate_not_on_or_after) + assert_match(/Current time is on or after NotOnOrAfter/, logout_request.errors[0]) + end + end + + it "raise when the logout request has an invalid NotOnOrAfter" do + Timecop.freeze Time.parse('2014-07-17T01:01:49Z') do + logout_request.document.root.attributes['NotOnOrAfter'] = '2014-07-17T01:01:48Z' + logout_request.soft = false + assert_raises(OneLogin::RubySaml::ValidationError, "Current time is on or after NotOnOrAfter") do + logout_request.send(:validate_not_on_or_after) + end + end + end + + it "optionally allows for clock drift" do + # Java Floats behave differently than MRI + java = jruby? || truffleruby? + + logout_request.soft = true + logout_request.document.root.attributes['NotOnOrAfter'] = '2011-06-14T18:31:01.516Z' + + # The NotBefore condition in the document is 2011-06-1418:31:01.516Z + Timecop.freeze(Time.parse("2011-06-14T18:31:02Z")) do + logout_request.options[:allowed_clock_drift] = 0.483 + assert !logout_request.send(:validate_not_on_or_after) + + logout_request.options[:allowed_clock_drift] = java ? 0.485 : 0.484 + assert logout_request.send(:validate_not_on_or_after) + + logout_request.options[:allowed_clock_drift] = '0.483' + assert !logout_request.send(:validate_not_on_or_after) + + logout_request.options[:allowed_clock_drift] = java ? '0.485' : '0.484' + assert logout_request.send(:validate_not_on_or_after) + end + end + end + + describe "#validate_request_state" do + it "return true when valid logout request xml" do + assert logout_request.send(:validate_request_state) + assert_empty logout_request.errors + assert logout_request.send(:validate_request_state) + assert_empty logout_request.errors + end + + it "return false when invalid logout request xml" do + logout_request_blank = OneLogin::RubySaml::SloLogoutrequest.new('') + logout_request_blank.soft = true + assert !logout_request_blank.send(:validate_request_state) + assert_includes logout_request_blank.errors, "Blank logout request" + end + + it "raise error for invalid xml" do + logout_request_blank = OneLogin::RubySaml::SloLogoutrequest.new('') + logout_request_blank.soft = false + assert_raises(OneLogin::RubySaml::ValidationError, "Blank logout request") do + logout_request_blank.send(:validate_request_state) + end + end + end + + describe "#validate_structure" do + it "return true when encountering a valid Logout Request xml" do + assert logout_request.send(:validate_structure) + assert_empty logout_request.errors + end + + it "return false when encountering a Logout Request bad formatted" do + assert !invalid_logout_request.send(:validate_structure) + assert_includes invalid_logout_request.errors, "Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd" + end + + it "raise when encountering a Logout Request bad formatted" do + invalid_logout_request.soft = false + assert_raises(OneLogin::RubySaml::ValidationError, "Element '{urn:oasis:names:tc:SAML:2.0:assertion}Issuer': This element is not expected") do + invalid_logout_request.send(:validate_structure) + end + end + end + + describe "#validate_issuer" do + it "return true when the issuer of the Logout Request matchs the IdP entityId" do + logout_request.settings.idp_entity_id = 'https://app.onelogin.com/saml/metadata/SOMEACCOUNT' + assert logout_request.send(:validate_issuer) + end + + it "return false when the issuer of the Logout Request does not match the IdP entityId" do + logout_request.settings.idp_entity_id = 'http://idp.example.com/invalid' + assert !logout_request.send(:validate_issuer) + assert_includes logout_request.errors, "Doesn't match the issuer, expected: <#{logout_request.settings.idp_entity_id}>, but was: " + end + + it "raise when the issuer of the Logout Request does not match the IdP entityId" do + logout_request.settings.idp_entity_id = 'http://idp.example.com/invalid' + logout_request.soft = false + assert_raises(OneLogin::RubySaml::ValidationError, "Doesn't match the issuer, expected: <#{logout_request.settings.idp_entity_id}>, but was: ") do + logout_request.send(:validate_issuer) + end + end + end + + describe "#validate_signature" do + before do + settings.security[:logout_requests_signed] = true + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + settings.idp_cert = ruby_saml_cert_text + end + + it "return true when no idp_cert is provided and option :relax_signature_validation is present" do + settings.idp_cert = nil + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + options[:relax_signature_validation] = true + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + logout_request_sign_test.settings = settings + assert logout_request_sign_test.send(:validate_signature) + end + + it "return false when no idp_cert is provided and no option :relax_signature_validation is present" do + settings.idp_cert = nil + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + logout_request_sign_test.settings = settings + assert !logout_request_sign_test.send(:validate_signature) + end + + it "return true when valid RSA_SHA1 Signature" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + logout_request_sign_test.settings = settings + assert logout_request_sign_test.send(:validate_signature) + end + + it "return true when valid RSA_SHA256 Signature" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + options = {} + options[:get_params] = params + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + params['RelayState'] = params[:RelayState] + logout_request_sign_test.settings = settings + assert logout_request_sign_test.send(:validate_signature) + end + + it "return false when invalid RSA_SHA1 Signature" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params['RelayState'] = 'http://invalid.example.com' + params[:RelayState] = params['RelayState'] + options = {} + options[:get_params] = params + + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + logout_request_sign_test.settings = settings + assert !logout_request_sign_test.send(:validate_signature) + end + + it "raise when invalid RSA_SHA1 Signature" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.soft = false + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params['RelayState'] = 'http://invalid.example.com' + params[:RelayState] = params['RelayState'] + options = {} + options[:get_params] = params + options[:settings] = settings + + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + assert_raises(OneLogin::RubySaml::ValidationError, "Invalid Signature on Logout Request") do + logout_request_sign_test.send(:validate_signature) + end + end + + it "raise when get_params encoding differs from what this library generates" do + # Use Logoutrequest only to build the SAMLRequest parameter. + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.soft = false + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, "RelayState" => "http://example.com") + # Assemble query string. + query = OneLogin::RubySaml::Utils.build_query( + :type => 'SAMLRequest', + :data => params['SAMLRequest'], + :relay_state => params['RelayState'], + :sig_alg => params['SigAlg'] + ) + # Modify the query string so that it encodes the same values, + # but with different percent-encoding. Sanity-check that they + # really are equialent before moving on. + original_query = query.dup + query.gsub!("example", "ex%61mple") + refute_equal(query, original_query) + assert_equal(CGI.unescape(query), CGI.unescape(original_query)) + # Make normalised signature based on our modified params. + sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) + params['Signature'] = Base64.encode64(signature).gsub(/\n/, "") + # Construct SloLogoutrequest and ask it to validate the signature. + # It will do it incorrectly, because it will compute it based on re-encoded + # query parameters, rather than their original encodings. + options = {} + options[:get_params] = params + options[:settings] = settings + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + assert_raises(OneLogin::RubySaml::ValidationError, "Invalid Signature on Logout Request") do + logout_request_sign_test.send(:validate_signature) + end + end + + it "return true even if raw_get_params encoding differs from what this library generates" do + # Use Logoutrequest only to build the SAMLRequest parameter. + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.soft = false + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, "RelayState" => "http://example.com") + # Assemble query string. + query = OneLogin::RubySaml::Utils.build_query( + :type => 'SAMLRequest', + :data => params['SAMLRequest'], + :relay_state => params['RelayState'], + :sig_alg => params['SigAlg'] + ) + # Modify the query string so that it encodes the same values, + # but with different percent-encoding. Sanity-check that they + # really are equialent before moving on. + original_query = query.dup + query.gsub!("example", "ex%61mple") + refute_equal(query, original_query) + assert_equal(CGI.unescape(query), CGI.unescape(original_query)) + # Make normalised signature based on our modified params. + sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method]) + signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) + params['Signature'] = Base64.encode64(signature).gsub(/\n/, "") + # Construct SloLogoutrequest and ask it to validate the signature. + # Provide the altered parameter in its raw URI-encoded form, + # so that we don't have to guess the value that contributed to the signature. + options = {} + options[:get_params] = params + options[:get_params].delete("RelayState") + options[:raw_get_params] = { + "RelayState" => "http%3A%2F%2Fex%61mple.com", + } + options[:settings] = settings + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + assert logout_request_sign_test.send(:validate_signature) + end + + it "handles Azure AD downcased request encoding" do + # Use Logoutrequest only to build the SAMLRequest parameter. + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + settings.soft = false + + # Creating the query manually to tweak it later instead of using + # OneLogin::RubySaml::Utils.build_query + request_doc = OneLogin::RubySaml::Logoutrequest.new.create_logout_request_xml_doc(settings) + request = Zlib::Deflate.deflate(request_doc.to_s, 9)[2..-5] + base64_request = Base64.encode64(request).gsub(/\n/, "") + # The original request received from Azure AD comes with downcased + # encoded characters, like %2f instead of %2F, and the signature they + # send is based on this base64 request. + params = { + 'SAMLRequest' => downcased_escape(base64_request), + 'SigAlg' => downcased_escape(settings.security[:signature_method]), + } + # Assemble query string. + query = "SAMLRequest=#{params['SAMLRequest']}&SigAlg=#{params['SigAlg']}" + # Make normalised signature based on our modified params. + sign_algorithm = XMLSecurity::BaseDocument.new.algorithm( + settings.security[:signature_method] + ) + signature = settings.get_sp_signing_key.sign(sign_algorithm.new, query) + params['Signature'] = downcased_escape(Base64.encode64(signature).gsub(/\n/, "")) + + # Then parameters are usually unescaped, like we manage them in rails + params = params.map { |k, v| [k, CGI.unescape(v)] }.to_h + # Construct SloLogoutrequest and ask it to validate the signature. + # It will fail because the signature is based on the downcased request + logout_request_downcased_test = OneLogin::RubySaml::SloLogoutrequest.new( + params['SAMLRequest'], get_params: params, settings: settings, + ) + assert_raises(OneLogin::RubySaml::ValidationError, "Invalid Signature on Logout Request") do + logout_request_downcased_test.send(:validate_signature) + end + + # For this case, the parameters will be forced to be downcased after + # being escaped with :lowercase_url_encoding security option + settings.security[:lowercase_url_encoding] = true + logout_request_force_downcasing_test = OneLogin::RubySaml::SloLogoutrequest.new( + params['SAMLRequest'], get_params: params, settings: settings + ) + assert logout_request_force_downcasing_test.send(:validate_signature) + end + end + + describe "#validate_signature with multiple idp certs" do + before do + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + settings.idp_cert = nil + settings.security[:logout_requests_signed] = true + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + end + + it "return true when at least a idp_cert is valid" do + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + settings.idp_cert_multi = { + :signing => [ruby_saml_cert_text2, ruby_saml_cert_text], + :encryption => [] + } + logout_request_sign_test.settings = settings + assert logout_request_sign_test.send(:validate_signature) + end + + it "return false when cert expired and check_idp_cert_expiration expired" do + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + settings.security[:check_idp_cert_expiration] = true + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + settings.idp_cert = nil + settings.idp_cert_multi = { + :signing => [ruby_saml_cert_text], + :encryption => [] + } + logout_request_sign_test.settings = settings + assert !logout_request_sign_test.send(:validate_signature) + assert_includes logout_request_sign_test.errors, "IdP x509 certificate expired" + end + + it "return false when none cert on idp_cert_multi is valid" do + params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com') + params['RelayState'] = params[:RelayState] + options = {} + options[:get_params] = params + logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options) + settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint + settings.idp_cert_multi = { + :signing => [ruby_saml_cert_text2, ruby_saml_cert_text2], + :encryption => [] + } + logout_request_sign_test.settings = settings + assert !logout_request_sign_test.send(:validate_signature) + assert_includes logout_request_sign_test.errors, "Invalid Signature on Logout Request" end end end diff --git a/test/slo_logoutresponse_test.rb b/test/slo_logoutresponse_test.rb index 6f8f85ea7..7c9669f01 100644 --- a/test/slo_logoutresponse_test.rb +++ b/test/slo_logoutresponse_test.rb @@ -6,125 +6,240 @@ class SloLogoutresponseTest < Minitest::Test describe "SloLogoutresponse" do let(:settings) { OneLogin::RubySaml::Settings.new } + let(:logout_request) { OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) } - it "create the deflated SAMLResponse URL parameter" do - settings.idp_slo_target_url = "http://unauth.com/logout" + before do + settings.idp_entity_id = 'https://app.onelogin.com/saml/metadata/SOMEACCOUNT' + settings.idp_slo_service_url = "http://unauth.com/logout" settings.name_identifier_value = "f00f00" settings.compress_request = true + settings.certificate = ruby_saml_cert_text + settings.private_key = ruby_saml_key_text + logout_request.settings = settings + end - request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) - - assert request.is_valid? - - unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, request.id) - assert unauth_url =~ /^http:\/\/unauth\.com\/logout\?SAMLResponse=/ + it "create the deflated SAMLResponse URL parameter" do + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id) + assert_match(/^http:\/\/unauth\.com\/logout\?SAMLResponse=/, unauth_url) inflated = decode_saml_response_payload(unauth_url) - - assert_match /^ nil }) + assert_match(/&hello=$/, unauth_url) + + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { :foo => "bar" }) + assert_match(/&foo=bar$/, unauth_url) + + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { :RelayState => "http://idp.example.com" }) + assert_match(/&RelayState=http%3A%2F%2Fidp.example.com$/, unauth_url) + end - request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) + it "RelayState cases" do + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { :RelayState => nil }) + assert !unauth_url.include?('RelayState') - unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, request.id, nil, { :hello => nil }) - assert unauth_url =~ /&hello=$/ + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { :RelayState => "http://example.com" }) + assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com') - unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, request.id, nil, { :foo => "bar" }) - assert unauth_url =~ /&foo=bar$/ + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { 'RelayState' => nil }) + assert !unauth_url.include?('RelayState') - unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, request.id, nil, { :RelayState => "http://idp.example.com" }) - assert unauth_url =~ /&RelayState=http%3A%2F%2Fidp.example.com$/ + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, { 'RelayState' => "http://example.com" }) + assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com') end it "set InResponseTo to the ID from the logout request" do - settings.idp_slo_target_url = "http://unauth.com/logout" - settings.name_identifier_value = "f00f00" - settings.compress_request = true + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id) + + inflated = decode_saml_response_payload(unauth_url) + assert_match(/InResponseTo='_c0348950-935b-0131-1060-782bcb56fcaa'/, inflated) + end - request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) - unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, request.id) + it "set a custom successful logout message on the response" do + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, "Custom Logout Message") inflated = decode_saml_response_payload(unauth_url) + assert_match(/Custom Logout Message<\/samlp:StatusMessage>/, inflated) + end - assert_match /InResponseTo='_c0348950-935b-0131-1060-782bcb56fcaa'/, inflated + it "set a custom logout message and an status on the response" do + unauth_url = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, nil, "Custom Logout Message", {}, "urn:oasis:names:tc:SAML:2.0:status:PartialLogout") + + inflated = decode_saml_response_payload(unauth_url) + assert_match(/Custom Logout Message<\/samlp:StatusMessage>/, inflated) + assert_match(/Custom Logout Message<\/samlp:StatusMessage>/, inflated + assert_match(/^_/, request.uuid) + end + + it "creates request with ID is prefixed, when :id_prefix is passed" do + OneLogin::RubySaml::Utils::set_prefix("test") + request = OneLogin::RubySaml::SloLogoutresponse.new + assert_match(/^test/, request.uuid) + OneLogin::RubySaml::Utils::set_prefix("_") + end end - describe "when the settings indicate to sign (embedded) logout response" do - it "create a signed logout response" do - settings = OneLogin::RubySaml::Settings.new + describe "signing with HTTP-POST binding" do + before do + settings.idp_sso_service_binding = :redirect + settings.idp_slo_service_binding = :post settings.compress_response = false - settings.idp_slo_target_url = "http://example.com?field=value" settings.security[:logout_responses_signed] = true - settings.security[:embed_sign] = true - settings.certificate = ruby_saml_cert_text - settings.private_key = ruby_saml_key_text + end + + it "doesn't sign through create_xml_document" do + unauth_res = OneLogin::RubySaml::SloLogoutresponse.new + inflated = unauth_res.create_xml_document(settings).to_s + + refute_match %r[([a-zA-Z0-9/+=]+)], inflated + refute_match %r[], inflated + refute_match %r[], inflated + end + + it "sign unsigned request" do + unauth_res = OneLogin::RubySaml::SloLogoutresponse.new + unauth_res_doc = unauth_res.create_xml_document(settings) + inflated = unauth_res_doc.to_s + + refute_match %r[([a-zA-Z0-9/+=]+)], inflated + refute_match %r[], inflated + refute_match %r[], inflated + + inflated = unauth_res.sign_document(unauth_res_doc, settings).to_s + + assert_match %r[([a-zA-Z0-9/+=]+)], inflated + assert_match %r[], inflated + assert_match %r[], inflated + end - request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) - params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, request.id, "Custom Logout Message") + it "signs through create_logout_response_xml_doc" do + unauth_res = OneLogin::RubySaml::SloLogoutresponse.new + inflated = unauth_res.create_logout_response_xml_doc(settings).to_s + + assert_match %r[([a-zA-Z0-9/+=]+)], inflated + assert_match %r[], inflated + assert_match %r[], inflated + end + + it "create a signed logout response" do + logout_request.settings = settings + + params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message") response_xml = Base64.decode64(params["SAMLResponse"]) assert_match %r[([a-zA-Z0-9/+=]+)], response_xml - response_xml =~ // - response_xml =~ // + assert_match(//, response_xml) + assert_match(//, response_xml) end it "create a signed logout response with 256 digest and signature methods" do - settings = OneLogin::RubySaml::Settings.new - settings.compress_response = false - settings.idp_slo_target_url = "http://example.com?field=value" - settings.security[:logout_responses_signed] = true - settings.security[:embed_sign] = true settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + settings.security[:digest_method] = XMLSecurity::Document::SHA256 + logout_request.settings = settings + + params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message") + + response_xml = Base64.decode64(params["SAMLResponse"]) + assert_match %r[([a-zA-Z0-9/+=]+)], response_xml + assert_match(//, response_xml) + assert_match(//, response_xml) + end + + it "create a signed logout response with 512 digest and signature method RSA_SHA384" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384 settings.security[:digest_method] = XMLSecurity::Document::SHA512 - settings.certificate = ruby_saml_cert_text - settings.private_key = ruby_saml_key_text + logout_request.settings = settings + + params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message") + + response_xml = Base64.decode64(params["SAMLResponse"]) + assert_match %r[([a-zA-Z0-9/+=]+)], response_xml + assert_match(//, response_xml) + assert_match(//, response_xml) + end - request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) - params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, request.id, "Custom Logout Message") + it "create a signed logout response using the first certificate and key" do + settings.certificate = nil + settings.private_key = nil + settings.sp_cert_multi = { + signing: [ + { certificate: ruby_saml_cert_text, private_key: ruby_saml_key_text }, + CertificateHelper.generate_pair_hash + ] + } + logout_request.settings = settings + + params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message") + + response_xml = Base64.decode64(params["SAMLResponse"]) + assert_match %r[([a-zA-Z0-9/+=]+)], response_xml + assert_match(//, response_xml) + assert_match(//, response_xml) + end + + it "create a signed logout response using the first valid certificate and key when :check_sp_cert_expiration is true" do + settings.certificate = nil + settings.private_key = nil + settings.security[:check_sp_cert_expiration] = true + settings.sp_cert_multi = { + signing: [ + { certificate: ruby_saml_cert_text, private_key: ruby_saml_key_text }, + CertificateHelper.generate_pair_hash + ] + } + logout_request.settings = settings + + params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message") response_xml = Base64.decode64(params["SAMLResponse"]) assert_match %r[([a-zA-Z0-9/+=]+)], response_xml - response_xml =~ // - response_xml =~ // + assert_match(//, response_xml) + assert_match(//, response_xml) + end + + it "raises error when no valid certs and :check_sp_cert_expiration is true" do + settings.security[:check_sp_cert_expiration] = true + logout_request.settings = settings + + assert_raises(OneLogin::RubySaml::ValidationError, 'The SP certificate expired.') do + OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message") + end end end - describe "#create_params when the settings indicate to sign the logout response" do - def setup - @settings = OneLogin::RubySaml::Settings.new - @settings.compress_response = false - @settings.idp_slo_target_url = "http://example.com?field=value" - @settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" - @settings.security[:logout_responses_signed] = true - @settings.security[:embed_sign] = false - @settings.certificate = ruby_saml_cert_text - @settings.private_key = ruby_saml_key_text - @cert = OpenSSL::X509::Certificate.new(ruby_saml_cert_text) - @request = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) + describe "signing with HTTP-Redirect binding" do + let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) } + + before do + settings.idp_sso_service_binding = :post + settings.idp_slo_service_binding = :redirect + settings.compress_response = false + settings.security[:logout_responses_signed] = true end it "create a signature parameter with RSA_SHA1 and validate it" do settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 - params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(@settings, @request.id, "Custom Logout Message", :RelayState => 'http://example.com') + params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com') assert params['SAMLResponse'] assert params[:RelayState] assert params['Signature'] @@ -136,13 +251,13 @@ def setup signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA1 - assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end - it "create a signature parameter with RSA_SHA256 and validate it" do - @settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 + it "create a signature parameter with RSA_SHA256 /SHA256 and validate it" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256 - params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(@settings, @request.id, "Custom Logout Message", :RelayState => 'http://example.com') + params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com') assert params['SAMLResponse'] assert params[:RelayState] assert params['Signature'] @@ -155,9 +270,153 @@ def setup signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) assert_equal signature_algorithm, OpenSSL::Digest::SHA256 - assert @cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) end + it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384 + + params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com') + assert params['SAMLResponse'] + assert params[:RelayState] + assert params['Signature'] + + assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA384 + + query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + assert_equal signature_algorithm, OpenSSL::Digest::SHA384 + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + end + + it "create a signature parameter with RSA_SHA512 / SHA512 and validate it" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512 + + params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com') + assert params['SAMLResponse'] + assert params[:RelayState] + assert params['Signature'] + + assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA512 + + query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + assert_equal signature_algorithm, OpenSSL::Digest::SHA512 + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + end + + it "create a signature parameter using the first certificate and key" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + settings.compress_request = false + settings.certificate = nil + settings.private_key = nil + settings.sp_cert_multi = { + signing: [ + { certificate: ruby_saml_cert_text, private_key: ruby_saml_key_text }, + CertificateHelper.generate_pair_hash + ] + } + + params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com') + assert params['SAMLResponse'] + assert params[:RelayState] + assert params['Signature'] + assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + + query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + assert_equal signature_algorithm, OpenSSL::Digest::SHA1 + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + end + + it "raises error when no valid certs and :check_sp_cert_expiration is true" do + settings.security[:check_sp_cert_expiration] = true + + assert_raises(OneLogin::RubySaml::ValidationError, 'The SP certificate expired.') do + OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com') + end + end + end + + describe "DEPRECATED: signing with HTTP-POST binding via :embed_sign" do + before do + settings.compress_response = false + settings.security[:logout_responses_signed] = true + settings.security[:embed_sign] = true + end + + it "doesn't sign through create_xml_document" do + unauth_res = OneLogin::RubySaml::SloLogoutresponse.new + inflated = unauth_res.create_xml_document(settings).to_s + + refute_match %r[([a-zA-Z0-9/+=]+)], inflated + refute_match %r[], inflated + refute_match %r[], inflated + end + + it "sign unsigned request" do + unauth_res = OneLogin::RubySaml::SloLogoutresponse.new + unauth_res_doc = unauth_res.create_xml_document(settings) + inflated = unauth_res_doc.to_s + + refute_match %r[([a-zA-Z0-9/+=]+)], inflated + refute_match %r[], inflated + refute_match %r[], inflated + + inflated = unauth_res.sign_document(unauth_res_doc, settings).to_s + + assert_match %r[([a-zA-Z0-9/+=]+)], inflated + assert_match %r[], inflated + assert_match %r[], inflated + end + end + + describe "DEPRECATED: signing with HTTP-Redirect binding via :embed_sign" do + let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) } + + before do + settings.compress_response = false + settings.security[:logout_responses_signed] = true + settings.security[:embed_sign] = false + end + + it "create a signature parameter with RSA_SHA1 and validate it" do + settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1 + + params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, logout_request.id, "Custom Logout Message", :RelayState => 'http://example.com') + assert params['SAMLResponse'] + assert params[:RelayState] + assert params['Signature'] + assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1 + + query_string = "SAMLResponse=#{CGI.escape(params['SAMLResponse'])}" + query_string << "&RelayState=#{CGI.escape(params[:RelayState])}" + query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}" + + signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg']) + assert_equal signature_algorithm, OpenSSL::Digest::SHA1 + assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string) + end + end + + describe "#manipulate response_id" do + it "be able to modify the response id" do + logoutresponse = OneLogin::RubySaml::SloLogoutresponse.new + response_id = logoutresponse.response_id + assert_equal response_id, logoutresponse.uuid + logoutresponse.uuid = "new_uuid" + assert_equal logoutresponse.response_id, logoutresponse.uuid + assert_equal "new_uuid", logoutresponse.response_id + end end end end diff --git a/test/test_helper.rb b/test/test_helper.rb index d3e6f5807..39dbce25a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,16 +1,51 @@ +require 'simplecov' +require 'simplecov-lcov' + +SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true +SimpleCov::Formatter::LcovFormatter.config.output_directory = 'coverage' +SimpleCov::Formatter::LcovFormatter.config.lcov_file_name = 'lcov.info' +SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter +SimpleCov.start do + add_filter "test/" + add_filter "vendor/" + add_filter "lib/onelogin/ruby-saml/logging.rb" +end + +require 'stringio' require 'rubygems' require 'bundler' require 'minitest/autorun' require 'mocha/setup' +require 'timecop' +Dir[File.expand_path('../helpers/**/*.rb', __FILE__)].each { |f| require f } Bundler.require :default, :test $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.dirname(__FILE__)) -ENV["ruby-saml/testing"] = "1" +require 'onelogin/ruby-saml/logging' + +TEST_LOGGER = Logger.new(StringIO.new) +OneLogin::RubySaml::Logging.logger = TEST_LOGGER class Minitest::Test + def self.jruby? + defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' + end + + def jruby? + self.class.jruby? + end + + def self.truffleruby? + defined?(RUBY_ENGINE) && RUBY_ENGINE == 'truffleruby' + end + + def truffleruby? + self.class.truffleruby? + end + def fixture(document, base64 = true) response = Dir.glob(File.join(File.dirname(__FILE__), "responses", "#{document}*")).first if base64 && response =~ /\.xml$/ @@ -20,84 +55,257 @@ def fixture(document, base64 = true) end end - def response_document - @response_document ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response1.xml.base64')) + def read_response(response) + File.read(File.join(File.dirname(__FILE__), "responses", response)) end - def response_document_2 - @response_document2 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response2.xml.base64')) + def read_invalid_response(response) + File.read(File.join(File.dirname(__FILE__), "responses", "invalids", response)) end - def response_document_3 - @response_document3 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response3.xml.base64')) + def read_logout_request(request) + File.read(File.join(File.dirname(__FILE__), "logout_requests", request)) end - def response_document_4 - @response_document4 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response4.xml.base64')) + def read_certificate(certificate) + File.read(File.join(File.dirname(__FILE__), "certificates", certificate)) end - def response_document_5 - @response_document5 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response5.xml.base64')) + def response_document_valid_signed + @response_document_valid_signed ||= read_response("valid_response.xml.base64") end - def r1_response_document_6 - @response_document6 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'r1_response6.xml.base64')) + def response_document_valid_signed_without_x509certificate + @response_document_valid_signed_without_x509certificate ||= read_response("valid_response_without_x509certificate.xml.base64") end - def ampersands_response - @ampersands_response ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_with_ampersands.xml.base64')) + def response_document_without_recipient + @response_document_without_recipient ||= read_response("response_with_undefined_recipient.xml.base64") end - def response_document_6 - doc = Base64.decode64(response_document) + def signed_response_document_without_recipient + @signed_response_document_without_recipient ||= read_response("signed_response_with_undefined_recipient.xml.base64") + end + + def response_document_without_recipient_with_time_updated + doc = Base64.decode64(response_document_without_recipient) doc.gsub!(/NotBefore=\"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z\"/, "NotBefore=\"#{(Time.now-300).getutc.strftime("%Y-%m-%dT%XZ")}\"") doc.gsub!(/NotOnOrAfter=\"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z\"/, "NotOnOrAfter=\"#{(Time.now+300).getutc.strftime("%Y-%m-%dT%XZ")}\"") Base64.encode64(doc) end - def response_document_7 - @response_document7 ||= Base64.encode64(File.read(File.join(File.dirname(__FILE__), 'responses', 'response_no_cert_and_encrypted_attrs.xml'))) + def response_document_without_attributes + @response_document_without_attributes ||= read_response("response_without_attributes.xml.base64") end - def wrapped_response_2 - @wrapped_response_2 ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'wrapped_response_2.xml.base64')) + def response_document_without_reference_uri + @response_document_without_reference_uri ||= read_response("response_without_reference_uri.xml.base64") + end + + def response_document_with_signed_assertion + @response_document_with_signed_assertion ||= read_response("response_with_signed_assertion.xml.base64") + end + + def response_document_with_signed_assertion_2 + @response_document_with_signed_assertion_2 ||= read_response("response_with_signed_assertion_2.xml.base64") + end + + def response_document_with_ds_namespace_at_the_root + @response_document_with_ds_namespace_at_the_root ||= read_response("response_with_ds_namespace_at_the_root.xml.base64") + end + + def response_document_unsigned + @response_document_unsigned ||= read_response("response_unsigned_xml_base64") + end + + def response_document_with_saml2_namespace + @response_document_with_saml2_namespace ||= read_response("response_with_saml2_namespace.xml.base64") + end + + def ampersands_document + @ampersands_response ||= read_response("response_with_ampersands.xml.base64") + end + + def response_document_no_cert_and_encrypted_attrs + @response_document_no_cert_and_encrypted_attrs ||= Base64.encode64(read_response("response_no_cert_and_encrypted_attrs.xml")) + end + + def response_document_wrapped + @response_document_wrapped ||= read_response("response_wrapped.xml.base64") + end + + def response_document_assertion_wrapped + @response_document_assertion_wrapped ||= read_response("response_assertion_wrapped.xml.base64") + end + + def response_document_encrypted_nameid + @response_document_encrypted_nameid ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_encrypted_nameid.xml.base64')) + end + + def signed_message_encrypted_unsigned_assertion + @signed_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_unsigned_assertion.xml.base64')) + end + + def signed_message_encrypted_signed_assertion + @signed_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'signed_message_encrypted_signed_assertion.xml.base64')) + end + + def unsigned_message_encrypted_signed_assertion + @unsigned_message_encrypted_signed_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_signed_assertion.xml.base64')) + end + + def unsigned_message_encrypted_unsigned_assertion + @unsigned_message_encrypted_unsigned_assertion ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'unsigned_message_encrypted_unsigned_assertion.xml.base64')) + end + + def response_document_encrypted_attrs + @response_document_encrypted_attrs ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_encrypted_attrs.xml.base64')) + end + + def response_document_double_status_code + @response_document_double_status_code ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'response_double_status_code.xml.base64')) end def signature_fingerprint_1 @signature_fingerprint1 ||= "C5:19:85:D9:47:F1:BE:57:08:20:25:05:08:46:EB:27:F6:CA:B7:83" end + # certificate used on response_with_undefined_recipient def signature_1 - @signature1 ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'certificate1')) + @signature1 ||= read_certificate("certificate1") + end + + # certificate used on response_document_with_signed_assertion_2 + def certificate_without_head_foot + @certificate_without_head_foot ||= read_certificate("certificate_without_head_foot") + end + + def idp_metadata_descriptor + @idp_metadata_descriptor ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor.xml')) end - def r1_signature_2 - @signature2 ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'r1_certificate2_base64')) + def idp_metadata_descriptor2 + @idp_metadata_descriptor2 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_2.xml')) end - def idp_metadata - @idp_metadata ||= File.read(File.join(File.dirname(__FILE__), 'responses', 'idp_descriptor.xml')) + def idp_metadata_descriptor3 + @idp_metadata_descriptor3 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_3.xml')) + end + + def idp_metadata_descriptor4 + @idp_metadata_descriptor4 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_4.xml')) + end + + def idp_metadata_descriptor5 + @idp_metadata_descriptor5 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_5.xml')) + end + + def idp_metadata_descriptor6 + @idp_metadata_descriptor6 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_descriptor_6.xml')) + end + + def no_idp_metadata_descriptor + @no_idp_metadata_descriptor ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'no_idp_descriptor.xml')) + end + + def idp_metadata_multiple_descriptors + @idp_metadata_multiple_descriptors ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_multiple_descriptors.xml')) + end + + def idp_metadata_multiple_descriptors2 + @idp_metadata_multiple_descriptors2 ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_multiple_descriptors_2.xml')) + end + + def idp_metadata_multiple_certs + @idp_metadata_multiple_descriptors ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_multi_certs.xml')) + end + + def idp_metadata_multiple_signing_certs + @idp_metadata_multiple_signing_certs ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_multi_signing_certs.xml')) + end + + def idp_metadata_same_sign_and_encrypt_cert + @idp_metadata_same_sign_and_encrypt_cert ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_same_sign_and_encrypt_cert.xml')) + end + + def idp_metadata_different_sign_and_encrypt_cert + @idp_metadata_different_sign_and_encrypt_cert ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_metadata_different_sign_and_encrypt_cert.xml')) + end + + def idp_different_slo_response_location + @idp_different_slo_response_location ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_different_slo_response_location.xml')) + end + + def idp_without_slo_response_location + @idp_without_slo_response_location ||= File.read(File.join(File.dirname(__FILE__), 'metadata', 'idp_without_slo_response_location.xml')) end def logout_request_document unless @logout_request_document - xml = File.read(File.join(File.dirname(__FILE__), 'responses', 'slo_request.xml')) + xml = read_logout_request("slo_request.xml") deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] @logout_request_document = Base64.encode64(deflated) end @logout_request_document end + def logout_request_document_with_name_id_format + unless @logout_request_document_with_name_id_format + xml = read_logout_request("slo_request_with_name_id_format.xml") + deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] + @logout_request_document_with_name_id_format = Base64.encode64(deflated) + end + @logout_request_document_with_name_id_format + end + + def logout_request_encrypted_nameid_document + unless @logout_request_encrypted_nameid_document + xml = read_logout_request("slo_request_encrypted_nameid.xml") + deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] + @logout_request_encrypted_nameid_document = Base64.encode64(deflated) + end + @logout_request_encrypted_nameid_document + end + + def logout_request_xml_with_session_index + @logout_request_xml_with_session_index ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request_with_session_index.xml')) + end + + def invalid_logout_request_document + unless @invalid_logout_request_document + xml = File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'invalid_slo_request.xml')) + deflated = Zlib::Deflate.deflate(xml, 9)[2..-5] + @invalid_logout_request_document = Base64.encode64(deflated) + end + @invalid_logout_request_document + end + + def logout_request_base64 + @logout_request_base64 ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request.xml.base64')) + end + + def logout_request_deflated_base64 + @logout_request_deflated_base64 ||= File.read(File.join(File.dirname(__FILE__), 'logout_requests', 'slo_request_deflated.xml.base64')) + end + def ruby_saml_cert @ruby_saml_cert ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text) end + def ruby_saml_cert2 + @ruby_saml_cert2 ||= OpenSSL::X509::Certificate.new(ruby_saml_cert_text2) + end + def ruby_saml_cert_fingerprint @ruby_saml_cert_fingerprint ||= Digest::SHA1.hexdigest(ruby_saml_cert.to_der).scan(/../).join(":") end def ruby_saml_cert_text - File.read(File.join(File.dirname(__FILE__), 'certificates', 'ruby-saml.crt')) + read_certificate("ruby-saml.crt") + end + + def ruby_saml_cert_text2 + read_certificate("ruby-saml-2.crt") end def ruby_saml_key @@ -105,14 +313,14 @@ def ruby_saml_key end def ruby_saml_key_text - File.read(File.join(File.dirname(__FILE__), 'certificates', 'ruby-saml.key')) + read_certificate("ruby-saml.key") end # # logoutresponse fixtures # def random_id - "_#{UUID.new.generate}" + "_#{OneLogin::RubySaml::Utils.uuid}" end # @@ -142,4 +350,53 @@ def decode_saml_request_payload(unauth_url) zstream.close inflated end + + SCHEMA_DIR = File.expand_path(File.join(__FILE__, '../../lib/schemas')) + + # + # validate an xml document against the given schema + # + def validate_xml!(document, schema) + Dir.chdir(SCHEMA_DIR) do + xsd = if schema.is_a? Nokogiri::XML::Schema + schema + else + Nokogiri::XML::Schema(File.read(schema)) + end + + xml = if document.is_a? Nokogiri::XML::Document + document + else + Nokogiri::XML(document) { |c| c.strict } + end + + result = xsd.validate(xml) + + if result.length != 0 + raise "Schema validation failed! XSD validation errors: #{result.join(", ")}" + else + true + end + end + end + + # Allows to emulate Azure AD request behavior + def downcased_escape(str) + CGI.escape(str).gsub(/%[A-Fa-f0-9]{2}/) { |match| match.downcase } + end +end + +# Remove after https://github.com/jruby/jruby/issues/6613 is fixed +if Minitest::Test.jruby? + module JRubyZlibTestExtension + def capture_exceptions + super + + if failures && failures.reject! { |e| e.error && e.error.is_a?(Zlib::BufError) } # nil if nothing rejected + failures << Minitest::Skip.new('Skipping Zlib::BufError in JRuby. See: https://github.com/jruby/jruby/issues/6613') + end + end + end + + Minitest::Test.prepend(JRubyZlibTestExtension) end diff --git a/test/utils_test.rb b/test/utils_test.rb new file mode 100644 index 000000000..80a3cb96c --- /dev/null +++ b/test/utils_test.rb @@ -0,0 +1,458 @@ +require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) + +class UtilsTest < Minitest::Test + describe ".parse_duration" do + DURATIONS_FROM_EPOCH = { + # Basic formats + "P1Y1M1D" => "1971-02-02T00:00:00.000Z", + "PT1H1M1S" => "1970-01-01T01:01:01.000Z", + "P1W" => "1970-01-08T00:00:00.000Z", + "P1Y1M1DT1H1M1S" => "1971-02-02T01:01:01.000Z", + + # Negative duration + "-P1Y1M1DT1H1M1S" => "1968-11-29T22:58:59.000Z", + + # Nominal wraparounds + "P13M" => "1971-02-01T00:00:00.000Z", + "P31D" => "1970-02-01T00:00:00.000Z", + + # Decimal seconds + "PT0.5S" => "1970-01-01T00:00:00.500Z", + "PT0,5S" => "1970-01-01T00:00:00.500Z" + } + + def result(duration, reference = 0) + return nil if RUBY_VERSION < '1.9' + Time.at( + OneLogin::RubySaml::Utils.parse_duration(duration, reference) + ).utc.iso8601(3) + end + + DURATIONS_FROM_EPOCH.each do |duration, expected| + it "parses #{duration} to return #{expected} from the given timestamp" do + return if RUBY_VERSION < '1.9' + assert_equal expected, result(duration) + end + end + + it "returns the last calendar day of the next month when advancing from a longer month to a shorter one" do + initial_timestamp = Time.iso8601("1970-01-31T00:00:00.000Z").to_i + return if RUBY_VERSION < '1.9' + assert_equal "1970-02-28T00:00:00.000Z", result("P1M", initial_timestamp) + end + end + + describe ".format_cert" do + let(:formatted_certificate) {read_certificate("formatted_certificate")} + let(:formatted_chained_certificate) {read_certificate("formatted_chained_certificate")} + + it "returns empty string when the cert is an empty string" do + cert = "" + assert_equal "", OneLogin::RubySaml::Utils.format_cert(cert) + end + + it "returns nil when the cert is nil" do + cert = nil + assert_nil OneLogin::RubySaml::Utils.format_cert(cert) + end + + it "returns the certificate when it is valid" do + assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(formatted_certificate) + end + + it "reformats the certificate when there are spaces and no line breaks" do + invalid_certificate1 = read_certificate("invalid_certificate1") + assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate1) + end + + it "reformats the certificate when there are spaces and no headers" do + invalid_certificate2 = read_certificate("invalid_certificate2") + assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate2) + end + + it "returns the cert when it's encoded" do + encoded_certificate = read_certificate("certificate.der") + assert_equal encoded_certificate, OneLogin::RubySaml::Utils.format_cert(encoded_certificate) + end + + it "reformats the certificate when there line breaks and no headers" do + invalid_certificate3 = read_certificate("invalid_certificate3") + assert_equal formatted_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_certificate3) + end + + it "returns the chained certificate when it is a valid chained certificate" do + assert_equal formatted_chained_certificate, OneLogin::RubySaml::Utils.format_cert(formatted_chained_certificate) + end + + it "reformats the chained certificate when there are spaces and no line breaks" do + invalid_chained_certificate1 = read_certificate("invalid_chained_certificate1") + assert_equal formatted_chained_certificate, OneLogin::RubySaml::Utils.format_cert(invalid_chained_certificate1) + end + end + + describe ".format_private_key" do + let(:formatted_private_key) do + read_certificate("formatted_private_key") + end + + it "returns empty string when the private key is an empty string" do + private_key = "" + assert_equal "", OneLogin::RubySaml::Utils.format_private_key(private_key) + end + + it "returns nil when the private key is nil" do + private_key = nil + assert_nil OneLogin::RubySaml::Utils.format_private_key(private_key) + end + + it "returns the private key when it is valid" do + assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(formatted_private_key) + end + + it "reformats the private key when there are spaces and no line breaks" do + invalid_private_key1 = read_certificate("invalid_private_key1") + assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_private_key1) + end + + it "reformats the private key when there are spaces and no headers" do + invalid_private_key2 = read_certificate("invalid_private_key2") + assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_private_key2) + end + + it "reformats the private key when there line breaks and no headers" do + invalid_private_key3 = read_certificate("invalid_private_key3") + assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_private_key3) + end + + describe "an RSA public key" do + let(:formatted_rsa_private_key) do + read_certificate("formatted_rsa_private_key") + end + + it "returns the private key when it is valid" do + assert_equal formatted_rsa_private_key, OneLogin::RubySaml::Utils.format_private_key(formatted_rsa_private_key) + end + + it "reformats the private key when there are spaces and no line breaks" do + invalid_rsa_private_key1 = read_certificate("invalid_rsa_private_key1") + assert_equal formatted_rsa_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_rsa_private_key1) + end + + it "reformats the private key when there are spaces and no headers" do + invalid_rsa_private_key2 = read_certificate("invalid_rsa_private_key2") + assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_rsa_private_key2) + end + + it "reformats the private key when there line breaks and no headers" do + invalid_rsa_private_key3 = read_certificate("invalid_rsa_private_key3") + assert_equal formatted_private_key, OneLogin::RubySaml::Utils.format_private_key(invalid_rsa_private_key3) + end + end + end + + describe '.build_cert_object' do + it 'returns a certificate object for valid certificate string' do + cert_object = OneLogin::RubySaml::Utils.build_cert_object(ruby_saml_cert_text) + assert_instance_of OpenSSL::X509::Certificate, cert_object + end + + it 'returns nil for nil certificate string' do + assert_nil OneLogin::RubySaml::Utils.build_cert_object(nil) + end + + it 'returns nil for empty certificate string' do + assert_nil OneLogin::RubySaml::Utils.build_cert_object('') + end + + it 'raises error when given an invalid certificate string' do + assert_raises OpenSSL::X509::CertificateError do + OneLogin::RubySaml::Utils.build_cert_object('Foobar') + end + end + end + + describe '.build_private_key_object' do + it 'returns a private key object for valid private key string' do + private_key_object = OneLogin::RubySaml::Utils.build_private_key_object(ruby_saml_key_text) + assert_instance_of OpenSSL::PKey::RSA, private_key_object + end + + it 'returns nil for nil private key string' do + assert_nil OneLogin::RubySaml::Utils.build_private_key_object(nil) + end + + it 'returns nil for empty private key string' do + assert_nil OneLogin::RubySaml::Utils.build_private_key_object('') + end + + it 'raises error when given an invalid private key string' do + assert_raises OpenSSL::PKey::RSAError do + OneLogin::RubySaml::Utils.build_private_key_object('Foobar') + end + end + end + + describe ".build_query" do + it "returns the query string" do + params = {} + params[:type] = "SAMLRequest" + params[:data] = "PHNhbWxwOkF1dGhuUmVxdWVzdCBEZXN0aW5hdGlvbj0naHR0cDovL2V4YW1wbGUuY29tP2ZpZWxkPXZhbHVlJyBJRD0nXzk4NmUxZDEwLWVhY2ItMDEzMi01MGRkLTAwOTBmNWRlZGQ3NycgSXNzdWVJbnN0YW50PScyMDE1LTA2LTAxVDIwOjM0OjU5WicgVmVyc2lvbj0nMi4wJyB4bWxuczpzYW1sPSd1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uJyB4bWxuczpzYW1scD0ndXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sJy8+" + params[:relay_state] = "http://example.com" + params[:sig_alg] = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + query_string = OneLogin::RubySaml::Utils.build_query(params) + assert_equal "SAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCBEZXN0aW5hdGlvbj0naHR0cDovL2V4YW1wbGUuY29tP2ZpZWxkPXZhbHVlJyBJRD0nXzk4NmUxZDEwLWVhY2ItMDEzMi01MGRkLTAwOTBmNWRlZGQ3NycgSXNzdWVJbnN0YW50PScyMDE1LTA2LTAxVDIwOjM0OjU5WicgVmVyc2lvbj0nMi4wJyB4bWxuczpzYW1sPSd1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uJyB4bWxuczpzYW1scD0ndXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sJy8%2B&RelayState=http%3A%2F%2Fexample.com&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1", query_string + end + end + + describe ".verify_signature" do + before do + @params = {} + @params[:cert] = ruby_saml_cert + @params[:sig_alg] = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + @params[:query_string] = "SAMLRequest=PHNhbWxwOkF1dGhuUmVxdWVzdCBEZXN0aW5hdGlvbj0naHR0cDovL2V4YW1wbGUuY29tP2ZpZWxkPXZhbHVlJyBJRD0nXzk4NmUxZDEwLWVhY2ItMDEzMi01MGRkLTAwOTBmNWRlZGQ3NycgSXNzdWVJbnN0YW50PScyMDE1LTA2LTAxVDIwOjM0OjU5WicgVmVyc2lvbj0nMi4wJyB4bWxuczpzYW1sPSd1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uJyB4bWxuczpzYW1scD0ndXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sJy8%2B&RelayState=http%3A%2F%2Fexample.com&SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1" + end + + it "returns true when the signature is valid" do + @params[:signature] = "uWJm/T4gKLYEsVu1j/ZmjDeHp9zYPXPXWTXHFJZf2KKnWg57fUw3x2l6KTyRQ+Xjigb+sfYdGnnwmIz6KngXYRnh7nO6inspRLWOwkqQFy9iR9LDlMcfpXV/0g3oAxBxO6tX8MUHqR2R62SYZRGd1rxC9apg4vQiP97+atOI8t4=" + assert OneLogin::RubySaml::Utils.verify_signature(@params) + end + + it "returns false when the signature is invalid" do + @params[:signature] = "uWJm/InVaLiDsVu1j/ZmjDeHp9zYPXPXWTXHFJZf2KKnWg57fUw3x2l6KTyRQ+Xjigb+sfYdGnnwmIz6KngXYRnh7nO6inspRLWOwkqQFy9iR9LDlMcfpXV/0g3oAxBxO6tX8MUHqR2R62SYZRGd1rxC9apg4vQiP97+atOI8t4=" + assert !OneLogin::RubySaml::Utils.verify_signature(@params) + end + end + + describe ".status_error_msg" do + it "returns a error msg with status_code and status message" do + error_msg = "The status code of the Logout Response was not Success" + status_code = "urn:oasis:names:tc:SAML:2.0:status:Requester" + status_message = "The request could not be performed due to an error on the part of the requester." + status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code, status_message) + assert_equal "The status code of the Logout Response was not Success, was Requester -> The request could not be performed due to an error on the part of the requester.", status_error_msg + end + + it "returns a error msg with status_code" do + error_msg = "The status code of the Logout Response was not Success" + status_code = "urn:oasis:names:tc:SAML:2.0:status:Requester" + status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code) + assert_equal "The status code of the Logout Response was not Success, was Requester", status_error_msg + end + + it "returns a error msg" do + error_msg = "The status code of the Logout Response was not Success" + status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg) + assert_equal "The status code of the Logout Response was not Success", status_error_msg + end + end + + describe "Utils" do + + describe ".uuid" do + it "returns a uuid starting with an underscore" do + assert_match(/^_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/, OneLogin::RubySaml::Utils.uuid) + end + + it "doesn't return the same value twice" do + refute_equal OneLogin::RubySaml::Utils.uuid, OneLogin::RubySaml::Utils.uuid + end + end + + describe '.uri_match?' do + it 'matches two urls' do + destination = 'http://www.example.com/test?var=stuff' + settings = 'http://www.example.com/test?var=stuff' + assert OneLogin::RubySaml::Utils.uri_match?(destination, settings) + end + + it 'fails to match two urls' do + destination = 'http://www.example.com/test?var=stuff' + settings = 'http://www.example.com/othertest?var=stuff' + assert !OneLogin::RubySaml::Utils.uri_match?(destination, settings) + end + + it "matches two URLs if the scheme case doesn't match" do + destination = 'http://www.example.com/test?var=stuff' + settings = 'HTTP://www.example.com/test?var=stuff' + assert OneLogin::RubySaml::Utils.uri_match?(destination, settings) + end + + it "matches two URLs if the host case doesn't match" do + destination = 'http://www.EXAMPLE.com/test?var=stuff' + settings = 'http://www.example.com/test?var=stuff' + assert OneLogin::RubySaml::Utils.uri_match?(destination, settings) + end + + it "fails to match two URLs if the path case doesn't match" do + destination = 'http://www.example.com/TEST?var=stuff' + settings = 'http://www.example.com/test?var=stuff' + assert !OneLogin::RubySaml::Utils.uri_match?(destination, settings) + end + + it "fails to match two URLs if the query case doesn't match" do + destination = 'http://www.example.com/test?var=stuff' + settings = 'http://www.example.com/test?var=STUFF' + assert !OneLogin::RubySaml::Utils.uri_match?(destination, settings) + end + + it 'matches two non urls' do + destination = 'stuff' + settings = 'stuff' + assert OneLogin::RubySaml::Utils.uri_match?(destination, settings) + end + + it "fails to match two non urls" do + destination = 'stuff' + settings = 'not stuff' + assert !OneLogin::RubySaml::Utils.uri_match?(destination, settings) + end + end + + describe '.element_text' do + it 'returns the element text' do + element = REXML::Document.new('element text').elements.first + assert_equal 'element text', OneLogin::RubySaml::Utils.element_text(element) + end + + it 'returns all segments of the element text' do + element = REXML::Document.new('element text').elements.first + assert_equal 'element text', OneLogin::RubySaml::Utils.element_text(element) + end + + it 'returns normalized element text' do + element = REXML::Document.new('element & text').elements.first + assert_equal 'element & text', OneLogin::RubySaml::Utils.element_text(element) + end + + it 'returns the CDATA element text' do + element = REXML::Document.new('').elements.first + assert_equal 'element & text', OneLogin::RubySaml::Utils.element_text(element) + end + + it 'returns the element text with newlines and additional whitespace' do + element = REXML::Document.new(" element \n text ").elements.first + assert_equal " element \n text ", OneLogin::RubySaml::Utils.element_text(element) + end + + it 'returns nil when element is nil' do + assert_nil OneLogin::RubySaml::Utils.element_text(nil) + end + + it 'returns empty string when element has no text' do + element = REXML::Document.new('').elements.first + assert_equal '', OneLogin::RubySaml::Utils.element_text(element) + end + end + end + + describe '.decrypt_multi' do + let(:private_key) { ruby_saml_key } + let(:invalid_key1) { CertificateHelper.generate_key } + let(:invalid_key2) { CertificateHelper.generate_key } + let(:settings) { OneLogin::RubySaml::Settings.new(:private_key => private_key.to_pem) } + let(:response) { OneLogin::RubySaml::Response.new(signed_message_encrypted_unsigned_assertion, :settings => settings) } + let(:encrypted) do + REXML::XPath.first( + response.document, + "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", + { "p" => "urn:oasis:names:tc:SAML:2.0:protocol", "a" => "urn:oasis:names:tc:SAML:2.0:assertion" } + ) + end + + it 'successfully decrypts with the first private key' do + assert_match %r{\A.*<\/ds:X509Certificate>/, "") - document = XMLSecurity::SignedDocument.new(response) - assert !document.validate_document("a fingerprint", true) # The fingerprint isn't relevant to this test + decoded_response.sub!(/.*<\/ds:X509Certificate>/, "") + mod_document = XMLSecurity::SignedDocument.new(decoded_response) + assert !mod_document.validate_document("a fingerprint", true) # The fingerprint isn't relevant to this test end it "should raise Fingerprint mismatch" do exception = assert_raises(OneLogin::RubySaml::ValidationError) do - @document.validate_document("no:fi:ng:er:pr:in:t", false) + document.validate_document("no:fi:ng:er:pr:in:t", false) end assert_equal("Fingerprint mismatch", exception.message) - assert @document.errors.include? "Fingerprint mismatch" + assert_includes document.errors, "Fingerprint mismatch" end it "should raise Digest mismatch" do exception = assert_raises(OneLogin::RubySaml::ValidationError) do - @document.validate_signature(@base64cert, false) + document.validate_signature(@base64cert, false) end assert_equal("Digest mismatch", exception.message) - assert @document.errors.include? "Digest mismatch" + assert_includes document.errors, "Digest mismatch" end it "should raise Key validation error" do - response = Base64.decode64(response_document) - response.sub!("pJQ7MS/ek4KRRWGmv/H43ReHYMs=", + decoded_response.sub!("pJQ7MS/ek4KRRWGmv/H43ReHYMs=", "b9xsAXLsynugg3Wc1CI3kpWku+0=") - document = XMLSecurity::SignedDocument.new(response) - base64cert = document.elements["//ds:X509Certificate"].text + mod_document = XMLSecurity::SignedDocument.new(decoded_response) + base64cert = mod_document.elements["//ds:X509Certificate"].text exception = assert_raises(OneLogin::RubySaml::ValidationError) do - document.validate_signature(base64cert, false) + mod_document.validate_signature(base64cert, false) end assert_equal("Key validation error", exception.message) - assert document.errors.include? "Key validation error" + assert_includes mod_document.errors, "Key validation error" end it "correctly obtain the digest method with alternate namespace declaration" do - document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_xmlns, false)) - base64cert = document.elements["//X509Certificate"].text - assert document.validate_signature(base64cert, false) + adfs_document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_xmlns, false)) + base64cert = adfs_document.elements["//X509Certificate"].text + assert adfs_document.validate_signature(base64cert, false) end - it "raise validation error when the X509Certificate is missing" do - response = Base64.decode64(response_document) - response.sub!(/.*<\/ds:X509Certificate>/, "") - document = XMLSecurity::SignedDocument.new(response) + it "raise validation error when the X509Certificate is missing and no cert provided" do + decoded_response.sub!(/.*<\/ds:X509Certificate>/, "") + mod_document = XMLSecurity::SignedDocument.new(decoded_response) exception = assert_raises(OneLogin::RubySaml::ValidationError) do - document.validate_document("a fingerprint", false) # The fingerprint isn't relevant to this test + mod_document.validate_document("a fingerprint", false) # The fingerprint isn't relevant to this test end - assert_equal("Certificate element missing in response (ds:X509Certificate)", exception.message) + assert_equal("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", exception.message) + end + + it "invalidaties when the X509Certificate is missing and the cert is provided but mismatches" do + decoded_response.sub!(/.*<\/ds:X509Certificate>/, "") + mod_document = XMLSecurity::SignedDocument.new(decoded_response) + cert = OpenSSL::X509::Certificate.new(ruby_saml_cert) + assert !mod_document.validate_document("a fingerprint", true, :cert => cert) # The fingerprint isn't relevant to this test + end + end + + describe "#canon_algorithm" do + it "C14N_EXCLUSIVE_1_0" do + canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0 + assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/2001/10/xml-exc-c14n#") + assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/2001/10/xml-exc-c14n#WithComments") + assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("other") + end + + it "C14N_1_0" do + canon_algorithm = Nokogiri::XML::XML_C14N_1_0 + assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/TR/2001/REC-xml-c14n-20010315") + assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments") + end + + it "XML_C14N_1_1" do + canon_algorithm = Nokogiri::XML::XML_C14N_1_1 + assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/2006/12/xml-c14n11") + assert_equal canon_algorithm, XMLSecurity::BaseDocument.new.canon_algorithm("http://www.w3.org/2006/12/xml-c14n11#WithComments") end end - describe "Algorithms" do + describe "#algorithm" do + it "SHA1" do + alg = OpenSSL::Digest::SHA1 + assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2000/09/xmldsig#rsa-sha1") + assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2000/09/xmldsig#sha1") + assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("other") + end + + it "SHA256" do + alg = OpenSSL::Digest::SHA256 + assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256") + assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#sha256") + end + + it "SHA384" do + alg = OpenSSL::Digest::SHA384 + assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha384") + assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#sha384") + end + + it "SHA512" do + alg = OpenSSL::Digest::SHA512 + assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha512") + assert_equal alg, XMLSecurity::BaseDocument.new.algorithm("http://www.w3.org/2001/04/xmldsig-more#sha512") + end + end + + describe "Fingerprint Algorithms" do + let(:response_fingerprint_test) { OneLogin::RubySaml::Response.new(fixture(:adfs_response_sha1, false)) } + it "validate using SHA1" do - @document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha1, false)) - assert @document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72") + sha1_fingerprint = "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72" + sha1_fingerprint_downcase = "f13c6b80905a030e6c913e5d15faddb016454872" + + assert response_fingerprint_test.document.validate_document(sha1_fingerprint) + assert response_fingerprint_test.document.validate_document(sha1_fingerprint, true, :fingerprint_alg => XMLSecurity::Document::SHA1) + + assert response_fingerprint_test.document.validate_document(sha1_fingerprint_downcase) + assert response_fingerprint_test.document.validate_document(sha1_fingerprint_downcase, true, :fingerprint_alg => XMLSecurity::Document::SHA1) end it "validate using SHA256" do - @document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha256, false)) - assert @document.validate_document("28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA") + sha256_fingerprint = "C4:C6:BD:41:EC:AD:57:97:CE:7B:7D:80:06:C3:E4:30:53:29:02:0B:DD:2D:47:02:9E:BD:85:AD:93:02:45:21" + + assert !response_fingerprint_test.document.validate_document(sha256_fingerprint) + assert response_fingerprint_test.document.validate_document(sha256_fingerprint, true, :fingerprint_alg => XMLSecurity::Document::SHA256) end it "validate using SHA384" do - @document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha384, false)) - assert @document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72") + sha384_fingerprint = "98:FE:17:90:31:E7:68:18:8A:65:4D:DA:F5:76:E2:09:97:BE:8B:E3:7E:AA:8D:63:64:7C:0C:38:23:9A:AC:A2:EC:CE:48:A6:74:4D:E0:4C:50:80:40:B4:8D:55:14:14" + + assert !response_fingerprint_test.document.validate_document(sha384_fingerprint) + assert response_fingerprint_test.document.validate_document(sha384_fingerprint, true, :fingerprint_alg => XMLSecurity::Document::SHA384) end it "validate using SHA512" do - @document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha512, false)) - assert @document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72") + sha512_fingerprint = "5A:AE:BA:D0:BA:9D:1E:25:05:01:1E:1A:C9:E9:FF:DB:ED:FA:6E:F7:52:EB:45:49:BD:DB:06:D8:A3:7E:CC:63:3A:04:A2:DD:DF:EE:61:05:D9:58:95:2A:77:17:30:4B:EB:4A:9F:48:4A:44:1C:D0:9E:0B:1E:04:77:FD:A3:D2" + + assert !response_fingerprint_test.document.validate_document(sha512_fingerprint) + assert response_fingerprint_test.document.validate_document(sha512_fingerprint, true, :fingerprint_alg => XMLSecurity::Document::SHA512) + end + + end + + describe "Signature Algorithms" do + it "validate using SHA1" do + document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha1, false)) + assert document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72") + end + + it "validate using SHA256" do + document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha256, false)) + assert document.validate_document("28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA") + end + + it "validate using SHA384" do + document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha384, false)) + assert document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72") + end + + it "validate using SHA512" do + document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha512, false)) + assert document.validate_document("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72") end end @@ -124,98 +216,253 @@ class XmlSecurityTest < Minitest::Test response = OneLogin::RubySaml::Response.new(fixture("tdnf_response.xml")) response.stubs(:conditions).returns(nil) assert !response.is_valid? - settings = OneLogin::RubySaml::Settings.new assert !response.is_valid? response.settings = settings assert !response.is_valid? settings.idp_cert_fingerprint = "e6 38 9a 20 b7 4f 13 db 6a bc b1 42 6a e7 52 1d d6 56 d4 1b".upcase.gsub(" ", ":") - assert response.validate! + assert response.is_valid? end - it "return an empty list when inclusive namespace element is missing" do + it "return nil when inclusive namespace element is missing" do response = fixture(:no_signature_ns, false) response.slice! %r{} document = XMLSecurity::SignedDocument.new(response) inclusive_namespaces = document.send(:extract_inclusive_namespaces) - assert inclusive_namespaces.empty? + assert inclusive_namespaces.nil? end end describe "XMLSecurity::DSIG" do - it "sign a AuthNRequest" do - settings = OneLogin::RubySaml::Settings.new({ - :idp_sso_target_url => "https://idp.example.com/sso", - :protocol_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - :issuer => "https://sp.example.com/saml2", - :assertion_consumer_service_url => "https://sp.example.com/acs" - }) + before do + settings.idp_sso_service_url = "https://idp.example.com/sso" + settings.protocol_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + settings.idp_slo_service_url = "https://idp.example.com/slo", + settings.sp_entity_id = "https://sp.example.com/saml2" + settings.assertion_consumer_service_url = "https://sp.example.com/acs" + settings.single_logout_service_url = "https://sp.example.com/sls" + end + it "sign an AuthNRequest" do request = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings) request.sign_document(ruby_saml_key, ruby_saml_cert) - # verify our signature signed_doc = XMLSecurity::SignedDocument.new(request.to_s) assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false) + + request2 = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings) + request2.sign_document(ruby_saml_key, ruby_saml_cert_text) + # verify our signature + signed_doc2 = XMLSecurity::SignedDocument.new(request2.to_s) + assert signed_doc2.validate_document(ruby_saml_cert_fingerprint, false) end - it "sign a LogoutRequest" do - settings = OneLogin::RubySaml::Settings.new({ - :idp_slo_target_url => "https://idp.example.com/slo", - :protocol_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - :issuer => "https://sp.example.com/saml2", - :single_logout_service_url => "https://sp.example.com/sls" - }) - - request = OneLogin::RubySaml::Logoutrequest.new.create_logout_request_xml_doc(settings) - request.sign_document(ruby_saml_key, ruby_saml_cert) + it "sign an AuthNRequest with certificate as text" do + request = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings) + request.sign_document(ruby_saml_key, ruby_saml_cert_text) # verify our signature signed_doc = XMLSecurity::SignedDocument.new(request.to_s) assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false) end - it "sign a LogoutResponse" do - settings = OneLogin::RubySaml::Settings.new({ - :idp_slo_target_url => "https://idp.example.com/slo", - :protocol_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - :issuer => "https://sp.example.com/saml2", - :single_logout_service_url => "https://sp.example.com/sls" - }) + it "sign a LogoutRequest" do + logout_request = OneLogin::RubySaml::Logoutrequest.new.create_logout_request_xml_doc(settings) + logout_request.sign_document(ruby_saml_key, ruby_saml_cert) + # verify our signature + signed_doc = XMLSecurity::SignedDocument.new(logout_request.to_s) + assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false) - response = OneLogin::RubySaml::SloLogoutresponse.new.create_logout_response_xml_doc(settings, 'request_id_example', "Custom Logout Message") - response.sign_document(ruby_saml_key, ruby_saml_cert) + logout_request2 = OneLogin::RubySaml::Logoutrequest.new.create_logout_request_xml_doc(settings) + logout_request2.sign_document(ruby_saml_key, ruby_saml_cert_text) + # verify our signature + signed_doc2 = XMLSecurity::SignedDocument.new(logout_request2.to_s) + signed_doc2.validate_document(ruby_saml_cert_fingerprint, false) + assert signed_doc2.validate_document(ruby_saml_cert_fingerprint, false) + end + it "sign a LogoutResponse" do + logout_response = OneLogin::RubySaml::SloLogoutresponse.new.create_logout_response_xml_doc(settings, 'request_id_example', "Custom Logout Message") + logout_response.sign_document(ruby_saml_key, ruby_saml_cert) # verify our signature - signed_doc = XMLSecurity::SignedDocument.new(response.to_s) + signed_doc = XMLSecurity::SignedDocument.new(logout_response.to_s) assert signed_doc.validate_document(ruby_saml_cert_fingerprint, false) + + logout_response2 = OneLogin::RubySaml::SloLogoutresponse.new.create_logout_response_xml_doc(settings, 'request_id_example', "Custom Logout Message") + logout_response2.sign_document(ruby_saml_key, ruby_saml_cert_text) + # verify our signature + signed_doc2 = XMLSecurity::SignedDocument.new(logout_response2.to_s) + signed_doc2.validate_document(ruby_saml_cert_fingerprint, false) + assert signed_doc2.validate_document(ruby_saml_cert_fingerprint, false) end end describe "StarfieldTMS" do + let (:response) { OneLogin::RubySaml::Response.new(fixture(:starfield_response)) } + before do - @response = OneLogin::RubySaml::Response.new(fixture(:starfield_response)) - @response.settings = OneLogin::RubySaml::Settings.new( - :idp_cert_fingerprint => "8D:BA:53:8E:A3:B6:F9:F1:69:6C:BB:D9:D8:BD:41:B3:AC:4F:9D:4D" - ) + response.settings = OneLogin::RubySaml::Settings.new(:idp_cert_fingerprint => "8D:BA:53:8E:A3:B6:F9:F1:69:6C:BB:D9:D8:BD:41:B3:AC:4F:9D:4D") end it "be able to validate a good response" do Timecop.freeze Time.parse('2012-11-28 17:55:00 UTC') do - assert @response.validate! + response.stubs(:validate_subject_confirmation).returns(true) + assert response.is_valid? end end it "fail before response is valid" do Timecop.freeze Time.parse('2012-11-20 17:55:00 UTC') do - assert ! @response.is_valid? + assert !response.is_valid? + + time_1 = '2012-11-20 17:55:00 UTC < 2012-11-28 17:53:45 UTC' + time_2 = 'Tue Nov 20 17:55:00 UTC 2012 < Wed Nov 28 17:53:45 UTC 2012' + + errors = [time_1, time_2].map do |time| + "Current time is earlier than NotBefore condition (#{time} - 1s)" + end + + assert_predicate(response.errors & errors, :any?) end end it "fail after response expires" do Timecop.freeze Time.parse('2012-11-30 17:55:00 UTC') do - assert ! @response.is_valid? + assert !response.is_valid? + + contains_expected_error = response.errors.include?("Current time is on or after NotOnOrAfter condition (2012-11-30 17:55:00 UTC >= 2012-11-28 18:33:45 UTC + 1s)") + contains_expected_error ||= response.errors.include?("Current time is on or after NotOnOrAfter condition (Fri Nov 30 17:55:00 UTC 2012 >= Wed Nov 28 18:33:45 UTC 2012 + 1s)") + assert contains_expected_error + end + end + end + + describe '#validate_document' do + describe 'with valid document' do + describe 'when response has signed message and assertion' do + let(:document_data) { read_response('response_with_signed_message_and_assertion.xml') } + let(:document) { OneLogin::RubySaml::Response.new(document_data).document } + let(:fingerprint) { '4b68c453c7d994aad9025c99d5efcf566287fe8d' } + + it 'is valid' do + assert document.validate_document(fingerprint, true), 'Document should be valid' + end + end + + describe 'when response has signed assertion' do + let(:document_data) { read_response('response_with_signed_assertion_3.xml') } + let(:document) { OneLogin::RubySaml::Response.new(document_data).document } + let(:fingerprint) { '4b68c453c7d994aad9025c99d5efcf566287fe8d' } + + it 'is valid' do + assert document.validate_document(fingerprint, true), 'Document should be valid' + end + end + end + + describe 'signature_wrapping_attack' do + let(:document_data) { read_invalid_response("signature_wrapping_attack.xml.base64") } + let(:document) { OneLogin::RubySaml::Response.new(document_data).document } + let(:fingerprint) { 'afe71c28ef740bc87425be13a2263d37971da1f9' } + + it 'is invalid' do + assert !document.validate_document(fingerprint, true), 'Document should be invalid' + end + end + + describe 'signature wrapping attack - doubled SAML response body' do + let(:document_data) { read_invalid_response("response_with_doubled_signed_assertion.xml") } + let(:document) { OneLogin::RubySaml::Response.new(document_data) } + let(:fingerprint) { '4b68c453c7d994aad9025c99d5efcf566287fe8d' } + + it 'is valid, but the unsigned information is ignored in favour of the signed information' do + assert document.document.validate_document(fingerprint, true), 'Document should be valid' + assert_equal 'someone@example.org', document.name_id, 'Document should expose only signed, valid details' + end + end + + describe 'signature wrapping attack - concealed SAML response body' do + let(:document_data) { read_invalid_response("response_with_concealed_signed_assertion.xml") } + let(:document) { OneLogin::RubySaml::Response.new(document_data) } + let(:fingerprint) { '4b68c453c7d994aad9025c99d5efcf566287fe8d' } + + it 'is valid, but the unsigned information is ignored in favour of the signed information' do + assert document.document.validate_document(fingerprint, true), 'Document should be valid' + assert_equal 'someone@example.org', document.name_id, 'Document should expose only signed, valid details' + end + end + end + + describe '#validate_document_with_cert' do + let(:document_data) { read_response('response_with_signed_message_and_assertion.xml') } + let(:document) { OneLogin::RubySaml::Response.new(document_data).document } + let(:idp_cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) } + let(:fingerprint) { '4b68c453c7d994aad9025c99d5efcf566287fe8d' } + + describe 'with invalid document ' do + describe 'when certificate is invalid' do + let(:document) { OneLogin::RubySaml::Response.new(document_data).document } + + it 'is invalid' do + wrong_document_data = document_data.sub(/.*<\/ds:X509Certificate>/, "invalid<\/ds:X509Certificate>") + wrong_document = OneLogin::RubySaml::Response.new(wrong_document_data).document + refute wrong_document.validate_document_with_cert(idp_cert), 'Document should be invalid' + end + end + end + + describe 'with valid document' do + describe 'when response has cert' do + it 'is valid' do + assert document.validate_document_with_cert(idp_cert), 'Document should be valid' + end + end + + describe 'when response has no cert but you have local cert' do + let(:document) { OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate).document } + let(:idp_cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) } + + it 'is valid' do + assert document.validate_document_with_cert(idp_cert), 'Document should be valid' + end + end + end + + describe 'when response has no cert but you have local cert' do + let(:document_data) { response_document_valid_signed_without_x509certificate } + + it 'is valid' do + assert document.validate_document_with_cert(idp_cert), 'Document should be valid' + end + end + + describe 'when response cert is invalid' do + let(:document_data) do + contents = read_response('response_with_signed_message_and_assertion.xml') + contents.sub(/.*<\/ds:X509Certificate>/, + "an-invalid-certificate") + end + + it 'is not valid' do + assert !document.validate_document_with_cert(idp_cert), 'Document should be valid' + assert_equal(["Document Certificate Error"], document.errors) + end + end + + describe 'when response cert is different from idp cert' do + let(:idp_cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text2) } + + it 'is not valid' do + exception = assert_raises(OneLogin::RubySaml::ValidationError) do + document.validate_document_with_cert(idp_cert, false) + end + assert_equal("Certificate of the Signature element does not match provided certificate", exception.message) + end + + it 'is not valid (soft = true)' do + document.validate_document_with_cert(idp_cert) + assert_equal(["Certificate of the Signature element does not match provided certificate"], document.errors) end end end