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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/exe/saml-kit-decode-http-post +8 -0
  3. data/lib/saml/kit.rb +4 -4
  4. data/lib/saml/kit/authentication_request.rb +3 -13
  5. data/lib/saml/kit/bindings.rb +45 -0
  6. data/lib/saml/kit/bindings/binding.rb +42 -0
  7. data/lib/saml/kit/bindings/http_post.rb +29 -0
  8. data/lib/saml/kit/bindings/http_redirect.rb +61 -0
  9. data/lib/saml/kit/bindings/url_builder.rb +40 -0
  10. data/lib/saml/kit/configuration.rb +22 -1
  11. data/lib/saml/kit/crypto.rb +16 -0
  12. data/lib/saml/kit/crypto/oaep_cipher.rb +22 -0
  13. data/lib/saml/kit/crypto/rsa_cipher.rb +23 -0
  14. data/lib/saml/kit/crypto/simple_cipher.rb +38 -0
  15. data/lib/saml/kit/crypto/unknown_cipher.rb +18 -0
  16. data/lib/saml/kit/cryptography.rb +30 -0
  17. data/lib/saml/kit/default_registry.rb +1 -0
  18. data/lib/saml/kit/document.rb +6 -2
  19. data/lib/saml/kit/identity_provider_metadata.rb +9 -9
  20. data/lib/saml/kit/locales/en.yml +4 -3
  21. data/lib/saml/kit/logout_request.rb +2 -2
  22. data/lib/saml/kit/logout_response.rb +3 -3
  23. data/lib/saml/kit/metadata.rb +12 -37
  24. data/lib/saml/kit/namespaces.rb +1 -13
  25. data/lib/saml/kit/respondable.rb +4 -0
  26. data/lib/saml/kit/response.rb +120 -37
  27. data/lib/saml/kit/service_provider_metadata.rb +16 -7
  28. data/lib/saml/kit/signature.rb +16 -13
  29. data/lib/saml/kit/trustable.rb +14 -6
  30. data/lib/saml/kit/version.rb +1 -1
  31. data/lib/saml/kit/xml.rb +19 -3
  32. data/saml-kit.gemspec +2 -2
  33. metadata +23 -14
  34. data/lib/saml/kit/binding.rb +0 -40
  35. data/lib/saml/kit/http_post_binding.rb +0 -27
  36. data/lib/saml/kit/http_redirect_binding.rb +0 -58
  37. 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: :post)
39
- @acs_urls.push(location: url, binding: Namespaces.binding_for(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: :post)
43
- @logout_urls.push(location: url, binding: Namespaces.binding_for(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(id, sign: sign) do |xml, signature|
47
+ Signature.sign(sign: sign) do |xml, signature|
48
48
  xml.instruct!
49
49
  xml.EntityDescriptor entity_descriptor_options do
50
- signature.template(xml)
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
@@ -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, :reference_id, :sign
19
+ attr_reader :configuration, :sign, :xml
20
20
 
21
- def initialize(reference_id, configuration: Saml::Kit.configuration, sign: true)
22
- @reference_id = reference_id
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(xml = ::Builder::XmlMarkup.new)
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(xml)
54
- if sign && reference_id.present?
55
- document = Xmldsig::SignedDocument.new(xml.target!)
56
- document.sign(private_key)
57
- else
58
- xml.target!
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(id, sign: true, xml: ::Builder::XmlMarkup.new)
63
- signature = new(id, sign: sign)
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(xml)
68
+ signature.finalize
66
69
  end
67
70
 
68
71
  private
@@ -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[name]['Signature'].present?
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.nil?
54
- errors[:provider] << error_message(:unregistered)
55
- return
56
- end
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
@@ -1,5 +1,5 @@
1
1
  module Saml
2
2
  module Kit
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
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, nil, nil, Nokogiri::XML::ParseOptions::STRICT) do |config|
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", "~> 5.1"
26
- spec.add_dependency "activesupport", "~> 5.1"
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.1.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-19 00:00:00.000000000 Z
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: '5.1'
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: '5.1'
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: '5.1'
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: '5.1'
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/binding.rb
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
@@ -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
@@ -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