Comparing changes
v0.2.3
→
v0.2.4
25 commits
54 files changed
Commits
Changed files (54)
lib
saml
kit
bindings
builders
locales
spec
saml
builders
exe/saml-kit-create-self-signed-certificate
@@ -1,6 +1,7 @@
#!/usr/bin/env ruby
require 'saml/kit'
+puts "Enter Password:"
password = STDIN.read.strip
certificate, private_key = Saml::Kit::SelfSignedCertificate.new(password).create
lib/saml/kit/bindings/http_redirect.rb
@@ -12,7 +12,7 @@ module Saml
builder.sign = false
builder.destination = location
document = builder.build
- [UrlBuilder.new.build(document, relay_state: relay_state), {}]
+ [UrlBuilder.new(configuration: builder.configuration).build(document, relay_state: relay_state), {}]
end
def deserialize(params)
lib/saml/kit/bindings/url_builder.rb
@@ -3,21 +3,25 @@ module Saml
module Bindings
class UrlBuilder
include Serializable
+ attr_reader :configuration
- def initialize(private_key: Saml::Kit.configuration.signing_private_key)
- @private_key = private_key
+ def initialize(configuration: Saml::Kit.configuration)
+ @configuration = configuration
end
def build(saml_document, relay_state: nil)
payload = canonicalize(saml_document, relay_state)
- "#{saml_document.destination}?#{payload}&Signature=#{signature_for(payload)}"
+ if configuration.sign?
+ "#{saml_document.destination}?#{payload}&Signature=#{signature_for(payload)}"
+ else
+ "#{saml_document.destination}?#{payload}"
+ end
end
private
- attr_reader :private_key
-
def signature_for(payload)
+ private_key = configuration.private_keys(use: :signing).sample
encode(private_key.sign(OpenSSL::Digest::SHA256.new, payload))
end
lib/saml/kit/builders/templates/assertion.builder
@@ -1,27 +1,29 @@
-xml.Assertion(assertion_options) do
- xml.Issuer issuer
- signature_for(reference_id: reference_id, xml: xml) unless encrypt
- 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
+encryption_for(xml: xml) do |xml|
+ xml.Assertion(assertion_options) do
+ xml.Issuer issuer
+ signature_for(reference_id: reference_id, xml: xml) unless encrypt
+ 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
end
- end
- xml.Conditions conditions_options do
- xml.AudienceRestriction do
- xml.Audience request.issuer
+ xml.Conditions conditions_options do
+ xml.AudienceRestriction do
+ xml.Audience request.issuer
+ end
end
- end
- xml.AuthnStatement authn_statement_options do
- xml.AuthnContext do
- xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
+ xml.AuthnStatement authn_statement_options do
+ xml.AuthnContext do
+ xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
+ end
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
+ 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
end
lib/saml/kit/builders/templates/identity_provider_metadata.builder
@@ -2,11 +2,11 @@ xml.instruct!
xml.EntityDescriptor entity_descriptor_options do
signature_for(reference_id: id, xml: xml)
xml.IDPSSODescriptor idp_sso_descriptor_options do
- if configuration.signing_certificate_pem.present?
- render configuration.signing_certificate, xml: xml
+ configuration.certificates(use: :signing).each do |certificate|
+ render certificate, xml: xml
end
- if configuration.encryption_certificate_pem.present?
- render configuration.encryption_certificate, xml: xml
+ configuration.certificates(use: :encryption).each do |certificate|
+ render certificate, xml: xml
end
logout_urls.each do |item|
xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
lib/saml/kit/builders/templates/nil_class.builder
lib/saml/kit/builders/templates/response.builder
@@ -5,7 +5,5 @@ xml.Response response_options do
xml.Status do
xml.StatusCode Value: status_code
end
- encryption_for(xml: xml) do |xml|
- render assertion, xml: xml
- end
+ render assertion, xml: xml
end
lib/saml/kit/builders/templates/service_provider_metadata.builder
@@ -2,11 +2,11 @@ xml.instruct!
xml.EntityDescriptor entity_descriptor_options do
signature_for(reference_id: id, xml: xml)
xml.SPSSODescriptor descriptor_options do
- if configuration.signing_certificate_pem.present?
- render configuration.signing_certificate, xml: xml
+ configuration.certificates(use: :signing).each do |certificate|
+ render certificate, xml: xml
end
- if configuration.encryption_certificate_pem.present?
- render configuration.encryption_certificate, xml: xml
+ configuration.certificates(use: :encryption).each do |certificate|
+ render certificate, xml: xml
end
logout_urls.each do |item|
xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
lib/saml/kit/builders/assertion.rb
@@ -5,7 +5,7 @@ module Saml
include Templatable
extend Forwardable
- def_delegators :@response_builder, :encrypt, :sign, :request, :issuer, :reference_id, :now, :configuration, :user, :version
+ def_delegators :@response_builder, :encrypt, :sign, :request, :issuer, :reference_id, :now, :configuration, :user, :version, :destination, :encryption_certificate
def initialize(response_builder)
@response_builder = response_builder
@@ -38,7 +38,7 @@ module Saml
{
InResponseTo: request.id,
NotOnOrAfter: 3.hours.since(now).utc.iso8601,
- Recipient: request.assertion_consumer_service_url,
+ Recipient: destination,
}
end
lib/saml/kit/builders/authentication_request.rb
@@ -3,17 +3,16 @@ module Saml
module Builders
class AuthenticationRequest
include Saml::Kit::Templatable
- attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :sign, :destination
+ 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, sign: true)
+ def initialize(configuration: Saml::Kit.configuration)
@configuration = configuration
@id = Id.generate
@issuer = configuration.issuer
@name_id_format = Namespaces::PERSISTENT
@now = Time.now.utc
- @sign = sign
@version = "2.0"
end
lib/saml/kit/builders/identity_provider_metadata.rb
@@ -4,18 +4,17 @@ module Saml
class IdentityProviderMetadata
include Saml::Kit::Templatable
attr_accessor :id, :organization_name, :organization_url, :contact_email, :entity_id, :attributes, :name_id_formats
- attr_accessor :want_authn_requests_signed, :sign
+ attr_accessor :want_authn_requests_signed
attr_reader :logout_urls, :single_sign_on_urls
attr_reader :configuration
- def initialize(configuration = Saml::Kit.configuration)
+ def initialize(configuration: Saml::Kit.configuration)
@attributes = []
@configuration = configuration
@entity_id = configuration.issuer
@id = Id.generate
@logout_urls = []
@name_id_formats = [Namespaces::PERSISTENT]
- @sign = true
@single_sign_on_urls = []
@want_authn_requests_signed = true
end
lib/saml/kit/builders/logout_request.rb
@@ -4,10 +4,10 @@ module Saml
class LogoutRequest
include Saml::Kit::Templatable
attr_accessor :id, :destination, :issuer, :name_id_format, :now
- attr_accessor :sign, :version
+ attr_accessor :version
attr_reader :user, :configuration
- def initialize(user, configuration: Saml::Kit.configuration, sign: true)
+ def initialize(user, configuration: Saml::Kit.configuration)
@configuration = configuration
@user = user
@id = "_#{SecureRandom.uuid}"
@@ -15,11 +15,10 @@ module Saml
@name_id_format = Saml::Kit::Namespaces::PERSISTENT
@now = Time.now.utc
@version = "2.0"
- @sign = sign
end
def build
- Saml::Kit::LogoutRequest.new(to_xml)
+ Saml::Kit::LogoutRequest.new(to_xml, configuration: configuration)
end
private
lib/saml/kit/builders/logout_response.rb
@@ -3,17 +3,16 @@ module Saml
module Builders
class LogoutResponse
include Saml::Kit::Templatable
- attr_accessor :id, :issuer, :version, :status_code, :sign, :now, :destination
+ attr_accessor :id, :issuer, :version, :status_code, :now, :destination
attr_reader :request
attr_reader :configuration
- def initialize(user, request, configuration: Saml::Kit.configuration, sign: true)
+ def initialize(user, request, configuration: Saml::Kit.configuration)
@configuration = configuration
@id = Id.generate
@issuer = configuration.issuer
@now = Time.now.utc
@request = request
- @sign = sign
@status_code = Namespaces::SUCCESS
@user = user
@version = "2.0"
lib/saml/kit/builders/response.rb
@@ -6,7 +6,7 @@ module Saml
attr_reader :user, :request
attr_accessor :id, :reference_id, :now
attr_accessor :version, :status_code
- attr_accessor :issuer, :sign, :destination, :encrypt
+ attr_accessor :issuer, :destination, :encrypt
attr_reader :configuration
def initialize(user, request, configuration: Saml::Kit.configuration)
@@ -18,9 +18,8 @@ module Saml
@version = "2.0"
@status_code = Namespaces::SUCCESS
@issuer = configuration.issuer
- @destination = destination_for(request)
@sign = want_assertions_signed
- @encrypt = false
+ @encrypt = encryption_certificate.present?
@configuration = configuration
end
@@ -28,29 +27,24 @@ module Saml
request.provider.want_assertions_signed
rescue => error
Saml::Kit.logger.error(error)
- true
+ nil
end
def build
- Saml::Kit::Response.new(to_xml, request_id: request.id)
- end
-
- private
-
- def assertion
- @assertion ||= Saml::Kit::Builders::Assertion.new(self)
+ Saml::Kit::Response.new(to_xml, request_id: request.id, configuration: configuration)
end
def encryption_certificate
request.provider.encryption_certificates.first
+ rescue => error
+ Saml::Kit.logger.error(error)
+ nil
end
- def destination_for(request)
- if request.signed? && request.trusted?
- request.assertion_consumer_service_url || request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
- else
- request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
- end
+ private
+
+ def assertion
+ @assertion ||= Saml::Kit::Builders::Assertion.new(self)
end
def response_options
lib/saml/kit/builders/service_provider_metadata.rb
@@ -3,19 +3,18 @@ module Saml
module Builders
class ServiceProviderMetadata
include Saml::Kit::Templatable
- attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats, :sign
+ attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats
attr_accessor :organization_name, :organization_url, :contact_email
attr_accessor :want_assertions_signed
attr_reader :configuration
- def initialize(configuration = Saml::Kit.configuration)
+ def initialize(configuration: Saml::Kit.configuration)
@acs_urls = []
@configuration = configuration
@entity_id = configuration.issuer
@id = Id.generate
@logout_urls = []
@name_id_formats = [Namespaces::PERSISTENT]
- @sign = true
@want_assertions_signed = true
end
@@ -43,7 +42,7 @@ module Saml
def descriptor_options
{
- AuthnRequestsSigned: sign,
+ AuthnRequestsSigned: sign?,
WantAssertionsSigned: want_assertions_signed,
protocolSupportEnumeration: Namespaces::PROTOCOL,
}
lib/saml/kit/builders/xml_signature.rb
@@ -21,11 +21,10 @@ module Saml
attr_reader :reference_id
attr_reader :x509_certificate
- def initialize(reference_id, configuration:, sign: true)
+ def initialize(reference_id, configuration:)
@configuration = configuration
@reference_id = reference_id
- @sign = sign
- @x509_certificate = configuration.signing_certificate.stripped
+ @x509_certificate = configuration.certificates(use: :signing).sample.stripped
end
def signature_method
lib/saml/kit/locales/en.yml
@@ -11,6 +11,9 @@ en:
invalid_signature: "invalid signature."
InvalidDocument:
invalid: "must contain valid SAMLRequest"
+ LogoutRequest:
+ invalid_fingerprint: "does not match."
+ unregistered: "is unregistered."
LogoutResponse:
unregistered: "is unregistered."
Response:
lib/saml/kit/assertion.rb
@@ -0,0 +1,73 @@
+module Saml
+ module Kit
+ class Assertion
+ def initialize(xml_hash, configuration:)
+ @xml_hash = xml_hash
+ @configuration = configuration
+ end
+
+ def name_id
+ assertion.fetch('Subject', {}).fetch('NameID', nil)
+ end
+
+ def signed?
+ assertion.fetch('Signature', nil).present?
+ end
+
+ def attributes
+ @attributes ||=
+ begin
+ attrs = assertion.fetch('AttributeStatement', {}).fetch('Attribute', [])
+ items = if attrs.is_a? Hash
+ [[attrs["Name"], attrs["AttributeValue"]]]
+ else
+ attrs.map { |item| [item['Name'], item['AttributeValue']] }
+ end
+ Hash[items].with_indifferent_access
+ end
+ end
+
+ def started_at
+ parse_date(assertion.fetch('Conditions', {}).fetch('NotBefore', nil))
+ end
+
+ def expired_at
+ parse_date(assertion.fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
+ end
+
+ def certificate
+ assertion.fetch('Signature', {}).fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
+ end
+
+ def audiences
+ Array(assertion['Conditions']['AudienceRestriction']['Audience'])
+ rescue => error
+ Saml::Kit.logger.error(error)
+ []
+ end
+
+ private
+
+ def encrypted?
+ @xml_hash.fetch('Response', {}).fetch('EncryptedAssertion', nil).present?
+ end
+
+ def assertion
+ if encrypted?
+ decrypted = XmlDecryption.new(configuration: @configuration).decrypt(@xml_hash['Response']['EncryptedAssertion'])
+ Saml::Kit.logger.debug(decrypted)
+ Hash.from_xml(decrypted)['Assertion']
+ else
+ @xml_hash.fetch('Response', {}).fetch('Assertion', {})
+ end
+ end
+
+ def parse_date(value)
+ DateTime.parse(value)
+ rescue => error
+ Saml::Kit.logger.error(error)
+ Time.at(0).to_datetime
+ end
+ end
+ end
+end
lib/saml/kit/authentication_request.rb
@@ -3,8 +3,8 @@ module Saml
class AuthenticationRequest < Document
include Requestable
- def initialize(xml)
- super(xml, name: "AuthnRequest")
+ def initialize(xml, configuration: Saml::Kit.configuration)
+ super(xml, name: "AuthnRequest", configuration: configuration)
end
def assertion_consumer_service_url
lib/saml/kit/buildable.rb
@@ -4,10 +4,8 @@ module Saml
extend ActiveSupport::Concern
class_methods do
- def build(*args)
- builder(*args).tap do |x|
- yield x if block_given?
- end.build
+ def build(*args, &block)
+ builder(*args, &block).build
end
def builder(*args)
lib/saml/kit/composite_metadata.rb
@@ -1,16 +1,19 @@
module Saml
module Kit
class CompositeMetadata < Metadata
+ include Enumerable
attr_reader :service_provider, :identity_provider
def initialize(xml)
super("IDPSSODescriptor", xml)
- @service_provider = Saml::Kit::ServiceProviderMetadata.new(xml)
- @identity_provider = Saml::Kit::IdentityProviderMetadata.new(xml)
+ @metadatum = [
+ Saml::Kit::ServiceProviderMetadata.new(xml),
+ Saml::Kit::IdentityProviderMetadata.new(xml),
+ ]
end
def services(type)
- xpath = "//md:EntityDescriptor/md:SPSSODescriptor/md:#{type}|//md:EntityDescriptor/md:IDPSSODescriptor/md:#{type}"
+ xpath = map { |x| "//md:EntityDescriptor/md:#{x.name}/md:#{type}" }.join("|")
document.find_all(xpath).map do |item|
binding = item.attribute("Binding").value
location = item.attribute("Location").value
@@ -19,14 +22,16 @@ module Saml
end
def certificates
- identity_provider.certificates + service_provider.certificates
+ flat_map(&:certificates)
+ end
+
+ def each(&block)
+ @metadatum.each(&block)
end
def method_missing(name, *args)
- if identity_provider.respond_to?(name)
- identity_provider.public_send(name, *args)
- elsif service_provider.respond_to?(name)
- service_provider.public_send(name, *args)
+ if target = find { |x| x.respond_to?(name) }
+ target.public_send(name, *args)
else
super
end
lib/saml/kit/configuration.rb
@@ -3,37 +3,62 @@ module Saml
class Configuration
attr_accessor :issuer
attr_accessor :signature_method, :digest_method
- attr_accessor :signing_certificate_pem, :signing_private_key_pem, :signing_private_key_password
- attr_accessor :encryption_certificate_pem, :encryption_private_key_pem, :encryption_private_key_password
attr_accessor :registry, :session_timeout
attr_accessor :logger
def initialize
@signature_method = :SHA256
@digest_method = :SHA256
- @signing_private_key_password = SecureRandom.uuid
- @encryption_private_key_password = SecureRandom.uuid
- @signing_certificate_pem, @signing_private_key_pem = SelfSignedCertificate.new(@signing_private_key_password).create
- @encryption_certificate_pem, @encryption_private_key_pem = SelfSignedCertificate.new(@encryption_private_key_password).create
@registry = DefaultRegistry.new
@session_timeout = 3.hours
@logger = Logger.new(STDOUT)
+ yield self if block_given?
end
- def signing_certificate
- Saml::Kit::Certificate.new(signing_certificate_pem, use: :signing)
+ def add_key_pair(certificate, private_key, password:, use: :signing)
+ key_pairs.push({
+ certificate: Saml::Kit::Certificate.new(certificate, use: use),
+ private_key: OpenSSL::PKey::RSA.new(private_key, password)
+ })
+ end
+
+ def generate_key_pair_for(use:, password: SecureRandom.uuid)
+ certificate, private_key = SelfSignedCertificate.new(password).create
+ add_key_pair(certificate, private_key, password: password, use: use)
+ end
+
+ def certificates(use: nil)
+ certificates = key_pairs.map { |x| x[:certificate] }
+ use.present? ? certificates.find_all { |x| x.for?(use) } : certificates
+ end
+
+ def private_keys(use: :signing)
+ key_pairs.find_all { |x| x[:certificate].for?(use) }.map { |x| x[:private_key] }
end
def encryption_certificate
- Saml::Kit::Certificate.new(encryption_certificate_pem, use: :encryption)
+ Saml::Kit.deprecate("encryption_certificate is deprecated. Use certificates(use: :encryption) instead")
+ certificates(use: :encryption).last
end
def signing_private_key
- OpenSSL::PKey::RSA.new(signing_private_key_pem, signing_private_key_password)
+ Saml::Kit.deprecate("signing_private_key is deprecated. Use private_keys(use: :signing) instead")
+ private_keys(use: :signing).last
end
def encryption_private_key
- OpenSSL::PKey::RSA.new(encryption_private_key_pem, encryption_private_key_password)
+ Saml::Kit.deprecate("encryption_private_key is deprecated. Use private_keys(use: :encryption) instead")
+ private_keys(use: :encryption).last
+ end
+
+ def sign?
+ certificates(use: :signing).any?
+ end
+
+ private
+
+ def key_pairs
+ @key_pairs ||= []
end
end
end
lib/saml/kit/document.rb
@@ -12,9 +12,10 @@ module Saml
validate :must_be_expected_type
validate :must_be_valid_version
- attr_reader :content, :name
+ attr_reader :content, :name, :configuration
- def initialize(xml, name:)
+ def initialize(xml, name:, configuration: Saml::Kit.configuration)
+ @configuration = configuration
@content = xml
@name = name
@xml_hash = Hash.from_xml(xml) || {}
lib/saml/kit/logout_request.rb
@@ -4,8 +4,8 @@ module Saml
include Requestable
validates_presence_of :single_logout_service, if: :expected_type?
- def initialize(xml)
- super(xml, name: "LogoutRequest")
+ def initialize(xml, configuration: Saml::Kit.configuration)
+ super(xml, name: "LogoutRequest", configuration: configuration)
end
def name_id
lib/saml/kit/metadata.rb
@@ -91,8 +91,8 @@ module Saml
end
def verify(algorithm, signature, data)
- signing_certificates.find do |cert|
- cert.public_key.verify(algorithm, signature, data)
+ signing_certificates.find do |certificate|
+ certificate.public_key.verify(algorithm, signature, data)
end
end
lib/saml/kit/response.rb
@@ -2,42 +2,16 @@ module Saml
module Kit
class Response < Document
include Respondable
+ extend Forwardable
+
+ def_delegators :assertion, :name_id, :[], :attributes, :started_at, :expired_at, :audiences
validate :must_be_active_session
validate :must_match_issuer
- def initialize(xml, request_id: nil)
+ def initialize(xml, request_id: nil, configuration: Saml::Kit.configuration)
@request_id = request_id
- super(xml, name: "Response")
- end
-
- def name_id
- assertion.fetch('Subject', {}).fetch('NameID', nil)
- end
-
- def [](key)
- attributes[key]
- end
-
- def attributes
- @attributes ||=
- begin
- attrs = assertion.fetch('AttributeStatement', {}).fetch('Attribute', [])
- items = if attrs.is_a? Hash
- [[attrs["Name"], attrs["AttributeValue"]]]
- else
- attrs.map { |item| [item['Name'], item['AttributeValue']] }
- end
- Hash[items].with_indifferent_access
- end
- end
-
- def started_at
- parse_date(assertion.fetch('Conditions', {}).fetch('NotBefore', nil))
- end
-
- def expired_at
- parse_date(assertion.fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
+ super(xml, name: "Response", configuration: configuration)
end
def expired?
@@ -48,29 +22,16 @@ module Saml
Time.current > started_at && !expired?
end
- def encrypted?
- to_h[name]['EncryptedAssertion'].present?
- end
-
def assertion
- @assertion =
- begin
- if encrypted?
- decrypted = XmlDecryption.new.decrypt(to_h.fetch(name, {}).fetch('EncryptedAssertion', {}))
- Saml::Kit.logger.debug(decrypted)
- Hash.from_xml(decrypted)['Assertion']
- else
- to_h.fetch(name, {}).fetch('Assertion', {})
- end
- end
+ @assertion = Saml::Kit::Assertion.new(to_h, configuration: @configuration)
end
def signed?
- super || assertion.fetch('Signature', nil).present?
+ super || assertion.signed?
end
def certificate
- super || assertion.fetch('Signature', {}).fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
+ super || assertion.certificate
end
private
@@ -85,25 +46,11 @@ module Saml
return unless expected_type?
return unless success?
- unless audiences.include?(Saml::Kit.configuration.issuer)
+ unless audiences.include?(configuration.issuer)
errors[:audience] << error_message(:must_match_issuer)
end
end
- def audiences
- Array(assertion['Conditions']['AudienceRestriction']['Audience'])
- rescue => error
- Saml::Kit.logger.error(error)
- []
- end
-
- def parse_date(value)
- DateTime.parse(value)
- rescue => error
- Saml::Kit.logger.error(error)
- Time.at(0).to_datetime
- end
-
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::Response::Builder', 'Saml::Kit::Builders::Response')
end
end
lib/saml/kit/self_signed_certificate.rb
@@ -17,15 +17,6 @@ module Saml
certificate.public_key = public_key
certificate.serial = 0x0
certificate.version = 2
- factory = OpenSSL::X509::ExtensionFactory.new
- factory.subject_certificate = factory.issuer_certificate = certificate
- certificate.extensions = [
- factory.create_extension("basicConstraints","CA:TRUE", true),
- factory.create_extension("subjectKeyIdentifier", "hash"),
- ]
- certificate.add_extension(
- factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
- )
certificate.sign(rsa_key, OpenSSL::Digest::SHA256.new)
[
certificate.to_pem,
lib/saml/kit/signature.rb
@@ -1,36 +1,23 @@
module Saml
module Kit
class Signature
- attr_reader :sign, :xml
- attr_reader :configuration
+ attr_reader :signatures
+ attr_reader :xml
- def initialize(xml, configuration:, sign: true)
- @configuration = configuration
- @sign = sign
+ def initialize(xml, signatures)
+ @signatures = signatures
@xml = xml
end
def template(reference_id)
- return unless sign
- signature = signatures.build(reference_id)
- Template.new(signature).to_xml(xml: xml)
+ Template.new(signatures.build(reference_id)).to_xml(xml: xml)
end
- def finalize
+ def self.sign(xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
+ signatures = Saml::Kit::Signatures.new(configuration: configuration)
+ yield xml, new(xml, signatures)
signatures.complete(xml.target!)
end
-
- def self.sign(sign: true, xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
- signature = new(xml, sign: sign, configuration: configuration)
- yield xml, signature
- signature.finalize
- end
-
- private
-
- def signatures
- @signatures ||= Saml::Kit::Signatures.new(configuration: configuration, sign: sign)
- end
end
end
end
lib/saml/kit/signatures.rb
@@ -1,28 +1,22 @@
module Saml
module Kit
class Signatures
- attr_reader :sign, :configuration
+ attr_reader :configuration
- def initialize(configuration:, sign: true)
+ def initialize(configuration:)
@configuration = configuration
- @sign = sign
end
def build(reference_id)
- Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration, sign: sign)
+ return nil unless configuration.sign?
+ Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration)
end
def complete(raw_xml)
- return raw_xml unless sign
-
+ return raw_xml unless configuration.sign?
+ private_key = configuration.private_keys(use: :signing).sample
Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
end
-
- private
-
- def private_key
- configuration.signing_private_key
- end
end
end
end
lib/saml/kit/templatable.rb
@@ -1,17 +1,23 @@
module Saml
module Kit
module Templatable
+ attr_accessor :sign
+
def to_xml(xml: ::Builder::XmlMarkup.new)
signatures.complete(render(self, xml: xml))
end
def signature_for(reference_id:, xml:)
- return unless sign
+ return unless sign?
render(signatures.build(reference_id), xml: xml)
end
+ def sign?
+ sign.nil? ? configuration.sign? : sign && configuration.sign?
+ end
+
def signatures
- @signatures ||= Saml::Kit::Signatures.new(configuration: configuration, sign: sign)
+ @signatures ||= Saml::Kit::Signatures.new(configuration: configuration)
end
def encryption_for(xml:)
lib/saml/kit/template.rb
@@ -18,7 +18,8 @@ module Saml
end
def template_path
- File.join(File.expand_path(File.dirname(__FILE__)), "builders/templates/#{template_name}")
+ root_path = File.expand_path(File.dirname(__FILE__))
+ File.join(root_path, "builders/templates/", template_name)
end
def template
lib/saml/kit/trustable.rb
@@ -30,7 +30,7 @@ module Saml
end
def provider
- Saml::Kit.registry.metadata_for(issuer)
+ configuration.registry.metadata_for(issuer)
end
def signature_verified!
lib/saml/kit/version.rb
@@ -1,5 +1,5 @@
module Saml
module Kit
- VERSION = "0.2.3"
+ VERSION = "0.2.4"
end
end
lib/saml/kit/xml.rb
@@ -7,6 +7,7 @@ module Saml
"ds": Namespaces::XMLDSIG,
"md": Namespaces::METADATA,
"saml": Namespaces::ASSERTION,
+ "pro": Namespaces::PROTOCOL,
}.freeze
attr_reader :raw_xml, :document
lib/saml/kit/xml_decryption.rb
@@ -3,8 +3,8 @@ module Saml
class XmlDecryption
attr_reader :private_key
- def initialize(private_key = Saml::Kit.configuration.encryption_private_key)
- @private_key = private_key
+ def initialize(configuration: Saml::Kit.configuration)
+ @private_key = configuration.private_keys(use: :encryption).sample
end
def decrypt(data)
lib/saml/kit.rb
@@ -26,6 +26,7 @@ require "saml/kit/requestable"
require "saml/kit/trustable"
require "saml/kit/document"
+require "saml/kit/assertion"
require "saml/kit/authentication_request"
require "saml/kit/bindings"
require "saml/kit/certificate"
spec/saml/bindings/http_post_spec.rb
@@ -1,14 +1,37 @@
require 'spec_helper'
RSpec.describe Saml::Kit::Bindings::HttpPost do
- let(:location) { FFaker::Internet.http_url }
+ let(:location) { FFaker::Internet.uri("https") }
subject { described_class.new(location: location) }
+ describe "equality" do
+ it 'is referentially equal' do
+ expect(subject).to eql(subject)
+ end
+
+ it 'is equal by value' do
+ expect(subject).to eql(
+ Saml::Kit::Bindings::HttpPost.new(location: location)
+ )
+ end
+
+ it 'is not equal' do
+ expect(subject).to_not eql(
+ described_class.new(location: FFaker::Internet.uri("https"))
+ )
+ end
+ end
+
describe "#serialize" do
let(:relay_state) { "ECHO" }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.generate_key_pair_for(use: :signing)
+ end
+ end
it 'encodes the request using the HTTP-POST encoding for a AuthenticationRequest' do
- builder = Saml::Kit::AuthenticationRequest.builder_class.new
+ builder = Saml::Kit::AuthenticationRequest.builder_class.new(configuration: configuration)
url, saml_params = subject.serialize(builder, relay_state: relay_state)
expect(url).to eql(location)
@@ -22,7 +45,7 @@ RSpec.describe Saml::Kit::Bindings::HttpPost do
it 'returns a SAMLRequest for a LogoutRequest' do
user = double(:user, name_id_for: SecureRandom.uuid)
- builder = Saml::Kit::LogoutRequest.builder_class.new(user)
+ builder = Saml::Kit::LogoutRequest.builder_class.new(user, configuration: configuration)
url, saml_params = subject.serialize(builder, relay_state: relay_state)
expect(url).to eql(location)
@@ -37,7 +60,7 @@ RSpec.describe Saml::Kit::Bindings::HttpPost do
it 'returns a SAMLResponse for a LogoutResponse' do
user = double(:user, name_id_for: SecureRandom.uuid)
request = instance_double(Saml::Kit::AuthenticationRequest, id: SecureRandom.uuid)
- builder = Saml::Kit::LogoutResponse.builder_class.new(user, request)
+ builder = Saml::Kit::LogoutResponse.builder_class.new(user, request, configuration: configuration)
url, saml_params = subject.serialize(builder, relay_state: relay_state)
expect(url).to eql(location)
spec/saml/bindings/http_redirect_spec.rb
@@ -6,9 +6,14 @@ RSpec.describe Saml::Kit::Bindings::HttpRedirect do
describe "#serialize" do
let(:relay_state) { "ECHO" }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.generate_key_pair_for(use: :signing)
+ end
+ end
it 'encodes the request using the HTTP-Redirect encoding' do
- builder = Saml::Kit::AuthenticationRequest.builder_class.new
+ builder = Saml::Kit::AuthenticationRequest.builder_class.new(configuration: configuration)
url, _ = subject.serialize(builder, relay_state: relay_state)
expect(url).to start_with(location)
expect(url).to have_query_param('SAMLRequest')
spec/saml/bindings/url_builder_spec.rb
@@ -13,6 +13,13 @@ RSpec.describe Saml::Kit::Bindings::UrlBuilder do
[Saml::Kit::LogoutResponse, 'SAMLResponse'],
].each do |(response_type, query_string_parameter)|
describe response_type.to_s do
+ subject { described_class.new(configuration: configuration) }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.generate_key_pair_for(use: :signing)
+ end
+ end
+
let(:response) { instance_double(response_type, destination: destination, to_xml: xml, query_string_parameter: query_string_parameter) }
def to_query_params(url)
@@ -54,7 +61,8 @@ RSpec.describe Saml::Kit::Bindings::UrlBuilder do
payload = "#{query_string_parameter}=#{query_params[query_string_parameter]}"
payload << "&RelayState=#{query_params['RelayState']}"
payload << "&SigAlg=#{query_params['SigAlg']}"
- expected_signature = Base64.strict_encode64(Saml::Kit.configuration.signing_private_key.sign(OpenSSL::Digest::SHA256.new, payload))
+ private_key = configuration.private_keys(use: :signing).sample
+ expected_signature = Base64.strict_encode64(private_key.sign(OpenSSL::Digest::SHA256.new, payload))
expect(query_params['Signature']).to eql(expected_signature)
end
@@ -65,7 +73,8 @@ RSpec.describe Saml::Kit::Bindings::UrlBuilder do
payload = "#{query_string_parameter}=#{query_params[query_string_parameter]}"
payload << "&SigAlg=#{query_params['SigAlg']}"
- expected_signature = Base64.strict_encode64(Saml::Kit.configuration.signing_private_key.sign(OpenSSL::Digest::SHA256.new, payload))
+ private_key = configuration.private_keys(use: :signing).sample
+ expected_signature = Base64.strict_encode64(private_key.sign(OpenSSL::Digest::SHA256.new, payload))
expect(query_params['Signature']).to eql(expected_signature)
end
end
spec/saml/builders/identity_provider_metadata_spec.rb
@@ -1,7 +1,13 @@
require 'spec_helper'
RSpec.describe Saml::Kit::Builders::IdentityProviderMetadata do
- subject { described_class.new }
+ subject { described_class.new(configuration: configuration) }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.generate_key_pair_for(use: :signing)
+ config.generate_key_pair_for(use: :encryption)
+ end
+ end
let(:email) { FFaker::Internet.email }
let(:org_name) { FFaker::Movie.title }
let(:url) { FFaker::Internet.uri("https") }
@@ -38,10 +44,8 @@ RSpec.describe Saml::Kit::Builders::IdentityProviderMetadata do
expect(result['EntityDescriptor']['IDPSSODescriptor']['SingleLogoutService']['Location']).to eql("https://www.example.com/logout")
expect(result['EntityDescriptor']['IDPSSODescriptor']['Attribute']['Name']).to eql("id")
certificates = result['EntityDescriptor']['IDPSSODescriptor']['KeyDescriptor'].map { |x| x['KeyInfo']['X509Data']['X509Certificate'] }
- expect(certificates).to match_array([
- Saml::Kit.configuration.signing_certificate.stripped,
- Saml::Kit.configuration.encryption_certificate.stripped,
- ])
+ expected_certificates = configuration.certificates.map(&:stripped)
+ expect(certificates).to match_array(expected_certificates)
expect(result['EntityDescriptor']['Organization']['OrganizationName']).to eql(org_name)
expect(result['EntityDescriptor']['Organization']['OrganizationDisplayName']).to eql(org_name)
expect(result['EntityDescriptor']['Organization']['OrganizationURL']).to eql(url)
spec/saml/builders/logout_request_spec.rb
@@ -1,9 +1,14 @@
require 'spec_helper'
RSpec.describe Saml::Kit::Builders::LogoutRequest do
- subject { described_class.new(user) }
+ subject { described_class.new(user, configuration: configuration) }
let(:user) { double(:user, name_id_for: name_id) }
let(:name_id) { SecureRandom.uuid }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.generate_key_pair_for(use: :signing)
+ end
+ end
it 'produces the expected xml' do
travel_to 1.second.from_now
spec/saml/builders/response_spec.rb
@@ -1,26 +1,28 @@
require 'spec_helper'
RSpec.describe Saml::Kit::Builders::Response do
- subject { described_class.new(user, request) }
+ subject { described_class.new(user, request, configuration: configuration) }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.issuer = issuer
+ config.generate_key_pair_for(use: :signing)
+ config.generate_key_pair_for(use: :encryption)
+ end
+ end
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(:provider) { double(want_assertions_signed: false, encryption_certificates: [Saml::Kit::Certificate.new(encryption_pem, use: :encryption)]) }
- let(:encryption_pem) { Saml::Kit.configuration.encryption_certificate.stripped }
+ let(:provider) { double(:provider, want_assertions_signed: false, encryption_certificates: [configuration.certificates(use: :encryption).last] ) }
let(:issuer) { FFaker::Internet.uri("https") }
- before :each do
- allow(Saml::Kit.configuration).to receive(:issuer).and_return(issuer)
- end
-
describe "#build" do
it 'builds a response with the request_id' do
expect(subject.build.request_id).to eql(request.id)
end
it 'builds a valid encrypted assertion' do
- allow(Saml::Kit.configuration.registry).to receive(:metadata_for).with(issuer).and_return(provider)
+ allow(configuration.registry).to receive(:metadata_for).with(issuer).and_return(provider)
allow(provider).to receive(:matches?).and_return(true)
subject.sign = true
@@ -34,6 +36,8 @@ RSpec.describe Saml::Kit::Builders::Response do
it 'returns a proper response for the user' do
travel_to 1.second.from_now
allow(Saml::Kit.configuration).to receive(:issuer).and_return(issuer)
+ subject.destination = assertion_consumer_service_url
+ subject.encrypt = false
hash = Hash.from_xml(subject.to_xml)
expect(hash['Response']['ID']).to be_present
@@ -81,6 +85,7 @@ RSpec.describe Saml::Kit::Builders::Response do
metadata = builder.build
allow(request).to receive(:provider).and_return(metadata)
+ subject = described_class.new(user, request)
hash = Hash.from_xml(subject.to_xml)
expect(hash['Response']['Signature']).to be_nil
end
@@ -90,7 +95,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.decrypt(encrypted_assertion)
+ decrypted_assertion = Saml::Kit::XmlDecryption.new(configuration: configuration).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
@@ -120,29 +125,4 @@ RSpec.describe Saml::Kit::Builders::Response do
expect(result['Response']['Assertion']['Signature']).to be_present
end
end
-
- describe "#destination" do
- let(:assertion_consumer_service_url) { "https://#{FFaker::Internet.domain_name}/acs" }
- let(:user) { double(:user, name_id_for: SecureRandom.uuid, assertion_attributes_for: []) }
- subject { described_class.new(user, request).build }
-
- describe "when the request is signed and trusted" do
- let(:request) { instance_double(Saml::Kit::AuthenticationRequest, id: SecureRandom.uuid, assertion_consumer_service_url: assertion_consumer_service_url, issuer: FFaker::Movie.title, name_id_format: Saml::Kit::Namespaces::EMAIL_ADDRESS, provider: nil, signed?: true, trusted?: true) }
-
- it 'returns the ACS embedded in the request' do
- expect(subject.destination).to eql(assertion_consumer_service_url)
- end
- end
-
- describe "when the request is not trusted" do
- let(:registered_acs_url) { FFaker::Internet.uri("https") }
- let(:request) { instance_double(Saml::Kit::AuthenticationRequest, id: SecureRandom.uuid, assertion_consumer_service_url: assertion_consumer_service_url, issuer: FFaker::Movie.title, name_id_format: Saml::Kit::Namespaces::EMAIL_ADDRESS, provider: provider, signed?: true, trusted?: false) }
- let(:provider) { instance_double(Saml::Kit::ServiceProviderMetadata, want_assertions_signed: false) }
-
- it 'returns the registered ACS embedded in the metadata' do
- allow(provider).to receive(:assertion_consumer_service_for).and_return(double(location: registered_acs_url))
- expect(subject.destination).to eql(registered_acs_url)
- end
- end
- end
end
spec/saml/builders/service_provider_metadata_spec.rb
@@ -1,6 +1,13 @@
require 'spec_helper'
RSpec.describe Saml::Kit::Builders::ServiceProviderMetadata do
+ subject { described_class.new(configuration: configuration) }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.generate_key_pair_for(use: :signing)
+ config.generate_key_pair_for(use: :encryption)
+ end
+ end
let(:assertion_consumer_service_url) { FFaker::Internet.http_url }
let(:email) { FFaker::Internet.email }
let(:org_name) { FFaker::Movie.title }
@@ -37,10 +44,8 @@ RSpec.describe Saml::Kit::Builders::ServiceProviderMetadata do
expect(result['EntityDescriptor']['SPSSODescriptor']['AssertionConsumerService']['index']).to eql('0')
expect(result['EntityDescriptor']['Signature']).to be_present
expect(result['EntityDescriptor']['SPSSODescriptor']['KeyDescriptor'].map { |x| x['use'] }).to match_array(['signing', 'encryption'])
- expect(result['EntityDescriptor']['SPSSODescriptor']['KeyDescriptor'].map { |x| x['KeyInfo']['X509Data']['X509Certificate'] }).to match_array([
- Saml::Kit.configuration.signing_certificate.stripped,
- Saml::Kit.configuration.encryption_certificate.stripped,
- ])
+ expected_certificates = configuration.certificates.map(&:stripped)
+ expect(result['EntityDescriptor']['SPSSODescriptor']['KeyDescriptor'].map { |x| x['KeyInfo']['X509Data']['X509Certificate'] }).to match_array(expected_certificates)
expect(result['EntityDescriptor']['Organization']['OrganizationName']).to eql(org_name)
expect(result['EntityDescriptor']['Organization']['OrganizationDisplayName']).to eql(org_name)
expect(result['EntityDescriptor']['Organization']['OrganizationURL']).to eql(url)
spec/saml/authentication_request_spec.rb
@@ -1,14 +1,14 @@
require 'spec_helper'
RSpec.describe Saml::Kit::AuthenticationRequest do
- subject { described_class.new(raw_xml) }
+ subject { described_class.new(raw_xml, configuration: configuration) }
let(:id) { Saml::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 }
let(:name_id_format) { Saml::Kit::Namespaces::EMAIL_ADDRESS }
let(:raw_xml) do
- described_class.build do |builder|
+ described_class.build(configuration: configuration) do |builder|
builder.id = id
builder.now = Time.now.utc
builder.issuer = issuer
@@ -17,6 +17,11 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
builder.destination = destination
end.to_xml
end
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.generate_key_pair_for(use: :signing)
+ end
+ end
it { expect(subject.issuer).to eql(issuer) }
it { expect(subject.id).to eql(id) }
@@ -26,16 +31,16 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
describe "#valid?" do
let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
- let(:metadata) { instance_double(Saml::Kit::ServiceProviderMetadata) }
+ let(:metadata) { Saml::Kit::ServiceProviderMetadata.build(configuration: configuration) }
before :each do
- allow(Saml::Kit.configuration).to receive(:registry).and_return(registry)
+ allow(configuration).to receive(:registry).and_return(registry)
allow(registry).to receive(:metadata_for).and_return(metadata)
- allow(metadata).to receive(:matches?).and_return(true)
+ #allow(metadata).to receive(:matches?).and_return(true)
end
it 'is valid when left untampered' do
- subject = described_class.new(raw_xml)
+ subject = described_class.new(raw_xml, configuration: configuration)
expect(subject).to be_valid
end
@@ -78,7 +83,9 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
it 'validates the schema of the request' do
id = Saml::Kit::Id.generate
- signed_xml = Saml::Kit::Signature.sign(sign: true) do |xml, signature|
+ configuration = Saml::Kit::Configuration.new
+ configuration.generate_key_pair_for(use: :signing)
+ signed_xml = Saml::Kit::Signature.sign(configuration: configuration) 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
@@ -98,7 +105,7 @@ RSpec.describe Saml::Kit::AuthenticationRequest do
</samlp:AuthnRequest>
XML
- subject = described_class.new(raw_xml)
+ subject = described_class.new(raw_xml, configuration: configuration)
subject.signature_verified!
expect(subject).to be_valid
end
spec/saml/certificate_spec.rb
@@ -1,7 +1,12 @@
require 'spec_helper'
RSpec.describe Saml::Kit::Certificate do
- subject { Saml::Kit.configuration.signing_certificate }
+ #subject { Saml::Kit.configuration.certificates(use: :signing).last }
+ 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
spec/saml/composite_metadata_spec.rb.orig
@@ -1,187 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe Saml::Kit::CompositeMetadata do
- subject { described_class.new(xml) }
- let(:post_binding) { Saml::Kit::Bindings::HTTP_POST }
- let(:redirect_binding) { Saml::Kit::Bindings::HTTP_REDIRECT }
- let(:sign_on_service) { FFaker::Internet.uri("https") }
- let(:assertion_consumer_service) { FFaker::Internet.uri("https") }
- let(:sp_logout_service) { FFaker::Internet.uri("https") }
- let(:idp_logout_service) { FFaker::Internet.uri("https") }
- let(:entity_id) { FFaker::Internet.uri("https") }
- let(:xml) do
- <<-XML
-<EntityDescriptor xmlns="#{Saml::Kit::Namespaces::METADATA}" ID="#{Saml::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}">
- <X509Data>
- <X509Certificate>SP-Signing-Certificate</X509Certificate>
- </X509Data>
- </KeyInfo>
- </KeyDescriptor>
- <KeyDescriptor use="encryption">
- <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
- <X509Data>
- <X509Certificate>SP-Encryption-Certificate</X509Certificate>
- </X509Data>
- </KeyInfo>
- </KeyDescriptor>
- <SingleLogoutService Binding="#{post_binding}" Location="#{sp_logout_service}"/>
- <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
- <AssertionConsumerService Binding="#{post_binding}" Location="#{assertion_consumer_service}" index="0" isDefault="true"/>
- </SPSSODescriptor>
- <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="#{Saml::Kit::Namespaces::PROTOCOL}">
- <KeyDescriptor use="signing">
- <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
- <X509Data>
- <X509Certificate>IDP-Signing-Certificate</X509Certificate>
- </X509Data>
- </KeyInfo>
- </KeyDescriptor>
- <KeyDescriptor use="encryption">
- <KeyInfo xmlns="#{Saml::Kit::Namespaces::XMLDSIG}">
- <X509Data>
- <X509Certificate>IDP-Encryption-Certificate</X509Certificate>
- </X509Data>
- </KeyInfo>
- </KeyDescriptor>
- <SingleLogoutService Binding="#{post_binding}" Location="#{idp_logout_service}"/>
- <NameIDFormat>#{Saml::Kit::Namespaces::PERSISTENT}</NameIDFormat>
- <SingleSignOnService Binding="#{post_binding}" Location="#{sign_on_service}"/>
- <SingleSignOnService Binding="#{redirect_binding}" Location="#{sign_on_service}"/>
- <Attribute xmlns="#{Saml::Kit::Namespaces::ASSERTION}" Name="id" ></Attribute>
- </IDPSSODescriptor>
- <Organization>
- <OrganizationName xml:lang="en">Acme, Inc</OrganizationName>
- <OrganizationDisplayName xml:lang="en">Acme, Inc</OrganizationDisplayName>
- <OrganizationURL xml:lang="en">http://localhost:5000/</OrganizationURL>
- </Organization>
- <ContactPerson contactType="technical">
- <Company>mailto:hi@example.com</Company>
- </ContactPerson>
-</EntityDescriptor>
- XML
- end
-
- describe "#single_sign_on_services" do
- it 'returns the single sign on services from the idp' do
- expect(subject.single_sign_on_services).to match_array([
- Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
- Saml::Kit::Bindings::HttpRedirect.new(location: sign_on_service),
- ])
- end
- end
-
- describe "#single_sign_on_service_for" do
-<<<<<<< HEAD
- it 'returns the http post binding' do
-=======
- it 'returns the post binding' do
->>>>>>> 14771fc562487f9c80443197a89e776d256f47c2
- expect(subject.single_sign_on_service_for(binding: :http_post)).to eql(
- Saml::Kit::Bindings::HttpPost.new(location: sign_on_service)
- )
- end
-<<<<<<< HEAD
-
- it 'returns the http redirect binding' do
- expect(subject.single_sign_on_service_for(binding: :http_redirect)).to eql(
- Saml::Kit::Bindings::HttpRedirect.new(location: sign_on_service)
- )
- end
- end
-
- describe "#assertion_consumer_services" do
-=======
- end
-
- it { expect(subject.want_authn_requests_signed).to be_truthy }
- it { expect(subject.attributes).to match_array([name: 'id', format: nil]) }
- it { expect(subject.login_request_for(binding: :http_post)).to be_present }
- it do
- expect(subject.assertion_consumer_services).to match_array([
- Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service)
- ])
- end
- it do
- expect(subject.assertion_consumer_service_for(binding: :http_post)).to eql(
- Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service)
- )
- end
- it { expect(subject.want_assertions_signed).to be_truthy }
- it { expect(subject.entity_id).to eql(entity_id) }
- it { expect(subject.name_id_formats).to match_array([Saml::Kit::Namespaces::PERSISTENT]) }
- it do
- expect(subject.certificates).to match_array([
- Saml::Kit::Certificate.new('SP-Signing-Certificate', use: :signing),
- Saml::Kit::Certificate.new('SP-Encryption-Certificate', use: :encryption),
- Saml::Kit::Certificate.new('IDP-Signing-Certificate', use: :signing),
- Saml::Kit::Certificate.new('IDP-Encryption-Certificate', use: :encryption),
- ])
- end
-
- it do
- expect(subject.encryption_certificates).to match_array([
- Saml::Kit::Certificate.new('SP-Encryption-Certificate', use: :encryption),
- Saml::Kit::Certificate.new('IDP-Encryption-Certificate', use: :encryption),
- ])
- end
- it do
- expect(subject.signing_certificates).to match_array([
- Saml::Kit::Certificate.new('SP-Signing-Certificate', use: :signing),
- Saml::Kit::Certificate.new('IDP-Signing-Certificate', use: :signing),
- ])
- end
- it do
- expect(subject.services('SingleLogoutService')).to match_array([
- Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service),
- Saml::Kit::Bindings::HttpPost.new(location: idp_logout_service),
- ])
- end
- it do
- expect(subject.service_for(type: 'SingleLogoutService', binding: :http_post)).to eql(
- Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service)
- )
- end
- it do
- expect(subject.services('AssertionConsumerService')).to match_array([
- Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service),
- ])
- end
- it do
- expect(subject.service_for(type: 'AssertionConsumerService', binding: :http_post)).to eql(
- Saml::Kit::Bindings::HttpPost.new(location: assertion_consumer_service),
- )
- end
- it do
- expect(subject.services('SingleSignOnService')).to match_array([
- Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
- Saml::Kit::Bindings::HttpRedirect.new(location: sign_on_service),
- ])
- end
- it do
- expect(subject.service_for(type: 'SingleSignOnService', binding: :http_post)).to eql(
- Saml::Kit::Bindings::HttpPost.new(location: sign_on_service),
- )
- end
-
- it do
- expect(subject.single_logout_services).to match_array([
- Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service),
- Saml::Kit::Bindings::HttpPost.new(location: idp_logout_service),
- ])
- end
-
- it do
- expect(subject.single_logout_service_for(binding: :http_post)).to eql(
- Saml::Kit::Bindings::HttpPost.new(location: sp_logout_service)
- )
- end
-
- it do
- user = double(:user, name_id_for: SecureRandom.uuid)
- expect(subject.logout_request_for(user)).to be_present
->>>>>>> 14771fc562487f9c80443197a89e776d256f47c2
- end
-end
spec/saml/http_post_spec.rb
@@ -1,26 +0,0 @@
-require 'spec_helper'
-
-RSpec.describe Saml::Kit::Bindings::HttpPost do
- describe "equality" do
- let(:location) { FFaker::Internet.uri("https") }
- subject { Saml::Kit::Bindings::HttpPost.new(location: location) }
-
- it 'is referentially equal' do
- expect(subject).to eql(subject)
- end
-
- it 'is equal by value' do
- expect(subject).to eql(
- Saml::Kit::Bindings::HttpPost.new(location: location)
- )
- end
-
- it 'is not equal' do
- expect(subject).to_not eql(
- Saml::Kit::Bindings::HttpPost.new(
- location: FFaker::Internet.uri("https")
- )
- )
- end
- end
-end
spec/saml/logout_request_spec.rb
@@ -1,13 +1,21 @@
require 'spec_helper'
RSpec.describe Saml::Kit::LogoutRequest do
- subject { described_class.build(user) }
+ subject { described_class.build(user, configuration: configuration) }
let(:user) { double(:user, name_id_for: name_id) }
let(:name_id) { SecureRandom.uuid }
+ let(:issuer) { FFaker::Internet.uri("https") }
+ let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.issuer = issuer
+ config.registry = registry
+ config.generate_key_pair_for(use: :signing)
+ end
+ end
it 'parses the issuer' do
- issuer = FFaker::Internet.uri("https")
- subject = described_class.build(user) do |builder|
+ subject = described_class.build(user, configuration: configuration) do |builder|
builder.issuer = issuer
end
expect(subject.issuer).to eql(issuer)
@@ -24,7 +32,7 @@ RSpec.describe Saml::Kit::LogoutRequest do
it 'parses the destination' do
destination = FFaker::Internet.uri("https")
- subject = described_class.build(user) do |builder|
+ subject = described_class.build(user, configuration: configuration) do |builder|
builder.destination = destination
end
expect(subject.destination).to eql(destination)
@@ -35,16 +43,15 @@ RSpec.describe Saml::Kit::LogoutRequest do
end
describe "#valid?" do
- let(:registry) { instance_double(Saml::Kit::DefaultRegistry) }
- let(:metadata) { instance_double(Saml::Kit::ServiceProviderMetadata) }
+ let(:metadata) do
+ Saml::Kit::ServiceProviderMetadata.build(configuration: configuration) do |builder|
+ builder.entity_id = issuer
+ builder.add_single_logout_service(FFaker::Internet.uri("https"), binding: :http_post)
+ end
+ end
before :each do
- allow(Saml::Kit.configuration).to receive(:registry).and_return(registry)
allow(registry).to receive(:metadata_for).and_return(metadata)
- allow(metadata).to receive(:matches?).and_return(true)
- allow(metadata).to receive(:single_logout_services).and_return([
- Saml::Kit::Bindings::HttpPost.new(location: FFaker::Internet.http_url)
- ])
end
it 'is valid when left untampered' do
@@ -53,7 +60,7 @@ RSpec.describe Saml::Kit::LogoutRequest do
it 'is invalid if the document has been tampered with' do
issuer = FFaker::Internet.uri("https")
- raw_xml = described_class.build(user) do |builder|
+ raw_xml = described_class.build(user, configuration: configuration) do |builder|
builder.issuer = issuer
end.to_xml.gsub(issuer, 'corrupt')
@@ -92,7 +99,7 @@ RSpec.describe Saml::Kit::LogoutRequest do
expect(subject.errors[:single_logout_service]).to be_present
end
- it 'is valid when a single lgout service url is available via the registry' do
+ it 'is valid when a single logout service url is available via the registry' do
issuer = FFaker::Internet.uri("https")
allow(registry).to receive(:metadata_for).with(issuer).and_return(metadata)
allow(metadata).to receive(:matches?).and_return(true)
@@ -100,7 +107,7 @@ RSpec.describe Saml::Kit::LogoutRequest do
Saml::Kit::Bindings::HttpPost.new(location: FFaker::Internet.uri("https"))
])
- subject = described_class.build(user) do |builder|
+ subject = described_class.build(user, configuration: configuration) do |builder|
builder.issuer = issuer
end
expect(subject).to be_valid
@@ -108,7 +115,9 @@ RSpec.describe Saml::Kit::LogoutRequest do
it 'validates the schema of the request' do
id = Saml::Kit::Id.generate
- signed_xml = Saml::Kit::Signature.sign(sign: true) do |xml, signature|
+ configuration = Saml::Kit::Configuration.new
+ configuration.generate_key_pair_for(use: :signing)
+ signed_xml = Saml::Kit::Signature.sign(configuration: configuration) do |xml, signature|
xml.LogoutRequest ID: id do
signature.template(id)
xml.Fake do
spec/saml/response_spec.rb
@@ -6,11 +6,13 @@ RSpec.describe Saml::Kit::Response do
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) }
- subject { described_class.build(user, request) }
-
- before :each do
- allow(Saml::Kit.configuration).to receive(:registry).and_return(registry)
- allow(Saml::Kit.configuration).to receive(:issuer).and_return(request.issuer)
+ subject { described_class.build(user, request, configuration: configuration) }
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.issuer = request.issuer
+ config.registry = registry
+ config.generate_key_pair_for(use: :signing)
+ end
end
it 'is valid' do
@@ -55,7 +57,9 @@ RSpec.describe Saml::Kit::Response do
allow(registry).to receive(:metadata_for).and_return(metadata)
allow(metadata).to receive(:matches?).and_return(true)
id = Saml::Kit::Id.generate
- signed_xml = Saml::Kit::Signature.sign(sign: true) do |xml, signature|
+ configuration = Saml::Kit::Configuration.new
+ configuration.generate_key_pair_for(use: :signing)
+ signed_xml = Saml::Kit::Signature.sign(configuration: configuration) do |xml, signature|
xml.tag! "samlp:Response", "xmlns:samlp" => Saml::Kit::Namespaces::PROTOCOL, ID: id do
signature.template(id)
xml.Fake do
@@ -132,7 +136,7 @@ RSpec.describe Saml::Kit::Response do
allow(registry).to receive(:metadata_for).and_return(metadata)
allow(metadata).to receive(:matches?).and_return(true)
- allow(Saml::Kit.configuration).to receive(:issuer).and_return(FFaker::Internet.uri("https"))
+ allow(configuration).to receive(:issuer).and_return(FFaker::Internet.uri("https"))
allow(request).to receive(:issuer).and_return(FFaker::Internet.uri("https"))
expect(subject).to be_invalid
@@ -362,7 +366,7 @@ XML
public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
private_key = OpenSSL::PKey::RSA.new(private_key_pem, password)
- allow(Saml::Kit.configuration).to receive(:encryption_private_key).and_return(private_key)
+ allow(Saml::Kit.configuration).to receive(:private_keys).with(use: :encryption).and_return([private_key])
cipher = OpenSSL::Cipher.new('AES-128-CBC')
cipher.encrypt
@@ -396,8 +400,10 @@ XML
XML
subject = described_class.new(xml)
- expect(subject).to be_encrypted
- expect(subject.attributes).to be_present
+ expect(subject.attributes).to match_array([
+ ["created_at", "2017-11-23T04:33:58Z"],
+ ["email", "sidney_bayer@nienowemmerich.com"]
+ ])
end
end
spec/saml/service_provider_metadata_spec.rb
@@ -19,10 +19,10 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
end
it 'returns each of the certificates' do
- expect(subject.certificates).to match_array([
- Saml::Kit::Certificate.new(Saml::Kit.configuration.signing_certificate.stripped, use: :signing),
- Saml::Kit::Certificate.new(Saml::Kit.configuration.encryption_certificate.stripped, use: :encryption),
- ])
+ expected_certificates = Saml::Kit.configuration.certificates.map do |x|
+ Saml::Kit::Certificate.new(x.stripped, use: x.use)
+ end
+ expect(subject.certificates).to match_array(expected_certificates)
end
it 'returns each acs url and binding' do
@@ -52,7 +52,7 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
describe "#validate" do
let(:service_provider_metadata) do
- described_class.build do |builder|
+ described_class.build(configuration: configuration) do |builder|
builder.entity_id = entity_id
builder.add_assertion_consumer_service(acs_post_url, binding: :http_post)
builder.add_assertion_consumer_service(acs_redirect_url, binding: :http_redirect)
@@ -60,6 +60,11 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
builder.add_single_logout_service(logout_redirect_url, binding: :http_redirect)
end.to_xml
end
+ let(:configuration) do
+ Saml::Kit::Configuration.new do |config|
+ config.generate_key_pair_for(use: :signing)
+ end
+ end
it 'valid when given valid service provider metadata' do
expect(described_class.new(service_provider_metadata)).to be_valid
@@ -113,7 +118,12 @@ RSpec.describe Saml::Kit::ServiceProviderMetadata do
end
describe "#matches?" do
- subject { Saml::Kit::ServiceProviderMetadata.build }
+ let(:configuration) do
+ config = Saml::Kit::Configuration.new
+ config.generate_key_pair_for(use: :signing)
+ config
+ end
+ subject { Saml::Kit::ServiceProviderMetadata.build(configuration: configuration) }
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']
spec/saml/signature_spec.rb
@@ -3,9 +3,7 @@ require "spec_helper"
RSpec.describe Saml::Kit::Signature do
let(:configuration) do
config = Saml::Kit::Configuration.new
- config.signing_certificate_pem = certificate
- config.signing_private_key_pem = private_key
- config.signing_private_key_password = password
+ config.add_key_pair(certificate, private_key, password: password, use: :signing)
config
end
@@ -20,14 +18,10 @@ RSpec.describe Saml::Kit::Signature do
x.public_key = public_key
x.serial = 0x0
x.version = 2
- factory = OpenSSL::X509::ExtensionFactory.new
- factory.subject_certificate = factory.issuer_certificate = x
- x.extensions = [ factory.create_extension("basicConstraints","CA:TRUE", true), factory.create_extension("subjectKeyIdentifier", "hash"), ]
- x.add_extension(factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always"))
x.sign(rsa_key, OpenSSL::Digest::SHA256.new)
x.to_pem
end
- let(:private_key) { rsa_key.to_pem(OpenSSL::Cipher::Cipher.new('des3'), password) }
+ let(:private_key) { rsa_key.to_pem(OpenSSL::Cipher.new('des3'), password) }
let(:password) { "password" }
it 'generates a signature' do
@@ -36,7 +30,7 @@ RSpec.describe Saml::Kit::Signature do
"xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion",
ID: reference_id,
}
- signed_xml = described_class.sign(sign: true, configuration: configuration) do |xml, signature|
+ 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")
@@ -63,7 +57,7 @@ RSpec.describe Saml::Kit::Signature do
end
it 'does not add a signature' do
- signed_xml = described_class.sign(sign: false, configuration: configuration) do |xml, signature|
+ signed_xml = described_class.sign(configuration: Saml::Kit::Configuration.new) do |xml, signature|
xml.AuthnRequest do
signature.template(reference_id)
xml.Issuer "MyEntityID"
spec/saml/xml_decryption_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Saml::Kit::XmlDecryption do
}
}
}
- subject = described_class.new(private_key)
+ subject = described_class.new(configuration: double(private_keys: [private_key]))
decrypted = subject.decrypt(data)
expect(decrypted.strip).to eql(secret)
end
spec/saml/xml_spec.rb
@@ -4,9 +4,14 @@ 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 do |builder|
+ 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)
.rspec
@@ -1,2 +1,2 @@
---format documentation
--color
+--require spec_helper