Comparing changes
v0.3.0
→
v0.3.1
28 commits
67 files changed
Commits
Changed files (67)
lib
saml
kit
bindings
builders
templates
spec
saml
bindings
builders
support
matchers
exe/saml-kit-create-self-signed-certificate
@@ -3,7 +3,7 @@ require 'saml/kit'
puts "Enter Passphrase:"
passphrase = STDIN.read.strip
-certificate, private_key = Saml::Kit::SelfSignedCertificate.new(passphrase).create
+certificate, private_key = ::Xml::Kit::SelfSignedCertificate.new(passphrase).create
puts "** BEGIN File Format **"
print certificate
exe/saml-kit-decode-http-post
@@ -2,7 +2,5 @@
require 'saml/kit'
saml = STDIN.read
-
binding = Saml::Kit::Bindings::HttpPost.new(location: '')
-xml = binding.deserialize('SAMLRequest' => saml).to_xml
-puts Nokogiri::XML(xml).to_xml(indent: 2)
+puts binding.deserialize('SAMLRequest' => saml).to_xml(pretty: true)
exe/saml-kit-decode-http-redirect
@@ -2,6 +2,5 @@
require 'saml/kit'
saml = STDIN.read
-binding = Saml::Kit::HttpRedirectBinding.new(location: '')
-xml = binding.deserialize('SAMLRequest' => saml).to_xml
-puts Nokogiri::XML(xml).to_xml(indent: 2)
+binding = Saml::Kit::Bindings::HttpRedirectBinding.new(location: '')
+puts binding.deserialize('SAMLRequest' => saml).to_xml(pretty: true)
lib/saml/kit/bindings/url_builder.rb
@@ -34,7 +34,7 @@ module Saml
to_query_string(
saml_document.query_string_parameter => serialize(saml_document.to_xml),
'RelayState' => relay_state,
- 'SigAlg' => Saml::Kit::Namespaces::SHA256,
+ 'SigAlg' => ::Xml::Kit::Namespaces::SHA256,
)
end
lib/saml/kit/builders/templates/assertion.builder
@@ -1,29 +1,27 @@
-encryption_for(xml: xml) do |xml|
- xml.Assertion(assertion_options) do
- xml.Issuer issuer
- signature_for(reference_id: reference_id, xml: xml)
- xml.Subject do
- xml.NameID name_id, Format: name_id_format
- xml.SubjectConfirmation Method: Saml::Kit::Namespaces::BEARER do
- xml.SubjectConfirmationData "", subject_confirmation_data_options
- end
+xml.Assertion(assertion_options) do
+ xml.Issuer issuer
+ signature_for(reference_id: reference_id, xml: xml)
+ xml.Subject do
+ xml.NameID name_id, Format: name_id_format
+ xml.SubjectConfirmation Method: Saml::Kit::Namespaces::BEARER do
+ xml.SubjectConfirmationData "", subject_confirmation_data_options
end
- xml.Conditions conditions_options do
- xml.AudienceRestriction do
- xml.Audience request.issuer
- end
+ end
+ xml.Conditions conditions_options do
+ xml.AudienceRestriction do
+ xml.Audience request.issuer
end
- xml.AuthnStatement authn_statement_options do
- xml.AuthnContext do
- xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
- end
+ end
+ xml.AuthnStatement authn_statement_options do
+ xml.AuthnContext do
+ xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
end
- if assertion_attributes.any?
- xml.AttributeStatement do
- assertion_attributes.each do |key, value|
- xml.Attribute Name: key, NameFormat: Saml::Kit::Namespaces::URI, FriendlyName: key do
- xml.AttributeValue value.to_s
- end
+ end
+ if assertion_attributes.any?
+ xml.AttributeStatement do
+ assertion_attributes.each do |key, value|
+ xml.Attribute Name: key, NameFormat: Saml::Kit::Namespaces::URI, FriendlyName: key do
+ xml.AttributeValue value.to_s
end
end
end
lib/saml/kit/builders/templates/certificate.builder
@@ -1,7 +0,0 @@
-xml.KeyDescriptor use: use do
- xml.KeyInfo "xmlns": Saml::Kit::Namespaces::XMLDSIG do
- xml.X509Data do
- xml.X509Certificate stripped
- end
- end
-end
lib/saml/kit/builders/templates/encrypted_assertion.builder
@@ -0,0 +1,5 @@
+xml.EncryptedAssertion xmlns: Saml::Kit::Namespaces::ASSERTION do
+ encryption_for(xml: xml) do |xml|
+ render assertion, xml: xml
+ end
+end
lib/saml/kit/builders/templates/nil_class.builder
lib/saml/kit/builders/templates/xml_encryption.builder
@@ -1,16 +0,0 @@
-xml.EncryptedAssertion xmlns: Saml::Kit::Namespaces::ASSERTION do
- xml.EncryptedData xmlns: Saml::Kit::Namespaces::XMLENC do
- xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
- xml.KeyInfo xmlns: Saml::Kit::Namespaces::XMLDSIG do
- xml.EncryptedKey xmlns: Saml::Kit::Namespaces::XMLENC do
- xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
- xml.CipherData do
- xml.CipherValue Base64.encode64(public_key.public_encrypt(key))
- end
- end
- end
- xml.CipherData do
- xml.CipherValue Base64.encode64(iv + encrypted)
- end
- end
-end
lib/saml/kit/builders/templates/xml_signature.builder
@@ -1,20 +0,0 @@
-xml.Signature "xmlns" => Saml::Kit::Namespaces::XMLDSIG do
- xml.SignedInfo do
- xml.CanonicalizationMethod Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#"
- xml.SignatureMethod Algorithm: signature_method
- xml.Reference URI: "##{reference_id}" do
- xml.Transforms do
- xml.Transform Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
- xml.Transform Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#"
- end
- xml.DigestMethod Algorithm: digest_method
- xml.DigestValue ""
- end
- end
- xml.SignatureValue ""
- xml.KeyInfo do
- xml.X509Data do
- xml.X509Certificate certificate.stripped
- end
- end
-end
lib/saml/kit/builders/assertion.rb
@@ -1,11 +1,12 @@
module Saml
module Kit
module Builders
+ # {include:file:lib/saml/kit/builders/templates/assertion.builder}
class Assertion
- include Templatable
+ include XmlTemplatable
extend Forwardable
- def_delegators :@response_builder, :encrypt, :embed_signature, :request, :issuer, :reference_id, :now, :configuration, :user, :version, :destination, :encryption_certificate
+ def_delegators :@response_builder, :embed_signature, :request, :issuer, :reference_id, :now, :configuration, :user, :version, :destination
def initialize(response_builder)
@response_builder = response_builder
lib/saml/kit/builders/authentication_request.rb
@@ -1,16 +1,17 @@
module Saml
module Kit
module Builders
+ # {include:file:lib/saml/kit/builders/templates/authentication_request.builder}
# {include:file:spec/saml/builders/authentication_request_spec.rb}
class AuthenticationRequest
- include Saml::Kit::Templatable
+ include XmlTemplatable
attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :destination
attr_accessor :version
attr_reader :configuration
def initialize(configuration: Saml::Kit.configuration)
@configuration = configuration
- @id = Id.generate
+ @id = ::Xml::Kit::Id.generate
@issuer = configuration.issuer
@name_id_format = Namespaces::PERSISTENT
@now = Time.now.utc
lib/saml/kit/builders/encrypted_assertion.rb
@@ -0,0 +1,20 @@
+module Saml
+ module Kit
+ module Builders
+ # {include:file:lib/saml/kit/builders/templates/encrypted_assertion.builder}
+ class EncryptedAssertion
+ include XmlTemplatable
+ extend Forwardable
+
+ attr_reader :assertion
+ def_delegators :@response_builder, :configuration, :encryption_certificate
+
+ def initialize(response_builder, assertion)
+ @response_builder = response_builder
+ @assertion = assertion
+ @encrypt = true
+ end
+ end
+ end
+ end
+end
lib/saml/kit/builders/identity_provider_metadata.rb
@@ -1,9 +1,10 @@
module Saml
module Kit
module Builders
+ # {include:file:lib/saml/kit/builders/templates/identity_provider_metadata.builder}
# {include:file:spec/saml/builders/identity_provider_metadata_spec.rb}
class IdentityProviderMetadata
- include Saml::Kit::Templatable
+ include XmlTemplatable
extend Forwardable
attr_accessor :attributes, :name_id_formats
attr_accessor :want_authn_requests_signed
@@ -16,7 +17,7 @@ module Saml
@attributes = []
@configuration = configuration
@entity_id = configuration.issuer
- @id = Id.generate
+ @id = ::Xml::Kit::Id.generate
@logout_urls = []
@name_id_formats = [Namespaces::PERSISTENT]
@single_sign_on_urls = []
@@ -42,7 +43,7 @@ module Saml
def entity_descriptor_options
{
'xmlns': Namespaces::METADATA,
- 'xmlns:ds': Namespaces::XMLDSIG,
+ 'xmlns:ds': ::Xml::Kit::Namespaces::XMLDSIG,
'xmlns:saml': Namespaces::ASSERTION,
ID: id,
entityID: entity_id,
lib/saml/kit/builders/logout_request.rb
@@ -1,9 +1,10 @@
module Saml
module Kit
module Builders
+ # {include:file:lib/saml/kit/builders/templates/logout_request.builder}
# {include:file:spec/saml/builders/logout_request_spec.rb}
class LogoutRequest
- include Saml::Kit::Templatable
+ include XmlTemplatable
attr_accessor :id, :destination, :issuer, :name_id_format, :now
attr_accessor :version
attr_reader :user, :configuration
@@ -11,7 +12,7 @@ module Saml
def initialize(user, configuration: Saml::Kit.configuration)
@configuration = configuration
@user = user
- @id = "_#{SecureRandom.uuid}"
+ @id = ::Xml::Kit::Id.generate
@issuer = configuration.issuer
@name_id_format = Saml::Kit::Namespaces::PERSISTENT
@now = Time.now.utc
lib/saml/kit/builders/logout_response.rb
@@ -1,16 +1,17 @@
module Saml
module Kit
module Builders
+ # {include:file:lib/saml/kit/builders/templates/logout_response.builder}
# {include:file:spec/saml/builders/logout_response_spec.rb}
class LogoutResponse
- include Saml::Kit::Templatable
+ include XmlTemplatable
attr_accessor :id, :issuer, :version, :status_code, :now, :destination
attr_reader :request
attr_reader :configuration
def initialize(request, configuration: Saml::Kit.configuration)
@configuration = configuration
- @id = Id.generate
+ @id = ::Xml::Kit::Id.generate
@issuer = configuration.issuer
@now = Time.now.utc
@request = request
lib/saml/kit/builders/metadata.rb
@@ -1,9 +1,10 @@
module Saml
module Kit
module Builders
+ # {include:file:lib/saml/kit/builders/templates/metadata.builder}
# {include:file:spec/saml/builders/metadata_spec.rb}
class Metadata
- include Templatable
+ include XmlTemplatable
attr_accessor :entity_id
attr_accessor :id
@@ -13,7 +14,7 @@ module Saml
attr_reader :configuration
def initialize(configuration: Saml::Kit.configuration)
- @id = Id.generate
+ @id = ::Xml::Kit::Id.generate
@entity_id = configuration.issuer
@configuration = configuration
end
@@ -39,7 +40,7 @@ module Saml
def entity_descriptor_options
{
'xmlns': Namespaces::METADATA,
- 'xmlns:ds': Namespaces::XMLDSIG,
+ 'xmlns:ds': ::Xml::Kit::Namespaces::XMLDSIG,
'xmlns:saml': Namespaces::ASSERTION,
ID: id,
entityID: entity_id,
lib/saml/kit/builders/response.rb
@@ -1,20 +1,21 @@
module Saml
module Kit
module Builders
+ # {include:file:lib/saml/kit/builders/templates/response.builder}
# {include:file:spec/saml/builders/response_spec.rb}
class Response
- include Templatable
+ include XmlTemplatable
attr_reader :user, :request
attr_accessor :id, :reference_id, :now
attr_accessor :version, :status_code
- attr_accessor :issuer, :destination, :encrypt
+ attr_accessor :issuer, :destination
attr_reader :configuration
def initialize(user, request, configuration: Saml::Kit.configuration)
@user = user
@request = request
- @id = Id.generate
- @reference_id = Id.generate
+ @id = ::Xml::Kit::Id.generate
+ @reference_id = ::Xml::Kit::Id.generate
@now = Time.now.utc
@version = "2.0"
@status_code = Namespaces::SUCCESS
@@ -37,7 +38,15 @@ module Saml
private
def assertion
- @assertion ||= Saml::Kit::Builders::Assertion.new(self)
+ @assertion ||=
+ begin
+ assertion = Saml::Kit::Builders::Assertion.new(self)
+ if encrypt
+ Saml::Kit::Builders::EncryptedAssertion.new(self, assertion)
+ else
+ assertion
+ end
+ end
end
def response_options
lib/saml/kit/builders/service_provider_metadata.rb
@@ -1,9 +1,10 @@
module Saml
module Kit
module Builders
+ # {include:file:lib/saml/kit/builders/templates/service_provider_metadata.builder}
# {include:file:spec/saml/builders/service_provider_metadata_spec.rb}
class ServiceProviderMetadata
- include Saml::Kit::Templatable
+ include XmlTemplatable
extend Forwardable
attr_accessor :acs_urls, :logout_urls, :name_id_formats
attr_accessor :want_assertions_signed
lib/saml/kit/builders/xml_encryption.rb
@@ -1,20 +0,0 @@
-module Saml
- module Kit
- module Builders
- class XmlEncryption
- attr_reader :public_key
- attr_reader :key, :iv, :encrypted
-
- def initialize(raw_xml, public_key)
- @public_key = public_key
- cipher = OpenSSL::Cipher.new('AES-256-CBC')
- cipher.encrypt
- @key = cipher.random_key
- @iv = cipher.random_iv
- @encrypted = cipher.update(raw_xml) + cipher.final
- end
- end
- end
- end
-end
-
lib/saml/kit/builders/xml_signature.rb
@@ -1,40 +0,0 @@
-module Saml
- module Kit
- module Builders
- class XmlSignature
- SIGNATURE_METHODS = {
- SHA1: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
- SHA224: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224",
- SHA256: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
- SHA384: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384",
- SHA512: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
- }.freeze
- DIGEST_METHODS = {
- SHA1: "http://www.w3.org/2000/09/xmldsig#SHA1",
- SHA224: "http://www.w3.org/2001/04/xmldsig-more#sha224",
- SHA256: "http://www.w3.org/2001/04/xmlenc#sha256",
- SHA384: "http://www.w3.org/2001/04/xmldsig-more#sha384",
- SHA512: "http://www.w3.org/2001/04/xmlenc#sha512",
- }.freeze
-
- attr_reader :embed_signature, :configuration
- attr_reader :reference_id
- attr_reader :certificate
-
- def initialize(reference_id, configuration:, certificate: )
- @configuration = configuration
- @reference_id = reference_id
- @certificate = certificate
- end
-
- def signature_method
- SIGNATURE_METHODS[configuration.signature_method]
- end
-
- def digest_method
- DIGEST_METHODS[configuration.digest_method]
- end
- end
- end
- end
-end
lib/saml/kit/crypto/oaep_cipher.rb
@@ -1,22 +0,0 @@
-module Saml
- module Kit
- module Crypto
- class OaepCipher
- ALGORITHMS = {
- 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' => true,
- }
- def initialize(algorithm, key)
- @key = key
- end
-
- def self.matches?(algorithm)
- ALGORITHMS[algorithm]
- end
-
- def decrypt(cipher_text)
- @key.private_decrypt(cipher_text, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
- end
- end
- end
- end
-end
lib/saml/kit/crypto/rsa_cipher.rb
@@ -1,23 +0,0 @@
-module Saml
- module Kit
- module Crypto
- class RsaCipher
- ALGORITHMS = {
- 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' => true,
- }
-
- def initialize(algorithm, key)
- @key = key
- end
-
- def self.matches?(algorithm)
- ALGORITHMS[algorithm]
- end
-
- def decrypt(cipher_text)
- @key.private_decrypt(cipher_text)
- end
- end
- end
- end
-end
lib/saml/kit/crypto/simple_cipher.rb
@@ -1,38 +0,0 @@
-module Saml
- module Kit
- module Crypto
- class SimpleCipher
- ALGORITHMS = {
- 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' => 'DES-EDE3-CBC',
- 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' => 'AES-128-CBC',
- 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' => 'AES-192-CBC',
- 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' => 'AES-256-CBC',
- }
-
- def initialize(algorithm, private_key)
- @algorithm = algorithm
- @private_key = private_key
- end
-
- def self.matches?(algorithm)
- ALGORITHMS[algorithm]
- end
-
- def decrypt(cipher_text)
- cipher = OpenSSL::Cipher.new(ALGORITHMS[@algorithm])
- cipher.decrypt
- iv = cipher_text[0..cipher.iv_len-1]
- data = cipher_text[cipher.iv_len..-1]
- #cipher.padding = 0
- cipher.key = @private_key
- cipher.iv = iv
-
- Saml::Kit.logger.debug ['-key', @private_key].inspect
- Saml::Kit.logger.debug ['-iv', iv].inspect
-
- cipher.update(data) + cipher.final
- end
- end
- end
- end
-end
lib/saml/kit/crypto/unknown_cipher.rb
@@ -1,18 +0,0 @@
-module Saml
- module Kit
- module Crypto
- class UnknownCipher
- def initialize(algorithm, key)
- end
-
- def self.matches?(algorithm)
- true
- end
-
- def decrypt(cipher_text)
- cipher_text
- end
- end
- end
- end
-end
lib/saml/kit/assertion.rb
@@ -72,17 +72,20 @@ module Saml
attr_reader :configuration
def assertion
- @assertion ||= if encrypted?
- decrypted = XmlDecryption.new(configuration: configuration).decrypt(@xml_hash['Response']['EncryptedAssertion'])
- Saml::Kit.logger.debug(decrypted)
- Hash.from_xml(decrypted)['Assertion']
- else
- result = @xml_hash.fetch('Response', {}).fetch('Assertion', {})
- return result if result.is_a?(Hash)
-
- errors[:assertion] << error_message(:must_contain_single_assertion)
- {}
- end
+ @assertion ||=
+ if encrypted?
+ private_keys = configuration.private_keys(use: :encryption)
+ decryptor = ::Xml::Kit::Decryption.new(private_keys: private_keys)
+ decrypted = decryptor.decrypt(@xml_hash['Response']['EncryptedAssertion'])
+ Saml::Kit.logger.debug(decrypted)
+ Hash.from_xml(decrypted)['Assertion']
+ else
+ result = @xml_hash.fetch('Response', {}).fetch('Assertion', {})
+ return result if result.is_a?(Hash)
+
+ errors[:assertion] << error_message(:must_contain_single_assertion)
+ {}
+ end
end
def parse_date(value)
lib/saml/kit/builders.rb
@@ -1,13 +1,13 @@
+require 'saml/kit/xml_templatable'
require 'saml/kit/builders/assertion'
require 'saml/kit/builders/authentication_request'
+require 'saml/kit/builders/encrypted_assertion'
require 'saml/kit/builders/identity_provider_metadata'
require 'saml/kit/builders/logout_request'
require 'saml/kit/builders/logout_response'
require 'saml/kit/builders/metadata'
require 'saml/kit/builders/response'
require 'saml/kit/builders/service_provider_metadata'
-require 'saml/kit/builders/xml_encryption'
-require 'saml/kit/builders/xml_signature'
module Saml
module Kit
lib/saml/kit/certificate.rb
@@ -1,96 +0,0 @@
-module Saml
- module Kit
- # {include:file:spec/saml/certificate_spec.rb}
- class Certificate
- BEGIN_CERT=/-----BEGIN CERTIFICATE-----/
- END_CERT=/-----END CERTIFICATE-----/
- # The use can be `:signing` or `:encryption`
- attr_reader :use
-
- def initialize(value, use:)
- @value = value
- @use = use.downcase.to_sym
- end
-
- # @return [Saml::Kit::Fingerprint] the certificate fingerprint.
- def fingerprint
- Fingerprint.new(value)
- end
-
- # Returns true if this certificate is for the specified use.
- #
- # @param use [Symbol] `:signing` or `:encryption`.
- # @return [Boolean] true or false.
- def for?(use)
- self.use == use.to_sym
- end
-
- # Returns true if this certificate is used for encryption.
- #
- # return [Boolean] true or false.
- def encryption?
- for?(:encryption)
- end
-
- # Returns true if this certificate is used for signing.
- #
- # return [Boolean] true or false.
- def signing?
- for?(:signing)
- end
-
- # Returns the x509 form.
- #
- # return [OpenSSL::X509::Certificate] the OpenSSL equivalent.
- def x509
- self.class.to_x509(value)
- end
-
- # Returns the public key.
- #
- # @return [OpenSSL::PKey::RSA] the RSA public key.
- def public_key
- x509.public_key
- end
-
- def ==(other)
- self.fingerprint == other.fingerprint
- end
-
- def eql?(other)
- self == other
- end
-
- def hash
- value.hash
- end
-
- def to_s
- value
- end
-
- def to_h
- { use: @use, fingerprint: fingerprint.to_s }
- end
-
- def inspect
- to_h.inspect
- end
-
- def stripped
- value.to_s.gsub(BEGIN_CERT, '').gsub(END_CERT, '').gsub(/\n/, '')
- end
-
- def self.to_x509(value)
- OpenSSL::X509::Certificate.new(Base64.decode64(value))
- rescue OpenSSL::X509::CertificateError => error
- Saml::Kit.logger.warn(error)
- OpenSSL::X509::Certificate.new(value)
- end
-
- private
-
- attr_reader :value
- end
- end
-end
lib/saml/kit/configuration.rb
@@ -53,7 +53,7 @@ module Saml
# @param passphrase [String] the password to decrypt the private key.
# @param use [Symbol] the type of key pair, `:signing` or `:encryption`
def add_key_pair(certificate, private_key, passphrase: '', use: :signing)
- @key_pairs.push(KeyPair.new(certificate, private_key, passphrase, use.to_sym))
+ @key_pairs.push(::Xml::Kit::KeyPair.new(certificate, private_key, passphrase, use.to_sym))
end
# Generates a unique key pair that can be used for signing or encryption.
@@ -61,7 +61,7 @@ module Saml
# @param use [Symbol] the type of key pair, `:signing` or `:encryption`
# @param passphrase [String] the private key passphrase to use.
def generate_key_pair_for(use:, passphrase: SecureRandom.uuid)
- certificate, private_key = SelfSignedCertificate.new(passphrase).create
+ certificate, private_key = ::Xml::Kit::SelfSignedCertificate.new(passphrase).create
add_key_pair(certificate, private_key, passphrase: passphrase, use: use)
end
lib/saml/kit/crypto.rb
@@ -1,17 +0,0 @@
-require 'saml/kit/crypto/oaep_cipher'
-require 'saml/kit/crypto/rsa_cipher'
-require 'saml/kit/crypto/simple_cipher'
-require 'saml/kit/crypto/unknown_cipher'
-
-module Saml
- module Kit
- module Crypto
- DECRYPTORS = [ SimpleCipher, RsaCipher, OaepCipher, UnknownCipher ]
-
- # @!visibility private
- def self.decryptor_for(algorithm, key)
- DECRYPTORS.find { |x| x.matches?(algorithm) }.new(algorithm, key)
- end
- end
- end
-end
lib/saml/kit/document.rb
@@ -2,6 +2,13 @@ module Saml
module Kit
class Document
PROTOCOL_XSD = File.expand_path("./xsd/saml-schema-protocol-2.0.xsd", File.dirname(__FILE__)).freeze
+ NAMESPACES = {
+ "NameFormat": ::Saml::Kit::Namespaces::ATTR_SPLAT,
+ "ds": ::Xml::Kit::Namespaces::XMLDSIG,
+ "md": ::Saml::Kit::Namespaces::METADATA,
+ "saml": ::Saml::Kit::Namespaces::ASSERTION,
+ "samlp": ::Saml::Kit::Namespaces::PROTOCOL,
+ }.freeze
include ActiveModel::Validations
include XsdValidatable
include Translatable
@@ -79,12 +86,15 @@ module Saml
# @param xml [String] the raw xml string.
# @param configuration [Saml::Kit::Configuration] the configuration to use for unpacking the document.
def to_saml_document(xml, configuration: Saml::Kit.configuration)
+ xml_document = ::Xml::Kit::Document.new(xml, namespaces: {
+ "samlp": ::Saml::Kit::Namespaces::PROTOCOL
+ })
constructor = {
"AuthnRequest" => Saml::Kit::AuthenticationRequest,
"LogoutRequest" => Saml::Kit::LogoutRequest,
"LogoutResponse" => Saml::Kit::LogoutResponse,
"Response" => Saml::Kit::Response,
- }[Saml::Kit::Xml.new(xml).find_by(XPATH).name] || InvalidDocument
+ }[xml_document.find_by(XPATH).name] || InvalidDocument
constructor.new(xml, configuration: configuration)
rescue => error
Saml::Kit.logger.error(error)
lib/saml/kit/fingerprint.rb
@@ -1,50 +0,0 @@
-module Saml
- module Kit
- # This generates a fingerprint for an X509 Certificate.
- #
- # certificate, _ = Saml::Kit::SelfSignedCertificate.new("password").create
- #
- # puts Saml::Kit::Fingerprint.new(certificate).to_s
- # # B7:AB:DC:BD:4D:23:58:65:FD:1A:99:0C:5F:89:EA:87:AD:F1:D7:83:34:7A:E9:E4:88:12:DD:46:1F:38:05:93
- #
- # {include:file:spec/saml/fingerprint_spec.rb}
- class Fingerprint
- # The OpenSSL::X509::Certificate
- attr_reader :x509
-
- def initialize(raw_certificate)
- @x509 = Certificate.to_x509(raw_certificate)
- end
-
- # Generates a formatted fingerprint using the specified hash algorithm.
- #
- # @param algorithm [OpenSSL::Digest] the openssl algorithm to use `OpenSSL::Digest::SHA256`, `OpenSSL::Digest::SHA1`.
- # @return [String] in the format of `"BF:ED:C5:F1:6C:AB:F5:B2:15:1F:BF:BD:7D:68:1A:F9:A5:4E:4C:19:30:BC:6D:25:B1:8E:98:D4:23:FD:B4:09"`
- def algorithm(algorithm)
- pretty_fingerprint(algorithm.new.hexdigest(x509.to_der))
- end
-
- def ==(other)
- self.to_s == other.to_s
- end
-
- def eql?(other)
- self == other
- end
-
- def hash
- to_s.hash
- end
-
- def to_s
- algorithm(OpenSSL::Digest::SHA256)
- end
-
- private
-
- def pretty_fingerprint(fingerprint)
- fingerprint.upcase.scan(/../).join(":")
- end
- end
- end
-end
lib/saml/kit/id.rb
@@ -1,14 +0,0 @@
-module Saml
- module Kit
- # This class is used primary for generating ID.
- #https://www.w3.org/2001/XMLSchema.xsd
- class Id
-
- # Generate an ID that conforms to the XML Schema.
- # https://www.w3.org/2001/XMLSchema.xsd
- def self.generate
- "_#{SecureRandom.uuid}"
- end
- end
- end
-end
lib/saml/kit/key_pair.rb
@@ -1,29 +0,0 @@
-module Saml
- module Kit
- class KeyPair # :nodoc:
- attr_reader :certificate, :private_key, :use
-
- def initialize(certificate, private_key, passphrase, use)
- @use = use
- @certificate = Saml::Kit::Certificate.new(certificate, use: use)
- @private_key = OpenSSL::PKey::RSA.new(private_key, passphrase)
- end
-
- # Returns true if the key pair is the designated use.
- #
- # @param use [Symbol] Can be either `:signing` or `:encryption`.
- def for?(use)
- @use == use
- end
-
- # Returns a generated self signed certificate with private key.
- #
- # @param use [Symbol] Can be either `:signing` or `:encryption`.
- # @param passphrase [String] the passphrase to use to encrypt the private key.
- def self.generate(use:, passphrase: SecureRandom.uuid)
- certificate, private_key = SelfSignedCertificate.new(passphrase).create
- new(certificate, private_key, passphrase, use)
- end
- end
- end
-end
lib/saml/kit/metadata.rb
@@ -24,6 +24,13 @@ module Saml
# {include:file:spec/examples/metadata_spec.rb}
class Metadata
METADATA_XSD = File.expand_path("./xsd/saml-schema-metadata-2.0.xsd", File.dirname(__FILE__)).freeze
+ NAMESPACES = {
+ "NameFormat": Namespaces::ATTR_SPLAT,
+ "ds": ::Xml::Kit::Namespaces::XMLDSIG,
+ "md": Namespaces::METADATA,
+ "saml": Namespaces::ASSERTION,
+ "samlp": Namespaces::PROTOCOL,
+ }.freeze
include ActiveModel::Validations
include XsdValidatable
include Translatable
@@ -69,8 +76,8 @@ module Saml
# Returns each of the X509 certificates.
def certificates
@certificates ||= document.find_all("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item|
- cert = item.at_xpath("./ds:KeyInfo/ds:X509Data/ds:X509Certificate", Xml::NAMESPACES).text
- Certificate.new(cert, use: item.attribute('use').value.to_sym)
+ cert = item.at_xpath("./ds:KeyInfo/ds:X509Data/ds:X509Certificate", NAMESPACES).text
+ ::Xml::Kit::Certificate.new(cert, use: item.attribute('use').value.to_sym)
end
end
@@ -134,7 +141,7 @@ module Saml
#
# @param fingerprint [Saml::Kit::Fingerprint] the fingerprint to search for.
# @param use [Symbol] the type of certificates to look at. Can be `:signing` or `:encryption`.
- # @return [Saml::Kit::Certificate] returns the matching `{Saml::Kit::Certificate}`
+ # @return [Xml::Kit::Certificate] returns the matching `{Xml::Kit::Certificate}`
def matches?(fingerprint, use: :signing)
certificates.find do |certificate|
certificate.for?(use) && certificate.fingerprint == fingerprint
@@ -163,7 +170,7 @@ module Saml
# @param algorithm [OpenSSL::Digest] the digest algorithm to use. E.g. `OpenSSL::Digest::SHA256`
# @param signature [String] the signature to verify
# @param data [String] the data that is used to produce the signature.
- # @return [Saml::Kit::Certificate] the certificate that was used to produce the signature.
+ # @return [Xml::Kit::Certificate] the certificate that was used to produce the signature.
def verify(algorithm, signature, data)
signing_certificates.find do |certificate|
certificate.public_key.verify(algorithm, signature, data)
@@ -196,7 +203,7 @@ module Saml
attr_reader :xml
def document
- @document ||= Xml.new(xml)
+ @document ||= ::Xml::Kit::Document.new(xml, namespaces: NAMESPACES)
end
def metadata
@@ -220,7 +227,7 @@ module Saml
end
def valid_signature?
- xml = Saml::Kit::Xml.new(to_xml)
+ xml = document
result = xml.valid?
xml.errors.each do |error|
errors[:base] << error
lib/saml/kit/namespaces.rb
@@ -6,7 +6,6 @@ module Saml
BASIC = "urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
BEARER = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
EMAIL_ADDRESS = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
- ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
PASSWORD = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
PASSWORD_PROTECTED = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
@@ -14,22 +13,12 @@ module Saml
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
REQUESTER_ERROR = "urn:oasis:names:tc:SAML:2.0:status:Requester"
RESPONDER_ERROR = "urn:oasis:names:tc:SAML:2.0:status:Responder"
- RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
- RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
- RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
- RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
- SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
- SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
- SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
- SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
UNSPECIFIED = "urn:oasis:names:tc:SAML:2.0:consent:unspecified"
UNSPECIFIED_NAMEID = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
VERSION_MISMATCH_ERROR = "urn:oasis:names:tc:SAML:2.0:status:VersionMismatch"
- XMLDSIG = "http://www.w3.org/2000/09/xmldsig#"
- XMLENC = "http://www.w3.org/2001/04/xmlenc#"
end
end
end
lib/saml/kit/self_signed_certificate.rb
@@ -1,28 +0,0 @@
-module Saml
- module Kit
- class SelfSignedCertificate
- SUBJECT="/C=CA/ST=Alberta/L=Calgary/O=SamlKit/OU=SamlKit/CN=SamlKit"
-
- def initialize(passphrase)
- @passphrase = passphrase
- end
-
- def create
- rsa_key = OpenSSL::PKey::RSA.new(2048)
- public_key = rsa_key.public_key
- certificate = OpenSSL::X509::Certificate.new
- certificate.subject = certificate.issuer = OpenSSL::X509::Name.parse(SUBJECT)
- certificate.not_before = DateTime.now.beginning_of_day
- certificate.not_after = 30.days.from_now
- certificate.public_key = public_key
- certificate.serial = 0x0
- certificate.version = 2
- certificate.sign(rsa_key, OpenSSL::Digest::SHA256.new)
- [
- certificate.to_pem,
- rsa_key.to_pem(OpenSSL::Cipher.new('AES-256-CBC'), @passphrase)
- ]
- end
- end
- end
-end
lib/saml/kit/signature.rb
@@ -9,7 +9,7 @@ module Saml
def certificate
value = to_h.fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
return if value.nil?
- Saml::Kit::Certificate.new(value, use: :signing)
+ ::Xml::Kit::Certificate.new(value, use: :signing)
end
# Returns true when the fingerprint of the certificate matches one of the certificates registered in the metadata.
lib/saml/kit/signatures.rb
@@ -1,57 +0,0 @@
-module Saml
- module Kit
- # @!visibility private
- class Signatures # :nodoc:
- # @!visibility private
- attr_reader :configuration
-
- # @!visibility private
- def initialize(configuration:)
- @configuration = configuration
- @key_pair = configuration.key_pairs(use: :signing).last
- end
-
- # @!visibility private
- def sign_with(key_pair)
- @key_pair = key_pair
- end
-
- # @!visibility private
- def build(reference_id)
- return nil unless configuration.sign?
- certificate = @key_pair.certificate
- Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration, certificate: certificate)
- end
-
- # @!visibility private
- def complete(raw_xml)
- return raw_xml unless configuration.sign?
- private_key = @key_pair.private_key
- Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
- end
-
- # @!visibility private
- def self.sign(xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
- signatures = Saml::Kit::Signatures.new(configuration: configuration)
- yield xml, XmlSignatureTemplate.new(xml, signatures)
- signatures.complete(xml.target!)
- end
-
- class XmlSignatureTemplate # :nodoc:
- # @!visibility private
- attr_reader :signatures, :xml
-
- # @!visibility private
- def initialize(xml, signatures)
- @signatures = signatures
- @xml = xml
- end
-
- # @!visibility private
- def template(reference_id)
- Template.new(signatures.build(reference_id)).to_xml(xml: xml)
- end
- end
- end
- end
-end
lib/saml/kit/templatable.rb
@@ -1,67 +0,0 @@
-module Saml
- module Kit
- module Templatable
- # Can be used to disable embeding a signature.
- # By default a signature will be embedded if a signing
- # certificate is available via the configuration.
- attr_accessor :embed_signature
-
- # @deprecated Use {#embed_signature=} instead of this method.
- def sign=(value)
- Saml::Kit.deprecate("sign= is deprecated. Use embed_signature= instead")
- self.embed_signature = value
- end
-
- # Returns the generated XML document with an XML Digital Signature and XML Encryption.
- def to_xml(xml: ::Builder::XmlMarkup.new)
- signatures.complete(render(self, xml: xml))
- end
-
- # @!visibility private
- def signature_for(reference_id:, xml:)
- return unless sign?
- render(signatures.build(reference_id), xml: xml)
- end
-
- # Allows you to specify which key pair to use for generating an XML digital signature.
- #
- # @param key_pair [Saml::Kit::KeyPair] the key pair to use for signing.
- def sign_with(key_pair)
- signatures.sign_with(key_pair)
- end
-
- # Returns true if an embedded signature is requested and at least one signing certificate is available via the configuration.
- def sign?
- embed_signature.nil? ? configuration.sign? : embed_signature && configuration.sign?
- end
-
- # @!visibility private
- def signatures
- @signatures ||= Saml::Kit::Signatures.new(configuration: configuration)
- end
-
- # @!visibility private
- def encryption_for(xml:)
- if encrypt?
- temp = ::Builder::XmlMarkup.new
- yield temp
- signed_xml = signatures.complete(temp.target!)
- xml_encryption = Saml::Kit::Builders::XmlEncryption.new(signed_xml, encryption_certificate.public_key)
- render(xml_encryption, xml: xml)
- else
- yield xml
- end
- end
-
- # @!visibility private
- def encrypt?
- encrypt && encryption_certificate
- end
-
- # @!visibility private
- def render(model, options)
- Saml::Kit::Template.new(model).to_xml(options)
- end
- end
- end
-end
lib/saml/kit/template.rb
@@ -1,33 +0,0 @@
-module Saml
- module Kit
- class Template
- attr_reader :target
-
- def initialize(target)
- @target = target
- end
-
- # Returns the compiled template as a [String].
- #
- # @param options [Hash] The options hash to pass to the template engine.
- def to_xml(options)
- template.render(target, options)
- end
-
- private
-
- def template_name
- "#{target.class.name.split("::").last.underscore}.builder"
- end
-
- def template_path
- root_path = File.expand_path(File.dirname(__FILE__))
- File.join(root_path, "builders/templates/", template_name)
- end
-
- def template
- Tilt.new(template_path)
- end
- end
- end
-end
lib/saml/kit/trustable.rb
@@ -44,7 +44,13 @@ module Saml
def must_have_valid_signature
return if to_xml.blank?
- xml = Saml::Kit::Xml.new(to_xml)
+ xml = ::Xml::Kit::Document.new(to_xml, namespaces: {
+ "NameFormat": Namespaces::ATTR_SPLAT,
+ "ds": ::Xml::Kit::Namespaces::XMLDSIG,
+ "md": Namespaces::METADATA,
+ "saml": Namespaces::ASSERTION,
+ "samlp": Namespaces::PROTOCOL,
+ })
xml.valid?
xml.errors.each do |error|
errors[:base] << error
lib/saml/kit/version.rb
@@ -1,5 +1,5 @@
module Saml
module Kit
- VERSION = "0.3.0"
+ VERSION = "0.3.1"
end
end
lib/saml/kit/xml.rb
@@ -1,80 +0,0 @@
-module Saml
- module Kit
- # {include:file:spec/saml/xml_spec.rb}
- class Xml # :nodoc:
- include ActiveModel::Validations
- NAMESPACES = {
- "NameFormat": Namespaces::ATTR_SPLAT,
- "ds": Namespaces::XMLDSIG,
- "md": Namespaces::METADATA,
- "saml": Namespaces::ASSERTION,
- "samlp": Namespaces::PROTOCOL,
- }.freeze
-
- validate :validate_signatures
- validate :validate_certificates
-
- def initialize(raw_xml)
- @raw_xml = raw_xml
- @document = Nokogiri::XML(raw_xml)
- end
-
- # Returns the first XML node found by searching the document with the provided XPath.
- #
- # @param xpath [String] the XPath to use to search the document
- def find_by(xpath)
- document.at_xpath(xpath, NAMESPACES)
- end
-
- # Returns all XML nodes found by searching the document with the provided XPath.
- #
- # @param xpath [String] the XPath to use to search the document
- def find_all(xpath)
- document.search(xpath, NAMESPACES)
- end
-
- # Return the XML document as a [String].
- #
- # @param pretty [Boolean] return the XML string in a human readable format if true.
- def to_xml(pretty: true)
- pretty ? document.to_xml(indent: 2) : raw_xml
- end
-
- private
-
- attr_reader :raw_xml, :document
-
- def validate_signatures
- invalid_signatures.flat_map(&:errors).uniq.each do |error|
- errors.add(error, "is invalid")
- end
- end
-
- def invalid_signatures
- signed_document = Xmldsig::SignedDocument.new(document, id_attr: 'ID=$uri or @Id')
- signed_document.signatures.find_all do |signature|
- x509_certificates.all? do |certificate|
- !signature.valid?(certificate)
- end
- end
- end
-
- def validate_certificates(now = Time.current)
- return if find_by('//ds:Signature').nil?
-
- x509_certificates.each do |certificate|
- inactive = now < certificate.not_before
- errors.add(:certificate, "Not valid before #{certificate.not_before}") if inactive
-
- expired = now > certificate.not_after
- errors.add(:certificate, "Not valid after #{certificate.not_after}") if expired
- end
- end
-
- def x509_certificates
- xpath = "//ds:KeyInfo/ds:X509Data/ds:X509Certificate"
- find_all(xpath).map { |item| Certificate.to_x509(item.text) }
- end
- end
- end
-end
lib/saml/kit/xml_decryption.rb
@@ -1,44 +0,0 @@
-module Saml
- module Kit
- # {include:file:spec/saml/xml_decryption_spec.rb}
- class XmlDecryption
- # The list of private keys to use to attempt to decrypt the document.
- attr_reader :private_keys
-
- def initialize(configuration: Saml::Kit.configuration)
- @private_keys = configuration.private_keys(use: :encryption)
- end
-
- # Decrypts an EncryptedData section of an XML document.
- #
- # @param data [Hash] the XML document converted to a [Hash] using Hash.from_xml.
- def decrypt(data)
- encrypted_data = data['EncryptedData']
- symmetric_key = symmetric_key_from(encrypted_data)
- cipher_text = Base64.decode64(encrypted_data["CipherData"]["CipherValue"])
- to_plaintext(cipher_text, symmetric_key, encrypted_data["EncryptionMethod"]['Algorithm'])
- end
-
- private
-
- def symmetric_key_from(encrypted_data)
- encrypted_key = encrypted_data['KeyInfo']['EncryptedKey']
- cipher_text = Base64.decode64(encrypted_key['CipherData']['CipherValue'])
- attempts = private_keys.count
- private_keys.each do |private_key|
- begin
- attempts -= 1
- return to_plaintext(cipher_text, private_key, encrypted_key["EncryptionMethod"]['Algorithm'])
- rescue OpenSSL::PKey::RSAError => error
- Saml::Kit.logger.error(error)
- raise if attempts.zero?
- end
- end
- end
-
- def to_plaintext(cipher_text, symmetric_key, algorithm)
- Crypto.decryptor_for(algorithm, symmetric_key).decrypt(cipher_text)
- end
- end
- end
-end
lib/saml/kit/xml_templatable.rb
@@ -0,0 +1,37 @@
+module Saml
+ module Kit
+ module XmlTemplatable
+ include ::Xml::Kit::Templatable
+
+ def template_path
+ root_path = File.expand_path(File.dirname(__FILE__))
+ template_name = "#{self.class.name.split("::").last.underscore}.builder"
+ File.join(root_path, "builders/templates/", template_name)
+ end
+
+ # Returns true if an embedded signature is requested and at least one signing certificate is available via the configuration.
+ def sign?
+ return configuration.sign? if embed_signature.nil?
+ embed_signature && configuration.sign?
+ end
+
+ # @deprecated Use {#embed_signature=} instead of this method.
+ def sign=(value)
+ Saml::Kit.deprecate("sign= is deprecated. Use embed_signature= instead.")
+ self.embed_signature = value
+ end
+
+ def digest_method
+ configuration.digest_method
+ end
+
+ def signature_method
+ configuration.signature_method
+ end
+
+ def signing_key_pair
+ configuration.key_pairs(use: :signing).last
+ end
+ end
+ end
+end
lib/saml/kit.rb
@@ -7,16 +7,13 @@ require "active_support/core_ext/hash/indifferent_access"
require "active_support/core_ext/numeric/time"
require "active_support/deprecation"
require "active_support/duration"
-require "builder"
require "logger"
require "net/http"
require "nokogiri"
require "securerandom"
-require "tilt"
-require "xmldsig"
+require "xml/kit"
require "saml/kit/buildable"
-require "saml/kit/templatable"
require "saml/kit/builders"
require "saml/kit/namespaces"
require "saml/kit/serializable"
@@ -30,27 +27,17 @@ require "saml/kit/document"
require "saml/kit/assertion"
require "saml/kit/authentication_request"
require "saml/kit/bindings"
-require "saml/kit/certificate"
require "saml/kit/configuration"
-require "saml/kit/crypto"
require "saml/kit/default_registry"
-require "saml/kit/fingerprint"
-require "saml/kit/key_pair"
require "saml/kit/logout_response"
require "saml/kit/logout_request"
require "saml/kit/metadata"
require "saml/kit/composite_metadata"
require "saml/kit/response"
-require "saml/kit/id"
require "saml/kit/identity_provider_metadata"
require "saml/kit/invalid_document"
-require "saml/kit/self_signed_certificate"
require "saml/kit/service_provider_metadata"
require "saml/kit/signature"
-require "saml/kit/signatures"
-require "saml/kit/template"
-require "saml/kit/xml"
-require "saml/kit/xml_decryption"
I18n.load_path += Dir[File.expand_path("kit/locales/*.yml", File.dirname(__FILE__))]
spec/saml/bindings/url_builder_spec.rb
@@ -54,7 +54,7 @@ RSpec.describe Saml::Kit::Bindings::UrlBuilder do
it 'includes a signature' do
result = subject.build(response, relay_state: relay_state)
query_params = to_query_params(result)
- expect(query_params['SigAlg']).to eql(CGI.escape(Saml::Kit::Namespaces::SHA256))
+ expect(query_params['SigAlg']).to eql(CGI.escape(::Xml::Kit::Namespaces::SHA256))
payload = "#{query_string_parameter}=#{query_params[query_string_parameter]}"
payload << "&RelayState=#{query_params['RelayState']}"
@@ -67,7 +67,7 @@ RSpec.describe Saml::Kit::Bindings::UrlBuilder do
it 'generates the signature correctly when the relay state is absent' do
result = subject.build(response)
query_params = to_query_params(result)
- expect(query_params['SigAlg']).to eql(CGI.escape(Saml::Kit::Namespaces::SHA256))
+ expect(query_params['SigAlg']).to eql(CGI.escape(::Xml::Kit::Namespaces::SHA256))
payload = "#{query_string_parameter}=#{query_params[query_string_parameter]}"
payload << "&SigAlg=#{query_params['SigAlg']}"
spec/saml/builders/logout_request_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Saml::Kit::Builders::LogoutRequest do
it 'produces the expected xml' do
travel_to 1.second.from_now
- subject.id = Saml::Kit::Id.generate
+ subject.id = Xml::Kit::Id.generate
subject.destination = FFaker::Internet.http_url
subject.issuer = FFaker::Internet.http_url
subject.name_id_format = Saml::Kit::Namespaces::TRANSIENT
spec/saml/builders/response_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe Saml::Kit::Builders::Response do
let(:email) { FFaker::Internet.email }
let(:assertion_consumer_service_url) { FFaker::Internet.uri("https") }
let(:user) { double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: { email: email, created_at: Time.now.utc.iso8601 }) }
- let(:request) { double(:request, id: Saml::Kit::Id.generate, assertion_consumer_service_url: assertion_consumer_service_url, issuer: issuer, name_id_format: Saml::Kit::Namespaces::EMAIL_ADDRESS, provider: provider, trusted?: true, signed?: true) }
+ let(:request) { double(:request, id: Xml::Kit::Id.generate, assertion_consumer_service_url: assertion_consumer_service_url, issuer: issuer, name_id_format: Saml::Kit::Namespaces::EMAIL_ADDRESS, provider: provider, trusted?: true, signed?: true) }
let(:provider) { double(:provider, want_assertions_signed: false, encryption_certificates: [configuration.certificates(use: :encryption).last] ) }
let(:issuer) { FFaker::Internet.uri("https") }
@@ -93,7 +93,7 @@ RSpec.describe Saml::Kit::Builders::Response do
result = Hash.from_xml(subject.to_xml)
expect(result['Response']['EncryptedAssertion']).to be_present
encrypted_assertion = result['Response']['EncryptedAssertion']
- decrypted_assertion = Saml::Kit::XmlDecryption.new(configuration: configuration).decrypt(encrypted_assertion)
+ decrypted_assertion = Xml::Kit::Decryption.new(private_keys: configuration.private_keys(use: :encryption)).decrypt(encrypted_assertion)
decrypted_hash = Hash.from_xml(decrypted_assertion)
expect(decrypted_hash['Assertion']).to be_present
expect(decrypted_hash['Assertion']['Issuer']).to be_present
spec/saml/authentication_request_spec.rb
@@ -1,6 +1,6 @@
RSpec.describe Saml::Kit::AuthenticationRequest do
subject { described_class.new(raw_xml, configuration: configuration) }
- let(:id) { Saml::Kit::Id.generate }
+ let(:id) { Xml::Kit::Id.generate }
let(:assertion_consumer_service_url) { "https://#{FFaker::Internet.domain_name}/acs" }
let(:issuer) { FFaker::Movie.title }
let(:destination) { FFaker::Internet.http_url }
@@ -79,10 +79,9 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
end
it 'validates the schema of the request' do
- id = Saml::Kit::Id.generate
- configuration = Saml::Kit::Configuration.new
- configuration.generate_key_pair_for(use: :signing)
- signed_xml = Saml::Kit::Signatures.sign(configuration: configuration) do |xml, signature|
+ id = Xml::Kit::Id.generate
+ key_pair = ::Xml::Kit::KeyPair.generate(use: :signing)
+ signed_xml = ::Xml::Kit::Signatures.sign(key_pair: key_pair) do |xml, signature|
xml.tag!('samlp:AuthnRequest', "xmlns:samlp" => Saml::Kit::Namespaces::PROTOCOL, AssertionConsumerServiceURL: assertion_consumer_service_url, ID: id) do
signature.template(id)
xml.Fake do
@@ -96,7 +95,7 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
it 'validates a request without a signature' do
now = Time.now.utc
raw_xml = <<-XML
-<samlp:AuthnRequest AssertionConsumerServiceURL='#{assertion_consumer_service_url}' ID='#{Saml::Kit::Id.generate}' IssueInstant='#{now.iso8601}' Version='2.0' xmlns:saml='#{Saml::Kit::Namespaces::ASSERTION}' xmlns:samlp='#{Saml::Kit::Namespaces::PROTOCOL}'>
+<samlp:AuthnRequest AssertionConsumerServiceURL='#{assertion_consumer_service_url}' ID='#{Xml::Kit::Id.generate}' IssueInstant='#{now.iso8601}' Version='2.0' xmlns:saml='#{Saml::Kit::Namespaces::ASSERTION}' xmlns:samlp='#{Saml::Kit::Namespaces::PROTOCOL}'>
<saml:Issuer>#{issuer}</saml:Issuer>
<samlp:NameIDPolicy AllowCreate='true' Format='#{Saml::Kit::Namespaces::EMAIL_ADDRESS}'/>
</samlp:AuthnRequest>
@@ -110,7 +109,7 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
it 'is valid when there is no signature, and the issuer is registered' do
now = Time.now.utc
raw_xml = <<-XML
-<samlp:AuthnRequest AssertionConsumerServiceURL='#{assertion_consumer_service_url}' ID='#{Saml::Kit::Id.generate}' IssueInstant='#{now.iso8601}' Version='2.0' xmlns:saml='#{Saml::Kit::Namespaces::ASSERTION}' xmlns:samlp='#{Saml::Kit::Namespaces::PROTOCOL}'>
+<samlp:AuthnRequest AssertionConsumerServiceURL='#{assertion_consumer_service_url}' ID='#{Xml::Kit::Id.generate}' IssueInstant='#{now.iso8601}' Version='2.0' xmlns:saml='#{Saml::Kit::Namespaces::ASSERTION}' xmlns:samlp='#{Saml::Kit::Namespaces::PROTOCOL}'>
<saml:Issuer>#{issuer}</saml:Issuer>
<samlp:NameIDPolicy AllowCreate='true' Format='#{Saml::Kit::Namespaces::PERSISTENT}'/>
</samlp:AuthnRequest>
@@ -124,7 +123,7 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
it 'is invalid when there is no signature, and the issuer is not registered' do
now = Time.now.utc
raw_xml = <<-XML
-<samlp:AuthnRequest AssertionConsumerServiceURL='#{assertion_consumer_service_url}' ID='#{Saml::Kit::Id.generate}' IssueInstant='#{now.iso8601}' Version='2.0' xmlns:saml='#{Saml::Kit::Namespaces::ASSERTION}' xmlns:samlp='#{Saml::Kit::Namespaces::PROTOCOL}'>
+<samlp:AuthnRequest AssertionConsumerServiceURL='#{assertion_consumer_service_url}' ID='#{Xml::Kit::Id.generate}' IssueInstant='#{now.iso8601}' Version='2.0' xmlns:saml='#{Saml::Kit::Namespaces::ASSERTION}' xmlns:samlp='#{Saml::Kit::Namespaces::PROTOCOL}'>
<saml:Issuer>#{issuer}</saml:Issuer>
<samlp:NameIDPolicy AllowCreate='true' Format='#{Saml::Kit::Namespaces::PERSISTENT}'/>
</samlp:AuthnRequest>
spec/saml/certificate_spec.rb
@@ -1,13 +0,0 @@
-RSpec.describe Saml::Kit::Certificate do
- subject { described_class.new(certificate, use: :signing) }
- let(:certificate) do
- cert, _ = Saml::Kit::SelfSignedCertificate.new('password').create
- cert
- end
-
- describe "#fingerprint" do
- it 'returns a fingerprint' do
- expect(subject.fingerprint).to be_instance_of(Saml::Kit::Fingerprint)
- end
- end
-end
spec/saml/composite_metadata_spec.rb
@@ -7,23 +7,23 @@ RSpec.describe Saml::Kit::CompositeMetadata do
let(:sp_logout_service) { FFaker::Internet.uri("https") }
let(:idp_logout_service) { FFaker::Internet.uri("https") }
let(:entity_id) { FFaker::Internet.uri("https") }
- let(:sp_signing_certificate) { Saml::Kit::KeyPair.generate(use: :signing).certificate }
- let(:sp_encryption_certificate) { Saml::Kit::KeyPair.generate(use: :encryption).certificate }
- let(:idp_signing_certificate) { Saml::Kit::KeyPair.generate(use: :signing).certificate }
- let(:idp_encryption_certificate) { Saml::Kit::KeyPair.generate(use: :encryption).certificate }
+ let(:sp_signing_certificate) { ::Xml::Kit::KeyPair.generate(use: :signing).certificate }
+ let(:sp_encryption_certificate) { ::Xml::Kit::KeyPair.generate(use: :encryption).certificate }
+ let(:idp_signing_certificate) { ::Xml::Kit::KeyPair.generate(use: :signing).certificate }
+ let(:idp_encryption_certificate) { ::Xml::Kit::KeyPair.generate(use: :encryption).certificate }
let(:xml) do
<<-XML
-<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Saml::Kit::Id.generate}" entityID="#{entity_id}">
+<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{::Xml::Kit::Id.generate}" entityID="#{entity_id}">
<SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
<KeyDescriptor use="signing">
- <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+ <KeyInfo xmlns="#{::Xml::Kit::Namespaces::XMLDSIG}">
<X509Data>
<X509Certificate>#{sp_signing_certificate.stripped}</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
- <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+ <KeyInfo xmlns="#{::Xml::Kit::Namespaces::XMLDSIG}">
<X509Data>
<X509Certificate>#{sp_encryption_certificate.stripped}</X509Certificate>
</X509Data>
@@ -35,14 +35,14 @@ RSpec.describe Saml::Kit::CompositeMetadata do
</SPSSODescriptor>
<IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
<KeyDescriptor use="signing">
- <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+ <KeyInfo xmlns="#{::Xml::Kit::Namespaces::XMLDSIG}">
<X509Data>
<X509Certificate>#{idp_signing_certificate.stripped}</X509Certificate>
</X509Data>
</KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
- <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
+ <KeyInfo xmlns="#{::Xml::Kit::Namespaces::XMLDSIG}">
<X509Data>
<X509Certificate>#{idp_encryption_certificate.stripped}</X509Certificate>
</X509Data>
spec/saml/default_registry_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe Saml::Kit::DefaultRegistry do
it 'registers metadata that serves as both an IDP and SP' do
xml = <<-XML
-<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Saml::Kit::Id.generate}" entityID="#{entity_id}">
+<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{::Xml::Kit::Id.generate}" entityID="#{entity_id}">
<SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
<SingleLogoutService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri("https")}"/>
<NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
spec/saml/document_spec.rb
@@ -2,7 +2,7 @@ RSpec.describe Saml::Kit::Document do
describe ".to_saml_document" do
subject { described_class }
let(:user) { double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: { id: SecureRandom.uuid }) }
- let(:request) { instance_double(Saml::Kit::AuthenticationRequest, id: Saml::Kit::Id.generate, issuer: FFaker::Internet.http_url, assertion_consumer_service_url: FFaker::Internet.http_url, name_id_format: Saml::Kit::Namespaces::PERSISTENT, provider: nil, signed?: true, trusted?: true) }
+ let(:request) { instance_double(Saml::Kit::AuthenticationRequest, id: Xml::Kit::Id.generate, issuer: FFaker::Internet.http_url, assertion_consumer_service_url: FFaker::Internet.http_url, name_id_format: Saml::Kit::Namespaces::PERSISTENT, provider: nil, signed?: true, trusted?: true) }
it 'returns a Response' do
xml = Saml::Kit::Response.build_xml(user, request)
spec/saml/fingerprint_spec.rb
@@ -1,27 +0,0 @@
-RSpec.describe Saml::Kit::Fingerprint do
- describe "#sha" do
- it 'returns the SHA256' do
- certificate, _ = Saml::Kit::SelfSignedCertificate.new("password").create
- x509 = OpenSSL::X509::Certificate.new(certificate)
- sha256 = OpenSSL::Digest::SHA256.new.hexdigest(x509.to_der).upcase.scan(/../).join(":")
-
- expect(described_class.new(certificate).algorithm(OpenSSL::Digest::SHA256)).to eql(sha256)
- end
-
- it 'returns the SHA1' do
- certificate, _ = Saml::Kit::SelfSignedCertificate.new("password").create
- x509 = OpenSSL::X509::Certificate.new(certificate)
- sha1 = OpenSSL::Digest::SHA1.new.hexdigest(x509.to_der).upcase.scan(/../).join(":")
-
- expect(described_class.new(certificate).algorithm(OpenSSL::Digest::SHA1)).to eql(sha1)
- end
- end
-
- it 'produces correct hash keys' do
- certificate, _ = Saml::Kit::SelfSignedCertificate.new("password").create
- items = { }
- items[described_class.new(certificate)] = "HI"
- items[described_class.new(certificate)] = "BYE"
- expect(items.keys.count).to eql(1)
- end
-end
spec/saml/identity_provider_metadata_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Saml::Kit::IdentityProviderMetadata do
it { expect(subject.single_logout_services).to be_empty }
it do
fingerprint = "9F:74:13:3B:BC:5A:7B:8B:2D:4F:8B:EF:1E:88:EB:D1:AE:BC:19:BF:CA:19:C6:2F:0F:4B:31:1D:68:98:B0:1B"
- expect(subject.certificates).to match_array([Saml::Kit::Certificate.new(certificate, use: :signing)])
+ expect(subject.certificates).to match_array([::Xml::Kit::Certificate.new(certificate, use: :signing)])
expect(subject.certificates.first.fingerprint.to_s).to eql(fingerprint)
end
it { expect(subject.attributes).to be_empty }
@@ -59,8 +59,8 @@ RSpec.describe Saml::Kit::IdentityProviderMetadata do
end
it do
expect(subject.certificates).to match_array([
- Saml::Kit::Certificate.new(signing_certificate, use: :signing),
- Saml::Kit::Certificate.new(encryption_certificate, use: :encryption),
+ ::Xml::Kit::Certificate.new(signing_certificate, use: :signing),
+ ::Xml::Kit::Certificate.new(encryption_certificate, use: :encryption),
])
end
it { expect(subject.attributes).to be_present }
spec/saml/logout_request_spec.rb
@@ -112,10 +112,9 @@ RSpec.describe Saml::Kit::LogoutRequest do
end
it 'validates the schema of the request' do
- id = Saml::Kit::Id.generate
- configuration = Saml::Kit::Configuration.new
- configuration.generate_key_pair_for(use: :signing)
- signed_xml = Saml::Kit::Signatures.sign(configuration: configuration) do |xml, signature|
+ id = Xml::Kit::Id.generate
+ key_pair = ::Xml::Kit::KeyPair.generate(use: :signing)
+ signed_xml = ::Xml::Kit::Signatures.sign(key_pair: key_pair) do |xml, signature|
xml.LogoutRequest ID: id do
signature.template(id)
xml.Fake do
spec/saml/metadata_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Saml::Kit::Metadata do
it 'returns a composite' do
xml = <<-XML
-<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Saml::Kit::Id.generate}" entityID="#{FFaker::Internet.uri("https")}">
+<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Xml::Kit::Id.generate}" entityID="#{FFaker::Internet.uri("https")}">
<SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
<SingleLogoutService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri("https")}"/>
<NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
spec/saml/response_spec.rb
@@ -1,6 +1,6 @@
RSpec.describe Saml::Kit::Response do
describe "#valid?" do
- let(:request) { instance_double(Saml::Kit::AuthenticationRequest, id: Saml::Kit::Id.generate, issuer: FFaker::Internet.http_url, assertion_consumer_service_url: FFaker::Internet.http_url, name_id_format: Saml::Kit::Namespaces::PERSISTENT, provider: nil, signed?: true, trusted?: true) }
+ let(:request) { instance_double(Saml::Kit::AuthenticationRequest, id: ::Xml::Kit::Id.generate, issuer: FFaker::Internet.http_url, assertion_consumer_service_url: FFaker::Internet.http_url, name_id_format: Saml::Kit::Namespaces::PERSISTENT, provider: nil, signed?: true, trusted?: true) }
let(:user) { double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: { id: SecureRandom.uuid }) }
let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
let(:metadata) { instance_double(Saml::Kit::IdentityProviderMetadata) }
@@ -54,10 +54,9 @@ RSpec.describe Saml::Kit::Response do
it 'validates the schema of the response' do
allow(registry).to receive(:metadata_for).and_return(metadata)
allow(metadata).to receive(:matches?).and_return(true)
- id = Saml::Kit::Id.generate
- configuration = Saml::Kit::Configuration.new
- configuration.generate_key_pair_for(use: :signing)
- signed_xml = Saml::Kit::Signatures.sign(configuration: configuration) do |xml, signature|
+ id = Xml::Kit::Id.generate
+ key_pair = ::Xml::Kit::KeyPair.generate(use: :signing)
+ signed_xml = ::Xml::Kit::Signatures.sign(key_pair: key_pair) do |xml, signature|
xml.tag! "samlp:Response", "xmlns:samlp" => Saml::Kit::Namespaces::PROTOCOL, ID: id do
signature.template(id)
xml.Fake do
@@ -146,7 +145,7 @@ RSpec.describe Saml::Kit::Response do
destination = FFaker::Internet.uri("https")
raw_xml = <<-XML
<?xml version="1.0"?>
-<samlp:Response xmlns:samlp="#{Saml::Kit::Namespaces::PROTOCOL}" ID="#{Saml::Kit::Id.generate}" Version="2.0" IssueInstant="#{now.iso8601}" Destination="#{destination}" Consent="#{Saml::Kit::Namespaces::UNSPECIFIED}" InResponseTo="#{request.id}">
+<samlp:Response xmlns:samlp="#{Saml::Kit::Namespaces::PROTOCOL}" ID="#{Xml::Kit::Id.generate}" Version="2.0" IssueInstant="#{now.iso8601}" Destination="#{destination}" Consent="#{Saml::Kit::Namespaces::UNSPECIFIED}" InResponseTo="#{request.id}">
<Issuer xmlns="#{Saml::Kit::Namespaces::ASSERTION}">#{request.issuer}</Issuer>
<samlp:Status>
<samlp:StatusCode Value="#{Saml::Kit::Namespaces::RESPONDER_ERROR}"/>
@@ -160,11 +159,9 @@ RSpec.describe Saml::Kit::Response do
end
it 'is invalid when there are 2 assertions' do
- id = Saml::Kit::Id.generate
+ id = Xml::Kit::Id.generate
issuer = FFaker::Internet.uri("https")
- configuration = Saml::Kit::Configuration.new do |config|
- config.generate_key_pair_for(use: :signing)
- end
+ key_pair = ::Xml::Kit::KeyPair.generate(use: :signing)
response_options = {
ID: id,
Version: "2.0",
@@ -174,12 +171,12 @@ RSpec.describe Saml::Kit::Response do
xmlns: Saml::Kit::Namespaces::PROTOCOL,
}
assertion_options = {
- ID: Saml::Kit::Id.generate,
+ ID: Xml::Kit::Id.generate,
IssueInstant: Time.now.iso8601,
Version: "2.0",
xmlns: Saml::Kit::Namespaces::ASSERTION,
}
- xml = Saml::Kit::Signatures.sign(configuration: configuration) do |xml, signature|
+ xml = ::Xml::Kit::Signatures.sign(key_pair: key_pair) do |xml, signature|
xml.instruct!
xml.Response response_options do
xml.Issuer(issuer, xmlns: Saml::Kit::Namespaces::ASSERTION)
@@ -206,7 +203,7 @@ RSpec.describe Saml::Kit::Response do
end
end
end
- new_options = assertion_options.merge(ID: Saml::Kit::Id.generate)
+ new_options = assertion_options.merge(ID: Xml::Kit::Id.generate)
xml.Assertion(new_options) do
xml.Issuer issuer
xml.Subject do
@@ -236,7 +233,7 @@ RSpec.describe Saml::Kit::Response do
describe "#signed?" do
let(:now) { Time.now.utc }
- let(:id) { Saml::Kit::Id.generate }
+ let(:id) { Xml::Kit::Id.generate }
let(:url) { FFaker::Internet.uri("https") }
it 'returns true when the Assertion is signed' do
@@ -317,11 +314,11 @@ RSpec.describe Saml::Kit::Response do
describe "#certificate" do
let(:now) { Time.now.utc }
- let(:id) { Saml::Kit::Id.generate }
+ let(:id) { Xml::Kit::Id.generate }
let(:url) { FFaker::Internet.uri("https") }
let(:certificate) do
- Saml::Kit::Certificate.new(
- Saml::Kit::SelfSignedCertificate.new("password").create[0],
+ ::Xml::Kit::Certificate.new(
+ ::Xml::Kit::SelfSignedCertificate.new("password").create[0],
use: :signing
)
end
@@ -404,7 +401,7 @@ RSpec.describe Saml::Kit::Response do
end
describe "encrypted assertion" do
- let(:id) { Saml::Kit::Id.generate }
+ let(:id) { Xml::Kit::Id.generate }
let(:now) { Time.now.utc }
let(:assertion_consumer_service_url) { FFaker::Internet.uri("https") }
let(:password) { FFaker::Movie.title }
@@ -442,7 +439,7 @@ XML
end
it 'parses the encrypted assertion' do
- certificate_pem, private_key_pem = Saml::Kit::SelfSignedCertificate.new(password).create
+ certificate_pem, private_key_pem = ::Xml::Kit::SelfSignedCertificate.new(password).create
public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
private_key = OpenSSL::PKey::RSA.new(private_key_pem, password)
@@ -455,7 +452,7 @@ XML
encrypted = cipher.update(assertion) + cipher.final
xml = <<-XML
-<samlp:Response xmlns:samlp="#{Saml::Kit::Namespaces::PROTOCOL}" xmlns:saml="#{Saml::Kit::Namespaces::ASSERTION}" ID="#{id}" Version="2.0" IssueInstant="#{now.iso8601}" Destination="#{assertion_consumer_service_url}" InResponseTo="#{Saml::Kit::Id.generate}">
+<samlp:Response xmlns:samlp="#{Saml::Kit::Namespaces::PROTOCOL}" xmlns:saml="#{Saml::Kit::Namespaces::ASSERTION}" ID="#{id}" Version="2.0" IssueInstant="#{now.iso8601}" Destination="#{assertion_consumer_service_url}" InResponseTo="#{Xml::Kit::Id.generate}">
<saml:Issuer>#{FFaker::Internet.uri("https")}</saml:Issuer>
<samlp:Status>
<samlp:StatusCode Value="#{Saml::Kit::Namespaces::SUCCESS}"/>
@@ -489,7 +486,7 @@ XML
describe "parsing" do
let(:user) { double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: attributes) }
- let(:request) { double(:request, id: Saml::Kit::Id.generate, signed?: true, trusted?: true, provider: nil, assertion_consumer_service_url: FFaker::Internet.uri("https"), name_id_format: '', issuer: FFaker::Internet.uri("https")) }
+ let(:request) { double(:request, id: Xml::Kit::Id.generate, signed?: true, trusted?: true, provider: nil, assertion_consumer_service_url: FFaker::Internet.uri("https"), name_id_format: '', issuer: FFaker::Internet.uri("https")) }
let(:attributes) { { name: 'mo' } }
it 'returns the name id' do
spec/saml/service_provider_metadata_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
it 'returns each of the certificates' do
expected_certificates = Saml::Kit.configuration.certificates.map do |x|
- Saml::Kit::Certificate.new(x.stripped, use: x.use)
+ ::Xml::Kit::Certificate.new(x.stripped, use: x.use)
end
expect(subject.certificates).to match_array(expected_certificates)
end
@@ -104,7 +104,7 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
it 'is invalid when 0 ACS endpoints are specified' do
xml = <<-XML
<?xml version="1.0" encoding="UTF-8"?>
-<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Saml::Kit::Id.generate}" entityID="#{entity_id}">
+<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{::Xml::Kit::Id.generate}" entityID="#{entity_id}">
<SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
<SingleLogoutService Binding="#{Saml::Kit::Bindings::HTTP_POST}" Location="#{FFaker::Internet.uri("https")}"/>
<NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
@@ -125,13 +125,13 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
it 'returns true when the fingerprint matches one of the signing certificates' do
certificate = Hash.from_xml(subject.to_xml)['EntityDescriptor']['Signature']['KeyInfo']['X509Data']['X509Certificate']
- fingerprint = Saml::Kit::Fingerprint.new(certificate)
+ fingerprint = ::Xml::Kit::Fingerprint.new(certificate)
expect(subject.matches?(fingerprint)).to be_truthy
end
it 'returns false when the fingerprint does not match one of the signing certificates' do
- certificate, _ = Saml::Kit::SelfSignedCertificate.new('password').create
- fingerprint = Saml::Kit::Fingerprint.new(certificate)
+ certificate, _ = ::Xml::Kit::SelfSignedCertificate.new('password').create
+ fingerprint = ::Xml::Kit::Fingerprint.new(certificate)
expect(subject.matches?(fingerprint)).to be_falsey
end
end
spec/saml/signatures_spec.rb
@@ -1,68 +0,0 @@
-RSpec.describe Saml::Kit::Signatures do
- let(:configuration) do
- config = Saml::Kit::Configuration.new
- config.add_key_pair(certificate, private_key, passphrase: passphrase, use: :signing)
- config
- end
-
- let(:reference_id) { Saml::Kit::Id.generate }
- let(:rsa_key) { OpenSSL::PKey::RSA.new(2048) }
- let(:public_key) { rsa_key.public_key }
- let(:certificate) do
- x = OpenSSL::X509::Certificate.new
- x.subject = x.issuer = OpenSSL::X509::Name.parse("/C=CA/ST=Alberta/L=Calgary/O=Xsig/OU=Xsig/CN=Xsig")
- x.not_before = Time.now
- x.not_after = Time.now + 365 * 24 * 60 * 60
- x.public_key = public_key
- x.serial = 0x0
- x.version = 2
- x.sign(rsa_key, OpenSSL::Digest::SHA256.new)
- x.to_pem
- end
- let(:private_key) { rsa_key.to_pem(OpenSSL::Cipher.new('des3'), passphrase) }
- let(:passphrase) { "password" }
-
- it 'generates a signature' do
- options = {
- "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol",
- "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion",
- ID: reference_id,
- }
- signed_xml = described_class.sign(configuration: configuration) do |xml, signature|
- xml.tag!('samlp:AuthnRequest', options) do
- signature.template(reference_id)
- xml.tag!('saml:Issuer', "MyEntityID")
- end
- end
- result = Hash.from_xml(signed_xml)
-
- signature = result["AuthnRequest"]["Signature"]
- expect(signature['xmlns']).to eql("http://www.w3.org/2000/09/xmldsig#")
- expect(signature['SignedInfo']['CanonicalizationMethod']['Algorithm']).to eql('http://www.w3.org/2001/10/xml-exc-c14n#')
- expect(signature['SignedInfo']['SignatureMethod']['Algorithm']).to eql("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256")
-
- expect(signature['SignedInfo']['Reference']['URI']).to eql("##{reference_id}")
- expect(signature['SignedInfo']['Reference']['Transforms']['Transform']).to match_array([
- { "Algorithm" => "http://www.w3.org/2000/09/xmldsig#enveloped-signature" },
- { "Algorithm" => "http://www.w3.org/2001/10/xml-exc-c14n#" }
- ])
- expect(signature['SignedInfo']['Reference']['DigestMethod']['Algorithm']).to eql("http://www.w3.org/2001/04/xmlenc#sha256")
- expected_certificate = certificate.gsub(/\n/, '').gsub(/-----BEGIN CERTIFICATE-----/, '').gsub(/-----END CERTIFICATE-----/, '')
- expect(signature['KeyInfo']['X509Data']['X509Certificate']).to eql(expected_certificate)
- expect(signature['SignedInfo']['Reference']['DigestValue']).to be_present
- expect(signature['SignatureValue']).to be_present
- expect(OpenSSL::X509::Certificate.new(Base64.decode64(signature['KeyInfo']['X509Data']['X509Certificate']))).to be_present
- end
-
- it 'does not add a signature' do
- signed_xml = described_class.sign(configuration: Saml::Kit::Configuration.new) do |xml, signature|
- xml.AuthnRequest do
- signature.template(reference_id)
- xml.Issuer "MyEntityID"
- end
- end
- result = Hash.from_xml(signed_xml)
- expect(result['AuthnRequest']).to be_present
- expect(result["AuthnRequest"]["Signature"]).to be_nil
- end
-end
spec/saml/xml_decryption_spec.rb
@@ -1,133 +0,0 @@
-RSpec.describe Saml::Kit::XmlDecryption do
- describe "#decrypt" do
- let(:secret) { FFaker::Movie.title }
- let(:password) { FFaker::Movie.title }
-
- it 'decrypts the data' do
- certificate_pem, private_key_pem = Saml::Kit::SelfSignedCertificate.new(password).create
- public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
- private_key = OpenSSL::PKey::RSA.new(private_key_pem, password)
-
- cipher = OpenSSL::Cipher.new('AES-128-CBC')
- cipher.encrypt
- key = cipher.random_key
- iv = cipher.random_iv
- encrypted = cipher.update(secret) + cipher.final
-
- data = {
- "EncryptedData"=> {
- "xmlns:xenc"=>"http://www.w3.org/2001/04/xmlenc#",
- "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
- "Type"=>"http://www.w3.org/2001/04/xmlenc#Element",
- "EncryptionMethod"=> {
- "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#aes128-cbc"
- },
- "KeyInfo"=> {
- "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
- "EncryptedKey"=> {
- "EncryptionMethod"=>{
- "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#rsa-1_5"
- },
- "CipherData"=>{
- "CipherValue"=> Base64.encode64(public_key.public_encrypt(key))
- }
- }
- },
- "CipherData"=>{
- "CipherValue"=> Base64.encode64(iv + encrypted)
- }
- }
- }
- subject = described_class.new(configuration: double(private_keys: [private_key]))
- decrypted = subject.decrypt(data)
- expect(decrypted.strip).to eql(secret)
- end
-
- it 'attemps to decrypt with each encryption keypair' do
- certificate_pem, private_key_pem = Saml::Kit::SelfSignedCertificate.new(password).create
- public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
- private_key = OpenSSL::PKey::RSA.new(private_key_pem, password)
-
- cipher = OpenSSL::Cipher.new('AES-128-CBC')
- cipher.encrypt
- key = cipher.random_key
- iv = cipher.random_iv
- encrypted = cipher.update(secret) + cipher.final
-
- data = {
- "EncryptedData"=> {
- "xmlns:xenc"=>"http://www.w3.org/2001/04/xmlenc#",
- "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
- "Type"=>"http://www.w3.org/2001/04/xmlenc#Element",
- "EncryptionMethod"=> {
- "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#aes128-cbc"
- },
- "KeyInfo"=> {
- "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
- "EncryptedKey"=> {
- "EncryptionMethod"=>{
- "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#rsa-1_5"
- },
- "CipherData"=>{
- "CipherValue"=> Base64.encode64(public_key.public_encrypt(key))
- }
- }
- },
- "CipherData"=>{
- "CipherValue"=> Base64.encode64(iv + encrypted)
- }
- }
- }
-
- _, other_private_key_pem = Saml::Kit::SelfSignedCertificate.new(password).create
- other_private_key = OpenSSL::PKey::RSA.new(other_private_key_pem, password)
-
- subject = described_class.new(configuration: double(private_keys: [other_private_key, private_key]))
- decrypted = subject.decrypt(data)
- expect(decrypted.strip).to eql(secret)
- end
-
- it 'raise an error when it cannot decrypt the data' do
- certificate_pem, _ = Saml::Kit::SelfSignedCertificate.new(password).create
- public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
-
- cipher = OpenSSL::Cipher.new('AES-128-CBC')
- cipher.encrypt
- key = cipher.random_key
- iv = cipher.random_iv
- encrypted = cipher.update(secret) + cipher.final
-
- data = {
- "EncryptedData"=> {
- "xmlns:xenc"=>"http://www.w3.org/2001/04/xmlenc#",
- "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
- "Type"=>"http://www.w3.org/2001/04/xmlenc#Element",
- "EncryptionMethod"=> {
- "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#aes128-cbc"
- },
- "KeyInfo"=> {
- "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
- "EncryptedKey"=> {
- "EncryptionMethod"=>{
- "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#rsa-1_5"
- },
- "CipherData"=>{
- "CipherValue"=> Base64.encode64(public_key.public_encrypt(key))
- }
- }
- },
- "CipherData"=>{
- "CipherValue"=> Base64.encode64(iv + encrypted)
- }
- }
- }
-
- new_private_key_pem = Saml::Kit::SelfSignedCertificate.new(password).create[1]
- new_private_key = OpenSSL::PKey::RSA.new(new_private_key_pem, password)
- subject = described_class.new(configuration: double(private_keys: [new_private_key]))
- expect do
- subject.decrypt(data)
- end.to raise_error(OpenSSL::PKey::RSAError)
- end
- end
-end
spec/saml/xml_spec.rb
@@ -1,58 +0,0 @@
-RSpec.describe Saml::Kit::Xml do
- describe "#valid_signature?" do
- let(:login_url) { "https://#{FFaker::Internet.domain_name}/login" }
- let(:logout_url) { "https://#{FFaker::Internet.domain_name}/logout" }
- let(:configuration) do
- configuration = Saml::Kit::Configuration.new
- configuration.generate_key_pair_for(use: :signing)
- configuration
- end
-
- let(:signed_xml) do
- Saml::Kit::ServiceProviderMetadata.build(configuration: configuration) do |builder|
- builder.entity_id = FFaker::Movie.title
- builder.add_assertion_consumer_service(login_url, binding: :http_post)
- builder.add_assertion_consumer_service(login_url, binding: :http_redirect)
- builder.add_single_logout_service(logout_url, binding: :http_post)
- builder.add_single_logout_service(logout_url, binding: :http_redirect)
- end.to_xml
- end
-
- it 'returns true, when the digest and signature is valid' do
- subject = described_class.new(signed_xml)
- expect(subject).to be_valid
- end
-
- it 'returns false, when the SHA1 digest is not valid' do
- subject = described_class.new(signed_xml.gsub("EntityDescriptor", "uhoh"))
- expect(subject).to_not be_valid
- expect(subject.errors[:digest_value]).to be_present
- end
-
- it 'it is invalid when digest is incorrect' do
- old_digest = Hash.from_xml(signed_xml)['EntityDescriptor']['Signature']['SignedInfo']['Reference']['DigestValue']
- subject = described_class.new(signed_xml.gsub(old_digest, 'sabotage'))
- expect(subject).to_not be_valid
- expect(subject.errors[:digest_value]).to be_present
- end
-
- it 'returns false, when the signature is invalid' do
- old_signature = Hash.from_xml(signed_xml)['EntityDescriptor']['Signature']['SignatureValue']
- signed_xml.gsub!(old_signature, 'sabotage')
- subject = described_class.new(signed_xml)
- expect(subject).to_not be_valid
- expect(subject.errors[:signature]).to be_present
- end
-
- it 'is valid' do
- configuration = Saml::Kit::Configuration.new do |config|
- 5.times { config.generate_key_pair_for(use: :signing) }
- end
- signed_xml = Saml::Kit::Metadata.build_xml(configuration: configuration) do |builder|
- builder.build_identity_provider
- builder.build_service_provider
- end
- expect(described_class.new(signed_xml)).to be_valid
- end
- end
-end
spec/support/matchers/have_xml.rb
@@ -1,6 +1,13 @@
RSpec::Matchers.define :have_xpath do |xpath|
match do |actual|
- xml_document(actual).xpath(xpath, Saml::Kit::Xml::NAMESPACES).any?
+ namespaces = {
+ "NameFormat": Saml::Kit::Namespaces::ATTR_SPLAT,
+ "ds": ::Xml::Kit::Namespaces::XMLDSIG,
+ "md": Saml::Kit::Namespaces::METADATA,
+ "saml": Saml::Kit::Namespaces::ASSERTION,
+ "samlp": Saml::Kit::Namespaces::PROTOCOL,
+ }
+ xml_document(actual).xpath(xpath, namespaces).any?
end
failure_message do |actual|
spec/spec_helper.rb
@@ -8,7 +8,7 @@ require "active_support/testing/time_helpers"
require "ffaker"
require "webmock/rspec"
-Saml::Kit.configuration.logger.level = Logger::FATAL
+Saml::Kit.configuration.logger.level = Xml::Kit.logger.level = Logger::FATAL
Dir[File.join(Dir.pwd, 'spec/support/**/*.rb')].each { |f| require f }
RSpec.configure do |config|
saml-kit.gemspec
@@ -24,10 +24,8 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]
spec.add_dependency "activemodel", ">= 4.2.0"
- spec.add_dependency "builder", "~> 3.2"
spec.add_dependency "nokogiri", "~> 1.8"
- spec.add_dependency "tilt", "~> 2.0"
- spec.add_dependency "xmldsig", "~> 0.6"
+ spec.add_dependency "xml-kit", "0.1.0"
spec.add_development_dependency "bundler", "~> 1.15"
spec.add_development_dependency "ffaker", "~> 2.7"
spec.add_development_dependency "rake", "~> 10.0"