saml-kit 0.1.0 → 0.2.0
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/exe/saml-kit-decode-http-post +8 -0
- data/lib/saml/kit.rb +4 -4
- data/lib/saml/kit/authentication_request.rb +3 -13
- data/lib/saml/kit/bindings.rb +45 -0
- data/lib/saml/kit/bindings/binding.rb +42 -0
- data/lib/saml/kit/bindings/http_post.rb +29 -0
- data/lib/saml/kit/bindings/http_redirect.rb +61 -0
- data/lib/saml/kit/bindings/url_builder.rb +40 -0
- data/lib/saml/kit/configuration.rb +22 -1
- data/lib/saml/kit/crypto.rb +16 -0
- data/lib/saml/kit/crypto/oaep_cipher.rb +22 -0
- data/lib/saml/kit/crypto/rsa_cipher.rb +23 -0
- data/lib/saml/kit/crypto/simple_cipher.rb +38 -0
- data/lib/saml/kit/crypto/unknown_cipher.rb +18 -0
- data/lib/saml/kit/cryptography.rb +30 -0
- data/lib/saml/kit/default_registry.rb +1 -0
- data/lib/saml/kit/document.rb +6 -2
- data/lib/saml/kit/identity_provider_metadata.rb +9 -9
- data/lib/saml/kit/locales/en.yml +4 -3
- data/lib/saml/kit/logout_request.rb +2 -2
- data/lib/saml/kit/logout_response.rb +3 -3
- data/lib/saml/kit/metadata.rb +12 -37
- data/lib/saml/kit/namespaces.rb +1 -13
- data/lib/saml/kit/respondable.rb +4 -0
- data/lib/saml/kit/response.rb +120 -37
- data/lib/saml/kit/service_provider_metadata.rb +16 -7
- data/lib/saml/kit/signature.rb +16 -13
- data/lib/saml/kit/trustable.rb +14 -6
- data/lib/saml/kit/version.rb +1 -1
- data/lib/saml/kit/xml.rb +19 -3
- data/saml-kit.gemspec +2 -2
- metadata +23 -14
- data/lib/saml/kit/binding.rb +0 -40
- data/lib/saml/kit/http_post_binding.rb +0 -27
- data/lib/saml/kit/http_redirect_binding.rb +0 -58
- data/lib/saml/kit/url_builder.rb +0 -38
@@ -14,7 +14,7 @@ module Saml
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def want_assertions_signed
|
17
|
-
attribute = find_by("/md:EntityDescriptor/md:#{name}").attribute("WantAssertionsSigned")
|
17
|
+
attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute("WantAssertionsSigned")
|
18
18
|
attribute.text.downcase == "true"
|
19
19
|
end
|
20
20
|
|
@@ -35,19 +35,19 @@ module Saml
|
|
35
35
|
@want_assertions_signed = true
|
36
36
|
end
|
37
37
|
|
38
|
-
def add_assertion_consumer_service(url, binding: :
|
39
|
-
@acs_urls.push(location: url, binding:
|
38
|
+
def add_assertion_consumer_service(url, binding: :http_post)
|
39
|
+
@acs_urls.push(location: url, binding: Bindings.binding_for(binding))
|
40
40
|
end
|
41
41
|
|
42
|
-
def add_single_logout_service(url, binding: :
|
43
|
-
@logout_urls.push(location: url, binding:
|
42
|
+
def add_single_logout_service(url, binding: :http_post)
|
43
|
+
@logout_urls.push(location: url, binding: Bindings.binding_for(binding))
|
44
44
|
end
|
45
45
|
|
46
46
|
def to_xml
|
47
|
-
Signature.sign(
|
47
|
+
Signature.sign(sign: sign) do |xml, signature|
|
48
48
|
xml.instruct!
|
49
49
|
xml.EntityDescriptor entity_descriptor_options do
|
50
|
-
signature.template(
|
50
|
+
signature.template(id)
|
51
51
|
xml.SPSSODescriptor descriptor_options do
|
52
52
|
if @configuration.signing_certificate_pem.present?
|
53
53
|
xml.KeyDescriptor use: "signing" do
|
@@ -58,6 +58,15 @@ module Saml
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
61
|
+
if @configuration.encryption_certificate_pem.present?
|
62
|
+
xml.KeyDescriptor use: "encryption" do
|
63
|
+
xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
|
64
|
+
xml.X509Data do
|
65
|
+
xml.X509Certificate @configuration.stripped_encryption_certificate
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
61
70
|
logout_urls.each do |item|
|
62
71
|
xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
|
63
72
|
end
|
data/lib/saml/kit/signature.rb
CHANGED
@@ -16,17 +16,19 @@ module Saml
|
|
16
16
|
SHA512: "http://www.w3.org/2001/04/xmlenc#sha512",
|
17
17
|
}.freeze
|
18
18
|
|
19
|
-
attr_reader :configuration, :
|
19
|
+
attr_reader :configuration, :sign, :xml
|
20
20
|
|
21
|
-
def initialize(
|
22
|
-
@
|
21
|
+
def initialize(xml, configuration:, sign: true)
|
22
|
+
@xml = xml
|
23
23
|
@configuration = configuration
|
24
24
|
@sign = sign
|
25
|
+
@reference_ids = []
|
25
26
|
end
|
26
27
|
|
27
|
-
def template(
|
28
|
+
def template(reference_id)
|
28
29
|
return unless sign
|
29
30
|
return if reference_id.blank?
|
31
|
+
@reference_ids << reference_id
|
30
32
|
|
31
33
|
xml.Signature "xmlns" => Namespaces::XMLDSIG do
|
32
34
|
xml.SignedInfo do
|
@@ -50,19 +52,20 @@ module Saml
|
|
50
52
|
end
|
51
53
|
end
|
52
54
|
|
53
|
-
def finalize
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
def finalize
|
56
|
+
return xml.target! unless sign
|
57
|
+
|
58
|
+
raw_xml = xml.target!
|
59
|
+
@reference_ids.each do |reference_id|
|
60
|
+
raw_xml = Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
|
59
61
|
end
|
62
|
+
raw_xml
|
60
63
|
end
|
61
64
|
|
62
|
-
def self.sign(
|
63
|
-
signature = new(
|
65
|
+
def self.sign(sign: true, xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
|
66
|
+
signature = new(xml, sign: sign, configuration: configuration)
|
64
67
|
yield xml, signature
|
65
|
-
signature.finalize
|
68
|
+
signature.finalize
|
66
69
|
end
|
67
70
|
|
68
71
|
private
|
data/lib/saml/kit/trustable.rb
CHANGED
@@ -4,8 +4,9 @@ module Saml
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
included do
|
7
|
-
validate :must_have_valid_signature
|
7
|
+
validate :must_have_valid_signature, unless: :signature_manually_verified
|
8
8
|
validate :must_be_registered
|
9
|
+
validate :must_be_trusted, unless: :signature_manually_verified
|
9
10
|
end
|
10
11
|
|
11
12
|
def certificate
|
@@ -19,7 +20,7 @@ module Saml
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def signed?
|
22
|
-
to_h
|
23
|
+
to_h.fetch(name, {}).fetch('Signature', nil).present?
|
23
24
|
end
|
24
25
|
|
25
26
|
def trusted?
|
@@ -36,8 +37,14 @@ module Saml
|
|
36
37
|
Saml::Kit.configuration.registry
|
37
38
|
end
|
38
39
|
|
40
|
+
def signature_verified!
|
41
|
+
@signature_manually_verified = true
|
42
|
+
end
|
43
|
+
|
39
44
|
private
|
40
45
|
|
46
|
+
attr_reader :signature_manually_verified
|
47
|
+
|
41
48
|
def must_have_valid_signature
|
42
49
|
return if to_xml.blank?
|
43
50
|
|
@@ -50,10 +57,11 @@ module Saml
|
|
50
57
|
|
51
58
|
def must_be_registered
|
52
59
|
return unless expected_type?
|
53
|
-
if provider.
|
54
|
-
|
55
|
-
|
56
|
-
|
60
|
+
return if provider.present?
|
61
|
+
errors[:provider] << error_message(:unregistered)
|
62
|
+
end
|
63
|
+
|
64
|
+
def must_be_trusted
|
57
65
|
return if trusted?
|
58
66
|
errors[:fingerprint] << error_message(:invalid_fingerprint)
|
59
67
|
end
|
data/lib/saml/kit/version.rb
CHANGED
data/lib/saml/kit/xml.rb
CHANGED
@@ -2,6 +2,12 @@ module Saml
|
|
2
2
|
module Kit
|
3
3
|
class Xml
|
4
4
|
include ActiveModel::Validations
|
5
|
+
NAMESPACES = {
|
6
|
+
"NameFormat": Namespaces::ATTR_SPLAT,
|
7
|
+
"ds": Namespaces::XMLDSIG,
|
8
|
+
"md": Namespaces::METADATA,
|
9
|
+
"saml": Namespaces::ASSERTION,
|
10
|
+
}.freeze
|
5
11
|
|
6
12
|
attr_reader :raw_xml, :document
|
7
13
|
|
@@ -10,9 +16,7 @@ module Saml
|
|
10
16
|
|
11
17
|
def initialize(raw_xml)
|
12
18
|
@raw_xml = raw_xml
|
13
|
-
@document = Nokogiri::XML(raw_xml
|
14
|
-
config.noblanks
|
15
|
-
end
|
19
|
+
@document = Nokogiri::XML(raw_xml)
|
16
20
|
end
|
17
21
|
|
18
22
|
def x509_certificates
|
@@ -22,6 +26,18 @@ module Saml
|
|
22
26
|
end
|
23
27
|
end
|
24
28
|
|
29
|
+
def find_by(xpath)
|
30
|
+
document.at_xpath(xpath, NAMESPACES)
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_all(xpath)
|
34
|
+
document.search(xpath, NAMESPACES)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_xml(pretty: true)
|
38
|
+
pretty ? document.to_xml(indent: 2) : raw_xml
|
39
|
+
end
|
40
|
+
|
25
41
|
private
|
26
42
|
|
27
43
|
def validate_signatures
|
data/saml-kit.gemspec
CHANGED
@@ -22,8 +22,8 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
|
25
|
-
spec.add_dependency "activemodel", "
|
26
|
-
spec.add_dependency "activesupport", "
|
25
|
+
spec.add_dependency "activemodel", ">= 4.2.0"
|
26
|
+
spec.add_dependency "activesupport", ">= 4.2.0"
|
27
27
|
spec.add_dependency "builder", "~> 3.2"
|
28
28
|
spec.add_dependency "nokogiri", "~> 1.8"
|
29
29
|
spec.add_dependency "xmldsig", "~> 0.6"
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saml-kit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mo khan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-11-
|
11
|
+
date: 2017-11-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 4.2.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 4.2.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 4.2.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 4.2.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: builder
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -154,6 +154,7 @@ description: A simple toolkit for working with SAML.
|
|
154
154
|
email:
|
155
155
|
- mo@mokhan.ca
|
156
156
|
executables:
|
157
|
+
- saml-kit-decode-http-post
|
157
158
|
- saml-kit-decode-http-redirect
|
158
159
|
extensions: []
|
159
160
|
extra_rdoc_files: []
|
@@ -167,16 +168,25 @@ files:
|
|
167
168
|
- Rakefile
|
168
169
|
- bin/console
|
169
170
|
- bin/setup
|
171
|
+
- exe/saml-kit-decode-http-post
|
170
172
|
- exe/saml-kit-decode-http-redirect
|
171
173
|
- lib/saml/kit.rb
|
172
174
|
- lib/saml/kit/authentication_request.rb
|
173
|
-
- lib/saml/kit/
|
175
|
+
- lib/saml/kit/bindings.rb
|
176
|
+
- lib/saml/kit/bindings/binding.rb
|
177
|
+
- lib/saml/kit/bindings/http_post.rb
|
178
|
+
- lib/saml/kit/bindings/http_redirect.rb
|
179
|
+
- lib/saml/kit/bindings/url_builder.rb
|
174
180
|
- lib/saml/kit/configuration.rb
|
181
|
+
- lib/saml/kit/crypto.rb
|
182
|
+
- lib/saml/kit/crypto/oaep_cipher.rb
|
183
|
+
- lib/saml/kit/crypto/rsa_cipher.rb
|
184
|
+
- lib/saml/kit/crypto/simple_cipher.rb
|
185
|
+
- lib/saml/kit/crypto/unknown_cipher.rb
|
186
|
+
- lib/saml/kit/cryptography.rb
|
175
187
|
- lib/saml/kit/default_registry.rb
|
176
188
|
- lib/saml/kit/document.rb
|
177
189
|
- lib/saml/kit/fingerprint.rb
|
178
|
-
- lib/saml/kit/http_post_binding.rb
|
179
|
-
- lib/saml/kit/http_redirect_binding.rb
|
180
190
|
- lib/saml/kit/identity_provider_metadata.rb
|
181
191
|
- lib/saml/kit/invalid_document.rb
|
182
192
|
- lib/saml/kit/locales/en.yml
|
@@ -192,7 +202,6 @@ files:
|
|
192
202
|
- lib/saml/kit/service_provider_metadata.rb
|
193
203
|
- lib/saml/kit/signature.rb
|
194
204
|
- lib/saml/kit/trustable.rb
|
195
|
-
- lib/saml/kit/url_builder.rb
|
196
205
|
- lib/saml/kit/version.rb
|
197
206
|
- lib/saml/kit/xml.rb
|
198
207
|
- lib/saml/kit/xsd/MetadataExchange.xsd
|
data/lib/saml/kit/binding.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
module Saml
|
2
|
-
module Kit
|
3
|
-
class Binding
|
4
|
-
attr_reader :binding, :location
|
5
|
-
|
6
|
-
def initialize(binding:, location:)
|
7
|
-
@binding = binding
|
8
|
-
@location = location
|
9
|
-
end
|
10
|
-
|
11
|
-
def binding?(other)
|
12
|
-
binding == other
|
13
|
-
end
|
14
|
-
|
15
|
-
def serialize(builder, relay_state: nil)
|
16
|
-
[]
|
17
|
-
end
|
18
|
-
|
19
|
-
def deserialize(params)
|
20
|
-
raise ArgumentError.new("Unsupported binding")
|
21
|
-
end
|
22
|
-
|
23
|
-
def to_h
|
24
|
-
{ binding: binding, location: location }
|
25
|
-
end
|
26
|
-
|
27
|
-
protected
|
28
|
-
|
29
|
-
def saml_param_from(params)
|
30
|
-
if params['SAMLRequest'].present?
|
31
|
-
params['SAMLRequest']
|
32
|
-
elsif params['SAMLResponse'].present?
|
33
|
-
params['SAMLResponse']
|
34
|
-
else
|
35
|
-
raise ArgumentError.new("SAMLRequest or SAMLResponse parameter is required.")
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module Saml
|
2
|
-
module Kit
|
3
|
-
class HttpPostBinding < Binding
|
4
|
-
include Serializable
|
5
|
-
|
6
|
-
def initialize(location:)
|
7
|
-
super(binding: Saml::Kit::Namespaces::HTTP_POST, location: location)
|
8
|
-
end
|
9
|
-
|
10
|
-
def serialize(builder, relay_state: nil)
|
11
|
-
builder.sign = true
|
12
|
-
builder.destination = location
|
13
|
-
document = builder.build
|
14
|
-
saml_params = {
|
15
|
-
document.query_string_parameter => Base64.strict_encode64(document.to_xml),
|
16
|
-
}
|
17
|
-
saml_params['RelayState'] = relay_state if relay_state.present?
|
18
|
-
[location, saml_params]
|
19
|
-
end
|
20
|
-
|
21
|
-
def deserialize(params)
|
22
|
-
xml = decode(saml_param_from(params))
|
23
|
-
Saml::Kit::Document.to_saml_document(xml)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,58 +0,0 @@
|
|
1
|
-
module Saml
|
2
|
-
module Kit
|
3
|
-
class HttpRedirectBinding < Binding
|
4
|
-
include Serializable
|
5
|
-
|
6
|
-
def initialize(location:)
|
7
|
-
super(binding: Saml::Kit::Namespaces::HTTP_REDIRECT, location: location)
|
8
|
-
end
|
9
|
-
|
10
|
-
def serialize(builder, relay_state: nil)
|
11
|
-
builder.sign = false
|
12
|
-
builder.destination = location
|
13
|
-
document = builder.build
|
14
|
-
[UrlBuilder.new.build(document, relay_state: relay_state), {}]
|
15
|
-
end
|
16
|
-
|
17
|
-
def deserialize(params)
|
18
|
-
document = deserialize_document_from!(params)
|
19
|
-
ensure_valid_signature!(params, document)
|
20
|
-
document
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def deserialize_document_from!(params)
|
26
|
-
xml = inflate(decode(unescape(saml_param_from(params))))
|
27
|
-
Saml::Kit.logger.debug(xml)
|
28
|
-
Saml::Kit::Document.to_saml_document(xml)
|
29
|
-
end
|
30
|
-
|
31
|
-
def ensure_valid_signature!(params, document)
|
32
|
-
return if params['Signature'].blank? || params['SigAlg'].blank?
|
33
|
-
|
34
|
-
signature = decode(params['Signature'])
|
35
|
-
canonical_form = ['SAMLRequest', 'SAMLResponse', 'RelayState', 'SigAlg'].map do |key|
|
36
|
-
value = params[key]
|
37
|
-
value.present? ? "#{key}=#{value}" : nil
|
38
|
-
end.compact.join('&')
|
39
|
-
|
40
|
-
valid = document.provider.verify(algorithm_for(params['SigAlg']), signature, canonical_form)
|
41
|
-
raise ArgumentError.new("Invalid Signature") unless valid
|
42
|
-
end
|
43
|
-
|
44
|
-
def algorithm_for(algorithm)
|
45
|
-
case algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
|
46
|
-
when 256
|
47
|
-
OpenSSL::Digest::SHA256.new
|
48
|
-
when 384
|
49
|
-
OpenSSL::Digest::SHA384.new
|
50
|
-
when 512
|
51
|
-
OpenSSL::Digest::SHA512.new
|
52
|
-
else
|
53
|
-
OpenSSL::Digest::SHA1.new
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
data/lib/saml/kit/url_builder.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
module Saml
|
2
|
-
module Kit
|
3
|
-
class UrlBuilder
|
4
|
-
include Serializable
|
5
|
-
|
6
|
-
def initialize(private_key: Saml::Kit.configuration.signing_private_key)
|
7
|
-
@private_key = private_key
|
8
|
-
end
|
9
|
-
|
10
|
-
def build(saml_document, relay_state: nil)
|
11
|
-
payload = canonicalize(saml_document, relay_state)
|
12
|
-
"#{saml_document.destination}?#{payload}&Signature=#{signature_for(payload)}"
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
attr_reader :private_key
|
18
|
-
|
19
|
-
def signature_for(payload)
|
20
|
-
encode(private_key.sign(OpenSSL::Digest::SHA256.new, payload))
|
21
|
-
end
|
22
|
-
|
23
|
-
def canonicalize(saml_document, relay_state)
|
24
|
-
{
|
25
|
-
saml_document.query_string_parameter => serialize(saml_document.to_xml),
|
26
|
-
'RelayState' => relay_state,
|
27
|
-
'SigAlg' => Saml::Kit::Namespaces::SHA256,
|
28
|
-
}.map do |(key, value)|
|
29
|
-
value.present? ? "#{key}=#{escape(value)}" : nil
|
30
|
-
end.compact.join('&')
|
31
|
-
end
|
32
|
-
|
33
|
-
def serialize(value)
|
34
|
-
encode(deflate(value))
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|