spid 0.16.1 → 0.17.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: 7c1de73229206a3f51acebe2ffd176336baf6ecf7b7ab56587f2baa4338f5300
4
- data.tar.gz: 5c39d1327255aa6f6c91f8b893ff8d0698bcd2e8476a6fb55fc85cd2cd507c6b
3
+ metadata.gz: 8c918415822f2873b617da91c797c8987825252578e359c620a334ad9ba439d3
4
+ data.tar.gz: 2e699735bcefc0e37ed9efced5ed53ad845a29405b4c1616ec4985bcd497a346
5
5
  SHA512:
6
- metadata.gz: 8861c1b4c9d517f73dee139742e186e1cd96a10267ec9d73ed2987426bd65fc9832114ad41f34b00bcad05595d17e760602eb894344ff8d77f16309dda829757
7
- data.tar.gz: aace38dd40188f84b5dabc3d4f33dbaabe0005e81f4fa47a2777d437e9032112ab25fd6b981dbd99424017349eabbcf86e0b715582c0cb626c9aa0887338f7e0
6
+ metadata.gz: 2e80c2322252a27a0362c738a3eace0fdba76735026fe0a7dd344bbf25b4543d2a014724301915d87a97fb517399d3bf0695e792141d90b95d1e5df3d82ca40c
7
+ data.tar.gz: 9fd3aecb75d5a2dad2e6464532c774fd40f2737eecc471f693aabdabf688ca9c5644a6fa3f8c29ea8576076570616d0a402a389f5922f403f8e0c67087330709
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.17.0] - 2018-09-10
6
+ ### Added
7
+ - Ensure private key lenght and certificate validation
8
+ - Previous SPID specification use sp_entity_id for /samlp:Response/@Destination
9
+ - Validate Subject attributes
10
+ - Validate Issuer and Assertion/Issuer
11
+
5
12
  ## [0.16.1] - 2018-09-05
6
13
  ### Changed
7
14
  - Use login_path and logout_path instead of start_sso_path and start_slo_path
@@ -127,8 +134,9 @@
127
134
  - Coveralls Integration
128
135
  - Rubygems version badge in README
129
136
 
130
- [Unreleased]: https://github.com/italia/spid-ruby/compare/v0.16.1...HEAD
131
- [0.16.0]: https://github.com/italia/spid-ruby/compare/v0.16.0...v0.16.1
137
+ [Unreleased]: https://github.com/italia/spid-ruby/compare/v0.17.0...HEAD
138
+ [0.17.0]: https://github.com/italia/spid-ruby/compare/v0.16.1...v0.17.0
139
+ [0.16.1]: https://github.com/italia/spid-ruby/compare/v0.16.0...v0.16.1
132
140
  [0.16.0]: https://github.com/italia/spid-ruby/compare/v0.15.2...v0.16.0
133
141
  [0.15.2]: https://github.com/italia/spid-ruby/compare/v0.15.1...v0.15.2
134
142
  [0.15.1]: https://github.com/italia/spid-ruby/compare/v0.15.0...v0.15.1
data/lib/spid.rb CHANGED
@@ -18,6 +18,8 @@ module Spid # :nodoc:
18
18
  class UnknownSignatureMethodError < StandardError; end
19
19
  class UnknownAttributeFieldError < StandardError; end
20
20
  class MissingAttributeServicesError < StandardError; end
21
+ class PrivateKeyTooShortError < StandardError; end
22
+ class CertificateNotBelongsToPKeyError < StandardError; end
21
23
 
22
24
  EXACT_COMPARISON = :exact
23
25
  MINIMUM_COMPARISON = :minimum
data/lib/spid/saml2.rb CHANGED
@@ -4,6 +4,7 @@ require "spid/saml2/service_provider"
4
4
  require "spid/saml2/identity_provider"
5
5
  require "spid/saml2/settings"
6
6
  require "spid/saml2/authn_request"
7
+ require "spid/saml2/saml_parser"
7
8
  require "spid/saml2/response"
8
9
  require "spid/saml2/logout_request"
9
10
  require "spid/saml2/idp_logout_request"
