Comparing changes
v0.2.14
→
v0.2.15
8 commits
24 files changed
Commits
Changed files (24)
lib
saml
kit
builders
spec
saml
exe/saml-kit-create-self-signed-certificate
@@ -1,9 +1,9 @@
#!/usr/bin/env ruby
require 'saml/kit'
-puts "Enter Password:"
-password = STDIN.read.strip
-certificate, private_key = Saml::Kit::SelfSignedCertificate.new(password).create
+puts "Enter Passphrase:"
+passphrase = STDIN.read.strip
+certificate, private_key = Saml::Kit::SelfSignedCertificate.new(passphrase).create
puts "** BEGIN File Format **"
print certificate
@@ -18,5 +18,5 @@ puts private_key.inspect
puts "***********************"
puts
-puts "Private Key Password:"
-puts password.inspect
+puts "Private Key Passphrase:"
+puts passphrase.inspect
lib/saml/kit/builders/response.rb
@@ -18,18 +18,10 @@ module Saml
@version = "2.0"
@status_code = Namespaces::SUCCESS
@issuer = configuration.issuer
- @embed_signature = want_assertions_signed
@encrypt = encryption_certificate.present?
@configuration = configuration
end
- def want_assertions_signed
- request.provider.want_assertions_signed
- rescue => error
- Saml::Kit.logger.error(error)
- nil
- end
-
def build
Saml::Kit::Response.new(to_xml, request_id: request.id, configuration: configuration)
end
lib/saml/kit/certificate.rb
@@ -3,33 +3,51 @@ module Saml
class Certificate
BEGIN_CERT=/-----BEGIN CERTIFICATE-----/
END_CERT=/-----END CERTIFICATE-----/
- attr_reader :value, :use
+ # 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
@@ -68,6 +86,10 @@ module Saml
Saml::Kit.logger.warn(error)
OpenSSL::X509::Certificate.new(value)
end
+
+ private
+
+ attr_reader :value
end
end
end
lib/saml/kit/configuration.rb
@@ -11,20 +11,20 @@ module Saml
# config.logger = Rails.logger
# end
#
- # To specify global configuration it is best to do this in an initialize
+ # To specify global configuration it is best to do this in an initializer
# that runs at the start of the program.
#
# Saml::Kit.configure do |configuration|
# configuration.issuer = "https://www.example.com/saml/metadata"
# configuration.generate_key_pair_for(use: :signing)
- # configuration.add_key_pair(ENV["X509_CERTIFICATE"], ENV["PRIVATE_KEY"], password: ENV['PRIVATE_KEY_PASSWORD'], use: :encryption)
+ # configuration.add_key_pair(ENV["X509_CERTIFICATE"], ENV["PRIVATE_KEY"], passphrase: ENV['PRIVATE_KEY_PASSPHRASE'], use: :encryption)
# end
class Configuration
# The issuer or entity_id to use.
attr_accessor :issuer
- # The signature method to use when generating signatures (See {SAML::Kit::Builders::XmlSignature::SIGNATURE_METHODS})
+ # The signature method to use when generating signatures (See {Saml::Kit::Builders::XmlSignature::SIGNATURE_METHODS})
attr_accessor :signature_method
- # The digest method to use when generating signatures (See {SAML::Kit::Builders::XmlSignature::DIGEST_METHODS})
+ # The digest method to use when generating signatures (See {Saml::Kit::Builders::XmlSignature::DIGEST_METHODS})
attr_accessor :digest_method
# The metadata registry to use for searching for metadata associated with an issuer.
attr_accessor :registry
@@ -47,19 +47,19 @@ module Saml
#
# @param certificate [String] the x509 certificate with public key.
# @param private_key [String] the plain text private key.
- # @param password [String] the password to decrypt the private key.
+ # @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, password: '', use: :signing)
- @key_pairs.push(KeyPair.new(certificate, private_key, password, use.to_sym))
+ def add_key_pair(certificate, private_key, passphrase: '', use: :signing)
+ @key_pairs.push(KeyPair.new(certificate, private_key, passphrase, use.to_sym))
end
# Generates a unique key pair that can be used for signing or encryption.
#
# @param use [Symbol] the type of key pair, `:signing` or `:encryption`
- # @param password [String] the private key password to use.
- 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)
+ # @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
+ add_key_pair(certificate, private_key, passphrase: passphrase, use: use)
end
# Return each key pair for a specific use.
lib/saml/kit/crypto.rb
@@ -8,6 +8,7 @@ module Saml
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
lib/saml/kit/default_registry.rb
@@ -2,6 +2,31 @@ module Saml
module Kit
# The default metadata registry is used to fetch the metadata associated with an issuer or entity id.
# The metadata associated with an issuer is used to verify trust for any SAML documents that are received.
+ #
+ # You can replace the default registry with your own at startup.
+ #
+ # Example:
+ #
+ # class OnDemandRegistry
+ # def initialize(original)
+ # @original = original
+ # end
+ #
+ # def metadata_for(entity_id)
+ # found = @original.metadata_for(entity_id)
+ # return found if found
+ #
+ # @original.register_url(entity_id, verify_ssl: Rails.env.production?)
+ # @original.metadata_for(entity_id)
+ # end
+ # end
+ #
+ # Saml::Kit.configure do |configuration|
+ # configuration.issuer = ENV['ISSUER']
+ # configuration.registry = OnDemandRegistry.new(configuration.registry)
+ # configuration.logger = Rails.logger
+ # end
+
class DefaultRegistry
def initialize(items = {})
@items = items
lib/saml/kit/fingerprint.rb
@@ -1,6 +1,11 @@
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
class Fingerprint
# The OpenSSL::X509::Certificate
attr_reader :x509
lib/saml/kit/id.rb
@@ -1,6 +1,11 @@
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
lib/saml/kit/identity_provider_metadata.rb
@@ -2,6 +2,7 @@ module Saml
module Kit
# This class is used to parse the IDPSSODescriptor from a SAML metadata document.
#
+ # raw_xml = <<-XML
# <?xml version="1.0" encoding="UTF-8"?>
# <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_cfa24e2f-0ec0-4ee3-abb8-b2fcfe394c1c" entityID="">
# <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
@@ -12,6 +13,19 @@ module Saml
# <saml:Attribute Name="id"/>
# </IDPSSODescriptor>
# </EntityDescriptor>
+ # XML
+ #
+ # metadata = Saml::Kit::IdentityProviderMetadata.new(raw_xml)
+ # puts metadata.entity_id
+ #
+ # It can also be used to generate IDP metadata.
+ #
+ # metadata = Saml::Kit::IdentityProviderMetadata.build do |builder|
+ # builder.entity_id = "my-entity-id"
+ # end
+ # puts metadata.to_xml
+ #
+ # For more details on generating metadata see {Saml::Kit::Metadata}.
class IdentityProviderMetadata < Metadata
def initialize(xml)
super("IDPSSODescriptor", xml)
lib/saml/kit/key_pair.rb
@@ -9,10 +9,17 @@ module Saml
@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)
lib/saml/kit/logout_request.rb
@@ -1,10 +1,33 @@
module Saml
module Kit
- # This class parses a LogoutRequest SAML document.
+ # This class can be used to parse a LogoutRequest SAML document.
+ #
+ # document = Saml::Kit::LogoutRequest.new(raw_xml)
+ #
+ # It can also be used to generate a new LogoutRequest.
+ #
+ # document = Saml::Kit::LogoutRequest.build do |builder|
+ # builder.issuer = "issuer"
+ # end
+ #
+ # puts document.to_xml(pretty: true)
+ #
+ # See {Saml::Kit::Builders::LogoutRequest} for a list of available settings.
+ #
+ # This class can also be used to generate the correspondong LogoutResponse for a LogoutRequest.
+ #
+ # document = Saml::Kit::LogoutRequest.new(raw_xml)
+ # url, saml_params = document.response_for(binding: :http_post)
+ #
+ # See {#response_for} for more information.
class LogoutRequest < Document
include Requestable
validates_presence_of :single_logout_service, if: :expected_type?
+ # A new instance of LogoutRequest
+ #
+ # @param xml [String] The raw xml string.
+ # @param configuration [Saml::Kit::Configuration] the configuration to use.
def initialize(xml, configuration: Saml::Kit.configuration)
super(xml, name: "LogoutRequest", configuration: configuration)
end
lib/saml/kit/logout_response.rb
@@ -1,6 +1,8 @@
module Saml
module Kit
# This class is used to parse a LogoutResponse SAML document.
+ #
+ # document = Saml::Kit::LogoutResponse.new(raw_xml)
class LogoutResponse < Document
include Respondable
lib/saml/kit/metadata.rb
@@ -1,5 +1,26 @@
module Saml
module Kit
+ # The Metadata object can be used to parse an XML string of metadata.
+ #
+ # metadata = Saml::Kit::Metadata.from(raw_xml)
+ #
+ # It can also be used to generate a new metadata string.
+ #
+ # metadata = Saml::Kit::Metadata.build do |builder|
+ # builder.entity_id = "my-issuer"
+ # builder.build_service_provider do |x|
+ # x.add_assertion_consumer_service(assertions_url, binding: :http_post)
+ # x.add_single_logout_service(logout_url, binding: :http_post)
+ # end
+ # builder.build_identity_provider do |x|
+ # x.add_single_sign_on_service(login_url, binding: :http_redirect)
+ # x.add_single_logout_service(logout_url, binding: :http_post)
+ # end
+ # end
+ # puts metadata.to_xml(pretty: true)
+ #
+ # See {Saml::Kit::Builders::ServiceProviderMetadata} and {Saml::Kit::Builders::IdentityProviderMetadata}
+ # for a list of options that can be specified.
class Metadata
METADATA_XSD = File.expand_path("./xsd/saml-schema-metadata-2.0.xsd", File.dirname(__FILE__)).freeze
include ActiveModel::Validations
@@ -12,35 +33,39 @@ module Saml
validate :must_match_xsd
validate :must_have_valid_signature
- attr_reader :xml, :name
- attr_accessor :hash_algorithm
+ attr_reader :name
def initialize(name, xml)
@name = name
@xml = xml
- @hash_algorithm = OpenSSL::Digest::SHA256
end
+ # Returns the /EntityDescriptor/@entityID
def entity_id
document.find_by("/md:EntityDescriptor/@entityID").value
end
+ # Returns the supported NameIDFormats.
def name_id_formats
document.find_all("/md:EntityDescriptor/md:#{name}/md:NameIDFormat").map(&:text)
end
+ # Returns the Organization Name
def organization_name
document.find_by("/md:EntityDescriptor/md:Organization/md:OrganizationName").try(:text)
end
+ # Returns the Organization URL
def organization_url
document.find_by("/md:EntityDescriptor/md:Organization/md:OrganizationURL").try(:text)
end
+ # Returns the Company
def contact_person_company
document.find_by("/md:EntityDescriptor/md:ContactPerson/md:Company").try(:text)
end
+ # 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
@@ -48,14 +73,19 @@ module Saml
end
end
+ # Returns the encryption certificates
def encryption_certificates
certificates.find_all(&:encryption?)
end
+ # Returns the signing certificates.
def signing_certificates
certificates.find_all(&:signing?)
end
+ # Returns each of the service endpoints supported by this metadata.
+ #
+ # @param type [String] the type of service. .E.g. `AssertionConsumerServiceURL`
def services(type)
document.find_all("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item|
binding = item.attribute("Binding").value
@@ -64,19 +94,33 @@ module Saml
end
end
+ # Returns a specifing service binding.
+ #
+ # @param binding [Symbol] can be `:http_post` or `:http_redirect`.
+ # @param type [Symbol] can be on the service element like `AssertionConsumerServiceURL`, `SingleSignOnService` or `SingleLogoutService`.
def service_for(binding:, type:)
binding = Saml::Kit::Bindings.binding_for(binding)
services(type).find { |x| x.binding?(binding) }
end
+ # Returns each of the SingleLogoutService bindings
def single_logout_services
services('SingleLogoutService')
end
+ # Returns the SingleLogoutService that matches the specified binding.
+ #
+ # @param binding [Symbol] can be `:http_post` or `:http_redirect`.
def single_logout_service_for(binding:)
service_for(binding: binding, type: 'SingleLogoutService')
end
+ # Creates a serialized LogoutRequest.
+ #
+ # @param user [Object] a user object that responds to `name_id_for` and `assertion_attributes_for`.
+ # @param binding [Symbol] can be `:http_post` or `:http_redirect`.
+ # @param relay_state [String] the relay state to have echo'd back.
+ # @return [Array] Returns an array with a url and Hash of parameters to send to the other party.
def logout_request_for(user, binding: :http_post, relay_state: nil)
builder = Saml::Kit::LogoutRequest.builder(user) do |x|
yield x if block_given?
@@ -85,30 +129,50 @@ module Saml
request_binding.serialize(builder, relay_state: relay_state)
end
+ # Returns the certificate that matches the fingerprint
+ #
+ # @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}`
def matches?(fingerprint, use: :signing)
certificates.find do |certificate|
certificate.for?(use) && certificate.fingerprint == fingerprint
end
end
+ # Returns the XML document converted to a Hash.
def to_h
@xml_hash ||= Hash.from_xml(to_xml)
end
+ # Returns the XML document as a String.
+ #
+ # @param pretty [Symbol] true to return a human friendly version of the XML.
def to_xml(pretty: false)
document.to_xml(pretty: pretty)
end
+ # Returns the XML document as a [String].
def to_s
to_xml
end
+ # Verifies the signature and data using the signing certificates.
+ #
+ # @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.
def verify(algorithm, signature, data)
signing_certificates.find do |certificate|
certificate.public_key.verify(algorithm, signature, data)
end
end
+ # Creates a `{Saml::Kit::Metadata}` object from a raw XML [String].
+ #
+ # @param content [String] the raw metadata XML.
+ # @return [Saml::Kit::Metadata] the metadata document or subclass.
def self.from(content)
hash = Hash.from_xml(content)
entity_descriptor = hash["EntityDescriptor"]
@@ -121,12 +185,15 @@ module Saml
end
end
+ # @!visibility private
def self.builder_class
Saml::Kit::Builders::Metadata
end
private
+ attr_reader :xml
+
def document
@document ||= Xml.new(xml)
end
lib/saml/kit/serializable.rb
@@ -1,28 +1,47 @@
module Saml
module Kit
module Serializable
+ # Base 64 decodes the value.
+ #
+ # @param value [String] the string to base 64 decode.
def decode(value)
Base64.decode64(value)
end
+ # Base 64 encodes the value.
+ #
+ # @param value [String] the string to base 64 encode.
def encode(value)
Base64.strict_encode64(value)
end
+ # Inflates the value using zlib decompression.
+ #
+ # @param value [String] the value to inflate.
def inflate(value)
inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
inflater.inflate(value)
end
- # drop header and checksum as per spec.
+ # Deflate the value and drop the header and checksum as per the SAML spec.
+ # https://en.wikipedia.org/wiki/SAML_2.0#HTTP_Redirect_Binding
+ #
+ # @param value [String] the value to deflate.
+ # @param level [Integer] the level of compression.
def deflate(value, level: Zlib::BEST_COMPRESSION)
Zlib::Deflate.deflate(value, level)[2..-5]
end
+ # URL unescape a value
+ #
+ # @param value [String] the value to unescape.
def unescape(value)
CGI.unescape(value)
end
+ # URL escape a value
+ #
+ # @param value [String] the value to escape.
def escape(value)
CGI.escape(value)
end
lib/saml/kit/service_provider_metadata.rb
@@ -5,24 +5,31 @@ module Saml
super("SPSSODescriptor", xml)
end
+ # Returns each of the AssertionConsumerService bindings.
def assertion_consumer_services
services('AssertionConsumerService')
end
+ # Returns the AssertionConsumerService for the specified binding.
+ #
+ # @param binding [Symbol] can be either `:http_post` or `:http_redirect`
def assertion_consumer_service_for(binding:)
service_for(binding: binding, type: 'AssertionConsumerService')
end
+ # Returns true when the metadata demands that Assertions must be signed.
def want_assertions_signed
attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute("WantAssertionsSigned")
return true if attribute.nil?
attribute.text.downcase == "true"
end
+ # @!visibility private
def self.builder_class
Saml::Kit::Builders::ServiceProviderMetadata
end
+ # @deprecated Use 'Saml::Kit::Builders::ServiceProviderMetadata'.
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::ServiceProviderMetadata::Builder', 'Saml::Kit::Builders::ServiceProviderMetadata')
end
end
lib/saml/kit/signature.rb
@@ -5,17 +5,20 @@ module Saml
@xml_hash = xml_hash
end
+ # Returns the embedded X509 Certificate
def certificate
value = to_h.fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
return if value.nil?
Saml::Kit::Certificate.new(value, use: :signing)
end
+ # Returns true when the fingerprint of the certificate matches one of the certificates registered in the metadata.
def trusted?(metadata)
return false if metadata.nil?
metadata.matches?(certificate.fingerprint, use: :signing)
end
+ # Returns the XML Hash.
def to_h
@xml_hash
end
lib/saml/kit/signatures.rb
@@ -1,5 +1,6 @@
module Saml
module Kit
+ # @!visibility private
class Signatures # :nodoc:
# @!visibility private
attr_reader :configuration
@@ -10,6 +11,7 @@ module Saml
@key_pair = configuration.key_pairs(use: :signing).last
end
+ # @!visibility private
def sign_with(key_pair)
@key_pair = key_pair
end
lib/saml/kit/templatable.rb
@@ -1,34 +1,46 @@
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
@@ -41,10 +53,12 @@ module Saml
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
lib/saml/kit/template.rb
@@ -7,6 +7,9 @@ module Saml
@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
lib/saml/kit/trustable.rb
@@ -9,6 +9,7 @@ module Saml
validate :must_be_trusted
end
+ # Returns true when the document has an embedded XML Signature or has been verified externally.
def signed?
signature_manually_verified || signature.present?
end
@@ -19,6 +20,7 @@ module Saml
xml_hash ? Signature.new(xml_hash) : nil
end
+ # Returns true when documents is signed and the signing certificate belongs to a known service entity.
def trusted?
return true if signature_manually_verified
return false unless signed?
lib/saml/kit/version.rb
@@ -1,5 +1,5 @@
module Saml
module Kit
- VERSION = "0.2.14"
+ VERSION = "0.2.15"
end
end
lib/saml/kit/xml.rb
@@ -10,8 +10,6 @@ module Saml
"samlp": Namespaces::PROTOCOL,
}.freeze
- attr_reader :raw_xml, :document
-
validate :validate_signatures
validate :validate_certificates
@@ -20,27 +18,31 @@ module Saml
@document = Nokogiri::XML(raw_xml)
end
- def x509_certificates
- xpath = "//ds:KeyInfo/ds:X509Data/ds:X509Certificate"
- document.search(xpath, Xmldsig::NAMESPACES).map do |item|
- Certificate.to_x509(item.text)
- end
- 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")
@@ -69,6 +71,13 @@ module Saml
end
end
end
+
+ def x509_certificates
+ xpath = "//ds:KeyInfo/ds:X509Data/ds:X509Certificate"
+ document.search(xpath, Xmldsig::NAMESPACES).map do |item|
+ Certificate.to_x509(item.text)
+ end
+ end
end
end
end
lib/saml/kit/xml_decryption.rb
@@ -1,12 +1,16 @@
module Saml
module Kit
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)
spec/saml/signatures_spec.rb
@@ -3,7 +3,7 @@ require "spec_helper"
RSpec.describe Saml::Kit::Signatures do
let(:configuration) do
config = Saml::Kit::Configuration.new
- config.add_key_pair(certificate, private_key, password: password, use: :signing)
+ config.add_key_pair(certificate, private_key, passphrase: passphrase, use: :signing)
config
end
@@ -21,8 +21,8 @@ RSpec.describe Saml::Kit::Signatures do
x.sign(rsa_key, OpenSSL::Digest::SHA256.new)
x.to_pem
end
- let(:private_key) { rsa_key.to_pem(OpenSSL::Cipher.new('des3'), password) }
- let(:password) { "password" }
+ let(:private_key) { rsa_key.to_pem(OpenSSL::Cipher.new('des3'), passphrase) }
+ let(:passphrase) { "password" }
it 'generates a signature' do
options = {