spid 0.12.0 → 0.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc64c3b3b469b3e5e031547db6c09e359902aa6c15b3d37b3426e7549ecc2ebd
4
- data.tar.gz: 02e57b8f55b111ec4c0ed1074c023e816077df9e687897f0acb1ad58d0b2b3d8
3
+ metadata.gz: 83dc2ec01f9adb676d89eb91c160f3aacacf30cb6473514fc5c4655b1f2c0f32
4
+ data.tar.gz: ce9606fdab6a9349888111b259979269977643add8e6f68a15a26c5286d38b82
5
5
  SHA512:
6
- metadata.gz: 9d2c88ef7f6a5ddaab7ba1e35b6e93c7fea274db9a1fddeaa066d070ec5a49fee3457ef882ced3303b5a5f55aeecdecf5cddccb5ecaf198049b2e770c0c23f91
7
- data.tar.gz: 3ba760f88fc7e765d4d71e6381753859d0fe84731bd606cffc884f083db0a83e9d367694a3c38716101a227e96171117989eae91acbe9d378b225aeef54b1396
6
+ metadata.gz: 5d5b83dcc34a604397c5d60a682081d04064777cc54cf72fc1f61e3e0a6703215856715c5bd553335ad128231ac4da0a80a2bc084063bd130860566b84369e86
7
+ data.tar.gz: dcea75e1ce343c0a3e3297a02f78f889150ae0693a3a68a1258fdab1d7d24999ec99a79d1e53a1b87544fd15c144559f91f4b0f19c0633814271b35c6c0760f0
data/.gitignore CHANGED
@@ -25,4 +25,5 @@ build-iPhoneOS/
25
25
  build-iPhoneSimulator/
26
26
  build/
27
27
  /Gemfile.lock