@@ -2,51 +2,37 @@
2
2
 
3
3
  module Spid
4
4
  module Saml2
5
- class IdpLogoutRequest # :nodoc:
6
- attr_reader :saml_message
7
- attr_reader :document
8
-
9
- def initialize(saml_message:)
10
- @saml_message = saml_message
11
- @document = REXML::Document.new(@saml_message)
12
- end
13
-
5
+ class IdpLogoutRequest < SamlParser # :nodoc:
14
6
  def id
15
- document.elements["/samlp:LogoutRequest/@ID"]&.value
7
+ element_from_xpath("/samlp:LogoutRequest/@ID")
16
8
  end
17
9
 
18
10
  def destination
19
- document.elements["/samlp:LogoutRequest/@Destination"]&.value
11
+ element_from_xpath("/samlp:LogoutRequest/@Destination")
20
12
  end
21
13
 
22
14
  def issue_instant
23
- document.elements["/samlp:LogoutRequest/@IssueInstant"]&.value
15
+ element_from_xpath("/samlp:LogoutRequest/@IssueInstant")
24
16
  end
25
17
 
26
18
  def issuer_name_qualifier
27
- document.elements[
28
- "/samlp:LogoutRequest/saml:Issuer/@NameQualifier"
29
- ]&.value
19
+ element_from_xpath("/samlp:LogoutRequest/saml:Issuer/@NameQualifier")
30
20
  end
31
21
 
32
22
  def name_id
33
- document.elements["/samlp:LogoutRequest/saml:NameID/text()"]&.value
23
+ element_from_xpath("/samlp:LogoutRequest/saml:NameID/text()")
34
24
  end
35
25
 
36
26
  def name_id_name_qualifier
37
- document.elements[
38
- "/samlp:LogoutRequest/saml:NameID/@NameQualifier"
39
- ]&.value
27
+ element_from_xpath("/samlp:LogoutRequest/saml:NameID/@NameQualifier")
40
28
  end
41
29
 
42
30
  def issuer
43
- document.elements["/samlp:LogoutRequest/saml:Issuer/text()"]&.value
31
+ element_from_xpath("/samlp:LogoutRequest/saml:Issuer/text()")
44
32
  end
45
33
 
46
34
  def session_index
47
- document.elements[
48
- "/samlp:LogoutRequest/saml:SessionIndex/text()"
49
- ]&.value
35
+ element_from_xpath("/samlp:LogoutRequest/saml:SessionIndex/text()")
50
36
  end
51
37
  end
52
38
  end
@@ -4,35 +4,17 @@ require "spid/saml2/utils"
4
4
 
5
5
  module Spid
6
6
  module Saml2
7
- class LogoutResponse # :nodoc:
8
- include Spid::Saml2::Utils
9
-
10
- attr_reader :body
11
- attr_reader :saml_message
12
- attr_reader :document
13
-
14
- def initialize(body:)
15
- @body = body
16
- @saml_message = decode_and_inflate(body)
17
- @document = REXML::Document.new(@saml_message)
18
- end
19
-
7
+ class LogoutResponse < SamlParser # :nodoc:
20
8
  def issuer
21
- document.elements[
22
- "/samlp:LogoutResponse/saml:Issuer/text()"
23
- ]&.value&.strip
9
+ element_from_xpath("/samlp:LogoutResponse/saml:Issuer/text()")
24
10
  end
25
11
 
26
12
  def in_response_to
27
- document.elements[
28
- "/samlp:LogoutResponse/@InResponseTo"
29
- ]&.value&.strip
13
+ element_from_xpath("/samlp:LogoutResponse/@InResponseTo")
30
14
  end
31
15
 
32
16
  def destination
33
- document.elements[
34
- "/samlp:LogoutResponse/@Destination"
35
- ]&.value
17
+ element_from_xpath("/samlp:LogoutResponse/@Destination")
36
18
  end
37
19
  end
38
20
  end
@@ -1,20 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "spid/saml2/utils"
4
-
5
3
  module Spid
6
4
  module Saml2
