spid 0.12.0 → 0.13.0

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