module SAML2::Signable

def sign(x509_certificate, private_key, algorithm_name = :sha256)

Returns:
  • (self) -

Parameters:
  • algorithm_name (Symbol) --
  • private_key (String) --
  • x509_certificate (String) --
def sign(x509_certificate, private_key, algorithm_name = :sha256)
  to_xml
  xml = @document.root
  xml.set_id_attribute('ID')
  xml.sign!(cert: x509_certificate, key: private_key, digest_alg: algorithm_name.to_s, signature_alg: "rsa-#{algorithm_name}", uri: "##{id}")
  # the Signature element must be the first element
  signature = xml.at_xpath("dsig:Signature", Namespaces::ALL)
  xml.children.first.add_previous_sibling(signature)
  self
end

def signature

Returns:
  • (Nokogiri::XML::Element, nil) -
def signature
  unless instance_variable_defined?(:@signature)
    @signature = xml.at_xpath('dsig:Signature', Namespaces::ALL)
    if @signature
      signed_node = @signature.at_xpath('dsig:SignedInfo/dsig:Reference', Namespaces::ALL)['URI']
      if signed_node == ''
        @signature = nil unless xml == xml.document.root
      elsif signed_node != "##{xml['ID']}"
        @signature = nil
      else
        # validating the schema will automatically add ID attributes, so check that first
        xml.set_id_attribute('ID') unless xml.document.get_id(xml['ID'])
      end
    end
  end
  @signature
end

def signed?

def signed?
  !!signature
end

def signing_key

Returns:
  • (KeyInfo, nil) -
def signing_key
  @signing_key ||= KeyInfo.from_xml(signature)
end

def valid_signature?(fingerprint: nil, cert: nil, verification_time: nil)

Returns:
  • (Boolean) -

Parameters:
  • () --
def valid_signature?(fingerprint: nil, cert: nil, verification_time: nil)
  validate_signature(fingerprint: fingerprint, cert: cert, verification_time: verification_time).empty?
end

def validate_signature(fingerprint: nil,

Returns:
  • (Array) - An empty array on success, details of errors on failure.

Parameters:
  • allow_expired_certificate (Boolean) --
  • cert () -- optional [Array, String]
  • fingerprint () -- optional [Array, String]
def validate_signature(fingerprint: nil,
                       cert: nil,
                       verification_time: nil,
                       allow_expired_certificate: false)
  return ["not signed"] unless signed?
  certs = Array(cert)
  certs = certs.dup if certs.equal?(cert)
  # see if any given fingerprints match the certificate embedded in the XML;
  # if so, extract the certificate, and add it to the allowed certificates list
  Array(fingerprint).each do |fp|
    certs << signing_key.certificate if signing_key&.fingerprint == KeyInfo.format_fingerprint(fp)
  end
  certs = certs.uniq
  return ["no trusted certificate found"] if certs.empty?
  verify_certificate = true
  if signing_key
    signing_cert = signing_key.certificate
    if allow_expired_certificate
      verification_time = signing_cert.not_after - 1
    end
    # we explicitly trust the signing certificate, but it's not self-signed;
    # xmlsec is weird and decides not to trust it in that case, so we skip
    # certificate verification by xmlsec, and do it ourselves
    if certs.include?(signing_cert) &&  signing_cert.issuer != signing_cert.subject
      verification_time ||= Time.now.utc
      return ["certificate has expired"] if verification_time > signing_cert.not_after
      return ["certificate is not yet valid"] if verification_time < signing_cert.not_before
      verify_certificate = false
    end
  end
  begin
    result = signature.verify_with(certs: certs,
                                   verification_time: verification_time,
                                   verify_certificates: verify_certificate)
    result ? [] : ["signature is invalid"]
  rescue XMLSec::VerificationError => e
    [e.message]
  end
end