Commit c96388a
Changed files (44)
xml-kit
lib
xml
kit
builders
spec
xml-kit/bin/console
@@ -1,14 +0,0 @@
-#!/usr/bin/env ruby
-
-require "bundler/setup"
-require "xml/kit"
-
-# You can add fixtures and/or initialization code here to make experimenting
-# with your gem easier. You can also use a different console, if you like.
-
-# (If you use this, don't forget to add pry to your Gemfile!)
-# require "pry"
-# Pry.start
-
-require "irb"
-IRB.start(__FILE__)
xml-kit/bin/setup
@@ -1,8 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-IFS=$'\n\t'
-set -vx
-
-bundle install
-
-# Do any other automated setup that you need to do here
xml-kit/lib/xml/kit/builders/templates/certificate.builder
@@ -1,7 +0,0 @@
-xml.KeyDescriptor use: use do
- xml.KeyInfo "xmlns": ::Xml::Kit::Namespaces::XMLDSIG do
- xml.X509Data do
- xml.X509Certificate stripped
- end
- end
-end
xml-kit/lib/xml/kit/builders/templates/encryption.builder
@@ -1,14 +0,0 @@
-xml.EncryptedData xmlns: ::Xml::Kit::Namespaces::XMLENC do
- xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
- xml.KeyInfo xmlns: ::Xml::Kit::Namespaces::XMLDSIG do
- xml.EncryptedKey xmlns: ::Xml::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
xml-kit/lib/xml/kit/builders/templates/nil_class.builder
xml-kit/lib/xml/kit/builders/templates/signature.builder
@@ -1,20 +0,0 @@
-xml.Signature "xmlns" => ::Xml::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
xml-kit/lib/xml/kit/builders/encryption.rb
@@ -1,20 +0,0 @@
-module Xml
- module Kit
- module Builders
- class Encryption
- 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
-
xml-kit/lib/xml/kit/builders/signature.rb
@@ -1,34 +0,0 @@
-module Xml
- module Kit
- module Builders
- class Signature
- 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 :certificate
- attr_reader :digest_method
- attr_reader :reference_id
- attr_reader :signature_method
-
- def initialize(reference_id, signature_method: :SH256, digest_method: :SHA256, certificate:)
- @certificate = certificate
- @digest_method = DIGEST_METHODS[digest_method]
- @reference_id = reference_id
- @signature_method = SIGNATURE_METHODS[signature_method]
- end
- end
- end
- end
-end
xml-kit/lib/xml/kit/crypto/oaep_cipher.rb
@@ -1,22 +0,0 @@
-module Xml
- 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
xml-kit/lib/xml/kit/crypto/rsa_cipher.rb
@@ -1,23 +0,0 @@
-module Xml
- 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
xml-kit/lib/xml/kit/crypto/simple_cipher.rb
@@ -1,34 +0,0 @@
-module Xml
- 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
- cipher.update(data) + cipher.final
- end
- end
- end
- end
-end
xml-kit/lib/xml/kit/crypto/unknown_cipher.rb
@@ -1,18 +0,0 @@
-module Xml
- 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
xml-kit/lib/xml/kit/certificate.rb
@@ -1,96 +0,0 @@
-module Xml
- module Kit
- # {include:file:spec/xml/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 [Xml::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
- ::Xml::Kit.logger.warn(error)
- OpenSSL::X509::Certificate.new(value)
- end
-
- private
-
- attr_reader :value
- end
- end
-end
xml-kit/lib/xml/kit/crypto.rb
@@ -1,17 +0,0 @@
-require 'xml/kit/crypto/oaep_cipher'
-require 'xml/kit/crypto/rsa_cipher'
-require 'xml/kit/crypto/simple_cipher'
-require 'xml/kit/crypto/unknown_cipher'
-
-module Xml
- 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
xml-kit/lib/xml/kit/decryption.rb
@@ -1,44 +0,0 @@
-module Xml
- module Kit
- # {include:file:spec/saml/xml_decryption_spec.rb}
- class Decryption
- # The list of private keys to use to attempt to decrypt the document.
- attr_reader :private_keys
-
- def initialize(private_keys:)
- @private_keys = private_keys
- 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
- ::Xml::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
xml-kit/lib/xml/kit/document.rb
@@ -1,75 +0,0 @@
-module Xml
- module Kit
- # {include:file:spec/saml/xml_spec.rb}
- class Document
- include ActiveModel::Validations
- NAMESPACES = { "ds": ::Xml::Kit::Namespaces::XMLDSIG }.freeze
-
- validate :validate_signatures
- validate :validate_certificates
-
- def initialize(raw_xml, namespaces: NAMESPACES)
- @raw_xml = raw_xml
- @namespaces = namespaces
- @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, :namespaces
-
- 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
xml-kit/lib/xml/kit/fingerprint.rb
@@ -1,50 +0,0 @@
-module Xml
- module Kit
- # This generates a fingerprint for an X509 Certificate.
- #
- # certificate, _ = Xml::Kit::SelfSignedCertificate.new("password").create
- #
- # puts Xml::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
xml-kit/lib/xml/kit/id.rb
@@ -1,13 +0,0 @@
-module Xml
- 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
xml-kit/lib/xml/kit/key_pair.rb
@@ -1,29 +0,0 @@
-module Xml
- module Kit
- class KeyPair # :nodoc:
- attr_reader :certificate, :private_key, :use
-
- def initialize(certificate, private_key, passphrase, use)
- @use = use
- @certificate = ::Xml::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 = ::Xml::Kit::SelfSignedCertificate.new(passphrase).create
- new(certificate, private_key, passphrase, use)
- end
- end
- end
-end
xml-kit/lib/xml/kit/namespaces.rb
@@ -1,17 +0,0 @@
-module Xml
- module Kit
- module Namespaces
- ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
- 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'
- XMLDSIG = "http://www.w3.org/2000/09/xmldsig#"
- XMLENC = "http://www.w3.org/2001/04/xmlenc#"
- end
- end
-end
xml-kit/lib/xml/kit/self_signed_certificate.rb
@@ -1,28 +0,0 @@
-module Xml
- module Kit
- class SelfSignedCertificate
- SUBJECT="/C=CA/ST=Alberta/L=Calgary/O=XmlKit/OU=XmlKit/CN=XmlKit"
-
- 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 = Time.now.to_i
- certificate.not_after = (Date.today + 30).to_time.to_i
- 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
xml-kit/lib/xml/kit/signatures.rb
@@ -1,67 +0,0 @@
-module Xml
- module Kit
- # @!visibility private
- class Signatures # :nodoc:
- attr_reader :key_pair, :signature_method, :digest_method
-
- # @!visibility private
- def initialize(key_pair:, signature_method:, digest_method:)
- @digest_method = digest_method
- @key_pair = key_pair
- @signature_method = signature_method
- end
-
- # @!visibility private
- def sign_with(key_pair)
- @key_pair = key_pair
- end
-
- # @!visibility private
- def build(reference_id)
- return nil if key_pair.nil?
-
- ::Xml::Kit::Builders::Signature.new(
- reference_id,
- certificate: key_pair.certificate,
- signature_method: signature_method,
- digest_method: digest_method
- )
- end
-
- # @!visibility private
- def complete(raw_xml)
- return raw_xml if key_pair.nil?
-
- private_key = key_pair.private_key
- Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
- end
-
- # @!visibility private
- def self.sign(xml: ::Builder::XmlMarkup.new, key_pair:, signature_method: :SHA256, digest_method: :SHA256)
- signatures = new(
- key_pair: key_pair,
- signature_method: signature_method,
- digest_method: digest_method,
- )
- 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
xml-kit/lib/xml/kit/templatable.rb
@@ -1,83 +0,0 @@
-module Xml
- 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.
- attr_accessor :embed_signature
-
- # Used to enable/disable encrypting the document.
- attr_accessor :encrypt
-
- # The [Xml::Kit::KeyPair] to use for generating a signature.
- attr_accessor :signing_key_pair
-
- # The [Xml::Kit::Certificate] that contains the public key to use for encrypting the document.
- attr_accessor :encryption_certificate
-
- # 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
-
- def encryption_for(xml:)
- if encrypt?
- temp = ::Builder::XmlMarkup.new
- yield temp
- signed_xml = signatures.complete(temp.target!)
- xml_encryption = ::Xml::Kit::Builders::Encryption.new(
- signed_xml,
- encryption_certificate.public_key
- )
- render(xml_encryption, xml: xml)
- else
- yield xml
- end
- end
-
- def render(model, options)
- ::Xml::Kit::Template.new(model).to_xml(options)
- end
-
- 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 [Xml::Kit::KeyPair] the key pair to use for signing.
- def sign_with(key_pair)
- signatures.sign_with(key_pair)
- end
-
- private
-
- def sign?
- embed_signature
- end
-
- # @!visibility private
- def signatures
- @signatures ||= ::Xml::Kit::Signatures.new(
- key_pair: signing_key_pair,
- digest_method: digest_method,
- signature_method: signature_method,
- )
- end
-
- def digest_method
- :SHA256
- end
-
- def signature_method
- :SHA256
- end
-
- # @!visibility private
- def encrypt?
- encrypt && encryption_certificate
- end
- end
- end
-end
xml-kit/lib/xml/kit/template.rb
@@ -1,32 +0,0 @@
-module Xml
- 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_path
- return target.template_path if target.respond_to?(:template_path)
-
- root_path = File.expand_path(File.dirname(__FILE__))
- template_name = "#{target.class.name.split("::").last.underscore}.builder"
- File.join(root_path, "builders/templates/", template_name)
- end
-
- def template
- Tilt.new(template_path)
- end
- end
- end
-end
xml-kit/lib/xml/kit/version.rb
@@ -1,5 +0,0 @@
-module Xml
- module Kit
- VERSION = "0.1.0"
- end
-end
xml-kit/lib/xml/kit.rb
@@ -1,40 +0,0 @@
-require "active_model"
-require "active_support/core_ext/numeric/time"
-require "base64"
-require "builder"
-require "logger"
-require "nokogiri"
-require "openssl"
-require "tilt"
-require "xmldsig"
-
-require "xml/kit/namespaces"
-
-require "xml/kit/builders/encryption"
-require "xml/kit/builders/signature"
-require "xml/kit/certificate"
-require "xml/kit/crypto"
-require "xml/kit/decryption"
-require "xml/kit/document"
-require "xml/kit/fingerprint"
-require "xml/kit/id"
-require "xml/kit/key_pair"
-require "xml/kit/self_signed_certificate"
-require "xml/kit/signatures"
-require "xml/kit/templatable"
-require "xml/kit/template"
-require "xml/kit/version"
-
-module Xml
- module Kit
- class << self
- def logger
- @logger ||= Logger.new(STDOUT)
- end
-
- def logger=(logger)
- @logger = logger
- end
- end
- end
-end
xml-kit/spec/fixtures/item.builder
@@ -1,4 +0,0 @@
-xml.instruct!
-xml.Item ID: id do
- signature_for(reference_id: id, xml: xml)
-end
xml-kit/spec/support/certificate_helper.rb
@@ -1,5 +0,0 @@
-module CertificateHelper
- def generate_key_pair(passphrase)
- ::Xml::Kit::SelfSignedCertificate.new(passphrase).create
- end
-end
xml-kit/spec/xml/certificate_spec.rb
@@ -1,10 +0,0 @@
-RSpec.describe Xml::Kit::Certificate do
- subject { described_class.new(certificate, use: :signing) }
- let(:certificate) { generate_key_pair('password')[0] }
-
- describe "#fingerprint" do
- it 'returns a fingerprint' do
- expect(subject.fingerprint).to be_instance_of(Xml::Kit::Fingerprint)
- end
- end
-end
xml-kit/spec/xml/decryption_spec.rb
@@ -1,134 +0,0 @@
-RSpec.describe Xml::Kit::Decryption do
- describe "#decrypt" do
- let(:secret) { FFaker::Movie.title }
- let(:password) { FFaker::Movie.title }
-
- it 'decrypts the data' do
- certificate_pem, private_key_pem = generate_key_pair(password)
-
- 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(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 = generate_key_pair(password)
- 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 = generate_key_pair(password)
- other_private_key = OpenSSL::PKey::RSA.new(other_private_key_pem, password)
-
- subject = described_class.new(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, _ = generate_key_pair(password)
- 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 = generate_key_pair(password)[1]
- new_private_key = OpenSSL::PKey::RSA.new(new_private_key_pem, password)
- subject = described_class.new(private_keys: [new_private_key])
- expect do
- subject.decrypt(data)
- end.to raise_error(OpenSSL::PKey::RSAError)
- end
- end
-end
xml-kit/spec/xml/document_spec.rb
@@ -1,50 +0,0 @@
-RSpec.describe Xml::Kit::Document do
- class Item
- include ::Xml::Kit::Templatable
-
- attr_reader :id, :signing_key_pair
-
- def initialize
- @id = ::Xml::Kit::Id.generate
- @signing_key_pair = ::Xml::Kit::KeyPair.generate(use: :signing)
- @embed_signature = true
- end
-
- def template_path
- current_path = File.expand_path(File.dirname(__FILE__))
- File.join(current_path, "../fixtures/item.builder")
- end
- end
-
- describe "#valid_signature?" do
- let(:login_url) { "https://#{FFaker::Internet.domain_name}/login" }
- let(:logout_url) { "https://#{FFaker::Internet.domain_name}/logout" }
- let(:signed_xml) { Item.new.to_xml }
-
- it 'returns true, when the digest and signature is valid' do
- expect(described_class.new(signed_xml)).to be_valid
- end
-
- it 'returns false, when the SHA1 digest is not valid' do
- subject = described_class.new(signed_xml.gsub("Item", "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)['Item']['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)['Item']['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
- end
-end
xml-kit/spec/xml/fingerprint_spec.rb
@@ -1,27 +0,0 @@
-RSpec.describe Xml::Kit::Fingerprint do
- describe "#sha" do
- it 'returns the SHA256' do
- certificate, _ = generate_key_pair("password")
- 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, _ = generate_key_pair("password")
- 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, _ = generate_key_pair("password")
- items = { }
- items[described_class.new(certificate)] = "HI"
- items[described_class.new(certificate)] = "BYE"
- expect(items.keys.count).to eql(1)
- end
-end
xml-kit/spec/xml/kit_spec.rb
@@ -1,5 +0,0 @@
-RSpec.describe Xml::Kit do
- it "has a version number" do
- expect(Xml::Kit::VERSION).not_to be nil
- end
-end
xml-kit/spec/xml/signatures_spec.rb
@@ -1,48 +0,0 @@
-RSpec.describe ::Xml::Kit::Signatures do
- let(:reference_id) { Xml::Kit::Id.generate }
-
- 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,
- }
- key_pair = ::Xml::Kit::KeyPair.generate(use: :signing)
- signed_xml = described_class.sign(key_pair: key_pair) 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 = key_pair.certificate.stripped
- 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(key_pair: nil) 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
xml-kit/spec/spec_helper.rb
@@ -1,20 +0,0 @@
-require "bundler/setup"
-require "xml/kit"
-require "ffaker"
-require "active_support/core_ext/hash/conversions"
-
-Xml::Kit.logger.level = Logger::FATAL
-
-Dir[File.join(Dir.pwd, 'spec/support/**/*.rb')].each { |f| require f }
-RSpec.configure do |config|
- # Enable flags like --only-failures and --next-failure
- config.example_status_persistence_file_path = ".rspec_status"
-
- # Disable RSpec exposing methods globally on `Module` and `main`
- config.disable_monkey_patching!
-
- config.expect_with :rspec do |c|
- c.syntax = :expect
- end
- config.include CertificateHelper
-end
xml-kit/.gitignore
@@ -1,11 +0,0 @@
-/.bundle/
-/.yardoc
-/_yardoc/
-/coverage/
-/doc/
-/pkg/
-/spec/reports/
-/tmp/
-
-# rspec failure tracking
-.rspec_status
xml-kit/.rspec
@@ -1,3 +0,0 @@
---format documentation
---color
---require spec_helper
xml-kit/.travis.yml
@@ -1,5 +0,0 @@
-sudo: false
-language: ruby
-rvm:
- - 2.4.3
-before_install: gem install bundler -v 1.16.0
xml-kit/Gemfile
@@ -1,6 +0,0 @@
-source "https://rubygems.org"
-
-git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
-
-# Specify your gem's dependencies in xml-kit.gemspec
-gemspec
xml-kit/LICENSE.txt
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2017 mo
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
xml-kit/Rakefile
@@ -1,6 +0,0 @@
-require "bundler/gem_tasks"
-require "rspec/core/rake_task"
-
-RSpec::Core::RakeTask.new(:spec)
-
-task :default => :spec
xml-kit/README.md
@@ -1,39 +0,0 @@
-# Xml::Kit
-
-Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/xml/kit`. To experiment with that code, run `bin/console` for an interactive prompt.
-
-TODO: Delete this and the text above, and describe your gem
-
-## Installation
-
-Add this line to your application's Gemfile:
-
-```ruby
-gem 'xml-kit'
-```
-
-And then execute:
-
- $ bundle
-
-Or install it yourself as:
-
- $ gem install xml-kit
-
-## Usage
-
-TODO: Write usage instructions here
-
-## Development
-
-After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
-
-To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
-
-## Contributing
-
-Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/xml-kit.
-
-## License
-
-The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
xml-kit/xml-kit.gemspec
@@ -1,35 +0,0 @@
-# coding: utf-8
-lib = File.expand_path("../lib", __FILE__)
-$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
-require "xml/kit/version"
-
-Gem::Specification.new do |spec|
- spec.name = "xml-kit"
- spec.version = Xml::Kit::VERSION
- spec.authors = ["mo khan"]
- spec.email = ["mo@mokhan.ca"]
-
- spec.summary = %q{A simple toolkit for working with XML.}
- spec.description = %q{A simple toolkit for working with XML.}
- spec.homepage = "http://www.mokhan.ca"
- spec.license = "MIT"
- spec.required_ruby_version = '>= 2.2.0'
-
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
- f.match(%r{^(test|spec|features)/})
- end
- spec.metadata["yard.run"] = "yri"
- spec.bindir = "exe"
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
- 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_development_dependency "bundler", "~> 1.16"
- spec.add_development_dependency "ffaker", "~> 2.7"
- spec.add_development_dependency "rake", "~> 10.0"
- spec.add_development_dependency "rspec", "~> 3.0"
-end
Gemfile
@@ -4,5 +4,3 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
# Specify your gem's dependencies in saml-kit.gemspec
gemspec
-
-gem 'xml-kit', path: 'xml-kit'