Skip to content

Commit

Permalink
Move XMLSecurity to RubySaml::XML
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnyshields committed Jul 9, 2024
1 parent a3d2045 commit 4301bab
Show file tree
Hide file tree
Showing 30 changed files with 623 additions and 610 deletions.
1 change: 0 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,3 @@ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ but it can be done as follows:
* Provide the XML to the parse method if the signature was validated
```ruby
require "xml_security"
require "ruby_saml/xml"
require "ruby_saml/utils"
require "ruby_saml/idp_metadata_parser"
Expand All @@ -431,7 +431,7 @@ get.basic_auth uri.user, uri.password if uri.user
response = http.request(get)
xml = response.body
errors = []
doc = XMLSecurity::SignedDocument.new(xml, errors)
doc = RubySaml::XML::SignedDocument.new(xml, errors)
cert_str = "<include_cert_here>"
cert = RubySaml::Utils.format_cert("cert_str")
metadata_sign_cert = OpenSSL::X509::Certificate.new(cert)
Expand Down Expand Up @@ -634,8 +634,8 @@ to specify different certificates for each function.
You may also globally set the SP signature and digest method, to be used in SP signing (functions 1 and 2 above):
```ruby
settings.security[:digest_method] = XMLSecurity::Document::SHA1
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
settings.security[:digest_method] = RubySaml::XML::Document::SHA1
settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1
```
#### Signing SP Metadata
Expand Down Expand Up @@ -979,3 +979,14 @@ end
# Output XML with custom metadata
MyMetadata.new.generate(settings)
```
## Attribution
Portions of the code in `RubySaml::XML` namespace is adapted from earlier work
copyrighted by either Oracle and/or Todd W. Saxton. The original code was distributed
under the Common Development and Distribution License (CDDL) 1.0. This code is planned to
be written entirely in future versions.
## License
RubySaml is made available under the MIT License. Refer to [LICENSE](LICENSE).
30 changes: 21 additions & 9 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,26 @@ Before attempting to upgrade to `2.0.0`:
- Upgrade your project to minimum Ruby 3.0, JRuby 9.4, or TruffleRuby 22.
- Upgrade RubySaml to `1.17.x`. Note that RubySaml `1.17.x` is compatible with up to Ruby 3.3.

### Root namespace changed to RubySaml
### Root "OneLogin" namespace changed to "RubySaml"

RubySaml version `2.0.0` changes the root namespace from `OneLogin::RubySaml::` to just `RubySaml::`. This will require you
to search your codebase for the string `OneLogin::` and remove it as appropriate. Aside from this namespace change,
RubySaml version `2.0.0` changes the root namespace from `OneLogin::RubySaml::` to just `RubySaml::`.
Please remove `OneLogin::` and `onelogin/` everywhere in your codebase. Aside from this namespace change,
the class names themselves have intentionally been kept the same.

For backward compatibility, the alias `OneLogin = Object` has been set, so `OneLogin::RubySaml::` will still work.
This alias will be removed in RubySaml version `2.1.0`.
Note that the project folder structure has also been updated accordingly. Notably, the directory
`lib/onelogin/schemas` is now `lib/ruby_saml/schemas`.

For backward compatibility, the alias `OneLogin = Object` has been set, so `OneLogin::RubySaml::` will still work
as before. This alias will be removed in RubySaml version `2.1.0`.

### Root "XMLSecurity" namespace changed to "RubySaml::XML"

RubySaml version `2.0.0` changes the namespace `RubySaml::XML::` to `RubySaml::XML::`. Please search your
codebase for `RubySaml::XML::` and replace it as appropriate. In addition, you must replace direct usage of
`require 'xml_security'` with `require 'ruby_saml/xml'`.

For backward compatibility, the alias `XMLSecurity = RubySaml::XML` has been set, so `RubySaml::XML::` will still work
as before. This alias will be removed in RubySaml version `2.1.0`.

### Security: Change default hashing algorithm to SHA-256 (was SHA-1)