28
- /idp_metadata/*.xml
28
+ /idp_metadata/*.xml
29
+ /.ruby-version
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.13.0] - 2018-08-29
6
+ ### Added
7
+ - Validation of Response and LogoutResponse
8
+
5
9
  ## [0.12.0] - 2018-08-27
6
10
  ### Added
7
11
  - AttributeConsumingService management
@@ -97,7 +101,8 @@
97
101
  - Coveralls Integration
98
102
  - Rubygems version badge in README
99
103
 
100
- [Unreleased]: https://github.com/italia/spid-ruby/compare/v0.12.0...HEAD
104
+ [Unreleased]: https://github.com/italia/spid-ruby/compare/v0.13.0...HEAD
105
+ [0.13.0]: https://github.com/italia/spid-ruby/compare/v0.12.0...v0.13.0
101
106
  [0.12.0]: https://github.com/italia/spid-ruby/compare/v0.11.0...v0.12.0
102
107
  [0.11.0]: https://github.com/italia/spid-ruby/compare/v0.10.0...v0.11.0
103
108
  [0.10.0]: https://github.com/italia/spid-ruby/compare/v0.9.0...v0.10.0
data/README.md CHANGED
@@ -35,31 +35,31 @@ gem "spid"
35
35
  |HTTP-POST binding||
36
36
  |`AssertionConsumerServiceURL` customization|✓|
37
37
  |`AssertionConsumerServiceIndex` customization||
38
- |`AttributeConsumingServiceIndex` customization||
38
+ |`AttributeConsumingServiceIndex` customization|✓|
39
39
  |`AuthnContextClassRef` (SPID level) customization|✓|
40
40
  |`RequestedAuthnContext/@Comparison` customization||
41
41
  |`RelayState` customization (1.2.2)|✓|
42
42
  |**Response/Assertion parsing**||
43
43
  |verification of `Response/Signature` value (if any)||
44
44
  |verification of `Response/Signature` certificate (if any) against IdP/AA metadata||
45
- |verification of `Assertion/Signature` value||
46
- |verification of `Assertion/Signature` certificate against IdP/AA metadata||
45
+ |verification of `Assertion/Signature` value|✓|
46
+ |verification of `Assertion/Signature` certificate against IdP/AA metadata|✓|
47
47
  |verification of `SubjectConfirmationData/@Recipient`||
48
48
  |verification of `SubjectConfirmationData/@NotOnOrAfter`||
49
49
  |verification of `SubjectConfirmationData/@InResponseTo`||
50
- |verification of `Issuer`||
51
- |verification of `Destination`||
52
- |verification of `Conditions/@NotBefore`||
53
- |verification of `Conditions/@NotOnOrAfter`||
54
- |verification of `Audience`||
50
+ |verification of `Issuer`|✓|
51
+ |verification of `Destination`|✓|
52
+ |verification of `Conditions/@NotBefore`|✓|
53
+ |verification of `Conditions/@NotOnOrAfter`|✓|
54
+ |verification of `Audience`|✓|
55
55
  |parsing of Response with no `Assertion` (authentication/query failure)||
56
56
  |parsing of failure `StatusCode` (Requester/Responder)||
57
57
  |**Response/Assertion parsing for SSO (1.2.1, 1.2.2.2, 1.3.1):**||
58
- |parsing of `NameID`||
58
+ |parsing of `NameID`|✓|
59
59
  |parsing of `AuthnContextClassRef` (SPID level)||
60
- |parsing of attributes||
61
- |**Response/Assertion parsing for attribute query (2.2.2.2, 2.3.1):**||
62
60
  |parsing of attributes|✓|
61
+ |**Response/Assertion parsing for attribute query (2.2.2.2, 2.3.1):**||
62
+ |parsing of attributes||
63
63
  |**LogoutRequest generation (for SP-initiated logout):**||
64
64
  |generation of LogoutRequest XML|✓|
65
65
  |HTTP-Redirect binding|✓|
@@ -68,8 +68,8 @@ gem "spid"
68
68
  |parsing of LogoutResponse XML|✓|
69
69
  |verification of `Response/Signature` value (if any)||
70
70
  |verification of `Response/Signature` certificate (if any) against IdP metadata||
71
- |verification of `Issuer`||
72
- |verification of `Destination`||
71
+ |verification of `Issuer`|✓|
72
+ |verification of `Destination`|✓|
73
73
  |PartialLogout detection||
74
74
  |**LogoutRequest parsing (for third-party-initiated logout):**||
75
75
  |parsing of LogoutRequest XML||
@@ -11,12 +11,12 @@ module Spid
11
11
  attr_accessor :slo_path
12
12
  attr_accessor :digest_method
13
13
  attr_accessor :signature_method
14
- attr_accessor :private_key
15
- attr_accessor :certificate
16
14
  attr_accessor :default_relay_state_path
17
15
  attr_accessor :acs_binding
18
16
  attr_accessor :slo_binding
19
17
  attr_accessor :attribute_services
18
+ attr_writer :private_key
19
+ attr_writer :certificate
20
20
 
21
21
  def initialize
22
22
  @idp_metadata_dir_path = "idp_metadata"
@@ -52,6 +52,14 @@ module Spid
52
52
  @certificate = nil
53
53
  end
54
54
 
55
+ def certificate
56
+ OpenSSL::X509::Certificate.new(@certificate) unless @certificate.nil?
57
+ end
58
+
59
+ def private_key
60
+ OpenSSL::PKey::RSA.new(@private_key) unless @private_key.nil?
61
+ end
62
+
55
63
  def service_provider
56
64
  @service_provider ||=
57
65
  begin
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "base64"
4
+
3
5
  module Spid
4
6
  class IdentityProviderManager # :nodoc:
7
+ extend Spid::Saml2::Utils
5
8
  include Singleton
6
9
 
7
10
  def identity_providers
@@ -37,7 +40,8 @@ module Spid
37
40
  entity_id: idp_settings[:idp_entity_id],
38
41
  sso_target_url: idp_settings[:idp_sso_target_url],
39
42
  slo_target_url: idp_settings[:idp_slo_target_url],
40
- cert_fingerprint: idp_settings[:idp_cert_fingerprint]
43
+ cert_fingerprint: idp_settings[:idp_cert_fingerprint],
44
+ certificate: certificate_from_encoded_der(idp_settings[:idp_cert])
41
45
  )
42
46
  end
43
47
 
data/lib/spid/saml2.rb CHANGED
@@ -10,6 +10,8 @@ require "spid/saml2/logout_response"
10
10
  require "spid/saml2/sp_metadata"
11
11
  require "spid/saml2/utils"
12
12
  require "spid/saml2/idp_metadata_parser"
13
+ require "spid/saml2/response_validator"
14
+ require "spid/saml2/logout_response_validator"
13
15
 
14
16
  module Spid
15
17
  module Saml2 # :nodoc:
@@ -8,20 +8,25 @@ module Spid
8
8
  attr_reader :sso_target_url
9
9
  attr_reader :slo_target_url
10
10
  attr_reader :cert_fingerprint
11
+ attr_reader :certificate
11
12
 
13
+ # rubocop:disable Metrics/ParameterLists
12
14
  def initialize(
13
15
  name:,
14
16
  entity_id:,
15
17
  sso_target_url:,
16
18
  slo_target_url:,
17
- cert_fingerprint:
19
+ cert_fingerprint:,
20
+ certificate:
18
21
  )
19
22
  @name = name
20
23
  @entity_id = entity_id
21
24
  @sso_target_url = sso_target_url
22
25
  @slo_target_url = slo_target_url
23
26
  @cert_fingerprint = cert_fingerprint
27
+ @certificate = certificate
24
28
  end
29
+ # rubocop:enable Metrics/ParameterLists
25
30
  end
26
31
  end
27
32
  end
@@ -28,6 +28,12 @@ module Spid
28
28
  "/samlp:LogoutResponse/@InResponseTo"
29
29
  ]&.value&.strip
30
30
  end
31
+
32
+ def destination
33
+ document.elements[
34
+ "/samlp:LogoutResponse/@Destination"
35
+ ]&.value
36
+ end
31
37
  end
32
38
  end
33
39
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spid
4
+ module Saml2
5
+ class LogoutResponseValidator # :nodoc:
6
+ attr_reader :response
7
+ attr_reader :settings
8
+
9
+ def initialize(response:, settings:)
10
+ @response = response
11
+ @settings = settings
12
+ end
13
+
14
+ def call
15
+ [
16
+ destination,
17
+ issuer
18
+ ].all?
19
+ end
20
+
21
+ def destination
22
+ response.destination == settings.sp_slo_service_url
23
+ end
24
+
25
+ def issuer
26
+ response.issuer == settings.idp_entity_id
27
+ end
28
+ end
29
+ end
30
+ end
@@ -7,13 +7,11 @@ module Spid
7
7
  class Response # :nodoc:
8
8
  include Spid::Saml2::Utils
9
9
 
10
- attr_reader :body
11
10
  attr_reader :saml_message
12
11
  attr_reader :document
13
12
 
14
- def initialize(body:)
15
- @body = body
16
- @saml_message = decode_and_inflate(body)
13
+ def initialize(saml_message:)
14
+ @saml_message = saml_message
17
15
  @document = REXML::Document.new(@saml_message)
18
16
  end
19
17
 
@@ -21,6 +19,22 @@ module Spid
21
19
  document.elements["/samlp:Response/saml:Issuer/text()"]&.value
22
20
  end
23
21
 
22
+ def name_id
23
+ document.elements[
24
+ "/samlp:Response/saml:Assertion/saml:Subject/saml:NameID/text()"
25
+ ]&.value
26
+ end
27
+
28
+ def raw_certificate
29
+ xpath = "/samlp:Response/saml:Assertion/ds:Signature/ds:KeyInfo"
30
+ xpath = "#{xpath}/ds:X509Data/ds:X509Certificate/text()"
31
+ document.elements[xpath]&.value
32
+ end
33
+
34
+ def certificate
35
+ certificate_from_encoded_der(raw_certificate)
36
+ end
37
+
24
38
  def assertion_issuer
25
39
  document.elements[
26
40
  "/samlp:Response/saml:Assertion/saml:Issuer/text()"
@@ -39,6 +53,24 @@ module Spid
39
53
  ]&.value
40
54
  end
41
55
 
56
+ def conditions_not_before
57
+ document.elements[
58
+ "/samlp:Response/saml:Assertion/saml:Conditions/@NotBefore"
59
+ ]&.value
60
+ end
61
+
62
+ def conditions_not_on_or_after
63
+ document.elements[
64
+ "/samlp:Response/saml:Assertion/saml:Conditions/@NotOnOrAfter"
65
+ ]&.value
66
+ end
67
+
68
+ def audience
69
+ xpath = "/samlp:Response/saml:Assertion/saml:Conditions"
70
+ xpath = "#{xpath}/saml:AudienceRestriction/saml:Audience/text()"
71
+ document.elements[xpath]&.value
72
+ end
73
+
42
74
  def attributes
43
75
  main_xpath = "/samlp:Response/saml:Assertion/saml:AttributeStatement"
44
76
  main_xpath = "#{main_xpath}/saml:Attribute"
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "xmldsig"
4
+
5
+ module Spid
6
+ module Saml2
7
+ class ResponseValidator # :nodoc:
8
+ attr_reader :response
9
+ attr_reader :settings
10
+
11
+ def initialize(response:, settings:)
12
+ @response = response
13
+ @settings = settings
14
+ end
15
+
16
+ def call
17
+ [
18
+ issuer,
19
+ certificate,
20
+ destination,
21
+ conditions,
22
+ audience,
23
+ signature
24
+ ].all?
25
+ end
26
+
27
+ def issuer
28
+ response.assertion_issuer == settings.idp_entity_id
29
+ end
30
+
31
+ def certificate
32
+ response.certificate.to_der == settings.idp_certificate.to_der
33
+ end
34
+
35
+ def destination
36
+ response.destination == settings.sp_acs_url
37
+ end
38
+
39
+ def conditions
40
+ time = Time.now.iso8601
41
+
42
+ response.conditions_not_before <= time &&
43
+ response.conditions_not_on_or_after > time
44
+ end
45
+
46
+ def audience
47
+ response.audience == settings.sp_entity_id
48
+ end
49
+
50
+ def signature
51
+ signed_document = Xmldsig::SignedDocument.new(response.saml_message)
52
+ signed_document.validate(response.certificate)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -72,6 +72,10 @@ module Spid
72
72
  service_provider.certificate
73
73
  end
74
74
 
75
+ def idp_certificate
76
+ identity_provider.certificate
77
+ end
78
+
75
79
  def signature_method
76
80
  service_provider.signature_method
77
81
  end
@@ -57,6 +57,11 @@ module Spid
57
57
  def escaped_query_string(params)
58
58
  query_string(escaped_params(params))
59
59
  end
60
+
61
+ def certificate_from_encoded_der(der_encoded)
62
+ der = Base64.decode64(der_encoded)
63
+ OpenSSL::X509::Certificate.new(der)
64
+ end
60
65
  end
61
66
  end
62
67
  end
@@ -20,7 +20,7 @@ module Spid
20
20
  relay_state: nil
21
21
  )
22
22
  @saml_message = saml_message.delete("\n")
23
- @private_key = OpenSSL::PKey::RSA.new(private_key)
23
+ @private_key = private_key
24
24
  @signature_method = signature_method
25
25
  @relay_state = relay_state
26
26
  end
@@ -14,7 +14,10 @@ module Spid
14
14
  end
15
15
 
16
16
  def valid?
17
- saml_response.in_response_to == matches_request_id
17
+ Spid::Saml2::LogoutResponseValidator.new(
18
+ response: saml_response,
19
+ settings: settings
20
+ ).call
18
21
  end
19
22
 
20
23
  def errors
@@ -35,6 +38,13 @@ module Spid
35
38
  saml_response.issuer
36
39
  end
37
40
 
41
+ def settings
42
+ @settings ||= Spid::Saml2::Settings.new(
43
+ service_provider: service_provider,
44
+ identity_provider: identity_provider
45
+ )
46
+ end
47
+
38
48
  def saml_response
39
49
  @saml_response ||= Spid::Saml2::LogoutResponse.new(
40
50
  body: body
@@ -1,22 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "spid/saml2/utils"
3
4
  require "active_support/inflector/methods"
4
5
 
5
6
  module Spid
6
7
  module Sso
7
8
  class Response # :nodoc:
9
+ include Spid::Saml2::Utils
10
+
8
11
  attr_reader :body
12
+ attr_reader :saml_message
9
13
 
10
14
  def initialize(body:)
11
15
  @body = body
16
+ @saml_message = decode_and_inflate(body)
12
17
  end
13
18
 
14
19
  def valid?
15
- saml_response.destination == service_provider.acs_url
20
+ Spid::Saml2::ResponseValidator.new(
21
+ response: saml_response,
22
+ settings: settings
23
+ ).call
16
24
  end
17
25
 
18
26
  def issuer
19
- saml_response.issuer
27
+ saml_response.assertion_issuer
20
28
  end
21
29
 
22
30
  def attributes
@@ -44,7 +52,14 @@ module Spid
44
52
  end
45
53
 
46
54
  def saml_response
47
- @saml_response ||= Spid::Saml2::Response.new(body: body)
55
+ @saml_response ||= Spid::Saml2::Response.new(saml_message: saml_message)
56
+ end
57
+
58
+ def settings
59
+ @settings ||= Spid::Saml2::Settings.new(
60
+ identity_provider: identity_provider,
61
+ service_provider: service_provider
62
+ )
48
63
  end
49
64
 
50
65
  private
data/lib/spid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Spid
4
- VERSION = "0.12.0"
4
+ VERSION = "0.13.0"
5
5
  end
data/spid.gemspec CHANGED
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  spec.add_runtime_dependency "activesupport", ">= 3.0.0", "< 5.3"
28
28
  spec.add_runtime_dependency "rack", ">= 1", "< 3"
29
+ spec.add_runtime_dependency "xmldsig", ">= 0.6.6"
29
30
 
30
31
  spec.add_development_dependency "bundler", "~> 1.16"
31
32
  spec.add_development_dependency "bundler-audit", "~> 0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Librera
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-27 00:00:00.000000000 Z
11
+ date: 2018-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -50,6 +50,20 @@ dependencies:
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
52
  version: '3'
53
+ - !ruby/object:Gem::Dependency
54
+ name: xmldsig
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 0.6.6
60
+ type: :runtime
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 0.6.6
53
67
  - !ruby/object:Gem::Dependency
54
68
  name: bundler
55
69
  requirement: !ruby/object:Gem::Requirement
@@ -313,7 +327,9 @@ files:
313
327
  - lib/spid/saml2/idp_metadata_parser.rb
314
328
  - lib/spid/saml2/logout_request.rb
315
329
  - lib/spid/saml2/logout_response.rb
330
+ - lib/spid/saml2/logout_response_validator.rb
316
331
  - lib/spid/saml2/response.rb
332
+ - lib/spid/saml2/response_validator.rb
317
333
  - lib/spid/saml2/service_provider.rb
318
334
  - lib/spid/saml2/settings.rb
319
335
  - lib/spid/saml2/sp_metadata.rb