=begin
    Copyright 2010-2022 Ecsypno <http://www.ecsypno.com>

    This file is part of the Arachni Framework project and is subject to
    redistribution and commercial restrictions. Please see the Arachni Framework
    web site for more information on licensing and terms of use.
=end

require 'openssl'
require "base64"

module Arachni
module Support::Crypto

# Simple hybrid crypto class using RSA for public key encryption and AES with CBC
# for bulk data encryption/decryption.
#
# RSA is used to encrypt the AES primitives which are used to encrypt the plaintext.
#
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
class RSA_AES_CBC

    # If only encryption is required the private key parameter can be omitted.
    #
    # @param  [String]  public_pem
    #   Location of the Public key in PEM format.
    # @param  [String]  private_pem
    #   Location of the Private key in PEM format.
    def initialize( public_pem, private_pem = nil )
        @public_pem  = public_pem
        @private_pem = private_pem
    end

    # Encrypts data and returns a Base64 representation of the ciphertext
    # and AES CBC primitives encrypted using the public key.
    #
    # @param  [String]  data
    #
    # @return  [String]
    #   Base64 representation of the ciphertext and AES CBC primitives encrypted
    #   using the public key.
    def encrypt( data )
        rsa = OpenSSL::PKey::RSA.new( File.read( @public_pem ) )

        # encrypt with 256 bit AES with CBC
        aes = OpenSSL::Cipher::Cipher.new( 'aes-256-cbc' )
        aes.encrypt

        # use random key and IV
        aes.key = key = aes.random_key
        aes.iv  = iv  = aes.random_iv

        # this will hold all primitives and ciphertext
        primitives = {}

        primitives['ciphertext'] = aes.update( data )
        primitives['ciphertext'] << aes.final

        primitives['key'] = rsa.public_encrypt( key )
        primitives['iv']  = rsa.public_encrypt( iv )

        # serialize everything and base64 encode it
        Base64.encode64( primitives.to_yaml )
    end

    # Decrypts data.
    #
    # @param  [String]  data
    #
    # @return  [String]
    #   Plaintext.
    def decrypt( data )
        rsa = OpenSSL::PKey::RSA.new( File.read( @private_pem ) )

        # decrypt with 256 bit AES with CBC
        aes = OpenSSL::Cipher::Cipher.new( 'aes-256-cbc' )
        aes.decrypt

        # unencode and unserialize to get the primitives and ciphertext
        primitives = YAML::load( Base64.decode64( data ) )

        aes.key = rsa.private_decrypt( primitives['key'] )
        aes.iv  = rsa.private_decrypt( primitives['iv'] )

        plaintext = aes.update( primitives['ciphertext'] )
        plaintext << aes.final

        plaintext
    end

end

end
end