7
- class Response # :nodoc:
8
- include Spid::Saml2::Utils
9
-
10
- attr_reader :saml_message
11
- attr_reader :document
12
-
13
- def initialize(saml_message:)
14
- @saml_message = saml_message
15
- @document = REXML::Document.new(@saml_message)
16
- end
17
-
5
+ class Response < SamlParser # :nodoc:
18
6
  def issuer
19
7
  document.elements["/samlp:Response/saml:Issuer/text()"]&.value
20
8
  end
@@ -24,15 +12,16 @@ module Spid
24
12
  end
25
13
 
26
14
  def name_id
27
- document.elements[
15
+ element_from_xpath(
28
16
  "/samlp:Response/saml:Assertion/saml:Subject/saml:NameID/text()"
29
- ]&.value
17
+ )
30
18
  end
31
19
 
32
20
  def raw_certificate
33
- xpath = "/samlp:Response/saml:Assertion/ds:Signature/ds:KeyInfo"
34
- xpath = "#{xpath}/ds:X509Data/ds:X509Certificate/text()"
35
- document.elements[xpath]&.value
21
+ element_from_xpath(
22
+ "/samlp:Response/saml:Assertion/ds:Signature/ds:KeyInfo" \
23
+ "/ds:X509Data/ds:X509Certificate/text()"
24
+ )
36
25
  end
37
26
 
38
27
  def certificate
@@ -40,59 +29,77 @@ module Spid
40
29
  end
41
30
 
42
31
  def assertion_issuer
43
- document.elements[
44
- "/samlp:Response/saml:Assertion/saml:Issuer/text()"
45
- ]&.value
32
+ element_from_xpath("/samlp:Response/saml:Assertion/saml:Issuer/text()")
46
33
  end
47
34
 
48
35
  def session_index
49
- document.elements[
36
+ element_from_xpath(
50
37
  "/samlp:Response/saml:Assertion/saml:AuthnStatement/@SessionIndex"
51
- ]&.value
38
+ )
52
39
  end
53
40
 
54
41
  def destination
55
- document.elements[
56
- "/samlp:Response/@Destination"
57
- ]&.value
42
+ element_from_xpath("/samlp:Response/@Destination")
58
43
  end
59
44
 
60
45
  def conditions_not_before
61
- document.elements[
46
+ element_from_xpath(
62
47
  "/samlp:Response/saml:Assertion/saml:Conditions/@NotBefore"
63
- ]&.value
48
+ )
64
49
  end
65
50
 
66
51
  def conditions_not_on_or_after
67
- document.elements[
52
+ element_from_xpath(
68
53
  "/samlp:Response/saml:Assertion/saml:Conditions/@NotOnOrAfter"
69
- ]&.value
54
+ )
70
55
  end
71
56
 
72
57
  def audience
73
- xpath = "/samlp:Response/saml:Assertion/saml:Conditions"
74
- xpath = "#{xpath}/saml:AudienceRestriction/saml:Audience/text()"
75
- document.elements[xpath]&.value
58
+ element_from_xpath(
59
+ "/samlp:Response/saml:Assertion/saml:Conditions" \
60
+ "/saml:AudienceRestriction/saml:Audience/text()"
61
+ )
62
+ end
63
+
64
+ def subject_confirmation_data_node_xpath
65
+ xpath = "/samlp:Response/saml:Assertion/saml:Subject/"
66
+ "#{xpath}/saml:SubjectConfirmation/saml:SubjectConfirmationData"
67
+ end
68
+
69
+ def subject_recipient
70
+ element_from_xpath("#{subject_confirmation_data_node_xpath}/@Recipient")
71
+ end
72
+
73
+ def subject_in_response_to
74
+ element_from_xpath(
75
+ "#{subject_confirmation_data_node_xpath}/@InResponseTo"
76
+ )
77
+ end
78
+
79
+ def subject_not_on_or_after
80
+ element_from_xpath(
81
+ "#{subject_confirmation_data_node_xpath}/@NotOnOrAfter"
82
+ )
76
83
  end
77
84
 
78
85
  def status_code