Expand All @@ -30,9 +42,9 @@ To preserve the old insecure SHA-1 behavior *(not recommended)*, you may set `Ru
```ruby
# Preserve RubySaml 1.x insecure SHA-1 behavior
settings = RubySaml::Settings.new
settings.idp_cert_fingerprint_algorithm = XMLSecurity::Document::SHA1
settings.security[:digest_method] = XMLSecurity::Document::SHA1
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
settings.idp_cert_fingerprint_algorithm = RubySaml::XML::Document::SHA1
settings.security[:digest_method] = RubySaml::XML::Document::SHA1
settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1
```

## Updating from 1.12.x to 1.13.0
Expand Down Expand Up @@ -108,7 +120,7 @@ The new preferred way to provide _SAMLResponse_, _RelayState_, and _SigAlg_ is v
# In this example `query_params` is assumed to contain decoded query parameters,
# and `raw_query_params` is assumed to contain encoded query parameters as sent by the IDP.
settings = {
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
settings.security[:signature_method] = RubySaml::XML::Document::RSA_SHA1
settings.soft = false
}
options = {
Expand Down
4 changes: 3 additions & 1 deletion lib/ruby_saml.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'ruby_saml/logging'
require 'ruby_saml/xml'
require 'ruby_saml/saml_message'
require 'ruby_saml/authrequest'
require 'ruby_saml/logoutrequest'
Expand All @@ -18,5 +19,6 @@
require 'ruby_saml/utils'
require 'ruby_saml/version'

# @deprecated This alias will be removed in version 2.1.0
# @deprecated These aliases add compatibility with v1.x and will be removed in v2.1.0
OneLogin = Object
XMLSecurity = RubySaml::XML
4 changes: 2 additions & 2 deletions lib/ruby_saml/authrequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def create_params(settings, params={})
relay_state: relay_state,
sig_alg: params['SigAlg']
)
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
sign_algorithm = RubySaml::XML::BaseDocument.new.algorithm(settings.security[:signature_method])
signature = sp_signing_key.sign(sign_algorithm.new, url_string)
params['Signature'] = encode(signature)
end
Expand All @@ -108,7 +108,7 @@ def create_authentication_xml_doc(settings)
def create_xml_document(settings)
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")

request_doc = XMLSecurity::Document.new
request_doc = RubySaml::XML::Document.new
request_doc.uuid = uuid

root = request_doc.add_element "samlp:AuthnRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
Expand Down
4 changes: 2 additions & 2 deletions lib/ruby_saml/idp_metadata_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -376,13 +376,13 @@ def certificates

# @return [String|nil] the fingerpint of the X509Certificate if it exists
#
def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA256)
def fingerprint(certificate, fingerprint_algorithm = RubySaml::XML::Document::SHA256)
@fingerprint ||= begin
return unless certificate

cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate))

fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
fingerprint_alg = RubySaml::XML::BaseDocument.new.algorithm(fingerprint_algorithm).new
fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/ruby_saml/logoutrequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def create_params(settings, params={})
relay_state: relay_state,
sig_alg: params['SigAlg']
)
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
sign_algorithm = RubySaml::XML::BaseDocument.new.algorithm(settings.security[:signature_method])
signature = settings.get_sp_signing_key.sign(sign_algorithm.new, url_string)
params['Signature'] = encode(signature)
end
Expand All @@ -105,7 +105,7 @@ def create_logout_request_xml_doc(settings)
def create_xml_document(settings)
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")

request_doc = XMLSecurity::Document.new
request_doc = RubySaml::XML::Document.new
request_doc.uuid = uuid

root = request_doc.add_element "samlp:LogoutRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
Expand Down
5 changes: 2 additions & 3 deletions lib/ruby_saml/logoutresponse.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# frozen_string_literal: true

require "xml_security"
require "ruby_saml/xml"
require "ruby_saml/saml_message"

require "time"

# Only supports SAML 2.0
Expand Down Expand Up @@ -45,7 +44,7 @@ def initialize(response, settings = nil, options = {})

@options = options
@response = decode_raw_saml(response, settings)
@document = XMLSecurity::SignedDocument.new(@response)
@document = RubySaml::XML::SignedDocument.new(@response)
super()
end

Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_saml/metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Metadata
# @return [String] XML Metadata of the Service Provider
#
def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil)
meta_doc = XMLSecurity::Document.new
meta_doc = RubySaml::XML::Document.new
add_xml_declaration(meta_doc)
root = add_root_element(meta_doc, settings, valid_until, cache_duration)
sp_sso = add_sp_sso_element(root, settings)
Expand Down
12 changes: 6 additions & 6 deletions lib/ruby_saml/response.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require "xml_security"
require "ruby_saml/xml"
require "ruby_saml/attributes"

require "time"
Expand Down Expand Up @@ -65,7 +65,7 @@ def initialize(response, options = {})
end

@response = decode_raw_saml(response, settings)
@document = XMLSecurity::SignedDocument.new(@response, @errors)
@document = RubySaml::XML::SignedDocument.new(@response, @errors)

if assertion_encrypted?
@decrypted_document = generate_decrypted_document
Expand Down Expand Up @@ -951,7 +951,7 @@ def xpath_from_signed_assertion(subelt=nil)
end

# Generates the decrypted_document
# @return [XMLSecurity::SignedDocument] The SAML Response with the assertion decrypted
# @return [RubySaml::XML::SignedDocument] The SAML Response with the assertion decrypted
#
def generate_decrypted_document
if settings.nil? || settings.get_sp_decryption_keys.empty?
Expand All @@ -964,8 +964,8 @@ def generate_decrypted_document
end

# Obtains a SAML Response with the EncryptedAssertion element decrypted
# @param document_copy [XMLSecurity::SignedDocument] A copy of the original SAML Response with the encrypted assertion
# @return [XMLSecurity::SignedDocument] The SAML Response with the assertion decrypted
# @param document_copy [RubySaml::XML::SignedDocument] A copy of the original SAML Response with the encrypted assertion
# @return [RubySaml::XML::SignedDocument] The SAML Response with the assertion decrypted
#
def decrypt_assertion_from_document(document_copy)
response_node = REXML::XPath.first(
Expand All @@ -980,7 +980,7 @@ def decrypt_assertion_from_document(document_copy)
)
response_node.add(decrypt_assertion(encrypted_assertion_node))
encrypted_assertion_node.remove
XMLSecurity::SignedDocument.new(response_node.to_s)
RubySaml::XML::SignedDocument.new(response_node.to_s)
end

# Decrypts an EncryptedAssertion element
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_saml/saml_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def id(document)
def valid_saml?(document, soft = true)
begin
xml = Nokogiri::XML(document.to_s) do |config|
config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
config.options = RubySaml::XML::BaseDocument::NOKOGIRI_OPTIONS
end
rescue StandardError => error
return false if soft
Expand Down
10 changes: 5 additions & 5 deletions lib/ruby_saml/settings.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require "xml_security"
require "ruby_saml/xml"
require "ruby_saml/attribute_service"
require "ruby_saml/utils"
require "ruby_saml/validation_error"
Expand Down Expand Up @@ -173,7 +173,7 @@ def get_fingerprint
idp_cert_fingerprint || begin
idp_cert = get_idp_cert
if idp_cert
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(idp_cert_fingerprint_algorithm).new
fingerprint_alg = RubySaml::XML::BaseDocument.new.algorithm(idp_cert_fingerprint_algorithm).new
fingerprint_alg.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":")
end
end
Expand Down Expand Up @@ -277,7 +277,7 @@ def get_binding(value)
DEFAULTS = {
assertion_consumer_service_binding: Utils::BINDINGS[:post],
single_logout_service_binding: Utils::BINDINGS[:redirect],
idp_cert_fingerprint_algorithm: XMLSecurity::Document::SHA256,
idp_cert_fingerprint_algorithm: RubySaml::XML::Document::SHA256,
compress_request: true,
compress_response: true,
message_max_bytesize: 250_000,
Expand All @@ -292,8 +292,8 @@ def get_binding(value)
want_name_id: false,
metadata_signed: false,
embed_sign: false, # Deprecated
digest_method: XMLSecurity::Document::SHA256,
signature_method: XMLSecurity::Document::RSA_SHA256,
digest_method: RubySaml::XML::Document::SHA256,
signature_method: RubySaml::XML::Document::RSA_SHA256,
check_idp_cert_expiration: false,
check_sp_cert_expiration: false,
strict_audience_validation: false,
Expand Down
4 changes: 2 additions & 2 deletions lib/ruby_saml/slo_logoutresponse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def create_params(settings, request_id = nil, logout_message = nil, params = {},
relay_state: relay_state,
sig_alg: params['SigAlg']
)
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
sign_algorithm = RubySaml::XML::BaseDocument.new.algorithm(settings.security[:signature_method])
signature = sp_signing_key.sign(sign_algorithm.new, url_string)
params['Signature'] = encode(signature)
end
Expand All @@ -117,7 +117,7 @@ def create_logout_response_xml_doc(settings, request_id = nil, logout_message =
def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')

response_doc = XMLSecurity::Document.new
response_doc = RubySaml::XML::Document.new
response_doc.uuid = uuid

destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_saml/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def self.escape_request_param(param, lowercase_url_encoding)
#
def self.verify_signature(params)
cert, sig_alg, signature, query_string = %i[cert sig_alg signature query_string].map { |k| params[k]}
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(sig_alg)
signature_algorithm = RubySaml::XML::BaseDocument.new.algorithm(sig_alg)
cert.public_key.verify(signature_algorithm.new, Base64.decode64(signature), query_string)
end

Expand Down
5 changes: 5 additions & 0 deletions lib/ruby_saml/xml.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

require 'ruby_saml/xml/base_document'
require 'ruby_saml/xml/document'
require 'ruby_saml/xml/signed_document'
56 changes: 56 additions & 0 deletions lib/ruby_saml/xml/base_document.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# frozen_string_literal: true

require 'rexml/document'
require 'rexml/xpath'
require 'nokogiri'
require 'openssl'
require 'digest/sha1'
require 'digest/sha2'

module RubySaml
module XML
class BaseDocument < REXML::Document
REXML::Document.entity_expansion_limit = 0

C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#'
DSIG = 'http://www.w3.org/2000/09/xmldsig#'
NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT |
Nokogiri::XML::ParseOptions::NONET

def canon_algorithm(element)
algorithm = element
if algorithm.is_a?(REXML::Element)
algorithm = element.attribute('Algorithm').value
end

case algorithm
when 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315',
'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments'
Nokogiri::XML::XML_C14N_1_0
when 'http://www.w3.org/2006/12/xml-c14n11',
'http://www.w3.org/2006/12/xml-c14n11#WithComments'
Nokogiri::XML::XML_C14N_1_1
else
Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
end
end

def algorithm(element)
algorithm = element
if algorithm.is_a?(REXML::Element)
algorithm = element.attribute('Algorithm').value
end

algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && ::Regexp.last_match(2).to_i

case algorithm
when 1 then OpenSSL::Digest::SHA1
when 384 then OpenSSL::Digest::SHA384
when 512 then OpenSSL::Digest::SHA512
else
OpenSSL::Digest::SHA256
end
end
end
end
end
Loading

0 comments on commit 4301bab

Please sign in to comment.