<commit_before># frozen_string_literal: true

module JWT
  module JWK
    class RSA < KeyBase # rubocop:disable Metrics/ClassLength
      BINARY = 2
      KTY    = 'RSA'
      KTYS   = [KTY, OpenSSL::PKey::RSA, JWT::JWK::RSA].freeze
      RSA_PUBLIC_KEY_ELEMENTS  = %i[kty n e].freeze
      RSA_PRIVATE_KEY_ELEMENTS = %i[d p q dp dq qi].freeze
      RSA_KEY_ELEMENTS = (RSA_PRIVATE_KEY_ELEMENTS + RSA_PUBLIC_KEY_ELEMENTS).freeze

      def initialize(key, params = nil, options = {})
        params ||= {}

        # For backwards compatibility when kid was a String
        params = { kid: params } if params.is_a?(String)

        key_params = extract_key_params(key)

        params = params.transform_keys(&:to_sym)
        check_jwk(key_params, params)

        super(options, key_params.merge(params))
      end

      def keypair
        @keypair ||= self.class.create_rsa_key(jwk_attributes(*(RSA_KEY_ELEMENTS - [:kty])))
      end

      def private?
        keypair.private?
      end

      def public_key
        keypair.public_key
      end

      def export(options = {})
        exported = parameters.clone
        exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
        exported
      end

      def members
        RSA_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
      end

      def key_digest
        sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
                                            OpenSSL::ASN1::Integer.new(public_key.e)])
        OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
      end

      def []=(key, value)
        if RSA_KEY_ELEMENTS.include?(key.to_sym)
          raise ArgumentError, 'cannot overwrite cryptographic key attributes'
        end

        super(key, value)
      end

      private

      def extract_key_params(key)
        case key
        when JWT::JWK::RSA
          key.export(include_private: true)
        when OpenSSL::PKey::RSA # Accept OpenSSL key as input
          @keypair = key # Preserve the object to avoid recreation
          parse_rsa_key(key)
        when Hash
          key.transform_keys(&:to_sym)
        else
          raise ArgumentError, 'key must be of type OpenSSL::PKey::RSA or Hash with key parameters'
        end
      end

      def check_jwk(keypair, params)
        raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (RSA_KEY_ELEMENTS & params.keys).empty?
        raise JWT::JWKError, "Incorrect 'kty' value: #{keypair[:kty]}, expected #{KTY}" unless keypair[:kty] == KTY
        raise JWT::JWKError, 'Key format is invalid for RSA' unless keypair[:n] && keypair[:e]
      end

      def parse_rsa_key(key)
        {
          kty: KTY,
          n: encode_open_ssl_bn(key.n),
          e: encode_open_ssl_bn(key.e),
          d: encode_open_ssl_bn(key.d),
          p: encode_open_ssl_bn(key.p),
          q: encode_open_ssl_bn(key.q),
          dp: encode_open_ssl_bn(key.dmp1),
          dq: encode_open_ssl_bn(key.dmq1),
          qi: encode_open_ssl_bn(key.iqmp)
        }.compact
      end

      def jwk_attributes(*attributes)
        attributes.each_with_object({}) do |attribute, hash|
          hash[attribute] = decode_open_ssl_bn(self[attribute])
        end
      end

      def encode_open_ssl_bn(key_part)
        return unless key_part

        ::JWT::Base64.url_encode(key_part.to_s(BINARY))
      end

      def decode_open_ssl_bn(jwk_data)
        self.class.decode_open_ssl_bn(jwk_data)
      end

      class << self
        def import(jwk_data)
          new(jwk_data)
        end

        def decode_open_ssl_bn(jwk_data)
          return nil unless jwk_data

          OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
        end

        RSA_OPT_PARAMS    = %i[p q dp dq qi].freeze
        RSA_ASN1_SEQUENCE = (%i[n e d] + RSA_OPT_PARAMS).freeze # https://www.rfc-editor.org/rfc/rfc3447#appendix-A.1.2

        def create_rsa_key_using_der(rsa_parameters)
          validate_rsa_parameters!(rsa_parameters)<commit_msg>Moved constants close to each other<commit_after># frozen_string_literal: true

