saml2 3.1.2 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
data/lib/saml2/conditions.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "active_support/core_ext/array/wrap"
|
4
4
|
|
5
5
|
module SAML2
|
6
6
|
class Conditions < Array
|
@@ -12,6 +12,7 @@ module SAML2
|
|
12
12
|
# (see Base.from_xml)
|
13
13
|
def self.from_xml(node)
|
14
14
|
return nil unless node
|
15
|
+
|
15
16
|
result = new
|
16
17
|
result.from_xml(node)
|
17
18
|
result
|
@@ -20,15 +21,15 @@ module SAML2
|
|
20
21
|
# (see Base#from_xml)
|
21
22
|
def from_xml(node)
|
22
23
|
@xml = node
|
23
|
-
@not_before = Time.parse(node[
|
24
|
-
@not_on_or_after = Time.parse(node[
|
24
|
+
@not_before = Time.parse(node["NotBefore"]) if node["NotBefore"]
|
25
|
+
@not_on_or_after = Time.parse(node["NotOnOrAfter"]) if node["NotOnOrAfter"]
|
25
26
|
|
26
27
|
replace(node.element_children.map do |restriction|
|
27
28
|
klass = if self.class.const_defined?(restriction.name, false)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
self.class.const_get(restriction.name, false)
|
30
|
+
else
|
31
|
+
Condition
|
32
|
+
end
|
32
33
|
klass.from_xml(restriction)
|
33
34
|
end)
|
34
35
|
end
|
@@ -67,9 +68,9 @@ module SAML2
|
|
67
68
|
|
68
69
|
# (see Base#build)
|
69
70
|
def build(builder)
|
70
|
-
builder[
|
71
|
-
conditions.parent[
|
72
|
-
conditions.parent[
|
71
|
+
builder["saml"].Conditions do |conditions|
|
72
|
+
conditions.parent["NotBefore"] = not_before.iso8601 if not_before
|
73
|
+
conditions.parent["NotOnOrAfter"] = not_on_or_after.iso8601 if not_on_or_after
|
73
74
|
|
74
75
|
each do |condition|
|
75
76
|
condition.build(conditions)
|
@@ -81,7 +82,7 @@ module SAML2
|
|
81
82
|
class Condition < Base
|
82
83
|
# @return []
|
83
84
|
def validate(_)
|
84
|
-
["unable to validate #{xml&.name ||
|
85
|
+
["unable to validate #{xml&.name || "unrecognized"} condition"]
|
85
86
|
end
|
86
87
|
|
87
88
|
def valid?(*args)
|
@@ -94,6 +95,7 @@ module SAML2
|
|
94
95
|
|
95
96
|
# @param audience [Array<String>]
|
96
97
|
def initialize(audience = [])
|
98
|
+
super()
|
97
99
|
@audience = audience
|
98
100
|
end
|
99
101
|
|
@@ -105,7 +107,7 @@ module SAML2
|
|
105
107
|
|
106
108
|
# @return [Array<String>] Allowed audiences
|
107
109
|
def audience
|
108
|
-
@audience ||= load_string_array(xml,
|
110
|
+
@audience ||= load_string_array(xml, "saml:Audience")
|
109
111
|
end
|
110
112
|
|
111
113
|
# @param audience [String]
|
@@ -113,16 +115,17 @@ module SAML2
|
|
113
115
|
return [] if ignore_audience_condition
|
114
116
|
|
115
117
|
unless Array.wrap(self.audience).include?(audience)
|
116
|
-
return ["audience #{audience} not in allowed list of #{Array.wrap(self.audience).join(
|
118
|
+
return ["audience #{audience} not in allowed list of #{Array.wrap(self.audience).join(", ")}"]
|
117
119
|
end
|
120
|
+
|
118
121
|
[]
|
119
122
|
end
|
120
123
|
|
121
124
|
# (see Base#build)
|
122
125
|
def build(builder)
|
123
|
-
builder[
|
126
|
+
builder["saml"].AudienceRestriction do |audience_restriction|
|
124
127
|
Array.wrap(audience).each do |single_audience|
|
125
|
-
audience_restriction[
|
128
|
+
audience_restriction["saml"].Audience(single_audience)
|
126
129
|
end
|
127
130
|
end
|
128
131
|
end
|
@@ -138,7 +141,7 @@ module SAML2
|
|
138
141
|
|
139
142
|
# (see Base#build)
|
140
143
|
def build(builder)
|
141
|
-
builder[
|
144
|
+
builder["saml"].OneTimeUse
|
142
145
|
end
|
143
146
|
end
|
144
147
|
end
|
data/lib/saml2/contact.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "saml2/base"
|
4
4
|
|
5
5
|
module SAML2
|
6
6
|
class Contact < Base
|
7
7
|
module Type
|
8
|
-
ADMINISTRATIVE =
|
9
|
-
BILLING =
|
10
|
-
OTHER =
|
11
|
-
SUPPORT =
|
12
|
-
TECHNICAL =
|
8
|
+
ADMINISTRATIVE = "administrative"
|
9
|
+
BILLING = "billing"
|
10
|
+
OTHER = "other"
|
11
|
+
SUPPORT = "support"
|
12
|
+
TECHNICAL = "technical"
|
13
13
|
end
|
14
14
|
|
15
15
|
# @see Type
|
@@ -22,6 +22,7 @@ module SAML2
|
|
22
22
|
|
23
23
|
# @param type [String]
|
24
24
|
def initialize(type = Type::OTHER)
|
25
|
+
super()
|
25
26
|
@type = type
|
26
27
|
@email_addresses = []
|
27
28
|
@telephone_numbers = []
|
@@ -29,29 +30,29 @@ module SAML2
|
|
29
30
|
|
30
31
|
# (see Base#from_xml)
|
31
32
|
def from_xml(node)
|
32
|
-
self.type = node[
|
33
|
-
company = node.at_xpath(
|
33
|
+
self.type = node["contactType"]
|
34
|
+
company = node.at_xpath("md:Company", Namespaces::ALL)
|
34
35
|
self.company = company && company.content && company.content.strip
|
35
|
-
given_name = node.at_xpath(
|
36
|
+
given_name = node.at_xpath("md:GivenName", Namespaces::ALL)
|
36
37
|
self.given_name = given_name && given_name.content && given_name.content.strip
|
37
|
-
surname = node.at_xpath(
|
38
|
+
surname = node.at_xpath("md:SurName", Namespaces::ALL)
|
38
39
|
self.surname = surname && surname.content && surname.content.strip
|
39
|
-
self.email_addresses = load_string_array(node,
|
40
|
-
self.telephone_numbers = load_string_array(node,
|
40
|
+
self.email_addresses = load_string_array(node, "md:EmailAddress")
|
41
|
+
self.telephone_numbers = load_string_array(node, "md:TelephoneNumber")
|
41
42
|
self
|
42
43
|
end
|
43
44
|
|
44
45
|
# (see Base#build)
|
45
46
|
def build(builder)
|
46
|
-
builder[
|
47
|
-
contact_person[
|
48
|
-
contact_person[
|
49
|
-
contact_person[
|
47
|
+
builder["md"].ContactPerson("contactType" => type) do |contact_person|
|
48
|
+
contact_person["md"].Company(company) if company
|
49
|
+
contact_person["md"].GivenName(given_name) if given_name
|
50
|
+
contact_person["md"].SurName(surname) if surname
|
50
51
|
email_addresses.each do |email|
|
51
|
-
contact_person[
|
52
|
+
contact_person["md"].EmailAddress(email)
|
52
53
|
end
|
53
54
|
telephone_numbers.each do |tel|
|
54
|
-
contact_person[
|
55
|
+
contact_person["md"].TelephoneNumber(tel)
|
55
56
|
end
|
56
57
|
end
|
57
58
|
end
|
data/lib/saml2/endpoint.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "saml2/bindings/http_post"
|
4
4
|
|
5
5
|
module SAML2
|
6
6
|
class Endpoint < Base
|
@@ -10,25 +10,27 @@ module SAML2
|
|
10
10
|
# @param location [String]
|
11
11
|
# @param binding [String]
|
12
12
|
def initialize(location = nil, binding = Bindings::HTTP_POST::URN)
|
13
|
-
|
13
|
+
super()
|
14
|
+
@location = location
|
15
|
+
@binding = binding
|
14
16
|
end
|
15
17
|
|
16
18
|
# @param rhs [Endpoint]
|
17
19
|
# @return [Boolean]
|
18
|
-
def ==(
|
19
|
-
location ==
|
20
|
+
def ==(other)
|
21
|
+
location == other.location && binding == other.binding
|
20
22
|
end
|
21
23
|
|
22
24
|
# (see Base#from_xml)
|
23
25
|
def from_xml(node)
|
24
26
|
super
|
25
|
-
@location = node[
|
26
|
-
@binding = node[
|
27
|
+
@location = node["Location"]
|
28
|
+
@binding = node["Binding"]
|
27
29
|
end
|
28
30
|
|
29
31
|
# (see Base#build)
|
30
32
|
def build(builder, element)
|
31
|
-
builder[
|
33
|
+
builder["md"].__send__(element, "Location" => location, "Binding" => binding)
|
32
34
|
end
|
33
35
|
|
34
36
|
class Indexed < Endpoint
|
@@ -40,12 +42,13 @@ module SAML2
|
|
40
42
|
# @param binding [String]
|
41
43
|
def initialize(location = nil, index = nil, is_default = nil, binding = Bindings::HTTP_POST::URN)
|
42
44
|
super(location, binding)
|
43
|
-
@index
|
45
|
+
@index = index
|
46
|
+
@is_default = is_default
|
44
47
|
end
|
45
48
|
|
46
|
-
def eql?(
|
47
|
-
location ==
|
48
|
-
|
49
|
+
def eql?(other)
|
50
|
+
location == other.location &&
|
51
|
+
binding == other.binding && super
|
49
52
|
end
|
50
53
|
end
|
51
54
|
end
|
data/lib/saml2/entity.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "nokogiri"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
5
|
+
require "saml2/base"
|
6
|
+
require "saml2/identity_provider"
|
7
|
+
require "saml2/organization_and_contacts"
|
8
|
+
require "saml2/service_provider"
|
9
|
+
require "saml2/signable"
|
10
10
|
|
11
11
|
module SAML2
|
12
12
|
class Entity < Base
|
@@ -39,15 +39,16 @@ module SAML2
|
|
39
39
|
include Enumerable
|
40
40
|
include Signable
|
41
41
|
|
42
|
-
[
|
42
|
+
%i[each \[\]].each do |method|
|
43
43
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
44
|
-
def #{method}(*args, &block)
|
45
|
-
@entities.#{method}(*args, &block)
|
46
|
-
end
|
44
|
+
def #{method}(*args, &block) # def each(*args, &block)
|
45
|
+
@entities.#{method}(*args, &block) # @entities.each(*args, &block)
|
46
|
+
end # end
|
47
47
|
RUBY
|
48
48
|
end
|
49
49
|
|
50
50
|
def initialize
|
51
|
+
super
|
51
52
|
@entities = []
|
52
53
|
@id = "_#{SecureRandom.uuid}"
|
53
54
|
@valid_until = nil
|
@@ -58,9 +59,10 @@ module SAML2
|
|
58
59
|
super
|
59
60
|
@id = nil
|
60
61
|
remove_instance_variable(:@valid_until)
|
61
|
-
@entities = Base.load_object_array(xml,
|
62
|
-
|
63
|
-
|
62
|
+
@entities = Base.load_object_array(xml,
|
63
|
+
"md:EntityDescriptor|md:EntitiesDescriptor",
|
64
|
+
"EntityDescriptor" => Entity,
|
65
|
+
"EntitiesDescriptor" => Group)
|
64
66
|
end
|
65
67
|
|
66
68
|
# (see Message#valid_schema?)
|
@@ -70,13 +72,13 @@ module SAML2
|
|
70
72
|
|
71
73
|
# (see Message#id)
|
72
74
|
def id
|
73
|
-
@id ||= xml[
|
75
|
+
@id ||= xml["ID"]
|
74
76
|
end
|
75
77
|
|
76
78
|
# @return [Time, nil]
|
77
79
|
def valid_until
|
78
80
|
unless instance_variable_defined?(:@valid_until)
|
79
|
-
@valid_until = xml[
|
81
|
+
@valid_until = xml["validUntil"] && Time.parse(xml["validUntil"])
|
80
82
|
end
|
81
83
|
@valid_until
|
82
84
|
end
|
@@ -106,19 +108,17 @@ module SAML2
|
|
106
108
|
|
107
109
|
# @return [String]
|
108
110
|
def entity_id
|
109
|
-
@entity_id || xml && xml[
|
111
|
+
@entity_id || (xml && xml["entityID"])
|
110
112
|
end
|
111
113
|
|
112
114
|
# (see Message#id)
|
113
115
|
def id
|
114
|
-
@id ||= xml[
|
116
|
+
@id ||= xml["ID"]
|
115
117
|
end
|
116
118
|
|
117
119
|
# @return [Time, nil]
|
118
120
|
def valid_until
|
119
|
-
unless instance_variable_defined?(:@valid_until)
|
120
|
-
@valid_until = xml['validUntil'] && Time.parse(xml['validUntil'])
|
121
|
-
end
|
121
|
+
@valid_until = xml["validUntil"] && Time.parse(xml["validUntil"]) unless instance_variable_defined?(:@valid_until)
|
122
122
|
@valid_until
|
123
123
|
end
|
124
124
|
|
@@ -134,17 +134,17 @@ module SAML2
|
|
134
134
|
|
135
135
|
# @return [Array<Role>]
|
136
136
|
def roles
|
137
|
-
@roles ||= load_object_array(xml,
|
138
|
-
|
137
|
+
@roles ||= load_object_array(xml, "md:IDPSSODescriptor", IdentityProvider) +
|
138
|
+
load_object_array(xml, "md:SPSSODescriptor", ServiceProvider)
|
139
139
|
end
|
140
140
|
|
141
141
|
# (see Base#build)
|
142
142
|
def build(builder)
|
143
|
-
builder[
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
entity_descriptor.parent[
|
143
|
+
builder["md"].EntityDescriptor("entityID" => entity_id,
|
144
|
+
"xmlns:md" => Namespaces::METADATA,
|
145
|
+
"xmlns:dsig" => Namespaces::DSIG,
|
146
|
+
"xmlns:xenc" => Namespaces::XENC) do |entity_descriptor|
|
147
|
+
entity_descriptor.parent["ID"] = id if id
|
148
148
|
|
149
149
|
roles.each do |role|
|
150
150
|
role.build(entity_descriptor)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "saml2/attribute"
|
4
|
+
require "saml2/sso"
|
5
5
|
|
6
6
|
module SAML2
|
7
7
|
class IdentityProvider < SSO
|
@@ -29,39 +29,42 @@ module SAML2
|
|
29
29
|
# @return [Boolean, nil]
|
30
30
|
def want_authn_requests_signed?
|
31
31
|
unless instance_variable_defined?(:@want_authn_requests_signed)
|
32
|
-
@want_authn_requests_signed = xml[
|
32
|
+
@want_authn_requests_signed = xml["WantAuthnRequestsSigned"] && xml["WantAuthnRequestsSigned"] == "true"
|
33
33
|
end
|
34
34
|
@want_authn_requests_signed
|
35
35
|
end
|
36
36
|
|
37
37
|
# @return [Array<Endpoint>]
|
38
38
|
def single_sign_on_services
|
39
|
-
@single_sign_on_services ||= load_object_array(xml,
|
39
|
+
@single_sign_on_services ||= load_object_array(xml, "md:SingleSignOnService", Endpoint)
|
40
40
|
end
|
41
41
|
|
42
42
|
# @return [Array<String>]
|
43
43
|
def attribute_profiles
|
44
|
-
@attribute_profiles ||= load_string_array(xml,
|
44
|
+
@attribute_profiles ||= load_string_array(xml, "md:AttributeProfile")
|
45
45
|
end
|
46
46
|
|
47
47
|
# @return [Array<Attribute>]
|
48
48
|
def attributes
|
49
|
-
@attributes ||= load_object_array(xml,
|
49
|
+
@attributes ||= load_object_array(xml, "saml:Attribute", Attribute)
|
50
50
|
end
|
51
51
|
|
52
52
|
# (see Base#build)
|
53
53
|
def build(builder)
|
54
|
-
builder[
|
54
|
+
builder["md"].IDPSSODescriptor do |idp_sso_descriptor|
|
55
55
|
super(idp_sso_descriptor)
|
56
56
|
|
57
|
-
|
57
|
+
unless want_authn_requests_signed?.nil?
|
58
|
+
idp_sso_descriptor.parent["WantAuthnRequestsSigned"] =
|
59
|
+
want_authn_requests_signed?
|
60
|
+
end
|
58
61
|
|
59
62
|
single_sign_on_services.each do |sso|
|
60
|
-
sso.build(idp_sso_descriptor,
|
63
|
+
sso.build(idp_sso_descriptor, "SingleSignOnService")
|
61
64
|
end
|
62
65
|
|
63
66
|
attribute_profiles.each do |ap|
|
64
|
-
idp_sso_descriptor[
|
67
|
+
idp_sso_descriptor["md"].AttributeProfile(ap)
|
65
68
|
end
|
66
69
|
|
67
70
|
attributes.each do |attr|
|
data/lib/saml2/indexed_object.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "saml2/base"
|
4
4
|
|
5
5
|
module SAML2
|
6
6
|
module IndexedObject
|
@@ -12,9 +12,9 @@ module SAML2
|
|
12
12
|
super
|
13
13
|
end
|
14
14
|
|
15
|
-
def eql?(
|
16
|
-
index ==
|
17
|
-
default? ==
|
15
|
+
def eql?(other)
|
16
|
+
index == other.index &&
|
17
|
+
default? == other.default? &&
|
18
18
|
super
|
19
19
|
end
|
20
20
|
|
@@ -28,8 +28,8 @@ module SAML2
|
|
28
28
|
|
29
29
|
# (see Base#from_xml)
|
30
30
|
def from_xml(node)
|
31
|
-
@index = node[
|
32
|
-
@is_default = node[
|
31
|
+
@index = node["index"]&.to_i
|
32
|
+
@is_default = node["isDefault"] && node["isDefault"] == "true"
|
33
33
|
super
|
34
34
|
end
|
35
35
|
|
@@ -41,10 +41,15 @@ module SAML2
|
|
41
41
|
attr_reader :default
|
42
42
|
|
43
43
|
def self.from_xml(nodes)
|
44
|
-
new(nodes.map
|
44
|
+
new(nodes.map do |node|
|
45
|
+
name.split("::")[1..-2].inject(SAML2) do |mod, klass|
|
46
|
+
mod.const_get(klass)
|
47
|
+
end.from_xml(node)
|
48
|
+
end).freeze
|
45
49
|
end
|
46
50
|
|
47
51
|
def initialize(objects = nil)
|
52
|
+
super()
|
48
53
|
replace(objects.sort_by { |object| object.index || 0 }) if objects
|
49
54
|
re_index
|
50
55
|
end
|
@@ -72,19 +77,17 @@ module SAML2
|
|
72
77
|
last_index = object.index
|
73
78
|
@index[object.index] = object
|
74
79
|
end
|
75
|
-
@default = find
|
80
|
+
@default = find(&:default?) || first
|
76
81
|
end
|
77
82
|
end
|
78
83
|
|
79
84
|
# (see Base#build)
|
80
85
|
def build(builder, *)
|
81
86
|
super
|
82
|
-
builder.parent.children.last[
|
83
|
-
builder.parent.children.last[
|
87
|
+
builder.parent.children.last["index"] = index
|
88
|
+
builder.parent.children.last["isDefault"] = default? if default_defined?
|
84
89
|
end
|
85
90
|
|
86
|
-
private
|
87
|
-
|
88
91
|
def self.included(klass)
|
89
92
|
klass.const_set(:Array, Array.dup)
|
90
93
|
end
|
data/lib/saml2/key.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "openssl"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
5
|
+
require "saml2/base"
|
6
|
+
require "saml2/namespaces"
|
7
7
|
|
8
8
|
module SAML2
|
9
9
|
# This represents the XML Signatures <KeyInfo> element, and actually contains a
|
@@ -16,20 +16,21 @@ module SAML2
|
|
16
16
|
|
17
17
|
# @param x509 [String] The PEM encoded certificate.
|
18
18
|
def initialize(x509 = nil)
|
19
|
+
super()
|
19
20
|
self.x509 = x509
|
20
21
|
end
|
21
22
|
|
22
23
|
# (see Base#from_xml)
|
23
24
|
def from_xml(node)
|
24
|
-
self.x509 = node.at_xpath(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
25
|
+
self.x509 = node.at_xpath("dsig:X509Data/dsig:X509Certificate", Namespaces::ALL)&.content&.strip
|
26
|
+
return unless (rsa_key_value = node.at_xpath("dsig:KeyValue/dsig:RSAKeyValue", Namespaces::ALL))
|
27
|
+
|
28
|
+
modulus = crypto_binary_to_integer(rsa_key_value.at_xpath("dsig:Modulus", Namespaces::ALL)&.content&.strip)
|
29
|
+
exponent = crypto_binary_to_integer(rsa_key_value.at_xpath("dsig:Exponent", Namespaces::ALL)&.content&.strip)
|
30
|
+
return unless modulus && exponent
|
31
|
+
|
32
|
+
@key = OpenSSL::PKey::RSA.new
|
33
|
+
key.set_key(modulus, exponent, nil)
|
33
34
|
end
|
34
35
|
|
35
36
|
def x509=(value)
|
@@ -39,6 +40,7 @@ module SAML2
|
|
39
40
|
# @return [OpenSSL::X509::Certificate]
|
40
41
|
def certificate
|
41
42
|
return nil if x509.nil?
|
43
|
+
|
42
44
|
@certificate ||= OpenSSL::X509::Certificate.new(Base64.decode64(x509))
|
43
45
|
end
|
44
46
|
|
@@ -52,28 +54,29 @@ module SAML2
|
|
52
54
|
# @param fingerprint [String]
|
53
55
|
# @return [String]
|
54
56
|
def self.format_fingerprint(fingerprint)
|
55
|
-
fingerprint.downcase.gsub(/[^0-9a-f]/,
|
57
|
+
fingerprint.downcase.gsub(/[^0-9a-f]/, "").gsub(/(\h{2})(?=\h)/, '\1:')
|
56
58
|
end
|
57
59
|
|
58
60
|
# @return [String]
|
59
61
|
def fingerprint
|
60
62
|
return nil unless certificate
|
63
|
+
|
61
64
|
@fingerprint ||= self.class.format_fingerprint(Digest::SHA1.hexdigest(certificate.to_der))
|
62
65
|
end
|
63
66
|
|
64
67
|
# (see Base#build)
|
65
68
|
def build(builder)
|
66
|
-
builder[
|
69
|
+
builder["dsig"].KeyInfo do |key_info|
|
67
70
|
if x509
|
68
|
-
key_info[
|
69
|
-
x509_data[
|
71
|
+
key_info["dsig"].X509Data do |x509_data|
|
72
|
+
x509_data["dsig"].X509Certificate(x509)
|
70
73
|
end
|
71
74
|
end
|
72
75
|
if key.is_a?(OpenSSL::PKey::RSA)
|
73
|
-
key_info[
|
74
|
-
key_value[
|
75
|
-
rsa_key_value[
|
76
|
-
rsa_key_value[
|
76
|
+
key_info["dsig"].KeyValue do |key_value|
|
77
|
+
key_value["dsig"].RSAKeyValue do |rsa_key_value|
|
78
|
+
rsa_key_value["dsig"].Modulus(Base64.encode64(key.n.to_s(2)))
|
79
|
+
rsa_key_value["dsig"].Exponent(Base64.encode64(key.e.to_s(2)))
|
77
80
|
end
|
78
81
|
end
|
79
82
|
end
|
@@ -84,19 +87,20 @@ module SAML2
|
|
84
87
|
|
85
88
|
def crypto_binary_to_integer(str)
|
86
89
|
return nil unless str
|
90
|
+
|
87
91
|
OpenSSL::BN.new(Base64.decode64(str), 2)
|
88
92
|
end
|
89
93
|
end
|
90
94
|
|
91
95
|
class KeyDescriptor < KeyInfo
|
92
96
|
module Type
|
93
|
-
ENCRYPTION =
|
94
|
-
SIGNING =
|
97
|
+
ENCRYPTION = "encryption"
|
98
|
+
SIGNING = "signing"
|
95
99
|
end
|
96
100
|
|
97
101
|
class EncryptionMethod < Base
|
98
102
|
module Algorithm
|
99
|
-
AES128_CBC =
|
103
|
+
AES128_CBC = "http://www.w3.org/2001/04/xmlenc#aes128-cbc"
|
100
104
|
end
|
101
105
|
|
102
106
|
# @see Algorithm
|
@@ -108,19 +112,21 @@ module SAML2
|
|
108
112
|
# @param algorithm [String]
|
109
113
|
# @param key_size [Integer]
|
110
114
|
def initialize(algorithm = Algorithm::AES128_CBC, key_size = 128)
|
111
|
-
|
115
|
+
super()
|
116
|
+
@algorithm = algorithm
|
117
|
+
@key_size = key_size
|
112
118
|
end
|
113
119
|
|
114
120
|
# (see Base#from_xml)
|
115
121
|
def from_xml(node)
|
116
|
-
self.algorithm = node[
|
117
|
-
self.key_size = node.at_xpath(
|
122
|
+
self.algorithm = node["Algorithm"]
|
123
|
+
self.key_size = node.at_xpath("xenc:KeySize", Namespaces::ALL)&.content&.to_i
|
118
124
|
end
|
119
125
|
|
120
126
|
# (see Base#build)
|
121
127
|
def build(builder)
|
122
|
-
builder[
|
123
|
-
encryption_method[
|
128
|
+
builder["md"].EncryptionMethod("Algorithm" => algorithm) do |encryption_method|
|
129
|
+
encryption_method["xenc"].KeySize(key_size) if key_size
|
124
130
|
end
|
125
131
|
end
|
126
132
|
end
|
@@ -133,16 +139,19 @@ module SAML2
|
|
133
139
|
|
134
140
|
# (see Base#from_xml)
|
135
141
|
def from_xml(node)
|
136
|
-
super(node.at_xpath(
|
137
|
-
self.use = node[
|
138
|
-
self.encryption_methods = load_object_array(node,
|
142
|
+
super(node.at_xpath("dsig:KeyInfo", Namespaces::ALL))
|
143
|
+
self.use = node["use"]
|
144
|
+
self.encryption_methods = load_object_array(node, "md:EncryptionMethod", EncryptionMethod)
|
139
145
|
end
|
140
146
|
|
141
147
|
# @param x509 [String] The PEM encoded certificate.
|
142
148
|
# @param use optional [String] See {Type}
|
143
149
|
# @param encryption_methods [Array<EncryptionMethod>]
|
144
150
|
def initialize(x509 = nil, use = nil, encryption_methods = [])
|
145
|
-
|
151
|
+
super()
|
152
|
+
@use = use
|
153
|
+
self.x509 = x509
|
154
|
+
@encryption_methods = encryption_methods
|
146
155
|
end
|
147
156
|
|
148
157
|
def encryption?
|
@@ -155,8 +164,8 @@ module SAML2
|
|
155
164
|
|
156
165
|
# (see Base#build)
|
157
166
|
def build(builder)
|
158
|
-
builder[
|
159
|
-
key_descriptor.parent[
|
167
|
+
builder["md"].KeyDescriptor do |key_descriptor|
|
168
|
+
key_descriptor.parent["use"] = use if use
|
160
169
|
super(key_descriptor)
|
161
170
|
encryption_methods.each do |method|
|
162
171
|
method.build(key_descriptor)
|