saml2 3.1.1 → 3.1.3
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 -137
- data/schemas/MetadataExchange.xsd +0 -112
- data/schemas/metadata_combined.xsd +0 -13
- data/schemas/oasis-200401-wss-wssecurity-secext-1.0.xsd +0 -195
- data/schemas/oasis-200401-wss-wssecurity-utility-1.0.xsd +0 -108
- data/schemas/saml-schema-assertion-2.0.xsd +0 -283
- data/schemas/saml-schema-metadata-2.0.xsd +0 -339
- data/schemas/saml-schema-protocol-2.0.xsd +0 -302
- data/schemas/sstc-saml-metadata-ext-query.xsd +0 -66
- data/schemas/ws-addr.xsd +0 -137
- data/schemas/ws-authorization.xsd +0 -145
- data/schemas/ws-federation.xsd +0 -471
- data/schemas/ws-securitypolicy-1.2.xsd +0 -1205
- data/schemas/xenc-schema.xsd +0 -136
- data/schemas/xml.xsd +0 -287
- data/schemas/xmldsig-core-schema.xsd +0 -309
- 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
data/spec/lib/attribute_spec.rb
DELETED
@@ -1,149 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe Attribute do
|
7
|
-
def serialize(attribute)
|
8
|
-
doc = Nokogiri::XML::Builder.new do |builder|
|
9
|
-
builder['saml'].Root('xmlns:saml' => Namespaces::SAML) do |root|
|
10
|
-
attribute.build(root)
|
11
|
-
root.parent.child['xmlns:saml'] = Namespaces::SAML
|
12
|
-
end
|
13
|
-
end.doc
|
14
|
-
doc.root.child.to_s
|
15
|
-
end
|
16
|
-
|
17
|
-
let(:eduPersonPrincipalNameXML) { <<XML.strip
|
18
|
-
<saml:Attribute xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" FriendlyName="eduPersonPrincipalName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" x500:Encoding="LDAP" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
|
19
|
-
<saml:AttributeValue xsi:type="xs:string">user@domain</saml:AttributeValue>
|
20
|
-
</saml:Attribute>
|
21
|
-
XML
|
22
|
-
}
|
23
|
-
|
24
|
-
it "should auto-parse X500 attributes" do
|
25
|
-
attr = Attribute.from_xml(Nokogiri::XML(eduPersonPrincipalNameXML).root)
|
26
|
-
expect(attr).to be_instance_of Attribute::X500
|
27
|
-
expect(attr.value).to eq "user@domain"
|
28
|
-
expect(attr.name).to eq Attribute::X500::EduPerson::PRINCIPAL_NAME
|
29
|
-
expect(attr.friendly_name).to eq 'eduPersonPrincipalName'
|
30
|
-
expect(attr.name_format).to eq Attribute::NameFormats::URI
|
31
|
-
end
|
32
|
-
|
33
|
-
it "recognizes and X500 attribute without a NameFormat" do
|
34
|
-
xml = <<-XML
|
35
|
-
<saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"><saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user@domain</saml:AttributeValue></saml:Attribute>
|
36
|
-
XML
|
37
|
-
attr = Attribute.from_xml(Nokogiri::XML(xml).root)
|
38
|
-
expect(attr).to be_instance_of Attribute::X500
|
39
|
-
expect(attr.value).to eq "user@domain"
|
40
|
-
expect(attr.name).to eq Attribute::X500::EduPerson::PRINCIPAL_NAME
|
41
|
-
expect(attr.friendly_name).to eq 'eduPersonPrincipalName'
|
42
|
-
expect(attr.name_format).to eq nil
|
43
|
-
end
|
44
|
-
|
45
|
-
it "should serialize an X500 attribute correctly" do
|
46
|
-
attr = Attribute.create('eduPersonPrincipalName', 'user@domain')
|
47
|
-
expect(attr).to be_instance_of Attribute::X500
|
48
|
-
expect(attr.value).to eq "user@domain"
|
49
|
-
expect(attr.name).to eq Attribute::X500::EduPerson::PRINCIPAL_NAME
|
50
|
-
expect(attr.friendly_name).to eq 'eduPersonPrincipalName'
|
51
|
-
expect(attr.name_format).to eq Attribute::NameFormats::URI
|
52
|
-
|
53
|
-
expect(serialize(attr)).to eq eduPersonPrincipalNameXML
|
54
|
-
end
|
55
|
-
|
56
|
-
it "should parse and serialize boolean values" do
|
57
|
-
xml = <<XML.strip
|
58
|
-
<saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
|
59
|
-
<saml:Attribute Name="attr">
|
60
|
-
<saml:AttributeValue xsi:type="xs:boolean">1</saml:AttributeValue>
|
61
|
-
</saml:Attribute>
|
62
|
-
</saml:AttributeStatement>
|
63
|
-
XML
|
64
|
-
|
65
|
-
stmt = AttributeStatement.from_xml(Nokogiri::XML(xml).root)
|
66
|
-
expect(stmt.attributes.first.value).to eq true
|
67
|
-
|
68
|
-
# serializes canonically
|
69
|
-
expect(serialize(stmt)).to eq(xml.sub('>1<', '>true<'))
|
70
|
-
end
|
71
|
-
|
72
|
-
it "should parse and serialize dateTime values" do
|
73
|
-
xml = <<XML.strip
|
74
|
-
<saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
|
75
|
-
<saml:Attribute Name="attr">
|
76
|
-
<saml:AttributeValue xsi:type="xs:dateTime">2015-06-29T18:37:03Z</saml:AttributeValue>
|
77
|
-
</saml:Attribute>
|
78
|
-
</saml:AttributeStatement>
|
79
|
-
XML
|
80
|
-
|
81
|
-
stmt = AttributeStatement.from_xml(Nokogiri::XML(xml).root)
|
82
|
-
expect(stmt.attributes.first.value).to eq Time.at(1435603023)
|
83
|
-
|
84
|
-
# serializes canonically
|
85
|
-
expect(serialize(stmt)).to eq xml
|
86
|
-
end
|
87
|
-
|
88
|
-
it "should parse values with different namespace prefixes" do
|
89
|
-
xml = <<XML.strip
|
90
|
-
<saml:Attribute Name="attr" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xssi="http://www.w3.org/2001/XMLSchema-instance">
|
91
|
-
<saml:AttributeValue xssi:type="xsd:boolean">0</saml:AttributeValue>
|
92
|
-
</saml:Attribute>
|
93
|
-
XML
|
94
|
-
|
95
|
-
attr = Attribute.from_xml(Nokogiri::XML(xml).root)
|
96
|
-
expect(attr.value).to eq false
|
97
|
-
end
|
98
|
-
|
99
|
-
it "should parse untagged values" do
|
100
|
-
xml = <<XML.strip
|
101
|
-
<saml:Attribute Name="attr" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
|
102
|
-
<saml:AttributeValue>something</saml:AttributeValue>
|
103
|
-
</saml:Attribute>
|
104
|
-
XML
|
105
|
-
|
106
|
-
attr = Attribute.from_xml(Nokogiri::XML(xml).root)
|
107
|
-
expect(attr.value).to eq "something"
|
108
|
-
end
|
109
|
-
|
110
|
-
end
|
111
|
-
|
112
|
-
describe AttributeStatement do
|
113
|
-
describe "#to_h" do
|
114
|
-
it "works" do
|
115
|
-
attr_statement = Response.parse(fixture("response_with_attribute_signed.xml")).assertions.first.attribute_statements.first
|
116
|
-
expect(attr_statement.to_h(:friendly_name)).to eq('givenName' => 'cody')
|
117
|
-
expect(attr_statement.to_h(:name)).to eq("urn:oid:2.5.4.42" => 'cody')
|
118
|
-
expect(attr_statement.to_h(:both)).to eq('givenName' => 'cody', "urn:oid:2.5.4.42" => 'cody')
|
119
|
-
end
|
120
|
-
|
121
|
-
it "infers friendly names if possible" do
|
122
|
-
attr_statement = Response.parse(fixture("test3-response.xml")).assertions.first.attribute_statements.first
|
123
|
-
expect(attr_statement.to_h).to eq({
|
124
|
-
'urn:oid:1.3.6.1.4.1.5923.1.1.1.1' => 'member',
|
125
|
-
'urn:oid:1.3.6.1.4.1.5923.1.1.1.6' => 'student@example.edu',
|
126
|
-
'eduPersonAffiliation' => 'member',
|
127
|
-
'eduPersonPrincipalName' => 'student@example.edu'})
|
128
|
-
end
|
129
|
-
|
130
|
-
it "properly combines repeated attributes" do
|
131
|
-
attr_statement = AttributeStatement.from_xml(Nokogiri::XML(<<-XML).root)
|
132
|
-
<saml2:AttributeStatement xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
|
133
|
-
<saml2:Attribute FriendlyName="eduPersonScopedAffiliation" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.9" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
134
|
-
<saml2:AttributeValue>02</saml2:AttributeValue>
|
135
|
-
</saml2:Attribute>
|
136
|
-
<saml2:Attribute FriendlyName="eduPersonScopedAffiliation" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.9" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
137
|
-
<saml2:AttributeValue>employee@school.edu</saml2:AttributeValue>
|
138
|
-
<saml2:AttributeValue>students@school.edu</saml2:AttributeValue>
|
139
|
-
</saml2:Attribute>
|
140
|
-
</saml2:AttributeStatement>
|
141
|
-
XML
|
142
|
-
|
143
|
-
expect(attr_statement.to_h(:friendly_name)).to eq({
|
144
|
-
'eduPersonScopedAffiliation' => ['02', 'employee@school.edu', 'students@school.edu']
|
145
|
-
})
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe AuthnRequest do
|
7
|
-
let(:sp) { Entity.parse(fixture('service_provider.xml')).roles.first }
|
8
|
-
let(:request) { AuthnRequest.parse(fixture('authnrequest.xml')) }
|
9
|
-
|
10
|
-
it "should be valid" do
|
11
|
-
expect(request.valid_schema?).to eq true
|
12
|
-
expect(request.resolve(sp)).to eq true
|
13
|
-
expect(request.assertion_consumer_service.location).to eq "https://siteadmin.test.instructure.com/saml_consume"
|
14
|
-
end
|
15
|
-
|
16
|
-
it "should not be valid if the ACS url is not in the SP" do
|
17
|
-
allow(request).to receive(:assertion_consumer_service_url).and_return("garbage")
|
18
|
-
expect(request.resolve(sp)).to eq false
|
19
|
-
end
|
20
|
-
|
21
|
-
it "should use the default ACS if not specified" do
|
22
|
-
allow(request).to receive(:assertion_consumer_service_url).and_return(nil)
|
23
|
-
expect(request.resolve(sp)).to eq true
|
24
|
-
expect(request.assertion_consumer_service.location).to eq "https://siteadmin.instructure.com/saml_consume"
|
25
|
-
end
|
26
|
-
|
27
|
-
it "should find the ACS by index" do
|
28
|
-
allow(request).to receive(:assertion_consumer_service_url).and_return(nil)
|
29
|
-
allow(request).to receive(:assertion_consumer_service_index).and_return(2)
|
30
|
-
expect(request.resolve(sp)).to eq true
|
31
|
-
expect(request.assertion_consumer_service.location).to eq "https://siteadmin.beta.instructure.com/saml_consume"
|
32
|
-
end
|
33
|
-
|
34
|
-
it "should find the NameID policy" do
|
35
|
-
expect(request.name_id_policy).to eq NameID::Policy.new(true, NameID::Format::PERSISTENT, "moodle.bridge.feide.no")
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'serializes valid XML' do
|
39
|
-
authn_request = AuthnRequest.initiate(NameID.new("entity"),
|
40
|
-
assertion_consumer_service: Endpoint.new('https://somewhere/'))
|
41
|
-
authn_request.requested_authn_context = RequestedAuthnContext.new
|
42
|
-
authn_request.requested_authn_context.class_ref = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
|
43
|
-
authn_request.requested_authn_context.comparison = :exact
|
44
|
-
authn_request.passive = true
|
45
|
-
xml = authn_request.to_s
|
46
|
-
authn_request = AuthnRequest.parse(xml)
|
47
|
-
expect(authn_request).to be_valid_schema
|
48
|
-
expect(authn_request.force_authn?).to eq nil
|
49
|
-
expect(authn_request.passive?).to eq true
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,183 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../../spec_helper'
|
4
|
-
|
5
|
-
require 'openssl'
|
6
|
-
|
7
|
-
module SAML2
|
8
|
-
describe Bindings::HTTPRedirect do
|
9
|
-
describe '.decode' do
|
10
|
-
def check_error(wrapped, cause)
|
11
|
-
error = nil
|
12
|
-
begin
|
13
|
-
yield
|
14
|
-
rescue => e
|
15
|
-
error = e
|
16
|
-
end
|
17
|
-
expect(error).not_to be_nil
|
18
|
-
expect(error).to be_a(wrapped)
|
19
|
-
expect(error.cause).to be_a(cause)
|
20
|
-
end
|
21
|
-
|
22
|
-
it "complains about invalid URIs" do
|
23
|
-
check_error(CorruptMessage, URI::InvalidURIError) { Bindings::HTTPRedirect.decode(" ") }
|
24
|
-
end
|
25
|
-
|
26
|
-
it "complains about missing message" do
|
27
|
-
expect { Bindings::HTTPRedirect.decode("http://somewhere/") }.to raise_error(MissingMessage)
|
28
|
-
expect { Bindings::HTTPRedirect.decode("http://somewhere/?RelayState=bob") }.to raise_error(MissingMessage)
|
29
|
-
end
|
30
|
-
|
31
|
-
it "complains about malformed Base64" do
|
32
|
-
check_error(CorruptMessage, ArgumentError) { Bindings::HTTPRedirect.decode("http://somewhere/?SAMLRequest=%^") }
|
33
|
-
end
|
34
|
-
|
35
|
-
it "doesn't allow deflate bombs" do
|
36
|
-
message = double()
|
37
|
-
allow(message).to receive(:destination).and_return("http://somewhere/")
|
38
|
-
allow(message).to receive(:to_s).and_return("\0" * 2 * 1024 * 1024)
|
39
|
-
url = Bindings::HTTPRedirect.encode(message)
|
40
|
-
|
41
|
-
expect { Bindings::HTTPRedirect.decode(url) }.to raise_error(MessageTooLarge)
|
42
|
-
end
|
43
|
-
|
44
|
-
it "complains about malformed deflated data" do
|
45
|
-
check_error(CorruptMessage, Zlib::BufError) { Bindings::HTTPRedirect.decode("http://somewhere/?SAMLRequest=abcd") }
|
46
|
-
end
|
47
|
-
|
48
|
-
it "complains about zlibbed data" do
|
49
|
-
# SAML uses just Deflate, which has no header/footer; Zlib adds a simple header/footer
|
50
|
-
message = Base64.strict_encode64(Zlib::Deflate.deflate('abcd'))
|
51
|
-
check_error(CorruptMessage, Zlib::DataError) { Bindings::HTTPRedirect.decode("http://somewhere/?SAMLRequest=#{message}") }
|
52
|
-
end
|
53
|
-
|
54
|
-
it "validates encoding" do
|
55
|
-
message = double()
|
56
|
-
allow(message).to receive(:destination).and_return("http://somewhere/")
|
57
|
-
allow(message).to receive(:to_s).and_return("hi")
|
58
|
-
url = Bindings::HTTPRedirect.encode(message, relay_state: "abc")
|
59
|
-
url << "&SAMLEncoding=garbage"
|
60
|
-
expect { Bindings::HTTPRedirect.decode(url) }.to raise_error(UnsupportedEncoding)
|
61
|
-
end
|
62
|
-
|
63
|
-
it "returns relay state" do
|
64
|
-
message = double()
|
65
|
-
allow(message).to receive(:destination).and_return("http://somewhere/")
|
66
|
-
allow(message).to receive(:to_s).and_return("hi")
|
67
|
-
url = Bindings::HTTPRedirect.encode(message, relay_state: "abc")
|
68
|
-
allow(Message).to receive(:parse).with("hi").and_return("parsed")
|
69
|
-
message, relay_state = Bindings::HTTPRedirect.decode(url)
|
70
|
-
expect(message).to eq "parsed"
|
71
|
-
expect(relay_state).to eq "abc"
|
72
|
-
end
|
73
|
-
|
74
|
-
context "signature validation" do
|
75
|
-
# taken from logs of another system. It's a good one to test because the Signature
|
76
|
-
# has to be taken out of the middle, so you can't just use the whole query string
|
77
|
-
let(:url) { '/login/saml/logout?SAMLResponse=fZLNasMwEIRfxehuy7KVOhaOobSXQHtpSg%2b9FP2sE4EjGa9U2rev7JBDoOQmLTO7s5%2fUoTyPk3jxRx%2fDG%2bDkHUK2f96Rr%2fpBN4pXMtdcq5xXbJurbb3JN1JCZZpSqQ0n2QfMaL3bkaooSbZHjLB3GKQLqVSyJi95zup3xgVvRdkWVdN%2bkuwZMFgnw%2bo8hTChoNR%2b%2fwbQp8Im%2fxx1iDMU2p%2fp6I%2fW0SXockw5Sfa0xFxGxNkJL9GicPIMKIIWh8fXF5HSCH0RiehwAm0HCyYldNct3%2f2OaK3Yw8AkSKVqXrJqYIpVst4ao0xtymZbD21bbXja7ec8OhQrr%2ftzp9kHr%2f1I%2bm7lMV%2bs900SEeaFB%2bkXHgmHNAMWVyZg4lqgSfVtNSBNiDB09DKh7y7veAgyRLy9PXkD2YccI9xPgKtaHKJO7ZFktO%2fobVf632fp%2fwA%3d&Signature=eZejccsvxyLEZvkNdL3shazIPyBIfRwk0Ny5INfrwzhE40N%2bH4neEo7xoHi2ncJ0LQG6A71oxviVkqPWXUaePuAt3fbxTf%2bLkWPiXo0D6GVebSgPSZIMrsU%2fawfou68yX%2bI5dk8KtFiMOPCf5818oJvCdYjszN5o%2fU80UlJdjiDK%2bMF2rtzx6ZEXs04MoMDWKgouTZUFxNZP3KJeFQumLbK99ZfJVl8Wl4XDTs1DKq7eBJc1IgyH13LELxhHgCZMpARrCX65gCYhjnJWhmyTU4YFzdcwKeulYcP0eTbMEqRy9s1sEaOH%2fTx3I46Fl%2bv7j3GbRiRTwqF9%2bqjAKbJjpw%3d%3d&SigAlg=http%3a%2f%2fwww.w3.org%2f2000%2f09%2fxmldsig%23rsa-sha1' }
|
78
|
-
let(:certificate) { OpenSSL::X509::Certificate.new(Base64.decode64('MIIFnzCCBIegAwIBAgIQItX5wssh0ecd46K65PkSNDANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTAeFw0xNjA5MDgwMDAwMDBaFw0xOTEwMjUyMzU5NTlaMIGeMSEwHwYDVQQLExhEb21haW4gQ29udHJvbCBWYWxpZGF0ZWQxSTBHBgNVBAsTQElzc3VlZCB0aHJvdWdoIEl2eSBUZWNoIENvbW11bml0eSBDb2xsZWdlIG9mIEluZGlhbmEgRS1QS0kgTWFuYWcxEzARBgNVBAsTCkNPTU9ETyBTU0wxGTAXBgNVBAMTEGFkZnMuaXZ5dGVjaC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC58zHz7VsV9S2XZMRjgqiWxBZ6M9y6/3zkrbObJ9hZqO7giCoonNDuELUiNt8pBqF8aHef8qbDOecBBXkz8rPAJL1S6lzvbxHIBuvEy+xOpVdUNMoyOaAYHOI5T6ueL1Q4iGMKfnWuXSvVTyB+9wAF/aWVFSoz+alUOiQtqTYyfgIKzHIAmFX7/SjFA9UjKVtqatcvzWsSWZHL4imeTmPosXXjmJVZnl+jaeFsnmW59o66sdGR+NYkhsBcVRnuP3MdxVgr5xSJMN+/BgZwCncX+4LJq5664eeQcJM5Km9kbQ/jMFhYy765ejszcL0vWe/fS7tdXQCfoKjRZ5LzNEb3AgMBAAGjggHjMIIB3zAfBgNVHSMEGDAWgBSQr2o6lFoL2JDqElZz30O0Oija5zAdBgNVHQ4EFgQUdFr6SnHaXUqLAEdOL9qrTJS/3AYwDgYDVR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCME8GA1UdIARIMEYwOgYLKwYBBAGyMQECAgcwKzApBggrBgEFBQcCARYdaHR0cHM6Ly9zZWN1cmUuY29tb2RvLmNvbS9DUFMwCAYGZ4EMAQIBMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9ET1JTQURvbWFpblZhbGlkYXRpb25TZWN1cmVTZXJ2ZXJDQS5jcmwwgYUGCCsGAQUFBwEBBHkwdzBPBggrBgEFBQcwAoZDaHR0cDovL2NydC5jb21vZG9jYS5jb20vQ09NT0RPUlNBRG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMDEGA1UdEQQqMCiCEGFkZnMuaXZ5dGVjaC5lZHWCFHd3dy5hZGZzLml2eXRlY2guZWR1MA0GCSqGSIb3DQEBCwUAA4IBAQA0dXP0leDcdrr/iKk4nDSCofllPAWE8LE3mD9Yb9K+/oVymxpqNIVJesDPLtf1HqWk6S6eafcYvfzl9aTMcvwEkL27g2l9UQuICkQgqSEY5qTsK//u/2S98JqXep2oRyvxo3UHX+3Ouc3i49hQ0v05Faoeap/ZT3JEsMV2Go9UKRJbYBG9Nqq/CDBuTgyopKJ7fvCtsGxwsvlUAz/NMuNoUphPQ2S+O/SjabjR4XsAGU78Hji2tqJyvPyKPanxc0ioDdnL5lvrk4uZ/6Dy159C5FOFeLU2ZfiNLXRR85KFfhtX954qvX6jmM7CPmcidhzEnZV8fQv9G6XYPfrNL7bh')).public_key }
|
79
|
-
let(:incorrect_certificate) { OpenSSL::X509::Certificate.new(fixture('certificate.pem')).public_key }
|
80
|
-
|
81
|
-
it "validates the signature" do
|
82
|
-
# no exception raised
|
83
|
-
Bindings::HTTPRedirect.decode(url, public_key: certificate)
|
84
|
-
end
|
85
|
-
|
86
|
-
it "raises on invalid signature" do
|
87
|
-
expect { Bindings::HTTPRedirect.decode(url, public_key: incorrect_certificate) }.to raise_error(InvalidSignature)
|
88
|
-
end
|
89
|
-
|
90
|
-
it "raises on unsupported signature algorithm" do
|
91
|
-
x = url.dup
|
92
|
-
# SigAlg is now sha10
|
93
|
-
x << "0"
|
94
|
-
expect { Bindings::HTTPRedirect.decode(x, public_key: certificate) }.to raise_error(UnsupportedSignatureAlgorithm)
|
95
|
-
end
|
96
|
-
|
97
|
-
it "allows the caller to detect an unsigned message" do
|
98
|
-
message = double()
|
99
|
-
allow(message).to receive(:destination).and_return("http://somewhere/")
|
100
|
-
allow(message).to receive(:to_s).and_return("hi")
|
101
|
-
url = Bindings::HTTPRedirect.encode(message)
|
102
|
-
allow(Message).to receive(:parse).with("hi").and_return("parsed")
|
103
|
-
|
104
|
-
expect do
|
105
|
-
Bindings::HTTPRedirect.decode(url) do |_message, sig_alg|
|
106
|
-
expect(sig_alg).to be_nil
|
107
|
-
raise "no signature"
|
108
|
-
end
|
109
|
-
end.to raise_error("no signature")
|
110
|
-
end
|
111
|
-
|
112
|
-
it "requires a signature if a key is passed" do
|
113
|
-
message = double()
|
114
|
-
allow(message).to receive(:destination).and_return("http://somewhere/")
|
115
|
-
allow(message).to receive(:to_s).and_return("hi")
|
116
|
-
url = Bindings::HTTPRedirect.encode(message)
|
117
|
-
allow(Message).to receive(:parse).with("hi").and_return("parsed")
|
118
|
-
|
119
|
-
expect { Bindings::HTTPRedirect.decode(url, public_key: certificate) } .to raise_error(UnsignedMessage)
|
120
|
-
end
|
121
|
-
|
122
|
-
it "notifies the caller which key was used" do
|
123
|
-
called = 0
|
124
|
-
key_used = ->(key) do
|
125
|
-
expect(key).to eq certificate
|
126
|
-
called += 1
|
127
|
-
end
|
128
|
-
Bindings::HTTPRedirect.decode(url,
|
129
|
-
public_key: [incorrect_certificate,
|
130
|
-
certificate],
|
131
|
-
public_key_used: key_used)
|
132
|
-
expect(called).to eq 1
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
describe '.encode' do
|
138
|
-
it 'works' do
|
139
|
-
message = double()
|
140
|
-
allow(message).to receive(:destination).and_return("http://somewhere/")
|
141
|
-
allow(message).to receive(:to_s).and_return("hi")
|
142
|
-
url = Bindings::HTTPRedirect.encode(message, relay_state: "abc")
|
143
|
-
expect(url).to match(%r{^http://somewhere/\?SAMLResponse=(?:.*)&RelayState=abc})
|
144
|
-
end
|
145
|
-
|
146
|
-
it 'signs a message' do
|
147
|
-
message = double()
|
148
|
-
allow(message).to receive(:destination).and_return("http://somewhere/")
|
149
|
-
allow(message).to receive(:to_s).and_return("hi")
|
150
|
-
key = OpenSSL::PKey::RSA.new(fixture('privatekey.key'))
|
151
|
-
url = Bindings::HTTPRedirect.encode(message,
|
152
|
-
relay_state: "abc",
|
153
|
-
private_key: key)
|
154
|
-
|
155
|
-
# verify the signature
|
156
|
-
allow(Message).to receive(:parse).with("hi").and_return("parsed")
|
157
|
-
Bindings::HTTPRedirect.decode(url) do |_message, sig_alg|
|
158
|
-
expect(sig_alg).to eq Bindings::HTTPRedirect::SigAlgs::RSA_SHA1
|
159
|
-
OpenSSL::X509::Certificate.new(fixture('certificate.pem')).public_key
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
it 'signs a message with RSA-SHA256' do
|
164
|
-
message = double()
|
165
|
-
allow(message).to receive(:destination).and_return("http://somewhere/")
|
166
|
-
allow(message).to receive(:to_s).and_return("hi")
|
167
|
-
key = OpenSSL::PKey::RSA.new(fixture('privatekey.key'))
|
168
|
-
url = Bindings::HTTPRedirect.encode(message,
|
169
|
-
relay_state: "abc",
|
170
|
-
private_key: key,
|
171
|
-
sig_alg: Bindings::HTTPRedirect::SigAlgs::RSA_SHA256)
|
172
|
-
|
173
|
-
# verify the signature
|
174
|
-
allow(Message).to receive(:parse).with("hi").and_return("parsed")
|
175
|
-
Bindings::HTTPRedirect.decode(url) do |_message, sig_alg|
|
176
|
-
expect(sig_alg).to eq Bindings::HTTPRedirect::SigAlgs::RSA_SHA256
|
177
|
-
OpenSSL::X509::Certificate.new(fixture('certificate.pem')).public_key
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
data/spec/lib/conditions_spec.rb
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe Conditions do
|
7
|
-
it "empty should be valid" do
|
8
|
-
expect(Conditions.new.valid?).to eq true
|
9
|
-
end
|
10
|
-
|
11
|
-
it "should be invalid with unknown condition" do
|
12
|
-
conditions = Conditions.new
|
13
|
-
conditions << Conditions::Condition.new
|
14
|
-
expect(conditions.valid?).to eq false
|
15
|
-
end
|
16
|
-
|
17
|
-
it "should be valid with timestamps" do
|
18
|
-
conditions = Conditions.new
|
19
|
-
now = Time.now.utc
|
20
|
-
conditions.not_before = now - 5
|
21
|
-
conditions.not_on_or_after = now + 30
|
22
|
-
expect(conditions.valid?).to eq true
|
23
|
-
end
|
24
|
-
|
25
|
-
it "should be invalid with out of range timestamps" do
|
26
|
-
conditions = Conditions.new
|
27
|
-
now = Time.now.utc
|
28
|
-
conditions.not_before = now - 35
|
29
|
-
conditions.not_on_or_after = now - 5
|
30
|
-
expect(conditions.valid?).to eq false
|
31
|
-
end
|
32
|
-
|
33
|
-
it "should allow passing now" do
|
34
|
-
conditions = Conditions.new
|
35
|
-
now = Time.now.utc
|
36
|
-
conditions.not_before = now - 35
|
37
|
-
conditions.not_on_or_after = now - 5
|
38
|
-
expect(conditions.valid?(now: now - 10)).to eq true
|
39
|
-
end
|
40
|
-
|
41
|
-
it "should be invalid before indeterminate" do
|
42
|
-
conditions = Conditions.new
|
43
|
-
now = Time.now.utc
|
44
|
-
conditions.not_before = now + 5
|
45
|
-
conditions << Conditions::Condition.new
|
46
|
-
expect(conditions.valid?).to eq false
|
47
|
-
end
|
48
|
-
|
49
|
-
it "should be invalid before indeterminate (actual conditions)" do
|
50
|
-
conditions = Conditions.new
|
51
|
-
conditions << Conditions::Condition.new
|
52
|
-
conditions << Conditions::AudienceRestriction.new('audience')
|
53
|
-
expect(conditions.valid?).to eq false
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
describe Conditions::AudienceRestriction do
|
58
|
-
it "should be invalid" do
|
59
|
-
expect(Conditions::AudienceRestriction.new('expected').valid?(audience: 'actual')).to eq false
|
60
|
-
end
|
61
|
-
|
62
|
-
it "should be valid" do
|
63
|
-
expect(Conditions::AudienceRestriction.new('expected').valid?(audience: 'expected')).to eq true
|
64
|
-
end
|
65
|
-
|
66
|
-
it "should be valid with an array" do
|
67
|
-
expect(Conditions::AudienceRestriction.new(['expected', 'actual']).valid?(audience: 'actual')).to eq true
|
68
|
-
end
|
69
|
-
|
70
|
-
it "is valid when ignored" do
|
71
|
-
expect(Conditions::AudienceRestriction.new('expected').valid?(audience: 'actual', ignore_audience_condition: true)).to eq true
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
data/spec/lib/entity_spec.rb
DELETED
@@ -1,58 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe Entity do
|
7
|
-
it "should parse and validate" do
|
8
|
-
entity = Entity.parse(fixture('service_provider.xml'))
|
9
|
-
expect(entity.valid_schema?).to eq true
|
10
|
-
end
|
11
|
-
|
12
|
-
it "should return nil when not valid schema" do
|
13
|
-
entity = Entity.parse("<xml></xml>")
|
14
|
-
expect(entity).to be_nil
|
15
|
-
end
|
16
|
-
|
17
|
-
it "should return nil on non-XML" do
|
18
|
-
entity = Entity.parse("garbage")
|
19
|
-
expect(entity).to be_nil
|
20
|
-
end
|
21
|
-
|
22
|
-
describe "valid schema" do
|
23
|
-
let(:entity) { Entity.parse(fixture('service_provider.xml')) }
|
24
|
-
|
25
|
-
it "should find the id" do
|
26
|
-
expect(entity.entity_id).to eq "http://siteadmin.instructure.com/saml2"
|
27
|
-
end
|
28
|
-
|
29
|
-
it "should parse the organization" do
|
30
|
-
expect(entity.organization.display_name.to_s).to eq 'Canvas'
|
31
|
-
expect(entity.organization.display_name['en']).to eq 'Canvas'
|
32
|
-
expect(entity.organization.display_name['es']).to be_nil
|
33
|
-
expect(entity.organization.display_name[:all]).to eq en: 'Canvas'
|
34
|
-
end
|
35
|
-
|
36
|
-
it "validates metadata from ADFS containing lots of non-SAML schemas" do
|
37
|
-
expect(Entity.parse(fixture('FederationMetadata.xml')).valid_schema?).to eq true
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
it "should sign correctly" do
|
42
|
-
entity = Entity.parse(fixture('service_provider.xml'))
|
43
|
-
entity.sign(fixture('certificate.pem'), fixture('privatekey.key'))
|
44
|
-
entity2 = Entity.parse(entity.to_s)
|
45
|
-
expect(entity2.valid_schema?).to eq true
|
46
|
-
end
|
47
|
-
|
48
|
-
describe Entity::Group do
|
49
|
-
it "should parse and validate" do
|
50
|
-
group = Entity.parse(fixture('entities.xml'))
|
51
|
-
expect(group).to be_instance_of(Entity::Group)
|
52
|
-
expect(group.valid_schema?).to eq true
|
53
|
-
|
54
|
-
expect(group.map(&:entity_id)).to eq ['urn:entity1', 'urn:entity2']
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe IdentityProvider 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
|
-
idp = IdentityProvider.new
|
17
|
-
idp.name_id_formats << NameID::Format::PERSISTENT
|
18
|
-
idp.single_sign_on_services << Endpoint.new('https://sso.canvaslms.com/SAML2/Login')
|
19
|
-
idp.keys << KeyDescriptor.new('somedata', KeyDescriptor::Type::SIGNING)
|
20
|
-
|
21
|
-
entity.roles << idp
|
22
|
-
expect(Schemas.metadata.validate(Nokogiri::XML(entity.to_s))).to eq []
|
23
|
-
end
|
24
|
-
|
25
|
-
describe "valid metadata" do
|
26
|
-
let(:entity) { Entity.parse(fixture('identity_provider.xml')) }
|
27
|
-
let(:idp) { entity.roles.first }
|
28
|
-
|
29
|
-
it "should create the single_sign_on_services array" do
|
30
|
-
expect(idp.single_sign_on_services.length).to eq 3
|
31
|
-
expect(idp.single_sign_on_services.first.location).to eq 'https://sso.school.edu/idp/profile/Shibboleth/SSO'
|
32
|
-
end
|
33
|
-
|
34
|
-
it "should find the signing certificate" do
|
35
|
-
expect(idp.keys.first.x509).to match(/MIIE8TCCA9mgAwIBAgIJAITusxON60cKMA0GCSqGSIb3DQEBBQUAMIGrMQswCQYD/)
|
36
|
-
end
|
37
|
-
|
38
|
-
it "loads identity provider attributes" do
|
39
|
-
expect(idp.want_authn_requests_signed?).to be_truthy
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,71 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe IndexedObject do
|
7
|
-
describe "#default?" do
|
8
|
-
it "always returns a boolean" do
|
9
|
-
acs = Endpoint::Indexed.new('a', 0)
|
10
|
-
expect(acs.default?).to eq false
|
11
|
-
expect(acs.default_defined?).to eq false
|
12
|
-
end
|
13
|
-
|
14
|
-
it "#default_defined? works" do
|
15
|
-
acs = Endpoint::Indexed.new('a', 0, false)
|
16
|
-
expect(acs.default?).to eq false
|
17
|
-
expect(acs.default_defined?).to eq true
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
context "serialization" do
|
22
|
-
it "doesn't include isDefault when it's nil" do
|
23
|
-
acs = Endpoint::Indexed.new('a', 0)
|
24
|
-
builder = double()
|
25
|
-
expect(builder).to receive(:[]).and_return(builder).ordered
|
26
|
-
expect(builder).to receive(:"AssertionConsumerService").ordered
|
27
|
-
expect(builder).to receive(:parent).and_return(builder).ordered
|
28
|
-
expect(builder).to receive(:children).and_return(builder).ordered
|
29
|
-
expect(builder).to receive(:last).and_return(builder).ordered
|
30
|
-
expect(builder).to receive(:[]=).with("index", 0).ordered
|
31
|
-
|
32
|
-
acs.build(builder,"AssertionConsumerService")
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
describe IndexedObject::Array do
|
38
|
-
it "should sort by index" do
|
39
|
-
acses = Endpoint::Indexed::Array.new(
|
40
|
-
[Endpoint::Indexed.new('b', 1),
|
41
|
-
Endpoint::Indexed.new('a', 0)])
|
42
|
-
expect(acses.map(&:location)).to eq ['a', 'b']
|
43
|
-
end
|
44
|
-
|
45
|
-
it "should be accessible by index" do
|
46
|
-
acses = Endpoint::Indexed::Array.new(
|
47
|
-
[Endpoint::Indexed.new('b', 3),
|
48
|
-
Endpoint::Indexed.new('a', 1)])
|
49
|
-
expect(acses.map(&:location)).to eq ['a', 'b']
|
50
|
-
expect(acses[1].location).to eq 'a'
|
51
|
-
expect(acses[3].location).to eq 'b'
|
52
|
-
expect(acses[0]).to be_nil
|
53
|
-
end
|
54
|
-
|
55
|
-
describe "#default" do
|
56
|
-
it "should default to first entry if not otherwise specified" do
|
57
|
-
acses = Endpoint::Indexed::Array.new(
|
58
|
-
[Endpoint::Indexed.new('a', 0),
|
59
|
-
Endpoint::Indexed.new('b', 1)])
|
60
|
-
expect(acses.default.location).to eq 'a'
|
61
|
-
end
|
62
|
-
|
63
|
-
it "should default to a tagged default" do
|
64
|
-
acses = Endpoint::Indexed::Array.new(
|
65
|
-
[Endpoint::Indexed.new('a', 0),
|
66
|
-
Endpoint::Indexed.new('b', 1, true)])
|
67
|
-
expect(acses.default.location).to eq 'b'
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
data/spec/lib/key_spec.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper'
|
4
|
-
|
5
|
-
module SAML2
|
6
|
-
describe KeyInfo do
|
7
|
-
describe '.format_fingerprint' do
|
8
|
-
it 'strips non-hexadecimal characters' do
|
9
|
-
expect(KeyInfo.format_fingerprint("\u200F abcdefghijklmnop 1234567890-\n a1")).to eq("ab:cd:ef:12:34:56:78:90:a1")
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
describe '#certificate' do
|
14
|
-
it "doesn't asplode if the keyinfo is just an rsa key value" do
|
15
|
-
response = Nokogiri::XML(fixture("response_with_rsa_key_value.xml"))
|
16
|
-
key = KeyInfo.from_xml(response.at_xpath('//dsig:KeyInfo', Namespaces::ALL))
|
17
|
-
expect(key.certificate).to be_nil
|
18
|
-
expect(key.fingerprint).to be_nil
|
19
|
-
expect(key.key).not_to be_nil
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|