Commit 042cf81
Changed files (8)
lib
spec
lib/xml/kit/crypto/oaep_cipher.rb
@@ -0,0 +1,22 @@
+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
lib/xml/kit/crypto/rsa_cipher.rb
@@ -0,0 +1,23 @@
+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
lib/xml/kit/crypto/simple_cipher.rb
@@ -0,0 +1,38 @@
+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
+
+ Saml::Kit.logger.debug ['-key', @private_key].inspect
+ Saml::Kit.logger.debug ['-iv', iv].inspect
+
+ cipher.update(data) + cipher.final
+ end
+ end
+ end
+ end
+end
lib/xml/kit/crypto/unknown_cipher.rb
@@ -0,0 +1,18 @@
+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
lib/xml/kit/crypto.rb
@@ -0,0 +1,17 @@
+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
lib/xml/kit/xml_decryption.rb
@@ -0,0 +1,44 @@
+module Xml
+ module Kit
+ # {include:file:spec/saml/xml_decryption_spec.rb}
+ class XmlDecryption
+ # The list of private keys to use to attempt to decrypt the document.
+ attr_reader :private_keys
+
+ def initialize(configuration: Saml::Kit.configuration)
+ @private_keys = configuration.private_keys(use: :encryption)
+ end
+
+ # Decrypts an EncryptedData section of an XML document.
+ #
+ # @param data [Hash] the XML document converted to a [Hash] using Hash.from_xml.
+ def decrypt(data)
+ encrypted_data = data['EncryptedData']
+ symmetric_key = symmetric_key_from(encrypted_data)
+ cipher_text = Base64.decode64(encrypted_data["CipherData"]["CipherValue"])
+ to_plaintext(cipher_text, symmetric_key, encrypted_data["EncryptionMethod"]['Algorithm'])
+ end
+
+ private
+
+ def symmetric_key_from(encrypted_data)
+ encrypted_key = encrypted_data['KeyInfo']['EncryptedKey']
+ cipher_text = Base64.decode64(encrypted_key['CipherData']['CipherValue'])
+ attempts = private_keys.count
+ private_keys.each do |private_key|
+ begin
+ attempts -= 1
+ return to_plaintext(cipher_text, private_key, encrypted_key["EncryptionMethod"]['Algorithm'])
+ rescue OpenSSL::PKey::RSAError => error
+ Saml::Kit.logger.error(error)
+ raise if attempts.zero?
+ end
+ end
+ end
+
+ def to_plaintext(cipher_text, symmetric_key, algorithm)
+ Crypto.decryptor_for(algorithm, symmetric_key).decrypt(cipher_text)
+ end
+ end
+ end
+end
lib/xml/kit.rb
@@ -1,7 +1,9 @@
require "xml/kit/version"
+require "xml/kit/crypto"
+require "xml/kit/xml_decryption"
+
module Xml
module Kit
- # Your code goes here...
end
end
spec/xml/xml_decryption_spec.rb
@@ -0,0 +1,133 @@
+RSpec.describe Xml::Kit::XmlDecryption do
+ describe "#decrypt" do
+ let(:secret) { FFaker::Movie.title }
+ let(:password) { FFaker::Movie.title }
+
+ it 'decrypts the data' do
+ certificate_pem, private_key_pem = Saml::Kit::SelfSignedCertificate.new(password).create
+ public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
+ private_key = OpenSSL::PKey::RSA.new(private_key_pem, password)
+
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
+ cipher.encrypt
+ key = cipher.random_key
+ iv = cipher.random_iv
+ encrypted = cipher.update(secret) + cipher.final
+
+ data = {
+ "EncryptedData"=> {
+ "xmlns:xenc"=>"http://www.w3.org/2001/04/xmlenc#",
+ "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
+ "Type"=>"http://www.w3.org/2001/04/xmlenc#Element",
+ "EncryptionMethod"=> {
+ "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#aes128-cbc"
+ },
+ "KeyInfo"=> {
+ "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
+ "EncryptedKey"=> {
+ "EncryptionMethod"=>{
+ "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+ },
+ "CipherData"=>{
+ "CipherValue"=> Base64.encode64(public_key.public_encrypt(key))
+ }
+ }
+ },
+ "CipherData"=>{
+ "CipherValue"=> Base64.encode64(iv + encrypted)
+ }
+ }
+ }
+ subject = described_class.new(configuration: double(private_keys: [private_key]))
+ decrypted = subject.decrypt(data)
+ expect(decrypted.strip).to eql(secret)
+ end
+
+ it 'attemps to decrypt with each encryption keypair' do
+ certificate_pem, private_key_pem = Saml::Kit::SelfSignedCertificate.new(password).create
+ public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
+ private_key = OpenSSL::PKey::RSA.new(private_key_pem, password)
+
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
+ cipher.encrypt
+ key = cipher.random_key
+ iv = cipher.random_iv
+ encrypted = cipher.update(secret) + cipher.final
+
+ data = {
+ "EncryptedData"=> {
+ "xmlns:xenc"=>"http://www.w3.org/2001/04/xmlenc#",
+ "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
+ "Type"=>"http://www.w3.org/2001/04/xmlenc#Element",
+ "EncryptionMethod"=> {
+ "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#aes128-cbc"
+ },
+ "KeyInfo"=> {
+ "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
+ "EncryptedKey"=> {
+ "EncryptionMethod"=>{
+ "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+ },
+ "CipherData"=>{
+ "CipherValue"=> Base64.encode64(public_key.public_encrypt(key))
+ }
+ }
+ },
+ "CipherData"=>{
+ "CipherValue"=> Base64.encode64(iv + encrypted)
+ }
+ }
+ }
+
+ _, other_private_key_pem = Saml::Kit::SelfSignedCertificate.new(password).create
+ other_private_key = OpenSSL::PKey::RSA.new(other_private_key_pem, password)
+
+ subject = described_class.new(configuration: double(private_keys: [other_private_key, private_key]))
+ decrypted = subject.decrypt(data)
+ expect(decrypted.strip).to eql(secret)
+ end
+
+ it 'raise an error when it cannot decrypt the data' do
+ certificate_pem, _ = Saml::Kit::SelfSignedCertificate.new(password).create
+ public_key = OpenSSL::X509::Certificate.new(certificate_pem).public_key
+
+ cipher = OpenSSL::Cipher.new('AES-128-CBC')
+ cipher.encrypt
+ key = cipher.random_key
+ iv = cipher.random_iv
+ encrypted = cipher.update(secret) + cipher.final
+
+ data = {
+ "EncryptedData"=> {
+ "xmlns:xenc"=>"http://www.w3.org/2001/04/xmlenc#",
+ "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
+ "Type"=>"http://www.w3.org/2001/04/xmlenc#Element",
+ "EncryptionMethod"=> {
+ "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#aes128-cbc"
+ },
+ "KeyInfo"=> {
+ "xmlns:dsig"=>"http://www.w3.org/2000/09/xmldsig#",
+ "EncryptedKey"=> {
+ "EncryptionMethod"=>{
+ "Algorithm"=>"http://www.w3.org/2001/04/xmlenc#rsa-1_5"
+ },
+ "CipherData"=>{
+ "CipherValue"=> Base64.encode64(public_key.public_encrypt(key))
+ }
+ }
+ },
+ "CipherData"=>{
+ "CipherValue"=> Base64.encode64(iv + encrypted)
+ }
+ }
+ }
+
+ new_private_key_pem = Saml::Kit::SelfSignedCertificate.new(password).create[1]
+ new_private_key = OpenSSL::PKey::RSA.new(new_private_key_pem, password)
+ subject = described_class.new(configuration: double(private_keys: [new_private_key]))
+ expect do
+ subject.decrypt(data)
+ end.to raise_error(OpenSSL::PKey::RSAError)
+ end
+ end
+end