module JWT
  module JWK
    class RSA < KeyBase # rubocop:disable Metrics/ClassLength
      BINARY = 2
      KTY    = 'RSA'
      KTYS   = [KTY, OpenSSL::PKey::RSA, JWT::JWK::RSA].freeze
      RSA_PUBLIC_KEY_ELEMENTS  = %i[kty n e].freeze
      RSA_PRIVATE_KEY_ELEMENTS = %i[d p q dp dq qi].freeze
      RSA_KEY_ELEMENTS = (RSA_PRIVATE_KEY_ELEMENTS + RSA_PUBLIC_KEY_ELEMENTS).freeze

      RSA_OPT_PARAMS    = %i[p q dp dq qi].freeze
      RSA_ASN1_SEQUENCE = (%i[n e d] + RSA_OPT_PARAMS).freeze # https://www.rfc-editor.org/rfc/rfc3447#appendix-A.1.2

      def initialize(key, params = nil, options = {})
        params ||= {}

        # For backwards compatibility when kid was a String
        params = { kid: params } if params.is_a?(String)

        key_params = extract_key_params(key)

        params = params.transform_keys(&:to_sym)
        check_jwk(key_params, params)

        super(options, key_params.merge(params))
      end

      def keypair
        @keypair ||= self.class.create_rsa_key(jwk_attributes(*(RSA_KEY_ELEMENTS - [:kty])))
      end

      def private?
        keypair.private?
      end

      def public_key
        keypair.public_key
      end

      def export(options = {})
        exported = parameters.clone
        exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
        exported
      end

      def members
        RSA_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
      end

      def key_digest
        sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
                                            OpenSSL::ASN1::Integer.new(public_key.e)])
        OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
      end

      def []=(key, value)
        if RSA_KEY_ELEMENTS.include?(key.to_sym)
          raise ArgumentError, 'cannot overwrite cryptographic key attributes'
        end

        super(key, value)
      end

      private

      def extract_key_params(key)
        case key
        when JWT::JWK::RSA
          key.export(include_private: true)
        when OpenSSL::PKey::RSA # Accept OpenSSL key as input
          @keypair = key # Preserve the object to avoid recreation
          parse_rsa_key(key)
        when Hash
          key.transform_keys(&:to_sym)
        else
          raise ArgumentError, 'key must be of type OpenSSL::PKey::RSA or Hash with key parameters'
        end
      end

      def check_jwk(keypair, params)
        raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (RSA_KEY_ELEMENTS & params.keys).empty?
        raise JWT::JWKError, "Incorrect 'kty' value: #{keypair[:kty]}, expected #{KTY}" unless keypair[:kty] == KTY
        raise JWT::JWKError, 'Key format is invalid for RSA' unless keypair[:n] && keypair[:e]
      end

      def parse_rsa_key(key)
        {
          kty: KTY,
          n: encode_open_ssl_bn(key.n),
          e: encode_open_ssl_bn(key.e),
          d: encode_open_ssl_bn(key.d),
          p: encode_open_ssl_bn(key.p),
          q: encode_open_ssl_bn(key.q),
          dp: encode_open_ssl_bn(key.dmp1),
          dq: encode_open_ssl_bn(key.dmq1),
          qi: encode_open_ssl_bn(key.iqmp)
        }.compact
      end

      def jwk_attributes(*attributes)
        attributes.each_with_object({}) do |attribute, hash|
          hash[attribute] = decode_open_ssl_bn(self[attribute])
        end
      end

      def encode_open_ssl_bn(key_part)
        return unless key_part

        ::JWT::Base64.url_encode(key_part.to_s(BINARY))
      end

      def decode_open_ssl_bn(jwk_data)
        self.class.decode_open_ssl_bn(jwk_data)
      end

      class << self
        def import(jwk_data)
          new(jwk_data)
        end

        def decode_open_ssl_bn(jwk_data)
          return nil unless jwk_data

          OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
        end

        def create_rsa_key_using_der(rsa_parameters)
          validate_rsa_parameters!(rsa_parameters)
{'fraction_non_alphanumeric': 0.09907497565725414, 'fraction_numerical': 0.004868549172346641, 'mean_word_length': 2.3787006578947367, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '763256', 'n_tokens_mistral': 2897, 'n_tokens_neox': 2689, 'n_words': 556}