spec/lib/response_spec.rb in saml2-2.1.0 vs spec/lib/response_spec.rb in saml2-2.2.0
- old
+ new
@@ -1,10 +1,11 @@
require_relative '../spec_helper'
module SAML2
describe Response do
- let(:sp) { Entity.parse(fixture('service_provider.xml')).roles.first }
+ let(:sp_entity) { Entity.parse(fixture('service_provider.xml')) }
+ let(:sp) { sp_entity.roles.first }
let(:request) do
request = AuthnRequest.parse(fixture('authnrequest.xml'))
request.resolve(sp)
request
@@ -60,8 +61,216 @@
it "parses a serialized assertion" do
response2 = Message.parse(response.to_s)
expect(response2.assertions.length).to eq 1
expect(response2.assertions.first.subject.name_id.id).to eq 'jacob'
+ end
+
+ it "doesn't validate a response with XSLT transforms" do
+ response = Response.parse(fixture("xslt-transform-response.xml"))
+ expect(response).to be_valid_schema
+ expect(response.assertions.first.valid_signature?(fingerprint: 'bc71f7bacb36011694405dd0e2beafcc069de45f')).to eq false
+ end
+
+ it "doesn't validate a response with external URI reference in the signature" do
+ response = Response.parse(fixture("external-uri-reference-response.xml"))
+ expect(response).to be_valid_schema
+ expect(response.assertions.first.valid_signature?(fingerprint: 'bc71f7bacb36011694405dd0e2beafcc069de45f')).to eq false
+ end
+
+ it "can decrypt an EncryptedAssertion" do
+ # verifiable on the command line with:
+ # xmlsec1 decrypt --privkey-pem privatekey.key response_with_encrypted_assertion.xml
+ response = Response.parse(fixture("response_with_encrypted_assertion.xml"))
+ expect(response.decrypt(fixture("privatekey.key"))).to eq true
+ expect(response.assertions.length).to eq 1
+ expect(response.assertions.first.subject.name_id.id).to eq 'jacob'
+ end
+
+ it "allows non-ascii characters in attributes" do
+ response = Response.parse(fixture("test6-response.xml"))
+
+ attributes = response.assertions.first.attribute_statements.first.to_h
+ expect(attributes['eduPersonAffiliation']).to eq 'member'
+ expect(attributes['givenName']).to eq 'Canvas'
+ expect(attributes['displayName']).to eq 'Canvas Üser'
+ end
+
+ # see CVE-2017-11428
+ it "returns the full content of the NameID, even if a comment-insertion attack allows it to still validate the signature" do
+ response = Response.parse(fixture("test7-response.xml"))
+ # this file is a copy of test6-response.xml, with a comment inserted into the NameID
+
+ # the signature is still valid (we have to set a weird verification time because the response
+ # was signed with an expired signature)
+ expect(response.validate_signature(fingerprint: 'afe71c28ef740bc87425be13a2263d37971da1f9',
+ verification_time: Time.parse("2007-07-14 12:01:34Z"))).to eq []
+
+ # the comment is ignored, but doesn't truncate the nameid
+ expect(response.assertions.first.subject.name_id.id).to eq 'testuser@example.com'
+ end
+
+ describe "#validate" do
+ let (:idp_entity) do
+ idp_entity = Entity.new("issuer")
+ idp = IdentityProvider.new
+ idp.keys << KeyDescriptor.new(fixture("certificate.pem"))
+ idp_entity.roles << idp
+
+ idp_entity
+ end
+
+ before do
+ sp.private_keys << OpenSSL::PKey::RSA.new(fixture("privatekey.key"))
+ end
+
+ it "succeeds" do
+ response = Response.parse(fixture("response_signed.xml"))
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
+ expect(response.errors).to eq []
+ end
+
+ it "checks the issuer" do
+ response = Response.parse(fixture("response_signed.xml"))
+ idp_entity.entity_id = 'someoneelse'
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
+ expect(response.errors).to eq ["received unexpected message from 'issuer'; expected it to be from 'someoneelse'"]
+ end
+
+ it "complains about old message" do
+ response = Response.parse(fixture("response_signed.xml"))
+ sp_entity.valid_response?(response, idp_entity)
+ expect(response.errors.length).to eq 1
+ expect(response.errors.first).to match (/not_on_or_after .* is earlier than/)
+ end
+
+ it "complains about mismatched audience restriction" do
+ response = Response.parse(fixture("response_signed.xml"))
+ sp_entity.entity_id = 'someoneelse'
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
+ expect(response.errors).to eq ["audience someoneelse not in allowed list of http://siteadmin.instructure.com/saml2"]
+ end
+
+ it "complains about no signature" do
+ response = Response.parse(fixture("response_with_encrypted_assertion.xml"))
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
+ expect(response.errors).to eq ["neither response nor assertion were signed"]
+ end
+
+ it "complains if the signature has been tampered with" do
+ response = Response.parse(fixture("response_tampered_signature.xml"))
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
+ expect(response.errors).to eq ["signature is invalid"]
+ end
+
+ it "complains if the trusted certificate isn't what signed the response" do
+ idp_entity.identity_providers.first.keys.clear
+ idp_entity.identity_providers.first.fingerprints << "afe71c28ef740bc87425be13a2263d37971da1f9"
+
+ response = Response.parse(fixture("response_tampered_certificate.xml"))
+ sp_entity.valid_response?(response, idp_entity,
+ verification_time: Time.parse('2015-02-12T22:51:30Z'),
+ allow_expired_certificate: true)
+ expect(response.errors).to eq ["signature is invalid"]
+ end
+
+ it "complains when we don't have any trusted keys" do
+ response = Response.parse(fixture("response_signed.xml"))
+ idp_entity.identity_providers.first.keys.clear
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
+ expect(response.errors).to eq ["could not find certificate to validate message"]
+ end
+
+ it "complains about a valid signature we don't trust" do
+ response = Response.parse(fixture("response_signed.xml"))
+ idp_entity.identity_providers.first.keys.clear
+ idp_entity.identity_providers.first.keys << KeyDescriptor.new(fixture("othercertificate.pem"))
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
+ expect(response.errors.length).to eq 1
+ expect(response.errors.first).to start_with('error occurred during signature verification')
+ end
+
+ it "validates signature by fingerprint" do
+ response = Response.parse(fixture("response_signed.xml"))
+ idp_entity.identity_providers.first.keys.clear
+ idp_entity.identity_providers.first.fingerprints << "1c:37:7d:30:c1:83:18:ea:20:8b:dc:d5:35:b6:16:85:17:58:f7:c9"
+
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
+ expect(response.errors).to eq []
+ end
+
+ it "complains when we don't have any trusted fingerprints" do
+ response = Response.parse(fixture("response_signed.xml"))
+ idp_entity.identity_providers.first.keys.clear
+ idp_entity.identity_providers.first.fingerprints << "1c:37:7d:30:c1:83:18:ea:20:8b:dc:d5:35:b6:16:85:17:58:f7:ca"
+
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
+ expect(response.errors).to eq ["no trusted certificate found"]
+ end
+
+ it "protects against xml signature wrapping attacks targeting nameid" do
+ response = Response.parse(fixture("xml_signature_wrapping_attack_response_nameid.xml"))
+ idp_entity.identity_providers.first.keys.clear
+ idp_entity.identity_providers.first.fingerprints << "afe71c28ef740bc87425be13a2263d37971da1f9"
+
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2012-08-03T20:07:16Z'))
+ expect(response.errors.length).to eq 1
+ expect(response.errors.first.to_s).to eq "5:0: ERROR: Element '{urn:oasis:names:tc:SAML:2.0:assertion}Assertion': This element is not expected. Expected is one of ( {http://www.w3.org/2000/09/xmldsig#}Signature, {urn:oasis:names:tc:SAML:2.0:protocol}Extensions, {urn:oasis:names:tc:SAML:2.0:protocol}Status )."
+ end
+
+ it "protects against xml signature wrapping attacks targeting attributes" do
+ response = Response.parse(fixture("xml_signature_wrapping_attack_response_attributes.xml"))
+ idp_entity.identity_providers.first.keys.clear
+ idp_entity.identity_providers.first.fingerprints << "afe71c28ef740bc87425be13a2263d37971da1f9"
+
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2012-08-03T20:07:16Z'))
+ expect(response.errors.length).to eq 1
+ expect(response.errors.first.to_s).to eq "30:0: ERROR: Element '{urn:oasis:names:tc:SAML:2.0:assertion}Subject': This element is not expected."
+ end
+
+ it "protects against xml signature wrapping attacks with duplicate IDs" do
+ response = Response.parse(fixture("xml_signature_wrapping_attack_duplicate_ids.xml"))
+ idp_entity.identity_providers.first.keys.clear
+ idp_entity.identity_providers.first.fingerprints << "7292914fc5bffa6f3fe1e43fd47c205395fecfa2"
+
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2014-02-01T13:48:10.831Z'))
+ expect(response.errors.length).to eq 1
+ expect(response.errors.first.to_s).to eq "1:0: ERROR: Element '{urn:oasis:names:tc:SAML:2.0:assertion}Assertion', attribute 'ID': 'pfx77d6c794-8295-f1c4-298e-c25ecae8046d' is not a valid value of the atomic type 'xs:ID'."
+ end
+
+ it "protects against additional mis-signed assertions" do
+ response = Response.parse(fixture("xml_missigned_assertion.xml"))
+ idp_entity.identity_providers.first.keys.clear
+ idp_entity.identity_providers.first.fingerprints << "c38e789fcfbbd4727bd8ff7fc365b44fc3596bda"
+
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-27T19:12:52Z'))
+ expect(response.errors.map(&:to_s)).to eq ["2:0: ERROR: Element '{http://www.w3.org/2000/09/xmldsig#}Signature': This element is not expected.",
+ "43:0: ERROR: Element '{http://www.w3.org/2000/09/xmldsig#}Signature': This element is not expected."]
+ end
+
+ it "errors on expired certificate" do
+ response = Response.parse(fixture("test6-response.xml"))
+ idp_entity.entity_id = 'http://simplesamlphp.dev/simplesaml/saml2/idp/metadata.php'
+ idp_entity.identity_providers.first.keys.clear
+ idp_entity.identity_providers.first.fingerprints << "afe71c28ef740bc87425be13a2263d37971da1f9"
+
+ sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse("2012-08-03T20:07:15Z"))
+ expect(response.errors.length).to eq 1
+ expect(response.errors.first).to match(/certificate has expired/)
+ end
+
+ it "ignores expired certificate when requested" do
+ response = Response.parse(fixture("test6-response.xml"))
+ sp_entity.entity_id = 'http://shard-2.canvas.dev/saml2'
+ idp_entity.entity_id = 'http://simplesamlphp.dev/simplesaml/saml2/idp/metadata.php'
+ idp_entity.identity_providers.first.keys.clear
+ idp_entity.identity_providers.first.fingerprints << "afe71c28ef740bc87425be13a2263d37971da1f9"
+
+ sp_entity.valid_response?(response, idp_entity,
+ verification_time: Time.parse("2014-09-16T22:15:53Z"),
+ allow_expired_certificate: true)
+ expect(response.errors).to eq []
+ end
+
end
end
end