79
- document.elements[
86
+ element_from_xpath(
80
87
  "/samlp:Response/samlp:Status/samlp:StatusCode/@Value"
81
- ]&.value
88
+ )
82
89
  end
83
90
 
84
91
  def status_message
85
- document.elements[
86
- "/samlp:Response/samlp:Status/samlp:StatusCode/" \
87
- "samlp:StatusMessage/@Value"
88
- ]&.value
92
+ element_from_xpath(
93
+ "samlp:StatusMessage/@Value" \
94
+ "/samlp:Response/samlp:Status/samlp:StatusCode/"
95
+ )
89
96
  end
90
97
 
91
98
  def status_detail
92
- document.elements[
99
+ element_from_xpath(
93
100
  "/samlp:Response/samlp:Status/samlp:StatusCode/" \
94
101
  "samlp:StatusDetail/@Value"
95
- ]&.value
102
+ )
96
103
  end
97
104
 
98
105
  def attributes
@@ -4,6 +4,7 @@ require "xmldsig"
4
4
 
5
5
  module Spid
6
6
  module Saml2
7
+ # rubocop:disable Metrics/ClassLength
7
8
  class ResponseValidator # :nodoc:
8
9
  attr_reader :response
9
10
  attr_reader :settings
@@ -20,13 +21,8 @@ module Spid
20
21
  def call
21
22
  return false unless success?
22
23
  [
23
- matches_request_uuid,
24
- issuer,
25
- certificate,
26
- destination,
27
- conditions,
28
- audience,
29
- signature
24
+ matches_request_uuid, issuer, assertion_issuer, certificate,
25
+ destination, conditions, audience, signature
30
26
  ].all?
31
27
  end
32
28
 
@@ -50,11 +46,22 @@ module Spid
50
46
  end
51
47
 
52
48
  def issuer
53
- return true if response.assertion_issuer == settings.idp_entity_id
49
+ return true if response.issuer == settings.idp_entity_id
54
50
 
55
51
  @errors["issuer"] =
56
52
  begin
57
- "Response Issuer is '#{response.assertion_issuer}'" \
53
+ "Response Issuer is '#{response.issuer}'" \
54
+ " but was expected '#{settings.idp_entity_id}'"
55
+ end
56
+ false
57
+ end
58
+
59
+ def assertion_issuer
60
+ return true if response.assertion_issuer == settings.idp_entity_id
61
+
62
+ @errors["assertion_issuer"] =
63
+ begin
64
+ "Response Assertion Issuer is '#{response.assertion_issuer}'" \
58
65
  " but was expected '#{settings.idp_entity_id}'"
59
66
  end
60
67
  false
@@ -71,6 +78,7 @@ module Spid
71
78
 
72
79
  def destination
73
80
  return true if response.destination == settings.sp_acs_url
81
+ return true if response.destination == settings.sp_entity_id
74
82
 
75
83
  @errors["destination"] =
76
84
  begin
@@ -85,6 +93,7 @@ module Spid
85
93
 
86
94
  if response.conditions_not_before <= time &&
87
95
  response.conditions_not_on_or_after > time
96
+
88
97
  return true
89
98
  end
90
99
 
@@ -109,6 +118,21 @@ module Spid
109
118
  @errors["signature"] = "Signature mismatch"
110
119
  false
111
120
  end
121
+
122
+ def subject_recipient
123
+ return true if response.subject_recipient == settings.sp_acs_url
124
+ end
125
+
126
+ def subject_in_response_to
127
+ return true if response.subject_in_response_to == request_uuid
128
+ end
129
+
130
+ def subject_not_on_or_after
131
+ time = Time.now.utc.iso8601
132
+
133
+ return true if response.subject_not_on_or_after > time
134
+ end
112
135
  end
136
+ # rubocop:enable Metrics/ClassLength
113
137
  end
114
138
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spid/saml2/utils"
4
+
5
+ module Spid
6
+ module Saml2
7
+ class SamlParser # :nodoc:
8
+ include Spid::Saml2::Utils
9
+
10
+ attr_reader :saml_message
11
+ attr_reader :document
12
+
13
+ def initialize(saml_message:)
14
+ @saml_message = saml_message
15
+ @document = REXML::Document.new(@saml_message)
16
+ end
17
+
18
+ def element_from_xpath(xpath)
19
+ document.elements[xpath]&.value&.strip
20
+ end
21
+ end
22
+ end
23
+ end
@@ -45,6 +45,8 @@ module Spid
45
45
  @attribute_services = attribute_services
