ronin-rb/ronin-support

View on GitHub
lib/ronin/support/crypto/key/rsa.rb

Summary

Maintainability
A
40 mins
Test Coverage
# frozen_string_literal: true
#
# Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# Ronin Support is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ronin Support is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Ronin Support.  If not, see <http://www.gnu.org/licenses/>.
#

require 'ronin/support/crypto/key/methods'
require 'ronin/support/crypto/openssl'

module Ronin
  module Support
    module Crypto
      module Key
        #
        # Represents an RSA key.
        #
        # @see https://rubydoc.info/stdlib/openssl/OpenSSL/PKey/RSA.html
        #
        # @since 1.0.0
        #
        # @api public
        #
        class RSA < OpenSSL::PKey::RSA

          include Methods

          #
          # Generates a new random RSA key.
          #
          # @param [Integer] key_size
          #   The size of the key in bits.
          #
          # @param [Array] arguments
          #   Additional arguments for [OpenSSL::PKey::RSA.generate](https://rubydoc.info/gems/openssl/OpenSSL/PKey/RSA#generate-class_method).
          #
          # @return [RSA]
          #   The newly generated key.
          #
          def self.generate(key_size=4096,*arguments,&block)
            # HACK: openssl-3.0.0 will return an OpenSSL::PKey::RSA instance,
            # even though we subclassed OpenSSL::PKey::RSA.
            new_key = allocate
            new_key.send(:initialize_copy,super(key_size,*arguments,&block))
            new_key
          end

          #
          # The `n` variable for the RSA key.
          #
          # @return [OpenSSL::BN]
          #
          # @see https://rubydoc.info/stdlib/openssl/OpenSSL/BN
          #
          def n
            super()
          end

          #
          # The `e` variable for the RSA key.
          #
          # @return [OpenSSL::BN]
          #
          # @see https://rubydoc.info/stdlib/openssl/OpenSSL/BN
          #
          def e
            super()
          end

          #
          # The `d` variable for the RSA key.
          #
          # @return [OpenSSL::BN]
          #
          # @see https://rubydoc.info/stdlib/openssl/OpenSSL/BN
          #
          def d
            super()
          end

          #
          # The size of the RSA key in bits.
          #
          # @return [Integer]
          #   The key size in bits.
          #
          def size
            n.num_bits
          end

          # Mapping of padding names to pdding constants.
          #
          # @api private
          PADDINGS = {
            pkcs1_oaep: PKCS1_OAEP_PADDING,
            pkcs1:      PKCS1_PADDING,
            sslv23:     SSLV23_PADDING,

            nil      => NO_PADDING,
            false    => NO_PADDING
          }

          #
          # Encrypts data using the public key.
          #
          # @param [String] data
          #   The data to encrypt.
          #
          # @param [:pkcs1_oaep, :pkcs1, :sslv23, nil, false] padding
          #   Optional padding mode. `nil` and `false` will disable padding.
          #
          # @return [String]
          #   The encrypted data.
          #
          # @example
          #   rsa = Crypto::Key::RSA.load_file('key.pem')
          #   rsa.public_encrypt("top secret", padding: :pkcs1_oaep)
          #   # => "i;k\x89\xE9\x92\xA5\xAB\xBAc\xC6;\r\xB7\x18W\x11\x02\xCBf.\xC2\x87\xDF\xDD[|\xF0\x97\x15\xC6\xCF\xCD\x93\x1C\x11S&L\x89\xE6\xCA\xC9\xAD\xAD\x1F\xE6\x8D\x86\xF3$\x8BfS(3\x9F\x7F\xEFZ \xB7\xDC{f\xF1\xB7-\x18\x94\xB8}\x93%,{X\x85\xBD(\xBD\xAD\x00,O\xAC\xECJ}\x99\xC7\xE2\xB6\x11\x9D\xDF\x12\xA5\x8F|\xF8\xC3Q\xDA\x95\x12\xEFH\xFFt\xCD\x854jJ\xE9\xE7\xC4\xDD|\xD4}w\xDAJ8\xAE\x17"
          #
          def public_encrypt(data, padding: :pkcs1)
            padding_const = PADDINGS.fetch(padding) do
              raise(ArgumentError,"padding must be #{PADDINGS.keys.map(&:inspect).join(', ')}: #{padding.inspect}")
            end

            super(data,padding_const)
          end

          #
          # Decrypts data using the private key.
          #
          # @param [String] data
          #   The data to decrypt.
          #
          # @param [:pkcs1_oaep, :pkcs1, :sslv23, nil, false] padding
          #   Optional padding mode. `nil` and `false` will disable padding.
          #
          # @return [String]
          #   The decrypted data.
          #
          # @example
          #   encrypted = "i;k\x89\xE9\x92\xA5\xAB\xBAc\xC6;\r\xB7\x18W\x11\x02\xCBf.\xC2\x87\xDF\xDD[|\xF0\x97\x15\xC6\xCF\xCD\x93\x1C\x11S&L\x89\xE6\xCA\xC9\xAD\xAD\x1F\xE6\x8D\x86\xF3$\x8BfS(3\x9F\x7F\xEFZ \xB7\xDC{f\xF1\xB7-\x18\x94\xB8}\x93%,{X\x85\xBD(\xBD\xAD\x00,O\xAC\xECJ}\x99\xC7\xE2\xB6\x11\x9D\xDF\x12\xA5\x8F|\xF8\xC3Q\xDA\x95\x12\xEFH\xFFt\xCD\x854jJ\xE9\xE7\xC4\xDD|\xD4}w\xDAJ8\xAE\x17"
          #   rsa = Crypto::Key::RSA.load_file('key.pem')
          #   rsa.private_decrypt(encrypted, padding: pkcs1_oaep)
          #   # => "top secret"
          #
          def private_decrypt(data, padding: :pkcs1)
            padding_const = PADDINGS.fetch(padding) do
              raise(ArgumentError,"padding must be #{PADDINGS.keys.map(&:inspect).join(', ')}: #{padding.inspect}")
            end

            super(data,padding_const)
          end

        end
      end
    end
  end
end