saml-kit 0.2.1 → 0.2.2
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 +5 -5
- data/README.md +1 -1
- data/exe/saml-kit-create-self-signed-certificate +21 -0
- data/lib/saml/kit.rb +9 -1
- data/lib/saml/kit/authentication_request.rb +9 -47
- data/lib/saml/kit/bindings/http_post.rb +0 -1
- data/lib/saml/kit/buildable.rb +21 -0
- data/lib/saml/kit/builders.rb +13 -0
- data/lib/saml/kit/builders/authentication_request.rb +48 -0
- data/lib/saml/kit/builders/identity_provider_metadata.rb +103 -0
- data/lib/saml/kit/builders/logout_request.rb +55 -0
- data/lib/saml/kit/builders/logout_response.rb +50 -0
- data/lib/saml/kit/builders/response.rb +182 -0
- data/lib/saml/kit/builders/service_provider_metadata.rb +89 -0
- data/lib/saml/kit/document.rb +16 -0
- data/lib/saml/kit/id.rb +9 -0
- data/lib/saml/kit/identity_provider_metadata.rb +11 -96
- data/lib/saml/kit/logout_request.rb +7 -53
- data/lib/saml/kit/logout_response.rb +1 -50
- data/lib/saml/kit/metadata.rb +9 -0
- data/lib/saml/kit/response.rb +11 -181
- data/lib/saml/kit/self_signed_certificate.rb +12 -5
- data/lib/saml/kit/service_provider_metadata.rb +5 -82
- data/lib/saml/kit/signature.rb +1 -1
- data/lib/saml/kit/trustable.rb +1 -5
- data/lib/saml/kit/version.rb +1 -1
- data/lib/saml/kit/{cryptography.rb → xml_decryption.rb} +1 -1
- data/saml-kit.gemspec +2 -2
- metadata +32 -21
@@ -8,56 +8,7 @@ module Saml
|
|
8
8
|
super(xml, name: "LogoutResponse")
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
class Builder
|
14
|
-
attr_accessor :id, :issuer, :version, :status_code, :sign, :now, :destination
|
15
|
-
attr_reader :request
|
16
|
-
|
17
|
-
def initialize(user, request, configuration: Saml::Kit.configuration, sign: true)
|
18
|
-
@user = user
|
19
|
-
@now = Time.now.utc
|
20
|
-
@request = request
|
21
|
-
@id = SecureRandom.uuid
|
22
|
-
@version = "2.0"
|
23
|
-
@status_code = Namespaces::SUCCESS
|
24
|
-
@sign = sign
|
25
|
-
@issuer = configuration.issuer
|
26
|
-
provider = configuration.registry.metadata_for(@issuer)
|
27
|
-
if provider
|
28
|
-
@destination = provider.single_logout_service_for(binding: :http_post).try(:location)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def to_xml
|
33
|
-
Signature.sign(sign: sign) do |xml, signature|
|
34
|
-
xml.LogoutResponse logout_response_options do
|
35
|
-
xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
|
36
|
-
signature.template(id)
|
37
|
-
xml.Status do
|
38
|
-
xml.StatusCode Value: status_code
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def build
|
45
|
-
LogoutResponse.new(to_xml, request_id: request.id)
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def logout_response_options
|
51
|
-
{
|
52
|
-
xmlns: Namespaces::PROTOCOL,
|
53
|
-
ID: "_#{id}",
|
54
|
-
Version: version,
|
55
|
-
IssueInstant: now.utc.iso8601,
|
56
|
-
Destination: destination,
|
57
|
-
InResponseTo: request.id,
|
58
|
-
}
|
59
|
-
end
|
60
|
-
end
|
11
|
+
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::LogoutResponse::Builder', 'Saml::Kit::Builders::LogoutResponse')
|
61
12
|
end
|
62
13
|
end
|
63
14
|
end
|
data/lib/saml/kit/metadata.rb
CHANGED
@@ -3,6 +3,7 @@ module Saml
|
|
3
3
|
class Metadata
|
4
4
|
include ActiveModel::Validations
|
5
5
|
include XsdValidatable
|
6
|
+
include Buildable
|
6
7
|
METADATA_XSD = File.expand_path("./xsd/saml-schema-metadata-2.0.xsd", File.dirname(__FILE__)).freeze
|
7
8
|
|
8
9
|
validates_presence_of :metadata
|
@@ -63,6 +64,14 @@ module Saml
|
|
63
64
|
service_for(binding: binding, type: 'SingleLogoutService')
|
64
65
|
end
|
65
66
|
|
67
|
+
def logout_request_for(user, binding: :http_post, relay_state: nil)
|
68
|
+
builder = Saml::Kit::LogoutRequest.builder(user) do |x|
|
69
|
+
yield x if block_given?
|
70
|
+
end
|
71
|
+
request_binding = single_logout_service_for(binding: binding)
|
72
|
+
request_binding.serialize(builder, relay_state: relay_state)
|
73
|
+
end
|
74
|
+
|
66
75
|
def matches?(fingerprint, use: :signing)
|
67
76
|
certificates.find do |certificate|
|
68
77
|
certificate.for?(use) && certificate.fingerprint == fingerprint
|
data/lib/saml/kit/response.rb
CHANGED
@@ -20,11 +20,16 @@ module Saml
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def attributes
|
23
|
-
@attributes ||=
|
24
|
-
|
25
|
-
|
23
|
+
@attributes ||=
|
24
|
+
begin
|
25
|
+
attrs = assertion.fetch('AttributeStatement', {}).fetch('Attribute', [])
|
26
|
+
items = if attrs.is_a? Hash
|
27
|
+
[[attrs["Name"], attrs["AttributeValue"]]]
|
28
|
+
else
|
29
|
+
attrs.map { |item| [item['Name'], item['AttributeValue']] }
|
30
|
+
end
|
31
|
+
Hash[items].with_indifferent_access
|
26
32
|
end
|
27
|
-
].with_indifferent_access
|
28
33
|
end
|
29
34
|
|
30
35
|
def started_at
|
@@ -51,7 +56,7 @@ module Saml
|
|
51
56
|
@assertion =
|
52
57
|
begin
|
53
58
|
if encrypted?
|
54
|
-
decrypted =
|
59
|
+
decrypted = XmlDecryption.new.decrypt(to_h.fetch(name, {}).fetch('EncryptedAssertion', {}))
|
55
60
|
Saml::Kit.logger.debug(decrypted)
|
56
61
|
Hash.from_xml(decrypted)['Assertion']
|
57
62
|
else
|
@@ -99,182 +104,7 @@ module Saml
|
|
99
104
|
Time.at(0).to_datetime
|
100
105
|
end
|
101
106
|
|
102
|
-
|
103
|
-
attr_reader :user, :request
|
104
|
-
attr_accessor :id, :reference_id, :now
|
105
|
-
attr_accessor :version, :status_code
|
106
|
-
attr_accessor :issuer, :sign, :destination, :encrypt
|
107
|
-
|
108
|
-
def initialize(user, request)
|
109
|
-
@user = user
|
110
|
-
@request = request
|
111
|
-
@id = SecureRandom.uuid
|
112
|
-
@reference_id = SecureRandom.uuid
|
113
|
-
@now = Time.now.utc
|
114
|
-
@version = "2.0"
|
115
|
-
@status_code = Namespaces::SUCCESS
|
116
|
-
@issuer = configuration.issuer
|
117
|
-
@destination = destination_for(request)
|
118
|
-
@sign = want_assertions_signed
|
119
|
-
@encrypt = false
|
120
|
-
end
|
121
|
-
|
122
|
-
def want_assertions_signed
|
123
|
-
request.provider.want_assertions_signed
|
124
|
-
rescue => error
|
125
|
-
Saml::Kit.logger.error(error)
|
126
|
-
true
|
127
|
-
end
|
128
|
-
|
129
|
-
def to_xml
|
130
|
-
Signature.sign(sign: sign) do |xml, signature|
|
131
|
-
xml.Response response_options do
|
132
|
-
xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
|
133
|
-
signature.template(id)
|
134
|
-
xml.Status do
|
135
|
-
xml.StatusCode Value: status_code
|
136
|
-
end
|
137
|
-
assertion(xml, signature)
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def build
|
143
|
-
Response.new(to_xml, request_id: request.id)
|
144
|
-
end
|
145
|
-
|
146
|
-
private
|
147
|
-
|
148
|
-
def assertion(xml, signature)
|
149
|
-
with_encryption(xml) do |xml|
|
150
|
-
xml.Assertion(assertion_options) do
|
151
|
-
xml.Issuer issuer
|
152
|
-
signature.template(reference_id) unless encrypt
|
153
|
-
xml.Subject do
|
154
|
-
xml.NameID user.name_id_for(request.name_id_format), Format: request.name_id_format
|
155
|
-
xml.SubjectConfirmation Method: Namespaces::BEARER do
|
156
|
-
xml.SubjectConfirmationData "", subject_confirmation_data_options
|
157
|
-
end
|
158
|
-
end
|
159
|
-
xml.Conditions conditions_options do
|
160
|
-
xml.AudienceRestriction do
|
161
|
-
xml.Audience request.issuer
|
162
|
-
end
|
163
|
-
end
|
164
|
-
xml.AuthnStatement authn_statement_options do
|
165
|
-
xml.AuthnContext do
|
166
|
-
xml.AuthnContextClassRef Namespaces::PASSWORD
|
167
|
-
end
|
168
|
-
end
|
169
|
-
assertion_attributes = user.assertion_attributes_for(request)
|
170
|
-
if assertion_attributes.any?
|
171
|
-
xml.AttributeStatement do
|
172
|
-
assertion_attributes.each do |key, value|
|
173
|
-
xml.Attribute Name: key, NameFormat: Namespaces::URI, FriendlyName: key do
|
174
|
-
xml.AttributeValue value.to_s
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
def with_encryption(xml)
|
184
|
-
if encrypt
|
185
|
-
temp = ::Builder::XmlMarkup.new
|
186
|
-
yield temp
|
187
|
-
raw_xml_to_encrypt = temp.target!
|
188
|
-
|
189
|
-
encryption_certificate = request.provider.encryption_certificates.first
|
190
|
-
public_key = encryption_certificate.public_key
|
191
|
-
|
192
|
-
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
193
|
-
cipher.encrypt
|
194
|
-
key = cipher.random_key
|
195
|
-
iv = cipher.random_iv
|
196
|
-
encrypted = cipher.update(raw_xml_to_encrypt) + cipher.final
|
197
|
-
|
198
|
-
Saml::Kit.logger.debug ['+iv', iv].inspect
|
199
|
-
Saml::Kit.logger.debug ['+key', key].inspect
|
200
|
-
|
201
|
-
xml.EncryptedAssertion xmlns: Namespaces::ASSERTION do
|
202
|
-
xml.EncryptedData xmlns: Namespaces::XMLENC do
|
203
|
-
xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
|
204
|
-
xml.KeyInfo xmlns: Namespaces::XMLDSIG do
|
205
|
-
xml.EncryptedKey xmlns: Namespaces::XMLENC do
|
206
|
-
xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
|
207
|
-
xml.CipherData do
|
208
|
-
xml.CipherValue Base64.encode64(public_key.public_encrypt(key))
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
xml.CipherData do
|
213
|
-
xml.CipherValue Base64.encode64(iv + encrypted)
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
|
-
else
|
218
|
-
yield xml
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
def destination_for(request)
|
223
|
-
if request.signed? && request.trusted?
|
224
|
-
request.acs_url || request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
|
225
|
-
else
|
226
|
-
request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def configuration
|
231
|
-
Saml::Kit.configuration
|
232
|
-
end
|
233
|
-
|
234
|
-
def response_options
|
235
|
-
{
|
236
|
-
ID: id.present? ? "_#{id}" : nil,
|
237
|
-
Version: version,
|
238
|
-
IssueInstant: now.iso8601,
|
239
|
-
Destination: destination,
|
240
|
-
Consent: Namespaces::UNSPECIFIED,
|
241
|
-
InResponseTo: request.id,
|
242
|
-
xmlns: Namespaces::PROTOCOL,
|
243
|
-
}
|
244
|
-
end
|
245
|
-
|
246
|
-
def assertion_options
|
247
|
-
{
|
248
|
-
ID: "_#{reference_id}",
|
249
|
-
IssueInstant: now.iso8601,
|
250
|
-
Version: "2.0",
|
251
|
-
xmlns: Namespaces::ASSERTION,
|
252
|
-
}
|
253
|
-
end
|
254
|
-
|
255
|
-
def subject_confirmation_data_options
|
256
|
-
{
|
257
|
-
InResponseTo: request.id,
|
258
|
-
NotOnOrAfter: 3.hours.since(now).utc.iso8601,
|
259
|
-
Recipient: request.acs_url,
|
260
|
-
}
|
261
|
-
end
|
262
|
-
|
263
|
-
def conditions_options
|
264
|
-
{
|
265
|
-
NotBefore: now.utc.iso8601,
|
266
|
-
NotOnOrAfter: Saml::Kit.configuration.session_timeout.from_now.utc.iso8601,
|
267
|
-
}
|
268
|
-
end
|
269
|
-
|
270
|
-
def authn_statement_options
|
271
|
-
{
|
272
|
-
AuthnInstant: now.iso8601,
|
273
|
-
SessionIndex: assertion_options[:ID],
|
274
|
-
SessionNotOnOrAfter: 3.hours.since(now).utc.iso8601,
|
275
|
-
}
|
276
|
-
end
|
277
|
-
end
|
107
|
+
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::Response::Builder', 'Saml::Kit::Builders::Response')
|
278
108
|
end
|
279
109
|
end
|
280
110
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
3
|
class SelfSignedCertificate
|
4
|
+
SUBJECT="/C=CA/ST=Alberta/L=Calgary/O=SamlKit/OU=SamlKit/CN=SamlKit"
|
5
|
+
|
4
6
|
def initialize(password)
|
5
7
|
@password = password
|
6
8
|
end
|
@@ -9,20 +11,25 @@ module Saml
|
|
9
11
|
rsa_key = OpenSSL::PKey::RSA.new(2048)
|
10
12
|
public_key = rsa_key.public_key
|
11
13
|
certificate = OpenSSL::X509::Certificate.new
|
12
|
-
certificate.subject = certificate.issuer = OpenSSL::X509::Name.parse(
|
14
|
+
certificate.subject = certificate.issuer = OpenSSL::X509::Name.parse(SUBJECT)
|
13
15
|
certificate.not_before = DateTime.now.beginning_of_day
|
14
|
-
certificate.not_after =
|
16
|
+
certificate.not_after = 30.days.from_now
|
15
17
|
certificate.public_key = public_key
|
16
18
|
certificate.serial = 0x0
|
17
19
|
certificate.version = 2
|
18
20
|
factory = OpenSSL::X509::ExtensionFactory.new
|
19
21
|
factory.subject_certificate = factory.issuer_certificate = certificate
|
20
|
-
certificate.extensions = [
|
21
|
-
|
22
|
+
certificate.extensions = [
|
23
|
+
factory.create_extension("basicConstraints","CA:TRUE", true),
|
24
|
+
factory.create_extension("subjectKeyIdentifier", "hash"),
|
25
|
+
]
|
26
|
+
certificate.add_extension(
|
27
|
+
factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
|
28
|
+
)
|
22
29
|
certificate.sign(rsa_key, OpenSSL::Digest::SHA256.new)
|
23
30
|
[
|
24
31
|
certificate.to_pem,
|
25
|
-
rsa_key.to_pem(OpenSSL::Cipher
|
32
|
+
rsa_key.to_pem(OpenSSL::Cipher.new('AES-256-CBC'), @password)
|
26
33
|
]
|
27
34
|
end
|
28
35
|
end
|
@@ -15,92 +15,15 @@ module Saml
|
|
15
15
|
|
16
16
|
def want_assertions_signed
|
17
17
|
attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute("WantAssertionsSigned")
|
18
|
+
return true if attribute.nil?
|
18
19
|
attribute.text.downcase == "true"
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
attr_accessor :want_assertions_signed
|
24
|
-
|
25
|
-
def initialize(configuration = Saml::Kit.configuration)
|
26
|
-
@id = SecureRandom.uuid
|
27
|
-
@configuration = configuration
|
28
|
-
@entity_id = configuration.issuer
|
29
|
-
@acs_urls = []
|
30
|
-
@logout_urls = []
|
31
|
-
@name_id_formats = [Namespaces::PERSISTENT]
|
32
|
-
@sign = true
|
33
|
-
@want_assertions_signed = true
|
34
|
-
end
|
35
|
-
|
36
|
-
def add_assertion_consumer_service(url, binding: :http_post)
|
37
|
-
@acs_urls.push(location: url, binding: Bindings.binding_for(binding))
|
38
|
-
end
|
39
|
-
|
40
|
-
def add_single_logout_service(url, binding: :http_post)
|
41
|
-
@logout_urls.push(location: url, binding: Bindings.binding_for(binding))
|
42
|
-
end
|
43
|
-
|
44
|
-
def to_xml
|
45
|
-
Signature.sign(sign: sign) do |xml, signature|
|
46
|
-
xml.instruct!
|
47
|
-
xml.EntityDescriptor entity_descriptor_options do
|
48
|
-
signature.template(id)
|
49
|
-
xml.SPSSODescriptor descriptor_options do
|
50
|
-
if @configuration.signing_certificate_pem.present?
|
51
|
-
xml.KeyDescriptor use: "signing" do
|
52
|
-
xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
|
53
|
-
xml.X509Data do
|
54
|
-
xml.X509Certificate @configuration.stripped_signing_certificate
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
if @configuration.encryption_certificate_pem.present?
|
60
|
-
xml.KeyDescriptor use: "encryption" do
|
61
|
-
xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
|
62
|
-
xml.X509Data do
|
63
|
-
xml.X509Certificate @configuration.stripped_encryption_certificate
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
logout_urls.each do |item|
|
69
|
-
xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
|
70
|
-
end
|
71
|
-
name_id_formats.each do |format|
|
72
|
-
xml.NameIDFormat format
|
73
|
-
end
|
74
|
-
acs_urls.each_with_index do |item, index|
|
75
|
-
xml.AssertionConsumerService Binding: item[:binding], Location: item[:location], index: index, isDefault: index == 0 ? true : false
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def build
|
83
|
-
ServiceProviderMetadata.new(to_xml)
|
84
|
-
end
|
85
|
-
|
86
|
-
private
|
87
|
-
|
88
|
-
def entity_descriptor_options
|
89
|
-
{
|
90
|
-
'xmlns': Namespaces::METADATA,
|
91
|
-
ID: "_#{id}",
|
92
|
-
entityID: entity_id,
|
93
|
-
}
|
94
|
-
end
|
95
|
-
|
96
|
-
def descriptor_options
|
97
|
-
{
|
98
|
-
AuthnRequestsSigned: sign,
|
99
|
-
WantAssertionsSigned: want_assertions_signed,
|
100
|
-
protocolSupportEnumeration: Namespaces::PROTOCOL,
|
101
|
-
}
|
102
|
-
end
|
22
|
+
def self.builder_class
|
23
|
+
Saml::Kit::Builders::ServiceProviderMetadata
|
103
24
|
end
|
25
|
+
|
26
|
+
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::ServiceProviderMetadata::Builder', 'Saml::Kit::Builders::ServiceProviderMetadata')
|
104
27
|
end
|
105
28
|
end
|
106
29
|
end
|
data/lib/saml/kit/signature.rb
CHANGED
@@ -34,7 +34,7 @@ module Saml
|
|
34
34
|
xml.SignedInfo do
|
35
35
|
xml.CanonicalizationMethod Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#"
|
36
36
|
xml.SignatureMethod Algorithm: SIGNATURE_METHODS[configuration.signature_method]
|
37
|
-
xml.Reference URI: "
|
37
|
+
xml.Reference URI: "##{reference_id}" do
|
38
38
|
xml.Transforms do
|
39
39
|
xml.Transform Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
40
40
|
xml.Transform Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#"
|
data/lib/saml/kit/trustable.rb
CHANGED