46
46
  validate_digest_methods
47
47
  validate_attributes
48
+ validate_private_key
49
+ validate_certificate
48
50
  end
49
51
  # rubocop:enable Metrics/MethodLength
50
52
  # rubocop:enable Metrics/ParameterLists
@@ -92,6 +94,19 @@ module Spid
92
94
  " use one of #{SIGNATURE_METHODS.join(', ')}"
93
95
  end
94
96
  end
97
+
98
+ def validate_private_key
99
+ return true if private_key.n.num_bits >= 1024
100
+ raise PrivateKeyTooShortError,
101
+ "Private key is too short: provide at least a " \
102
+ " private key with 1024 bits"
103
+ end
104
+
105
+ def validate_certificate
106
+ return true if certificate.verify(private_key)
107
+ raise CertificateNotBelongsToPKeyError,
108
+ "Provided a certificate signed with current private key"
109
+ end
95
110
  end
96
111
  end
97
112
  end
@@ -91,8 +91,7 @@ module Spid
91
91
  def x509_certificate_der
92
92
  @x509_certificate_der ||=
93
93
  begin
94
- cert = OpenSSL::X509::Certificate.new(certificate)
95
- Base64.encode64(cert.to_der).delete("\n")
94
+ Base64.encode64(certificate.to_der).delete("\n")
96
95
  end
97
96
  end
98
97
  end
@@ -6,12 +6,10 @@ module Spid
6
6
  class SPMetadata # :nodoc:
7
7
  attr_reader :document
8
8
  attr_reader :settings
9
- attr_reader :uuid
10
9
 
11
- def initialize(settings:, uuid: nil)
10
+ def initialize(settings:)
12
11
  @document = REXML::Document.new
13
12
  @settings = settings
14
- @uuid = uuid || SecureRandom.uuid
15
13
  end
16
14
 
17
15
  def to_saml
@@ -34,7 +32,7 @@ module Spid
34
32
  "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
35
33
  "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata",
36
34
  "entityID" => settings.sp_entity_id,
37
- "ID" => "_#{uuid}"
35
+ "ID" => settings.sp_entity_id
38
36
  }
39
37
  end
40
38
 
@@ -3,14 +3,18 @@
3
3
  module Spid
4
4
  module Slo
5
5
  class Response # :nodoc:
6
+ include Spid::Saml2::Utils
7
+
6
8
  attr_reader :body
7
9
  attr_reader :session_index
8
10
  attr_reader :request_uuid
11
+ attr_reader :saml_message
9
12
 
10
13
  def initialize(body:, session_index:, request_uuid:)
11
14
  @body = body
12
15
  @session_index = session_index
13
16
  @request_uuid = request_uuid
17
+ @saml_message = decode_and_inflate(body)
14
18
  end
15
19
 
16
20
  def valid?
@@ -59,7 +63,7 @@ module Spid
59
63
 
60
64
  def saml_response
61
65
  @saml_response ||= Spid::Saml2::LogoutResponse.new(
62
- body: body
66
+ saml_message: saml_message
63
67
  )
64
68
  end
65
69
  end
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.16.1"
4
+ VERSION = "0.17.0"
5
5
  end
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.16.1
4
+ version: 0.17.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-09-05 00:00:00.000000000 Z
11
+ date: 2018-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -343,6 +343,7 @@ files:
343
343
  - lib/spid/saml2/logout_response_validator.rb
344
344
  - lib/spid/saml2/response.rb
345
345
  - lib/spid/saml2/response_validator.rb
346
+ - lib/spid/saml2/saml_parser.rb
346
347
  - lib/spid/saml2/service_provider.rb
347
348
  - lib/spid/saml2/settings.rb
348
349
  - lib/spid/saml2/sp_metadata.rb