saml-kit 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|