saml2 3.1.2 → 3.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +6 -4
- data/exe/bulk_verify_responses +94 -0
- data/lib/saml2/assertion.rb +7 -7
- data/lib/saml2/attribute/x500.rb +31 -28
- data/lib/saml2/attribute.rb +53 -49
- data/lib/saml2/attribute_consuming_service.rb +29 -31
- data/lib/saml2/authn_request.rb +54 -47
- data/lib/saml2/authn_statement.rb +31 -20
- data/lib/saml2/base.rb +72 -63
- data/lib/saml2/bindings/http_post.rb +7 -7
- data/lib/saml2/bindings/http_redirect.rb +37 -33
- data/lib/saml2/bindings.rb +1 -1
- data/lib/saml2/conditions.rb +19 -16
- data/lib/saml2/contact.rb +19 -18
- data/lib/saml2/endpoint.rb +14 -11
- data/lib/saml2/entity.rb +27 -27
- data/lib/saml2/identity_provider.rb +13 -10
- data/lib/saml2/indexed_object.rb +15 -12
- data/lib/saml2/key.rb +43 -34
- data/lib/saml2/localized_name.rb +11 -10
- data/lib/saml2/logout_request.rb +8 -8
- data/lib/saml2/logout_response.rb +4 -4
- data/lib/saml2/message.rb +24 -20
- data/lib/saml2/name_id.rb +45 -41
- data/lib/saml2/namespaces.rb +8 -8
- data/lib/saml2/organization.rb +11 -10
- data/lib/saml2/organization_and_contacts.rb +5 -5
- data/lib/saml2/request.rb +3 -3
- data/lib/saml2/requested_authn_context.rb +4 -4
- data/lib/saml2/response.rb +45 -33
- data/lib/saml2/role.rb +11 -11
- data/lib/saml2/schemas.rb +13 -10
- data/lib/saml2/service_provider.rb +11 -12
- data/lib/saml2/signable.rb +23 -18
- data/lib/saml2/sso.rb +5 -5
- data/lib/saml2/status.rb +9 -7
- data/lib/saml2/status_response.rb +5 -5
- data/lib/saml2/subject.rb +28 -28
- data/lib/saml2/version.rb +1 -1
- data/lib/saml2.rb +7 -7
- metadata +78 -122
- data/spec/fixtures/FederationMetadata.xml +0 -670
- data/spec/fixtures/authnrequest.xml +0 -12
- data/spec/fixtures/certificate.pem +0 -24
- data/spec/fixtures/entities.xml +0 -13
- data/spec/fixtures/external-uri-reference-response.xml +0 -48
- data/spec/fixtures/identity_provider.xml +0 -46
- data/spec/fixtures/noconditions_response.xml +0 -1
- data/spec/fixtures/othercertificate.pem +0 -25
- data/spec/fixtures/privatekey.key +0 -27
- data/spec/fixtures/response_assertion_signed_reffed_from_response.xml +0 -6
- data/spec/fixtures/response_signed.xml +0 -46
- data/spec/fixtures/response_tampered_certificate.xml +0 -25
- data/spec/fixtures/response_tampered_signature.xml +0 -46
- data/spec/fixtures/response_with_attribute_signed.xml +0 -46
- data/spec/fixtures/response_with_encrypted_assertion.xml +0 -58
- data/spec/fixtures/response_with_rsa_key_value.xml +0 -1
- data/spec/fixtures/response_with_signed_assertion_and_encrypted_subject.xml +0 -116
- data/spec/fixtures/response_without_keyinfo.xml +0 -1
- data/spec/fixtures/service_provider.xml +0 -79
- data/spec/fixtures/test3-response.xml +0 -9
- data/spec/fixtures/test6-response.xml +0 -10
- data/spec/fixtures/test7-response.xml +0 -10
- data/spec/fixtures/xml_missigned_assertion.xml +0 -84
- data/spec/fixtures/xml_signature_wrapping_attack_duplicate_ids.xml +0 -11
- data/spec/fixtures/xml_signature_wrapping_attack_response_attributes.xml +0 -45
- data/spec/fixtures/xml_signature_wrapping_attack_response_nameid.xml +0 -44
- data/spec/fixtures/xslt-transform-response.xml +0 -57
- data/spec/lib/attribute_consuming_service_spec.rb +0 -129
- data/spec/lib/attribute_spec.rb +0 -149
- data/spec/lib/authn_request_spec.rb +0 -52
- data/spec/lib/bindings/http_redirect_spec.rb +0 -183
- data/spec/lib/conditions_spec.rb +0 -74
- data/spec/lib/entity_spec.rb +0 -58
- data/spec/lib/identity_provider_spec.rb +0 -43
- data/spec/lib/indexed_object_spec.rb +0 -71
- data/spec/lib/key_spec.rb +0 -23
- data/spec/lib/logout_request_spec.rb +0 -33
- data/spec/lib/logout_response_spec.rb +0 -33
- data/spec/lib/message_spec.rb +0 -23
- data/spec/lib/response_spec.rb +0 -293
- data/spec/lib/service_provider_spec.rb +0 -76
- data/spec/lib/signable_spec.rb +0 -15
- data/spec/spec_helper.rb +0 -8
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe LogoutRequest do
|
7
|
-
let(:idp) { Entity.parse(fixture('identity_provider.xml')).roles.first }
|
8
|
-
|
9
|
-
let(:logout_request) {
|
10
|
-
LogoutRequest.initiate(idp,
|
11
|
-
NameID.new('issuer'),
|
12
|
-
NameID.new('jacob',
|
13
|
-
name_qualifier: "a",
|
14
|
-
sp_name_qualifier: "b"),
|
15
|
-
"abc")
|
16
|
-
}
|
17
|
-
|
18
|
-
it "should generate valid XML" do
|
19
|
-
xml = logout_request.to_s
|
20
|
-
expect(Schemas.protocol.validate(Nokogiri::XML(xml))).to eq []
|
21
|
-
end
|
22
|
-
|
23
|
-
it "parses" do
|
24
|
-
# yup, I'm lazy
|
25
|
-
new_request = LogoutRequest.parse(logout_request.to_s)
|
26
|
-
expect(new_request.issuer.id).to eq 'issuer'
|
27
|
-
expect(new_request.name_id.id).to eq 'jacob'
|
28
|
-
expect(new_request.name_id.name_qualifier).to eq 'a'
|
29
|
-
expect(new_request.name_id.sp_name_qualifier).to eq 'b'
|
30
|
-
expect(new_request.session_index).to eq ['abc']
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe LogoutResponse do
|
7
|
-
let(:idp) { Entity.parse(fixture('identity_provider.xml')).roles.first }
|
8
|
-
|
9
|
-
let(:logout_request) {
|
10
|
-
LogoutRequest.initiate(idp,
|
11
|
-
NameID.new('issuer'),
|
12
|
-
NameID.new('jacob',
|
13
|
-
name_qualifier: "a",
|
14
|
-
sp_name_qualifier: "b"),
|
15
|
-
"abc")
|
16
|
-
}
|
17
|
-
let(:logout_response) {
|
18
|
-
LogoutResponse.respond_to(logout_request, idp, NameID.new('issuer2'))
|
19
|
-
}
|
20
|
-
|
21
|
-
it "should generate valid XML" do
|
22
|
-
xml = logout_response.to_s
|
23
|
-
expect(Schemas.protocol.validate(Nokogiri::XML(xml))).to eq []
|
24
|
-
end
|
25
|
-
|
26
|
-
it "parses" do
|
27
|
-
# yup, I'm lazy
|
28
|
-
new_response = LogoutResponse.parse(logout_response.to_s)
|
29
|
-
expect(new_response.issuer.id).to eq 'issuer2'
|
30
|
-
expect(new_response.status.code).to eq Status::SUCCESS
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
data/spec/lib/message_spec.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe Message do
|
7
|
-
describe '.parse' do
|
8
|
-
it 'complains about invalid XML' do
|
9
|
-
expect { Message.parse("garbage") }.to raise_error(CorruptMessage)
|
10
|
-
end
|
11
|
-
|
12
|
-
it 'complains about getting the wrong type if calling on a subclass, and you get a different type' do
|
13
|
-
expect { Response.parse(fixture('authnrequest.xml')) }.to raise_error(UnexpectedMessage)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
describe '.from_xml' do
|
18
|
-
it "complains about unknown messages" do
|
19
|
-
expect { Message.parse("<Garbage></Garbage>") }.to raise_error(UnknownMessage)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
data/spec/lib/response_spec.rb
DELETED
@@ -1,293 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe Response do
|
7
|
-
let(:sp_entity) { Entity.parse(fixture('service_provider.xml')) }
|
8
|
-
let(:sp) { sp_entity.roles.first }
|
9
|
-
|
10
|
-
let(:request) do
|
11
|
-
request = AuthnRequest.parse(fixture('authnrequest.xml'))
|
12
|
-
request.resolve(sp)
|
13
|
-
request
|
14
|
-
end
|
15
|
-
|
16
|
-
let(:response) do
|
17
|
-
Response.respond_to(request,
|
18
|
-
NameID.new('issuer'),
|
19
|
-
NameID.new('jacob', NameID::Format::PERSISTENT))
|
20
|
-
end
|
21
|
-
|
22
|
-
it "should generate valid XML" do
|
23
|
-
xml = response.to_s
|
24
|
-
expect(Schemas.protocol.validate(Nokogiri::XML(xml))).to eq []
|
25
|
-
end
|
26
|
-
|
27
|
-
def freeze_response
|
28
|
-
response.instance_variable_set(:@id, "_9a15e699-2d04-4ba7-a521-cfa4dcd21f44")
|
29
|
-
assertion = response.assertions.first
|
30
|
-
assertion.instance_variable_set(:@id, "_cdfc3faf-90ad-462f-880d-677483210684")
|
31
|
-
response.instance_variable_set(:@issue_instant, Time.parse("2015-02-12T22:51:29Z"))
|
32
|
-
assertion.instance_variable_set(:@issue_instant, Time.parse("2015-02-12T22:51:29Z"))
|
33
|
-
assertion.conditions.not_before = Time.parse("2015-02-12T22:51:24Z")
|
34
|
-
assertion.conditions.not_on_or_after = Time.parse("2015-02-12T22:51:59Z")
|
35
|
-
assertion.statements.first.authn_instant = Time.parse("2015-02-12T22:51:29Z")
|
36
|
-
confirmation = assertion.subject.confirmation
|
37
|
-
confirmation.not_on_or_after = Time.parse("2015-02-12T22:54:29Z")
|
38
|
-
confirmation.recipient = response.destination
|
39
|
-
confirmation.in_response_to = response.in_response_to
|
40
|
-
end
|
41
|
-
|
42
|
-
it "should generate a valid signature" do
|
43
|
-
freeze_response
|
44
|
-
response.sign(fixture('certificate.pem'), fixture('privatekey.key'))
|
45
|
-
expect(Schemas.protocol.validate(response.to_xml)).to eq []
|
46
|
-
# verifiable on the command line with:
|
47
|
-
# xmlsec1 --verify --pubkey-cert-pem certificate.pem --privkey-pem privatekey.key --id-attr:ID urn:oasis:names:tc:SAML:2.0:assertion:Assertion response_signed.xml
|
48
|
-
expect(response.to_s).to eq fixture('response_signed.xml')
|
49
|
-
end
|
50
|
-
|
51
|
-
it "should generate a valid signature when attributes are present" do
|
52
|
-
freeze_response
|
53
|
-
response.assertions.first.statements << sp.attribute_consuming_services.default.create_statement('givenName' => 'cody')
|
54
|
-
response.sign(fixture('certificate.pem'), fixture('privatekey.key'))
|
55
|
-
expect(response.to_s).to eq fixture('response_with_attribute_signed.xml')
|
56
|
-
end
|
57
|
-
|
58
|
-
it "should generate valid XML for IdP initiated response" do
|
59
|
-
response = Response.initiate(sp, NameID.new('issuer'),
|
60
|
-
NameID.new('jacob', NameID::Format::PERSISTENT))
|
61
|
-
expect(Schemas.protocol.validate(Nokogiri::XML(response.to_s))).to eq []
|
62
|
-
end
|
63
|
-
|
64
|
-
it "parses a serialized assertion" do
|
65
|
-
response2 = Message.parse(response.to_s)
|
66
|
-
expect(response2.in_response_to).not_to be_nil
|
67
|
-
expect(response2.in_response_to).to eq response.in_response_to
|
68
|
-
expect(response2.assertions.length).to eq 1
|
69
|
-
expect(response2.assertions.first.subject.name_id.id).to eq 'jacob'
|
70
|
-
end
|
71
|
-
|
72
|
-
it "doesn't validate a response with XSLT transforms" do
|
73
|
-
response = Response.parse(fixture("xslt-transform-response.xml"))
|
74
|
-
expect(response).to be_valid_schema
|
75
|
-
expect(response.assertions.first.valid_signature?(fingerprint: 'bc71f7bacb36011694405dd0e2beafcc069de45f')).to eq false
|
76
|
-
end
|
77
|
-
|
78
|
-
it "doesn't validate a response with external URI reference in the signature" do
|
79
|
-
response = Response.parse(fixture("external-uri-reference-response.xml"))
|
80
|
-
expect(response).to be_valid_schema
|
81
|
-
expect(response.assertions.first.valid_signature?(fingerprint: 'bc71f7bacb36011694405dd0e2beafcc069de45f')).to eq false
|
82
|
-
end
|
83
|
-
|
84
|
-
it "can decrypt an EncryptedAssertion" do
|
85
|
-
# verifiable on the command line with:
|
86
|
-
# xmlsec1 decrypt --privkey-pem privatekey.key response_with_encrypted_assertion.xml
|
87
|
-
response = Response.parse(fixture("response_with_encrypted_assertion.xml"))
|
88
|
-
expect(response.decrypt(fixture("privatekey.key"))).to eq true
|
89
|
-
expect(response.assertions.length).to eq 1
|
90
|
-
expect(response.assertions.first.subject.name_id.id).to eq 'jacob'
|
91
|
-
end
|
92
|
-
|
93
|
-
it "allows non-ascii characters in attributes" do
|
94
|
-
response = Response.parse(fixture("test6-response.xml"))
|
95
|
-
|
96
|
-
attributes = response.assertions.first.attribute_statements.first.to_h
|
97
|
-
expect(attributes['eduPersonAffiliation']).to eq 'member'
|
98
|
-
expect(attributes['givenName']).to eq 'Canvas'
|
99
|
-
expect(attributes['displayName']).to eq 'Canvas Üser'
|
100
|
-
end
|
101
|
-
|
102
|
-
# see CVE-2017-11428
|
103
|
-
it "returns the full content of the NameID, even if a comment-insertion attack allows it to still validate the signature" do
|
104
|
-
response = Response.parse(fixture("test7-response.xml"))
|
105
|
-
# this file is a copy of test6-response.xml, with a comment inserted into the NameID
|
106
|
-
|
107
|
-
# the signature is still valid (we have to set a weird verification time because the response
|
108
|
-
# was signed with an expired signature)
|
109
|
-
expect(response.validate_signature(fingerprint: 'afe71c28ef740bc87425be13a2263d37971da1f9')).to eq []
|
110
|
-
|
111
|
-
# the comment is ignored, but doesn't truncate the nameid
|
112
|
-
expect(response.assertions.first.subject.name_id.id).to eq 'testuser@example.com'
|
113
|
-
end
|
114
|
-
|
115
|
-
it "doesn't choke on missing Conditions" do
|
116
|
-
response = Response.parse(fixture("noconditions_response.xml"))
|
117
|
-
expect(response.assertions.first.conditions).to eq nil
|
118
|
-
end
|
119
|
-
|
120
|
-
describe "#validate" do
|
121
|
-
let (:idp_entity) do
|
122
|
-
idp_entity = Entity.new("issuer")
|
123
|
-
idp = IdentityProvider.new
|
124
|
-
idp.keys << KeyDescriptor.new(fixture("certificate.pem"))
|
125
|
-
idp_entity.roles << idp
|
126
|
-
|
127
|
-
idp_entity
|
128
|
-
end
|
129
|
-
|
130
|
-
before do
|
131
|
-
sp.private_keys << OpenSSL::PKey::RSA.new(fixture("privatekey.key"))
|
132
|
-
end
|
133
|
-
|
134
|
-
it "succeeds" do
|
135
|
-
response = Response.parse(fixture("response_signed.xml"))
|
136
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
137
|
-
expect(response.errors).to eq []
|
138
|
-
end
|
139
|
-
|
140
|
-
it "checks the issuer" do
|
141
|
-
response = Response.parse(fixture("response_signed.xml"))
|
142
|
-
idp_entity.entity_id = 'someoneelse'
|
143
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
144
|
-
expect(response.errors).to eq ["received unexpected message from 'issuer'; expected it to be from 'someoneelse'"]
|
145
|
-
end
|
146
|
-
|
147
|
-
it "complains about old message" do
|
148
|
-
response = Response.parse(fixture("response_signed.xml"))
|
149
|
-
sp_entity.valid_response?(response, idp_entity)
|
150
|
-
expect(response.errors.length).to eq 1
|
151
|
-
expect(response.errors.first).to match (/not_on_or_after .* is earlier than/)
|
152
|
-
end
|
153
|
-
|
154
|
-
it "complains about mismatched audience restriction" do
|
155
|
-
response = Response.parse(fixture("response_signed.xml"))
|
156
|
-
sp_entity.entity_id = 'someoneelse'
|
157
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
158
|
-
expect(response.errors).to eq ["audience someoneelse not in allowed list of http://siteadmin.instructure.com/saml2"]
|
159
|
-
end
|
160
|
-
|
161
|
-
it "complains about no signature" do
|
162
|
-
response = Response.parse(fixture("response_with_encrypted_assertion.xml"))
|
163
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
164
|
-
expect(response.errors).to eq ["neither response nor assertion were signed"]
|
165
|
-
end
|
166
|
-
|
167
|
-
it "complains if the signature has been tampered with" do
|
168
|
-
response = Response.parse(fixture("response_tampered_signature.xml"))
|
169
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
170
|
-
expect(response.errors).to eq ["signature is invalid"]
|
171
|
-
end
|
172
|
-
|
173
|
-
it "complains if the trusted certificate isn't what signed the response" do
|
174
|
-
idp_entity.identity_providers.first.keys.clear
|
175
|
-
idp_entity.identity_providers.first.fingerprints << "afe71c28ef740bc87425be13a2263d37971da1f9"
|
176
|
-
|
177
|
-
response = Response.parse(fixture("response_tampered_certificate.xml"))
|
178
|
-
sp_entity.valid_response?(response, idp_entity,
|
179
|
-
verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
180
|
-
expect(response.errors).to eq ["signature is invalid"]
|
181
|
-
end
|
182
|
-
|
183
|
-
it "complains when we don't have any trusted keys" do
|
184
|
-
response = Response.parse(fixture("response_signed.xml"))
|
185
|
-
idp_entity.identity_providers.first.keys.clear
|
186
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
187
|
-
expect(response.errors).to eq ["could not find certificate to validate message"]
|
188
|
-
end
|
189
|
-
|
190
|
-
it "complains about a valid signature we don't trust" do
|
191
|
-
response = Response.parse(fixture("response_signed.xml"))
|
192
|
-
idp_entity.identity_providers.first.keys.clear
|
193
|
-
idp_entity.identity_providers.first.keys << KeyDescriptor.new(fixture("othercertificate.pem"))
|
194
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
195
|
-
expect(response.errors.length).to eq 1
|
196
|
-
expect(response.errors.first).to eq("no trusted signing key found")
|
197
|
-
end
|
198
|
-
|
199
|
-
it "validates signature by fingerprint" do
|
200
|
-
response = Response.parse(fixture("response_signed.xml"))
|
201
|
-
idp_entity.identity_providers.first.keys.clear
|
202
|
-
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"
|
203
|
-
|
204
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
205
|
-
expect(response.errors).to eq []
|
206
|
-
end
|
207
|
-
|
208
|
-
it "complains when we don't have any trusted fingerprints" do
|
209
|
-
response = Response.parse(fixture("response_signed.xml"))
|
210
|
-
idp_entity.identity_providers.first.keys.clear
|
211
|
-
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"
|
212
|
-
|
213
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
214
|
-
expect(response.errors).to eq ["no trusted signing key found"]
|
215
|
-
end
|
216
|
-
|
217
|
-
it "protects against xml signature wrapping attacks targeting nameid" do
|
218
|
-
response = Response.parse(fixture("xml_signature_wrapping_attack_response_nameid.xml"))
|
219
|
-
idp_entity.identity_providers.first.keys.clear
|
220
|
-
idp_entity.identity_providers.first.fingerprints << "afe71c28ef740bc87425be13a2263d37971da1f9"
|
221
|
-
|
222
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2012-08-03T20:07:16Z'))
|
223
|
-
expect(response.errors.length).to eq 1
|
224
|
-
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 )."
|
225
|
-
end
|
226
|
-
|
227
|
-
it "protects against xml signature wrapping attacks targeting attributes" do
|
228
|
-
response = Response.parse(fixture("xml_signature_wrapping_attack_response_attributes.xml"))
|
229
|
-
idp_entity.identity_providers.first.keys.clear
|
230
|
-
idp_entity.identity_providers.first.fingerprints << "afe71c28ef740bc87425be13a2263d37971da1f9"
|
231
|
-
|
232
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2012-08-03T20:07:16Z'))
|
233
|
-
expect(response.errors.length).to eq 1
|
234
|
-
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."
|
235
|
-
end
|
236
|
-
|
237
|
-
it "protects against xml signature wrapping attacks with duplicate IDs" do
|
238
|
-
response = Response.parse(fixture("xml_signature_wrapping_attack_duplicate_ids.xml"))
|
239
|
-
idp_entity.identity_providers.first.keys.clear
|
240
|
-
idp_entity.identity_providers.first.fingerprints << "7292914fc5bffa6f3fe1e43fd47c205395fecfa2"
|
241
|
-
|
242
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2014-02-01T13:48:10.831Z'))
|
243
|
-
expect(response.errors.length).to eq 1
|
244
|
-
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'."
|
245
|
-
end
|
246
|
-
|
247
|
-
it "protects against additional mis-signed assertions" do
|
248
|
-
response = Response.parse(fixture("xml_missigned_assertion.xml"))
|
249
|
-
idp_entity.identity_providers.first.keys.clear
|
250
|
-
idp_entity.identity_providers.first.fingerprints << "c38e789fcfbbd4727bd8ff7fc365b44fc3596bda"
|
251
|
-
|
252
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-27T19:12:52Z'))
|
253
|
-
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.",
|
254
|
-
"43:0: ERROR: Element '{http://www.w3.org/2000/09/xmldsig#}Signature': This element is not expected."]
|
255
|
-
end
|
256
|
-
|
257
|
-
it "doesn't break the signature by decrypting elements first" do
|
258
|
-
response = Response.parse(fixture("response_with_signed_assertion_and_encrypted_subject.xml"))
|
259
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2015-02-12T22:51:30Z'))
|
260
|
-
expect(response.errors).to eq []
|
261
|
-
expect(response.assertions.first.subject.name_id.id).to eq 'jacob'
|
262
|
-
end
|
263
|
-
|
264
|
-
it "allows signatures that don't include KeyInfo, if we have a full cert" do
|
265
|
-
response = Response.parse(fixture("response_without_keyinfo.xml"))
|
266
|
-
sp_entity.entity_id = 'http://unimelb-dev.instructure.com/saml2'
|
267
|
-
idp_entity.entity_id = 'https://authidm3tst.unimelb.edu.au:443/oam/fed'
|
268
|
-
idp_entity.identity_providers.first.keys.clear
|
269
|
-
idp_entity.identity_providers.first.keys << KeyDescriptor.new(<<-CERTIFICATE)
|
270
|
-
MIIB/jCCAWegAwIBAgIBCjANBgkqhkiG9w0BAQQFADAkMSIwIAYDVQQDExlhZGRlcjEuaXRzLnVuaW1lbGIuZWR1LmF1MB4XDTE3MDUyMjA2MzQzOFoXDTI3MDUyMDA2MzQzOFowJDEiMCAGA1UEAxMZYWRkZXIxLml0cy51bmltZWxiLmVkdS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAjJgoa5bPS+Jukq2vNaMwZ39L3IhAg6oOytz+bgOhmF+o5zYARbFqC67faa/rMSkfQwYIpp/MdsC8XHtHeR6HCJjbuPkH/EooHiREOClTI0EKZvI2Xv/DqexxEegRPxXiwPUEPozGGT1yWtSwkQTvRvA9tMpZl3yLg1LhDOP6s6MCAwEAAaNAMD4wDAYDVR0TAQH/BAIwADAPBgNVHQ8BAf8EBQMDB9gAMB0GA1UdDgQWBBRukBh7J1okLMIfSRpzF5opuj0LizANBgkqhkiG9w0BAQQFAAOBgQB0zySVaypIGRksTwpmjaQhMvNrYWGvj74Rs1iuqOdsEQkpgk5dQKRFiAFEr+6b7WN4k+IAH5S++l1R0bUG6k9HFSn7uy7AD+qZcdoUm9a39brtH2kefs0D3bQfrwkqggAtWKwqfU4r7nAcdtVE+CT3cny5QU2/mJav9W9bzFPMXQ==
|
271
|
-
CERTIFICATE
|
272
|
-
|
273
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: Time.parse('2019-04-16T00:56:03Z'))
|
274
|
-
expect(response.errors).to eq []
|
275
|
-
expect(response.assertions.first.subject.name_id.id).to eq 'testuserint.sso@staff.oimtest.unimelb.edu.au'
|
276
|
-
end
|
277
|
-
|
278
|
-
it "finds signatures the sign the assertion, not inside the assertion" do
|
279
|
-
response = Response.parse(fixture("response_assertion_signed_reffed_from_response.xml"))
|
280
|
-
sp_entity.entity_id = 'http://wscc.instructure.com/saml2'
|
281
|
-
idp_entity.entity_id = 'https://my.wscc.edu/idp'
|
282
|
-
idp_entity.identity_providers.first.keys.clear
|
283
|
-
idp_entity.identity_providers.first.fingerprints << "c4f473274116a3cbc295c3abf77c7ed1ade9b904"
|
284
|
-
|
285
|
-
sp_entity.valid_response?(response, idp_entity, verification_time: response.issue_instant)
|
286
|
-
expect(response.errors).to eq []
|
287
|
-
expect(response.assertions.first.subject.name_id.id).to eq 'narnold@wscc.edu'
|
288
|
-
expect(response).not_to be_signed
|
289
|
-
expect(response.assertions.first).to be_signed
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
end
|
@@ -1,76 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe ServiceProvider do
|
7
|
-
it "should serialize valid xml" do
|
8
|
-
entity = Entity.new
|
9
|
-
entity.entity_id = 'http://sso.canvaslms.com/SAML2'
|
10
|
-
entity.organization = Organization.new('Canvas', 'Canvas by Instructure', 'https://www.canvaslms.com/')
|
11
|
-
contact = Contact.new(Contact::Type::TECHNICAL)
|
12
|
-
contact.company = 'Instructure'
|
13
|
-
contact.email_addresses << 'mailto:ops@instructure.com'
|
14
|
-
entity.contacts << contact
|
15
|
-
|
16
|
-
sp = ServiceProvider.new
|
17
|
-
sp.single_logout_services << Endpoint.new('https://sso.canvaslms.com/SAML2/Logout',
|
18
|
-
Bindings::HTTPRedirect::URN)
|
19
|
-
sp.assertion_consumer_services << Endpoint::Indexed.new('https://sso.canvaslms.com/SAML2/Login1')
|
20
|
-
sp.assertion_consumer_services << Endpoint::Indexed.new('https://sso.canvaslms.com/SAML2/Login2')
|
21
|
-
sp.keys << KeyDescriptor.new('somedata', KeyDescriptor::Type::ENCRYPTION, [KeyDescriptor::EncryptionMethod.new])
|
22
|
-
sp.keys << KeyDescriptor.new('somedata', KeyDescriptor::Type::SIGNING)
|
23
|
-
acs = AttributeConsumingService.new
|
24
|
-
acs.name[:en] = 'service'
|
25
|
-
acs.requested_attributes << RequestedAttribute.create('uid')
|
26
|
-
sp.attribute_consuming_services << acs
|
27
|
-
|
28
|
-
entity.roles << sp
|
29
|
-
expect(Schemas.metadata.validate(Nokogiri::XML(entity.to_s))).to eq []
|
30
|
-
end
|
31
|
-
|
32
|
-
describe "valid metadata" do
|
33
|
-
let(:entity) { Entity.parse(fixture('service_provider.xml')) }
|
34
|
-
let(:sp) { entity.roles.first }
|
35
|
-
|
36
|
-
it "should create the assertion_consumer_services array" do
|
37
|
-
expect(sp.assertion_consumer_services.length).to eq 4
|
38
|
-
expect(sp.assertion_consumer_services.map(&:index)).to eq [0, 1, 2, 3]
|
39
|
-
expect(sp.assertion_consumer_services.first.location).to eq 'https://siteadmin.instructure.com/saml_consume'
|
40
|
-
end
|
41
|
-
|
42
|
-
it "should find the signing certificate" do
|
43
|
-
expect(sp.signing_keys.first.x509).to match(/MIIE8TCCA9mgAwIBAgIJAITusxON60cKMA0GCSqGSIb3DQEBBQUAMIGrMQswCQYD/)
|
44
|
-
end
|
45
|
-
|
46
|
-
it "should load the organization" do
|
47
|
-
expect(entity.organization.display_name.to_s).to eq 'Canvas'
|
48
|
-
end
|
49
|
-
|
50
|
-
it "should load contacts" do
|
51
|
-
expect(entity.contacts.length).to eq 1
|
52
|
-
expect(entity.contacts.first.type).to eq Contact::Type::TECHNICAL
|
53
|
-
expect(entity.contacts.first.surname).to eq 'Administrator'
|
54
|
-
end
|
55
|
-
|
56
|
-
it "loads attribute_consuming_services" do
|
57
|
-
expect(sp.attribute_consuming_services.length).to eq 1
|
58
|
-
acs = sp.attribute_consuming_services.first
|
59
|
-
expect(acs.index).to eq 0
|
60
|
-
expect(acs.name.to_s).to eq 'service'
|
61
|
-
expect(acs.requested_attributes.length).to eq 1
|
62
|
-
expect(acs.requested_attributes.first.name).to eq 'urn:oid:2.5.4.42'
|
63
|
-
end
|
64
|
-
|
65
|
-
it "loads the key info" do
|
66
|
-
expect(sp.keys.first.encryption_methods.first.algorithm).to eq KeyDescriptor::EncryptionMethod::Algorithm::AES128_CBC
|
67
|
-
expect(sp.keys.first.encryption_methods.first.key_size).to eq 128
|
68
|
-
end
|
69
|
-
|
70
|
-
it "loads service provider attributes" do
|
71
|
-
expect(sp.authn_requests_signed?).to be_truthy
|
72
|
-
expect(sp.want_assertions_signed?).to be_truthy
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
data/spec/lib/signable_spec.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe Signable do
|
7
|
-
describe '#valid_signature?' do
|
8
|
-
it 'can work with an explicit key from metadata' do
|
9
|
-
response = Response.parse(fixture("response_with_rsa_key_value.xml"))
|
10
|
-
key = response.assertions.first.signing_key.key
|
11
|
-
expect(response.assertions.first.valid_signature?(key: [key])).to eq true
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|