ruby-saml 1.16.0 → 1.17.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 +4 -4
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/test.yml +49 -3
- data/CHANGELOG.md +15 -1
- data/README.md +46 -45
- data/lib/onelogin/ruby-saml/authrequest.rb +6 -7
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +2 -2
- data/lib/onelogin/ruby-saml/logoutrequest.rb +5 -5
- data/lib/onelogin/ruby-saml/metadata.rb +20 -24
- data/lib/onelogin/ruby-saml/response.rb +18 -18
- data/lib/onelogin/ruby-saml/settings.rb +129 -31
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +5 -5
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +6 -6
- data/lib/onelogin/ruby-saml/utils.rb +76 -20
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +20 -7
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 188af740c1b9627be73d3a4cbd8261316773d3b3da0b74b0ef49b5d1c9c04f02
|
|
4
|
+
data.tar.gz: 85cb561ba597924b7037be197dac6fd175c1a626969829e170bb44d8e8a1a50d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0b154ce0a94f1f1b525179d33f8a98d5422cabafe527f32104646135ae0a9218064638639b7ec5735de1dda8ef55faa5571f22f563d57f44e040a81b6116b5a1
|
|
7
|
+
data.tar.gz: 78ea0021299530423e28935782b851894ed9740c1e93c610575518532d22bcc20829dbe26b0569f38ad21894cc145b3d785c75765c8bd0dde6e078598d864545
|
data/.github/FUNDING.yml
ADDED
data/.github/workflows/test.yml
CHANGED
|
@@ -8,11 +8,57 @@ jobs:
|
|
|
8
8
|
strategy:
|
|
9
9
|
fail-fast: false
|
|
10
10
|
matrix:
|
|
11
|
-
os:
|
|
12
|
-
|
|
11
|
+
os:
|
|
12
|
+
- ubuntu-20.04
|
|
13
|
+
- macos-latest
|
|
14
|
+
- windows-latest
|
|
15
|
+
ruby-version:
|
|
16
|
+
- 2.1
|
|
17
|
+
- 2.2
|
|
18
|
+
- 2.3
|
|
19
|
+
- 2.4
|
|
20
|
+
- 2.5
|
|
21
|
+
- 2.6
|
|
22
|
+
- 2.7
|
|
23
|
+
- 3.0
|
|
24
|
+
- 3.1
|
|
25
|
+
- 3.2
|
|
26
|
+
- 3.3
|
|
27
|
+
- jruby-9.1
|
|
28
|
+
- jruby-9.2
|
|
29
|
+
- jruby-9.3
|
|
30
|
+
- jruby-9.4
|
|
31
|
+
- truffleruby
|
|
32
|
+
exclude:
|
|
33
|
+
- os: macos-latest
|
|
34
|
+
ruby-version: 2.1
|
|
35
|
+
- os: macos-latest
|
|
36
|
+
ruby-version: 2.2
|
|
37
|
+
- os: macos-latest
|
|
38
|
+
ruby-version: 2.3
|
|
39
|
+
- os: macos-latest
|
|
40
|
+
ruby-version: 2.4
|
|
41
|
+
- os: macos-latest
|
|
42
|
+
ruby-version: 2.5
|
|
43
|
+
- os: macos-latest
|
|
44
|
+
ruby-version: jruby-9.1
|
|
45
|
+
- os: macos-latest
|
|
46
|
+
ruby-version: jruby-9.2
|
|
47
|
+
- os: windows-latest
|
|
48
|
+
ruby-version: 2.1
|
|
49
|
+
- os: windows-latest
|
|
50
|
+
ruby-version: jruby-9.1
|
|
51
|
+
- os: windows-latest
|
|
52
|
+
ruby-version: jruby-9.2
|
|
53
|
+
- os: windows-latest
|
|
54
|
+
ruby-version: jruby-9.3
|
|
55
|
+
- os: windows-latest
|
|
56
|
+
ruby-version: jruby-9.4
|
|
57
|
+
- os: windows-latest
|
|
58
|
+
ruby-version: truffleruby
|
|
13
59
|
runs-on: ${{ matrix.os }}
|
|
14
60
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
61
|
+
- uses: actions/checkout@v4
|
|
16
62
|
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
17
63
|
uses: ruby/setup-ruby@v1
|
|
18
64
|
with:
|
data/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
# Ruby SAML Changelog
|
|
2
|
+
|
|
3
|
+
### 1.17.0 (Sep 10, 2024)
|
|
4
|
+
* Fix for critical vulnerability CVE-2024-45409: SAML authentication bypass via Incorrect XPath selector
|
|
5
|
+
* [#687](https://github.com/SAML-Toolkits/ruby-saml/pull/687) Add CI coverage for Ruby 3.3 and Windows.
|
|
6
|
+
* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) Add `Settings#sp_cert_multi` paramter to facilitate SP certificate and key rotation.
|
|
7
|
+
* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) Support multiple simultaneous SP decryption keys via `Settings#sp_cert_multi` parameter.
|
|
8
|
+
* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) Deprecate `Settings#certificate_new` parameter.
|
|
9
|
+
* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) `:check_sp_cert_expiration` will use the first non-expired certificate/key when signing/decrypting. It will raise an error only if there are no valid certificates/keys.
|
|
10
|
+
* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) `:check_sp_cert_expiration` now validates the certificate `not_before` condition; previously it was only validating `not_after`.
|
|
11
|
+
* [#673](https://github.com/SAML-Toolkits/ruby-saml/pull/673) `:check_sp_cert_expiration` now causes the generated SP metadata to exclude any inactive/expired certificates.
|
|
12
|
+
|
|
2
13
|
### 1.16.0 (Oct 09, 2023)
|
|
3
14
|
* [#671](https://github.com/SAML-Toolkits/ruby-saml/pull/671) Add support on LogoutRequest with Encrypted NameID
|
|
4
15
|
|
|
5
16
|
### 1.15.0 (Jan 04, 2023)
|
|
6
17
|
* [#650](https://github.com/SAML-Toolkits/ruby-saml/pull/650) Replace strip! by strip on compute_digest method
|
|
7
18
|
* [#638](https://github.com/SAML-Toolkits/ruby-saml/pull/638) Fix dateTime format for the validUntil attribute of the generated metadata
|
|
8
|
-
* [#576](https://github.com/SAML-Toolkits/ruby-saml/pull/576) Support
|
|
19
|
+
* [#576](https://github.com/SAML-Toolkits/ruby-saml/pull/576) Support `Settings#idp_cert_multi` with string keys
|
|
9
20
|
* [#567](https://github.com/SAML-Toolkits/ruby-saml/pull/567) Improve Code quality
|
|
10
21
|
* Add info about new repo, new maintainer, new security contact
|
|
11
22
|
* Fix tests, Adjust dependencies, Add ruby 3.2 and new jruby versions tests to the CI. Add coveralls support
|
|
@@ -29,6 +40,9 @@
|
|
|
29
40
|
* Add warning about the use of IdpMetadataParser class and SSRF
|
|
30
41
|
* CI: Migrate from Travis to Github Actions
|
|
31
42
|
|
|
43
|
+
### 1.12.3 (Sep 10, 2024)
|
|
44
|
+
* Fix for critical vulnerability CVE-2024-45409: SAML authentication bypass via Incorrect XPath selector
|
|
45
|
+
|
|
32
46
|
### 1.12.2 (Apr 08, 2021)
|
|
33
47
|
* [#575](https://github.com/onelogin/ruby-saml/pull/575) Fix SloLogoutresponse bug on LogoutRequest
|
|
34
48
|
|
data/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
Ruby SAML minor and tiny versions may introduce breaking changes. Please read
|
|
8
8
|
[UPGRADING.md](UPGRADING.md) for guidance on upgrading to new Ruby SAML versions.
|
|
9
9
|
|
|
10
|
+
There is a critical vulnerability affecting ruby-saml < 1.17.0 (CVE-2024-45409). Make sure you are using an updated version. (1.12.3 is safe)
|
|
11
|
+
|
|
10
12
|
## Overview
|
|
11
13
|
|
|
12
14
|
The Ruby SAML library is for implementing the client side of a SAML authorization,
|
|
@@ -22,30 +24,10 @@ We created a demo project for Rails 4 that uses the latest version of this libra
|
|
|
22
24
|
|
|
23
25
|
The following Ruby versions are covered by CI testing:
|
|
24
26
|
|
|
25
|
-
* 2.1.
|
|
26
|
-
*
|
|
27
|
-
* 2.3.x
|
|
28
|
-
* 2.4.x
|
|
29
|
-
* 2.5.x
|
|
30
|
-
* 2.6.x
|
|
31
|
-
* 2.7.x
|
|
32
|
-
* 3.0.x
|
|
33
|
-
* 3.1
|
|
34
|
-
* 3.2
|
|
35
|
-
* JRuby 9.1.x
|
|
36
|
-
* JRuby 9.2.x
|
|
37
|
-
* JRuby 9.3.X
|
|
38
|
-
* JRuby 9.4.0
|
|
27
|
+
* Ruby (MRI) 2.1 to 3.3
|
|
28
|
+
* JRuby 9.1 to 9.4
|
|
39
29
|
* TruffleRuby (latest)
|
|
40
30
|
|
|
41
|
-
In addition, the following may work but are untested:
|
|
42
|
-
|
|
43
|
-
* 1.8.7
|
|
44
|
-
* 1.9.x
|
|
45
|
-
* 2.0.x
|
|
46
|
-
* JRuby 1.7.x
|
|
47
|
-
* JRuby 9.0.x
|
|
48
|
-
|
|
49
31
|
## Adding Features, Pull Requests
|
|
50
32
|
|
|
51
33
|
* Fork the repository
|
|
@@ -735,6 +717,48 @@ validation fails. You may disable such exceptions using the `settings.security[:
|
|
|
735
717
|
settings.security[:soft] = true # Do not raise error on failed signature/certificate validations
|
|
736
718
|
```
|
|
737
719
|
|
|
720
|
+
#### Advanced SP Certificate Usage & Key Rollover
|
|
721
|
+
|
|
722
|
+
Ruby SAML provides the `settings.sp_cert_multi` parameter to enable the following
|
|
723
|
+
advanced usage scenarios:
|
|
724
|
+
- Rotating SP certificates and private keys without disruption of service.
|
|
725
|
+
- Specifying separate SP certificates for signing and encryption.
|
|
726
|
+
|
|
727
|
+
The `sp_cert_multi` parameter replaces `certificate` and `private_key`
|
|
728
|
+
(you may not specify both pparameters at the same time.) `sp_cert_multi` has the following shape:
|
|
729
|
+
|
|
730
|
+
```ruby
|
|
731
|
+
settings.sp_cert_multi = {
|
|
732
|
+
signing: [
|
|
733
|
+
{ certificate: cert1, private_key: private_key1 },
|
|
734
|
+
{ certificate: cert2, private_key: private_key2 }
|
|
735
|
+
],
|
|
736
|
+
encryption: [
|
|
737
|
+
{ certificate: cert1, private_key: private_key1 },
|
|
738
|
+
{ certificate: cert3, private_key: private_key1 }
|
|
739
|
+
],
|
|
740
|
+
}
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
Certificate rotation is acheived by inserting new certificates at the bottom of each list,
|
|
744
|
+
and then removing the old certificates from the top of the list once your IdPs have migrated.
|
|
745
|
+
A common practice is for apps to publish the current SP metadata at a URL endpoint and have
|
|
746
|
+
the IdP regularly poll for updates.
|
|
747
|
+
|
|
748
|
+
Note the following:
|
|
749
|
+
- You may re-use the same certificate and/or private key in multiple places, including for both signing and encryption.
|
|
750
|
+
- The IdP should attempt to verify signatures with *all* `:signing` certificates,
|
|
751
|
+
and permit if *any one* succeeds. When signing, Ruby SAML will use the first SP certificate
|
|
752
|
+
in the `sp_cert_multi[:signing]` array. This will be the first active/non-expired certificate
|
|
753
|
+
in the array if `settings.security[:check_sp_cert_expiration]` is true.
|
|
754
|
+
- The IdP may encrypt with any of the SP certificates in the `sp_cert_multi[:encryption]`
|
|
755
|
+
array. When decrypting, Ruby SAML attempt to decrypt with each SP private key in
|
|
756
|
+
`sp_cert_multi[:encryption]` until the decryption is successful. This will skip private
|
|
757
|
+
keys for inactive/expired certificates if `:check_sp_cert_expiration` is true.
|
|
758
|
+
- If `:check_sp_cert_expiration` is true, the generated SP metadata XML will not include
|
|
759
|
+
inactive/expired certificates. This avoids validation errors when the IdP reads the SP
|
|
760
|
+
metadata.
|
|
761
|
+
|
|
738
762
|
#### Audience Validation
|
|
739
763
|
|
|
740
764
|
A service provider should only consider a SAML response valid if the IdP includes an <AudienceRestriction>
|
|
@@ -758,29 +782,6 @@ is invalid using the `settings.security[:strict_audience_validation]` parameter.
|
|
|
758
782
|
settings.security[:strict_audience_validation] = true
|
|
759
783
|
```
|
|
760
784
|
|
|
761
|
-
#### Key Rollover
|
|
762
|
-
|
|
763
|
-
To update the SP X.509 certificate and private key without disruption of service, you may define the parameter
|
|
764
|
-
`settings.certificate_new`. This will publish the new SP certificate in your metadata so that your IdP counterparties
|
|
765
|
-
may cache it in preparation for rollover.
|
|
766
|
-
|
|
767
|
-
For example, if you to rollover from `CERT A` to `CERT B`. Before rollover, your settings should look as follows.
|
|
768
|
-
Both `CERT A` and `CERT B` will now appear in your SP metadata, however `CERT A` will still be used for signing
|
|
769
|
-
and encryption at this time.
|
|
770
|
-
|
|
771
|
-
```ruby
|
|
772
|
-
settings.certificate = "CERT A"
|
|
773
|
-
settings.private_key = "PRIVATE KEY FOR CERT A"
|
|
774
|
-
settings.certificate_new = "CERT B"
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
After the IdP has cached `CERT B`, you may then change your settings as follows:
|
|
778
|
-
|
|
779
|
-
```ruby
|
|
780
|
-
settings.certificate = "CERT B"
|
|
781
|
-
settings.private_key = "PRIVATE KEY FOR CERT B"
|
|
782
|
-
```
|
|
783
|
-
|
|
784
785
|
## Single Log Out
|
|
785
786
|
|
|
786
787
|
Ruby SAML supports SP-initiated Single Logout and IdP-Initiated Single Logout.
|
|
@@ -72,9 +72,10 @@ module OneLogin
|
|
|
72
72
|
request = deflate(request) if settings.compress_request
|
|
73
73
|
base64_request = encode(request)
|
|
74
74
|
request_params = {"SAMLRequest" => base64_request}
|
|
75
|
+
sp_signing_key = settings.get_sp_signing_key
|
|
75
76
|
|
|
76
|
-
if settings.idp_sso_service_binding == Utils::BINDINGS[:redirect] && settings.security[:authn_requests_signed] &&
|
|
77
|
-
params['SigAlg']
|
|
77
|
+
if settings.idp_sso_service_binding == Utils::BINDINGS[:redirect] && settings.security[:authn_requests_signed] && sp_signing_key
|
|
78
|
+
params['SigAlg'] = settings.security[:signature_method]
|
|
78
79
|
url_string = OneLogin::RubySaml::Utils.build_query(
|
|
79
80
|
:type => 'SAMLRequest',
|
|
80
81
|
:data => base64_request,
|
|
@@ -82,7 +83,7 @@ module OneLogin
|
|
|
82
83
|
:sig_alg => params['SigAlg']
|
|
83
84
|
)
|
|
84
85
|
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
|
85
|
-
signature =
|
|
86
|
+
signature = sp_signing_key.sign(sign_algorithm.new, url_string)
|
|
86
87
|
params['Signature'] = encode(signature)
|
|
87
88
|
end
|
|
88
89
|
|
|
@@ -179,15 +180,13 @@ module OneLogin
|
|
|
179
180
|
end
|
|
180
181
|
|
|
181
182
|
def sign_document(document, settings)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
cert = settings.get_sp_cert
|
|
183
|
+
cert, private_key = settings.get_sp_signing_pair
|
|
184
|
+
if settings.idp_sso_service_binding == Utils::BINDINGS[:post] && settings.security[:authn_requests_signed] && private_key && cert
|
|
185
185
|
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
186
186
|
end
|
|
187
187
|
|
|
188
188
|
document
|
|
189
189
|
end
|
|
190
|
-
|
|
191
190
|
end
|
|
192
191
|
end
|
|
193
192
|
end
|
|
@@ -186,8 +186,6 @@ module OneLogin
|
|
|
186
186
|
idpsso_descriptors.map {|id| IdpMetadata.new(id, id.parent.attributes["entityID"])}
|
|
187
187
|
end
|
|
188
188
|
|
|
189
|
-
private
|
|
190
|
-
|
|
191
189
|
# Retrieve the remote IdP metadata from the URL or a cached copy.
|
|
192
190
|
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
|
|
193
191
|
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
|
|
@@ -220,6 +218,8 @@ module OneLogin
|
|
|
220
218
|
)
|
|
221
219
|
end
|
|
222
220
|
|
|
221
|
+
private
|
|
222
|
+
|
|
223
223
|
class IdpMetadata
|
|
224
224
|
attr_reader :idpsso_descriptor, :entity_id
|
|
225
225
|
|
|
@@ -69,8 +69,9 @@ module OneLogin
|
|
|
69
69
|
request = deflate(request) if settings.compress_request
|
|
70
70
|
base64_request = encode(request)
|
|
71
71
|
request_params = {"SAMLRequest" => base64_request}
|
|
72
|
+
sp_signing_key = settings.get_sp_signing_key
|
|
72
73
|
|
|
73
|
-
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_requests_signed] &&
|
|
74
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_requests_signed] && sp_signing_key
|
|
74
75
|
params['SigAlg'] = settings.security[:signature_method]
|
|
75
76
|
url_string = OneLogin::RubySaml::Utils.build_query(
|
|
76
77
|
:type => 'SAMLRequest',
|
|
@@ -79,7 +80,7 @@ module OneLogin
|
|
|
79
80
|
:sig_alg => params['SigAlg']
|
|
80
81
|
)
|
|
81
82
|
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
|
82
|
-
signature = settings.
|
|
83
|
+
signature = settings.get_sp_signing_key.sign(sign_algorithm.new, url_string)
|
|
83
84
|
params['Signature'] = encode(signature)
|
|
84
85
|
end
|
|
85
86
|
|
|
@@ -138,9 +139,8 @@ module OneLogin
|
|
|
138
139
|
|
|
139
140
|
def sign_document(document, settings)
|
|
140
141
|
# embed signature
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
cert = settings.get_sp_cert
|
|
142
|
+
cert, private_key = settings.get_sp_signing_pair
|
|
143
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.security[:logout_requests_signed] && private_key && cert
|
|
144
144
|
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
145
145
|
end
|
|
146
146
|
|
|
@@ -62,29 +62,14 @@ module OneLogin
|
|
|
62
62
|
}
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
# Add KeyDescriptor
|
|
66
|
-
# with SP certificate, and new SP certificate if any
|
|
65
|
+
# Add KeyDescriptor elements for SP certificates.
|
|
67
66
|
def add_sp_certificates(sp_sso, settings)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
kd = sp_sso.add_element "md:KeyDescriptor", { "use" => "signing" }
|
|
75
|
-
ki = kd.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
|
|
76
|
-
xd = ki.add_element "ds:X509Data"
|
|
77
|
-
xc = xd.add_element "ds:X509Certificate"
|
|
78
|
-
xc.text = cert_text
|
|
79
|
-
|
|
80
|
-
if settings.security[:want_assertions_encrypted]
|
|
81
|
-
kd2 = sp_sso.add_element "md:KeyDescriptor", { "use" => "encryption" }
|
|
82
|
-
ki2 = kd2.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
|
|
83
|
-
xd2 = ki2.add_element "ds:X509Data"
|
|
84
|
-
xc2 = xd2.add_element "ds:X509Certificate"
|
|
85
|
-
xc2.text = cert_text
|
|
86
|
-
end
|
|
87
|
-
end
|
|
67
|
+
certs = settings.get_sp_certs
|
|
68
|
+
|
|
69
|
+
certs[:signing].each { |cert, _| add_sp_cert_element(sp_sso, cert, :signing) }
|
|
70
|
+
|
|
71
|
+
if settings.security[:want_assertions_encrypted]
|
|
72
|
+
certs[:encryption].each { |cert, _| add_sp_cert_element(sp_sso, cert, :encryption) }
|
|
88
73
|
end
|
|
89
74
|
|
|
90
75
|
sp_sso
|
|
@@ -153,8 +138,7 @@ module OneLogin
|
|
|
153
138
|
def embed_signature(meta_doc, settings)
|
|
154
139
|
return unless settings.security[:metadata_signed]
|
|
155
140
|
|
|
156
|
-
private_key = settings.
|
|
157
|
-
cert = settings.get_sp_cert
|
|
141
|
+
cert, private_key = settings.get_sp_signing_pair
|
|
158
142
|
return unless private_key && cert
|
|
159
143
|
|
|
160
144
|
meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
@@ -172,6 +156,18 @@ module OneLogin
|
|
|
172
156
|
|
|
173
157
|
ret
|
|
174
158
|
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def add_sp_cert_element(sp_sso, cert, use)
|
|
163
|
+
return unless cert
|
|
164
|
+
cert_text = Base64.encode64(cert.to_der).gsub("\n", '')
|
|
165
|
+
kd = sp_sso.add_element "md:KeyDescriptor", { "use" => use.to_s }
|
|
166
|
+
ki = kd.add_element "ds:KeyInfo", { "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#" }
|
|
167
|
+
xd = ki.add_element "ds:X509Data"
|
|
168
|
+
xc = xd.add_element "ds:X509Certificate"
|
|
169
|
+
xc.text = cert_text
|
|
170
|
+
end
|
|
175
171
|
end
|
|
176
172
|
end
|
|
177
173
|
end
|
|
@@ -915,9 +915,9 @@ module OneLogin
|
|
|
915
915
|
begin
|
|
916
916
|
encrypted_node = xpath_first_from_signed_assertion('/a:Subject/a:EncryptedID')
|
|
917
917
|
if encrypted_node
|
|
918
|
-
|
|
918
|
+
decrypt_nameid(encrypted_node)
|
|
919
919
|
else
|
|
920
|
-
|
|
920
|
+
xpath_first_from_signed_assertion('/a:Subject/a:NameID')
|
|
921
921
|
end
|
|
922
922
|
end
|
|
923
923
|
end
|
|
@@ -969,7 +969,7 @@ module OneLogin
|
|
|
969
969
|
# @return [XMLSecurity::SignedDocument] The SAML Response with the assertion decrypted
|
|
970
970
|
#
|
|
971
971
|
def generate_decrypted_document
|
|
972
|
-
if settings.nil? ||
|
|
972
|
+
if settings.nil? || settings.get_sp_decryption_keys.empty?
|
|
973
973
|
raise ValidationError.new('An EncryptedAssertion found and no SP private key found on the settings to decrypt it. Be sure you provided the :settings parameter at the initialize method')
|
|
974
974
|
end
|
|
975
975
|
|
|
@@ -1012,42 +1012,42 @@ module OneLogin
|
|
|
1012
1012
|
end
|
|
1013
1013
|
|
|
1014
1014
|
# Decrypts an EncryptedID element
|
|
1015
|
-
# @param
|
|
1015
|
+
# @param encrypted_id_node [REXML::Element] The EncryptedID element
|
|
1016
1016
|
# @return [REXML::Document] The decrypted EncrypedtID element
|
|
1017
1017
|
#
|
|
1018
|
-
def decrypt_nameid(
|
|
1019
|
-
decrypt_element(
|
|
1018
|
+
def decrypt_nameid(encrypted_id_node)
|
|
1019
|
+
decrypt_element(encrypted_id_node, /(.*<\/(\w+:)?NameID>)/m)
|
|
1020
1020
|
end
|
|
1021
1021
|
|
|
1022
|
-
# Decrypts an
|
|
1023
|
-
# @param
|
|
1024
|
-
# @return [REXML::Document] The decrypted
|
|
1022
|
+
# Decrypts an EncryptedAttribute element
|
|
1023
|
+
# @param encrypted_attribute_node [REXML::Element] The EncryptedAttribute element
|
|
1024
|
+
# @return [REXML::Document] The decrypted EncryptedAttribute element
|
|
1025
1025
|
#
|
|
1026
|
-
def decrypt_attribute(
|
|
1027
|
-
decrypt_element(
|
|
1026
|
+
def decrypt_attribute(encrypted_attribute_node)
|
|
1027
|
+
decrypt_element(encrypted_attribute_node, /(.*<\/(\w+:)?Attribute>)/m)
|
|
1028
1028
|
end
|
|
1029
1029
|
|
|
1030
1030
|
# Decrypt an element
|
|
1031
|
-
# @param
|
|
1032
|
-
# @param
|
|
1031
|
+
# @param encrypt_node [REXML::Element] The encrypted element
|
|
1032
|
+
# @param regexp [Regexp] The regular expression to extract the decrypted data
|
|
1033
1033
|
# @return [REXML::Document] The decrypted element
|
|
1034
1034
|
#
|
|
1035
|
-
def decrypt_element(encrypt_node,
|
|
1036
|
-
if settings.nil? ||
|
|
1035
|
+
def decrypt_element(encrypt_node, regexp)
|
|
1036
|
+
if settings.nil? || settings.get_sp_decryption_keys.empty?
|
|
1037
1037
|
raise ValidationError.new('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it')
|
|
1038
1038
|
end
|
|
1039
1039
|
|
|
1040
|
-
|
|
1041
1040
|
if encrypt_node.name == 'EncryptedAttribute'
|
|
1042
1041
|
node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
|
|
1043
1042
|
else
|
|
1044
1043
|
node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">'
|
|
1045
1044
|
end
|
|
1046
1045
|
|
|
1047
|
-
elem_plaintext = OneLogin::RubySaml::Utils.
|
|
1046
|
+
elem_plaintext = OneLogin::RubySaml::Utils.decrypt_multi(encrypt_node, settings.get_sp_decryption_keys)
|
|
1047
|
+
|
|
1048
1048
|
# If we get some problematic noise in the plaintext after decrypting.
|
|
1049
1049
|
# This quick regexp parse will grab only the Element and discard the noise.
|
|
1050
|
-
elem_plaintext = elem_plaintext.match(
|
|
1050
|
+
elem_plaintext = elem_plaintext.match(regexp)[0]
|
|
1051
1051
|
|
|
1052
1052
|
# To avoid namespace errors if saml namespace is not defined
|
|
1053
1053
|
# create a parent node first with the namespace defined
|
|
@@ -60,8 +60,8 @@ module OneLogin
|
|
|
60
60
|
attr_accessor :attributes_index
|
|
61
61
|
attr_accessor :force_authn
|
|
62
62
|
attr_accessor :certificate
|
|
63
|
-
attr_accessor :certificate_new
|
|
64
63
|
attr_accessor :private_key
|
|
64
|
+
attr_accessor :sp_cert_multi
|
|
65
65
|
attr_accessor :authn_context
|
|
66
66
|
attr_accessor :authn_context_comparison
|
|
67
67
|
attr_accessor :authn_context_decl_ref
|
|
@@ -70,6 +70,7 @@ module OneLogin
|
|
|
70
70
|
attr_accessor :security
|
|
71
71
|
attr_accessor :soft
|
|
72
72
|
# Deprecated
|
|
73
|
+
attr_accessor :certificate_new
|
|
73
74
|
attr_accessor :assertion_consumer_logout_service_url
|
|
74
75
|
attr_reader :assertion_consumer_logout_service_binding
|
|
75
76
|
attr_accessor :issuer
|
|
@@ -180,10 +181,7 @@ module OneLogin
|
|
|
180
181
|
# @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it)
|
|
181
182
|
#
|
|
182
183
|
def get_idp_cert
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
|
|
186
|
-
OpenSSL::X509::Certificate.new(formatted_cert)
|
|
184
|
+
OneLogin::RubySaml::Utils.build_cert_object(idp_cert)
|
|
187
185
|
end
|
|
188
186
|
|
|
189
187
|
# @return [Hash with 2 arrays of OpenSSL::X509::Certificate] Build multiple IdP certificates from the settings.
|
|
@@ -191,7 +189,7 @@ module OneLogin
|
|
|
191
189
|
def get_idp_cert_multi
|
|
192
190
|
return nil if idp_cert_multi.nil? || idp_cert_multi.empty?
|
|
193
191
|
|
|
194
|
-
raise ArgumentError.new("Invalid value for idp_cert_multi")
|
|
192
|
+
raise ArgumentError.new("Invalid value for idp_cert_multi") unless idp_cert_multi.is_a?(Hash)
|
|
195
193
|
|
|
196
194
|
certs = {:signing => [], :encryption => [] }
|
|
197
195
|
|
|
@@ -200,49 +198,70 @@ module OneLogin
|
|
|
200
198
|
next if !certs_for_type || certs_for_type.empty?
|
|
201
199
|
|
|
202
200
|
certs_for_type.each do |idp_cert|
|
|
203
|
-
|
|
204
|
-
certs[type].push(OpenSSL::X509::Certificate.new(formatted_cert))
|
|
201
|
+
certs[type].push(OneLogin::RubySaml::Utils.build_cert_object(idp_cert))
|
|
205
202
|
end
|
|
206
203
|
end
|
|
207
204
|
|
|
208
205
|
certs
|
|
209
206
|
end
|
|
210
207
|
|
|
211
|
-
# @return [OpenSSL::X509::Certificate
|
|
212
|
-
#
|
|
213
|
-
|
|
214
|
-
|
|
208
|
+
# @return [Hash<Symbol, Array<Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>>>]
|
|
209
|
+
# Build the SP certificates and private keys from the settings. If
|
|
210
|
+
# check_sp_cert_expiration is true, only returns certificates and private keys
|
|
211
|
+
# that are not expired.
|
|
212
|
+
def get_sp_certs
|
|
213
|
+
certs = get_all_sp_certs
|
|
214
|
+
return certs unless security[:check_sp_cert_expiration]
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
active_certs = { signing: [], encryption: [] }
|
|
217
|
+
certs.each do |use, pairs|
|
|
218
|
+
next if pairs.empty?
|
|
218
219
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
pairs = pairs.select { |cert, _| !cert || OneLogin::RubySaml::Utils.is_cert_active(cert) }
|
|
221
|
+
raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.") if pairs.empty?
|
|
222
|
+
|
|
223
|
+
active_certs[use] = pairs.freeze
|
|
223
224
|
end
|
|
225
|
+
active_certs.freeze
|
|
226
|
+
end
|
|
224
227
|
|
|
225
|
-
|
|
228
|
+
# @return [Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>]
|
|
229
|
+
# The SP signing certificate and private key.
|
|
230
|
+
def get_sp_signing_pair
|
|
231
|
+
get_sp_certs[:signing].first
|
|
226
232
|
end
|
|
227
233
|
|
|
228
|
-
# @return [OpenSSL::X509::Certificate
|
|
229
|
-
#
|
|
230
|
-
def
|
|
231
|
-
|
|
234
|
+
# @return [OpenSSL::X509::Certificate] The SP signing certificate.
|
|
235
|
+
# @deprecated Use get_sp_signing_pair or get_sp_certs instead.
|
|
236
|
+
def get_sp_cert
|
|
237
|
+
node = get_sp_signing_pair
|
|
238
|
+
node[0] if node
|
|
239
|
+
end
|
|
232
240
|
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
# @return [OpenSSL::PKey::RSA] The SP signing key.
|
|
242
|
+
def get_sp_signing_key
|
|
243
|
+
node = get_sp_signing_pair
|
|
244
|
+
node[1] if node
|
|
235
245
|
end
|
|
236
246
|
|
|
237
|
-
# @
|
|
238
|
-
|
|
239
|
-
def get_sp_key
|
|
240
|
-
return nil if private_key.nil? || private_key.empty?
|
|
247
|
+
# @deprecated Use get_sp_signing_key or get_sp_certs instead.
|
|
248
|
+
alias_method :get_sp_key, :get_sp_signing_key
|
|
241
249
|
|
|
242
|
-
|
|
243
|
-
|
|
250
|
+
# @return [Array<OpenSSL::PKey::RSA>] The SP decryption keys.
|
|
251
|
+
def get_sp_decryption_keys
|
|
252
|
+
ary = get_sp_certs[:encryption].map { |pair| pair[1] }
|
|
253
|
+
ary.compact!
|
|
254
|
+
ary.uniq!(&:to_pem)
|
|
255
|
+
ary.freeze
|
|
244
256
|
end
|
|
245
257
|
|
|
258
|
+
# @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings.
|
|
259
|
+
#
|
|
260
|
+
# @deprecated Use get_sp_certs instead
|
|
261
|
+
def get_sp_cert_new
|
|
262
|
+
node = get_sp_certs[:signing].last
|
|
263
|
+
node[0] if node
|
|
264
|
+
end
|
|
246
265
|
|
|
247
266
|
def idp_binding_from_embed_sign
|
|
248
267
|
security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect]
|
|
@@ -280,6 +299,85 @@ module OneLogin
|
|
|
280
299
|
:lowercase_url_encoding => false
|
|
281
300
|
}.freeze
|
|
282
301
|
}.freeze
|
|
302
|
+
|
|
303
|
+
private
|
|
304
|
+
|
|
305
|
+
# @return [Hash<Symbol, Array<Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>>>]
|
|
306
|
+
# Build the SP certificates and private keys from the settings. Returns all
|
|
307
|
+
# certificates and private keys, even if they are expired.
|
|
308
|
+
def get_all_sp_certs
|
|
309
|
+
validate_sp_certs_params!
|
|
310
|
+
get_sp_certs_multi || get_sp_certs_single
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Validate certificate, certificate_new, private_key, and sp_cert_multi params.
|
|
314
|
+
def validate_sp_certs_params!
|
|
315
|
+
multi = sp_cert_multi && !sp_cert_multi.empty?
|
|
316
|
+
cert = certificate && !certificate.empty?
|
|
317
|
+
cert_new = certificate_new && !certificate_new.empty?
|
|
318
|
+
pk = private_key && !private_key.empty?
|
|
319
|
+
if multi && (cert || cert_new || pk)
|
|
320
|
+
raise ArgumentError.new("Cannot specify both sp_cert_multi and certificate, certificate_new, private_key parameters")
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Get certs from certificate, certificate_new, and private_key parameters.
|
|
325
|
+
def get_sp_certs_single
|
|
326
|
+
certs = { :signing => [], :encryption => [] }
|
|
327
|
+
|
|
328
|
+
sp_key = OneLogin::RubySaml::Utils.build_private_key_object(private_key)
|
|
329
|
+
cert = OneLogin::RubySaml::Utils.build_cert_object(certificate)
|
|
330
|
+
if cert || sp_key
|
|
331
|
+
ary = [cert, sp_key].freeze
|
|
332
|
+
certs[:signing] << ary
|
|
333
|
+
certs[:encryption] << ary
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
cert_new = OneLogin::RubySaml::Utils.build_cert_object(certificate_new)
|
|
337
|
+
if cert_new
|
|
338
|
+
ary = [cert_new, sp_key].freeze
|
|
339
|
+
certs[:signing] << ary
|
|
340
|
+
certs[:encryption] << ary
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
certs
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Get certs from get_sp_cert_multi parameter.
|
|
347
|
+
def get_sp_certs_multi
|
|
348
|
+
return if sp_cert_multi.nil? || sp_cert_multi.empty?
|
|
349
|
+
|
|
350
|
+
raise ArgumentError.new("sp_cert_multi must be a Hash") unless sp_cert_multi.is_a?(Hash)
|
|
351
|
+
|
|
352
|
+
certs = { :signing => [], :encryption => [] }.freeze
|
|
353
|
+
|
|
354
|
+
[:signing, :encryption].each do |type|
|
|
355
|
+
certs_for_type = sp_cert_multi[type] || sp_cert_multi[type.to_s]
|
|
356
|
+
next if !certs_for_type || certs_for_type.empty?
|
|
357
|
+
|
|
358
|
+
unless certs_for_type.is_a?(Array) && certs_for_type.all? { |cert| cert.is_a?(Hash) }
|
|
359
|
+
raise ArgumentError.new("sp_cert_multi :#{type} node must be an Array of Hashes")
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
certs_for_type.each do |pair|
|
|
363
|
+
cert = pair[:certificate] || pair['certificate'] || pair[:cert] || pair['cert']
|
|
364
|
+
key = pair[:private_key] || pair['private_key'] || pair[:key] || pair['key']
|
|
365
|
+
|
|
366
|
+
unless cert && key
|
|
367
|
+
raise ArgumentError.new("sp_cert_multi :#{type} node Hashes must specify keys :certificate and :private_key")
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
certs[type] << [
|
|
371
|
+
OneLogin::RubySaml::Utils.build_cert_object(cert),
|
|
372
|
+
OneLogin::RubySaml::Utils.build_private_key_object(key)
|
|
373
|
+
].freeze
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
certs.each { |_, ary| ary.freeze }
|
|
378
|
+
certs
|
|
379
|
+
end
|
|
283
380
|
end
|
|
284
381
|
end
|
|
285
382
|
end
|
|
383
|
+
|
|
@@ -91,16 +91,16 @@ module OneLogin
|
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
# Decrypts an EncryptedID element
|
|
94
|
-
# @param
|
|
94
|
+
# @param encrypted_id_node [REXML::Element] The EncryptedID element
|
|
95
95
|
# @return [REXML::Document] The decrypted EncrypedtID element
|
|
96
96
|
#
|
|
97
|
-
def decrypt_nameid(
|
|
97
|
+
def decrypt_nameid(encrypted_id_node)
|
|
98
98
|
|
|
99
|
-
if settings.nil? ||
|
|
100
|
-
raise ValidationError.new('An ' +
|
|
99
|
+
if settings.nil? || settings.get_sp_decryption_keys.empty?
|
|
100
|
+
raise ValidationError.new('An ' + encrypted_id_node.name + ' found and no SP private key found on the settings to decrypt it')
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
elem_plaintext = OneLogin::RubySaml::Utils.
|
|
103
|
+
elem_plaintext = OneLogin::RubySaml::Utils.decrypt_multi(encrypted_id_node, settings.get_sp_decryption_keys)
|
|
104
104
|
# If we get some problematic noise in the plaintext after decrypting.
|
|
105
105
|
# This quick regexp parse will grab only the Element and discard the noise.
|
|
106
106
|
elem_plaintext = elem_plaintext.match(/(.*<\/(\w+:)?NameID>)/m)[0]
|
|
@@ -78,9 +78,10 @@ module OneLogin
|
|
|
78
78
|
response = deflate(response) if settings.compress_response
|
|
79
79
|
base64_response = encode(response)
|
|
80
80
|
response_params = {"SAMLResponse" => base64_response}
|
|
81
|
+
sp_signing_key = settings.get_sp_signing_key
|
|
81
82
|
|
|
82
|
-
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] &&
|
|
83
|
-
params['SigAlg']
|
|
83
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && sp_signing_key
|
|
84
|
+
params['SigAlg'] = settings.security[:signature_method]
|
|
84
85
|
url_string = OneLogin::RubySaml::Utils.build_query(
|
|
85
86
|
:type => 'SAMLResponse',
|
|
86
87
|
:data => base64_response,
|
|
@@ -88,7 +89,7 @@ module OneLogin
|
|
|
88
89
|
:sig_alg => params['SigAlg']
|
|
89
90
|
)
|
|
90
91
|
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
|
91
|
-
signature =
|
|
92
|
+
signature = sp_signing_key.sign(sign_algorithm.new, url_string)
|
|
92
93
|
params['Signature'] = encode(signature)
|
|
93
94
|
end
|
|
94
95
|
|
|
@@ -150,9 +151,8 @@ module OneLogin
|
|
|
150
151
|
|
|
151
152
|
def sign_document(document, settings)
|
|
152
153
|
# embed signature
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
cert = settings.get_sp_cert
|
|
154
|
+
cert, private_key = settings.get_sp_signing_pair
|
|
155
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && private_key && cert
|
|
156
156
|
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
|
157
157
|
end
|
|
158
158
|
|
|
@@ -34,16 +34,24 @@ module OneLogin
|
|
|
34
34
|
$)x.freeze
|
|
35
35
|
UUID_PREFIX = '_'
|
|
36
36
|
|
|
37
|
-
# Checks if the x509 cert provided is expired
|
|
38
|
-
#
|
|
39
|
-
# @param cert [Certificate] The x509 certificate
|
|
37
|
+
# Checks if the x509 cert provided is expired.
|
|
40
38
|
#
|
|
39
|
+
# @param cert [OpenSSL::X509::Certificate|String] The x509 certificate.
|
|
40
|
+
# @return [true|false] Whether the certificate is expired.
|
|
41
41
|
def self.is_cert_expired(cert)
|
|
42
|
-
if cert.is_a?(String)
|
|
43
|
-
cert = OpenSSL::X509::Certificate.new(cert)
|
|
44
|
-
end
|
|
42
|
+
cert = OpenSSL::X509::Certificate.new(cert) if cert.is_a?(String)
|
|
45
43
|
|
|
46
|
-
|
|
44
|
+
cert.not_after < Time.now
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Checks if the x509 cert provided has both started and has not expired.
|
|
48
|
+
#
|
|
49
|
+
# @param cert [OpenSSL::X509::Certificate|String] The x509 certificate.
|
|
50
|
+
# @return [true|false] Whether the certificate is currently active.
|
|
51
|
+
def self.is_cert_active(cert)
|
|
52
|
+
cert = OpenSSL::X509::Certificate.new(cert) if cert.is_a?(String)
|
|
53
|
+
now = Time.now
|
|
54
|
+
cert.not_before <= now && cert.not_after >= now
|
|
47
55
|
end
|
|
48
56
|
|
|
49
57
|
# Interprets a ISO8601 duration value relative to a given timestamp.
|
|
@@ -61,20 +69,26 @@ module OneLogin
|
|
|
61
69
|
matches = duration.match(DURATION_FORMAT)
|
|
62
70
|
|
|
63
71
|
if matches.nil?
|
|
64
|
-
raise
|
|
72
|
+
raise StandardError.new("Invalid ISO 8601 duration")
|
|
65
73
|
end
|
|
66
74
|
|
|
67
75
|
sign = matches[1] == '-' ? -1 : 1
|
|
68
76
|
|
|
69
77
|
durYears, durMonths, durDays, durHours, durMinutes, durSeconds, durWeeks =
|
|
70
|
-
matches[2..8].map
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
matches[2..8].map do |match|
|
|
79
|
+
if match
|
|
80
|
+
match = match.tr(',', '.').gsub(/\.0*\z/, '')
|
|
81
|
+
sign * (match.include?('.') ? match.to_f : match.to_i)
|
|
82
|
+
else
|
|
83
|
+
0
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
datetime = Time.at(timestamp).utc.to_datetime
|
|
88
|
+
datetime = datetime.next_year(durYears)
|
|
89
|
+
datetime = datetime.next_month(durMonths)
|
|
90
|
+
datetime = datetime.next_day((7*durWeeks) + durDays)
|
|
91
|
+
datetime.to_time.utc.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds
|
|
78
92
|
end
|
|
79
93
|
|
|
80
94
|
# Return a properly formatted x509 certificate
|
|
@@ -128,6 +142,28 @@ module OneLogin
|
|
|
128
142
|
"-----BEGIN #{key_label}-----\n#{key}\n-----END #{key_label}-----"
|
|
129
143
|
end
|
|
130
144
|
|
|
145
|
+
# Given a certificate string, return an OpenSSL::X509::Certificate object.
|
|
146
|
+
#
|
|
147
|
+
# @param cert [String] The original certificate
|
|
148
|
+
# @return [OpenSSL::X509::Certificate] The certificate object
|
|
149
|
+
#
|
|
150
|
+
def self.build_cert_object(cert)
|
|
151
|
+
return nil if cert.nil? || cert.empty?
|
|
152
|
+
|
|
153
|
+
OpenSSL::X509::Certificate.new(format_cert(cert))
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Given a private key string, return an OpenSSL::PKey::RSA object.
|
|
157
|
+
#
|
|
158
|
+
# @param cert [String] The original private key
|
|
159
|
+
# @return [OpenSSL::PKey::RSA] The private key object
|
|
160
|
+
#
|
|
161
|
+
def self.build_private_key_object(private_key)
|
|
162
|
+
return nil if private_key.nil? || private_key.empty?
|
|
163
|
+
|
|
164
|
+
OpenSSL::PKey::RSA.new(format_private_key(private_key))
|
|
165
|
+
end
|
|
166
|
+
|
|
131
167
|
# Build the Query String signature that will be used in the HTTP-Redirect binding
|
|
132
168
|
# to generate the Signature
|
|
133
169
|
# @param params [Hash] Parameters to build the Query String
|
|
@@ -199,7 +235,7 @@ module OneLogin
|
|
|
199
235
|
|
|
200
236
|
# Validate the Signature parameter sent on the HTTP-Redirect binding
|
|
201
237
|
# @param params [Hash] Parameters to be used in the validation process
|
|
202
|
-
# @option params [OpenSSL::X509::Certificate] cert The
|
|
238
|
+
# @option params [OpenSSL::X509::Certificate] cert The IDP public certificate
|
|
203
239
|
# @option params [String] sig_alg The SigAlg parameter
|
|
204
240
|
# @option params [String] signature The Signature parameter (base64 encoded)
|
|
205
241
|
# @option params [String] query_string The full GET Query String to be compared
|
|
@@ -236,9 +272,29 @@ module OneLogin
|
|
|
236
272
|
error_msg
|
|
237
273
|
end
|
|
238
274
|
|
|
275
|
+
# Obtains the decrypted string from an Encrypted node element in XML,
|
|
276
|
+
# given multiple private keys to try.
|
|
277
|
+
# @param encrypted_node [REXML::Element] The Encrypted element
|
|
278
|
+
# @param private_keys [Array<OpenSSL::PKey::RSA>] The Service provider private key
|
|
279
|
+
# @return [String] The decrypted data
|
|
280
|
+
def self.decrypt_multi(encrypted_node, private_keys)
|
|
281
|
+
raise ArgumentError.new('private_keys must be specified') if !private_keys || private_keys.empty?
|
|
282
|
+
|
|
283
|
+
error = nil
|
|
284
|
+
private_keys.each do |key|
|
|
285
|
+
begin
|
|
286
|
+
return decrypt_data(encrypted_node, key)
|
|
287
|
+
rescue OpenSSL::PKey::PKeyError => e
|
|
288
|
+
error ||= e
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
raise(error) if error
|
|
293
|
+
end
|
|
294
|
+
|
|
239
295
|
# Obtains the decrypted string from an Encrypted node element in XML
|
|
240
|
-
# @param encrypted_node [REXML::Element]
|
|
241
|
-
# @param private_key
|
|
296
|
+
# @param encrypted_node [REXML::Element] The Encrypted element
|
|
297
|
+
# @param private_key [OpenSSL::PKey::RSA] The Service provider private key
|
|
242
298
|
# @return [String] The decrypted data
|
|
243
299
|
def self.decrypt_data(encrypted_node, private_key)
|
|
244
300
|
encrypt_data = REXML::XPath.first(
|
|
@@ -302,7 +358,7 @@ module OneLogin
|
|
|
302
358
|
|
|
303
359
|
# Obtains the deciphered text
|
|
304
360
|
# @param cipher_text [String] The ciphered text
|
|
305
|
-
# @param symmetric_key [String] The
|
|
361
|
+
# @param symmetric_key [String] The symmetric key used to encrypt the text
|
|
306
362
|
# @param algorithm [String] The encrypted algorithm
|
|
307
363
|
# @return [String] The deciphered text
|
|
308
364
|
def self.retrieve_plaintext(cipher_text, symmetric_key, algorithm)
|
data/lib/xml_security.rb
CHANGED
|
@@ -310,17 +310,30 @@ module XMLSecurity
|
|
|
310
310
|
canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
|
|
311
311
|
noko_sig_element.remove
|
|
312
312
|
|
|
313
|
+
# get signed info
|
|
314
|
+
signed_info_element = REXML::XPath.first(
|
|
315
|
+
sig_element,
|
|
316
|
+
"./ds:SignedInfo",
|
|
317
|
+
{ "ds" => DSIG }
|
|
318
|
+
)
|
|
319
|
+
|
|
313
320
|
# get inclusive namespaces
|
|
314
321
|
inclusive_namespaces = extract_inclusive_namespaces
|
|
315
322
|
|
|
316
323
|
# check digests
|
|
317
|
-
ref = REXML::XPath.first(
|
|
324
|
+
ref = REXML::XPath.first(signed_info_element, "./ds:Reference", {"ds"=>DSIG})
|
|
318
325
|
|
|
319
|
-
|
|
326
|
+
reference_nodes = document.xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id })
|
|
327
|
+
|
|
328
|
+
if reference_nodes.length > 1 # ensures no elements with same ID to prevent signature wrapping attack.
|
|
329
|
+
return append_error("Digest mismatch. Duplicated ID found", soft)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
hashed_element = reference_nodes[0]
|
|
320
333
|
|
|
321
334
|
canon_algorithm = canon_algorithm REXML::XPath.first(
|
|
322
|
-
|
|
323
|
-
'
|
|
335
|
+
signed_info_element,
|
|
336
|
+
'./ds:CanonicalizationMethod',
|
|
324
337
|
{ "ds" => DSIG }
|
|
325
338
|
)
|
|
326
339
|
|
|
@@ -330,13 +343,13 @@ module XMLSecurity
|
|
|
330
343
|
|
|
331
344
|
digest_algorithm = algorithm(REXML::XPath.first(
|
|
332
345
|
ref,
|
|
333
|
-
"
|
|
346
|
+
"./ds:DigestMethod",
|
|
334
347
|
{ "ds" => DSIG }
|
|
335
348
|
))
|
|
336
349
|
hash = digest_algorithm.digest(canon_hashed_element)
|
|
337
350
|
encoded_digest_value = REXML::XPath.first(
|
|
338
351
|
ref,
|
|
339
|
-
"
|
|
352
|
+
"./ds:DigestValue",
|
|
340
353
|
{ "ds" => DSIG }
|
|
341
354
|
)
|
|
342
355
|
digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value))
|
|
@@ -362,7 +375,7 @@ module XMLSecurity
|
|
|
362
375
|
def process_transforms(ref, canon_algorithm)
|
|
363
376
|
transforms = REXML::XPath.match(
|
|
364
377
|
ref,
|
|
365
|
-
"
|
|
378
|
+
"./ds:Transforms/ds:Transform",
|
|
366
379
|
{ "ds" => DSIG }
|
|
367
380
|
)
|
|
368
381
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby-saml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.17.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SAML Toolkit
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2024-09-10 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: nokogiri
|
|
@@ -183,6 +183,7 @@ extra_rdoc_files:
|
|
|
183
183
|
- README.md
|
|
184
184
|
files:
|
|
185
185
|
- ".document"
|
|
186
|
+
- ".github/FUNDING.yml"
|
|
186
187
|
- ".github/workflows/test.yml"
|
|
187
188
|
- ".gitignore"
|
|
188
189
|
- CHANGELOG.md
|
|
@@ -247,7 +248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
247
248
|
- !ruby/object:Gem::Version
|
|
248
249
|
version: '0'
|
|
249
250
|
requirements: []
|
|
250
|
-
rubygems_version: 3.
|
|
251
|
+
rubygems_version: 3.5.18
|
|
251
252
|
signing_key:
|
|
252
253
|
specification_version: 4
|
|
253
254
|
summary: SAML Ruby Tookit
|