# -*- coding: binary -*-

module Msf
  class Exploit
    class Remote
      module Kerberos
        module Client
          module AsRequest
            # Builds a kerberos AS request
            #
            # @param opts [Hash{Symbol => <Array<Rex::Proto::Kerberos::Model::PreAuthDataEntry>, Rex::Proto::Kerberos::Model::KdcRequestBody>}]
            # @option opts [Array<Rex::Proto::Kerberos::Model::PreAuthDataEntry>] :pa_data
            # @option opts [Rex::Proto::Kerberos::Model::KdcRequestBody] :body
            # @return [Rex::Proto::Kerberos::Model::KdcRequest]
            # @see [Rex::Proto::Kerberos::Model::KdcRequest]
            # @see #build_as_pa_time_stamp
            # @see #build_as_request_body
            def build_as_request(opts = {})
              pa_data = opts[:pa_data] || build_as_pa_time_stamp(opts)
              body = opts[:body] || build_as_request_body(opts)

              request = Rex::Proto::Kerberos::Model::KdcRequest.new(
                pvno: 5,
                msg_type: Rex::Proto::Kerberos::Model::AS_REQ,
                pa_data: pa_data,
                req_body: body
              )

              request
            end

            # Builds a kerberos PA-ENC-TIMESTAMP pre authenticated structure
            #
            # @param opts [Hash{Symbol => <Time, Integer, String>}]
            # @option opts [Time] :time_stamp
            # @option opts [Integer] :pausec
            # @option opts [Integer] :etype
            # @option opts [String] :key
            # @return [Rex::Proto::Kerberos::Model::PreAuthDataEntry]
            # @see Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp
            # @see Rex::Proto::Kerberos::Model::EncryptedData
            # @see Rex::Proto::Kerberos::Model::PreAuthDataEntry
            def build_as_pa_time_stamp(opts = {})
              time_stamp = opts[:time_stamp] || Time.now
              pausec = opts[:pausec] || time_stamp&.usec || 0
              etype = opts[:etype] || Rex::Proto::Kerberos::Crypto::Encryption::DefaultEncryptionType
              key = opts[:key] || OpenSSL::Random.random_bytes(16)

              pa_time_stamp = Rex::Proto::Kerberos::Model::PreAuthEncTimeStamp.new(
                  pa_time_stamp: time_stamp,
                  pausec: pausec
              )

              enc_time_stamp = Rex::Proto::Kerberos::Model::EncryptedData.new(
                  etype: etype,
                  cipher: pa_time_stamp.encrypt(etype, key)
              )

              pa_enc_time_stamp = Rex::Proto::Kerberos::Model::PreAuthDataEntry.new(
                  type: Rex::Proto::Kerberos::Model::PreAuthType::PA_ENC_TIMESTAMP,
                  value: enc_time_stamp.encode
              )

              pa_enc_time_stamp
            end

            # Builds a kerberos AS request body
            #
            # @param opts [Hash{Symbol => <Integer, Time, String, Rex::Proto::Kerberos::Model::PrincipalName>}]
            # @option opts [Integer] :options
            # @option opts [Time] :from
            # @option opts [Time] :till
            # @option opts [Time] :rtime
            # @option opts [Integer] :nonce
            # @option opts [Integer] :etype
            # @option opts [Rex::Proto::Kerberos::Model::PrincipalName] :cname
            # @option opts [String] :realm
            # @option opts [Rex::Proto::Kerberos::Model::PrincipalName] :sname
            # @return [Rex::Proto::Kerberos::Model::KdcRequestBody]
            # @see #build_client_name
            # @see #build_server_name
            # @see Rex::Proto::Kerberos::Model::KdcRequestBody
            def build_as_request_body(opts = {})
              options = opts.fetch(:options) { 0x50800000 } # Forwardable, Proxiable, Renewable
              from = opts.fetch(:from) { Time.at(0).utc }
              till = opts.fetch(:till) { Time.at(0).utc }
              rtime = opts.fetch(:rtime) { Time.at(0).utc }
              nonce = opts.fetch(:nonce) { rand(1 << 24) }
              etype = opts.fetch(:etype) { Rex::Proto::Kerberos::Crypto::Encryption::DefaultOfferedEtypes }
              cname = opts.fetch(:cname) { build_client_name(opts) }
              realm = opts.fetch(:realm) { '' }
              sname = opts.fetch(:sname) { build_server_name(opts) }

              body = Rex::Proto::Kerberos::Model::KdcRequestBody.new(
                options: options,
                cname: cname,
                realm: realm,
                sname: sname,
                from: from,
                till: till,
                rtime: rtime,
                nonce: nonce,
                etype: etype
              )

              body
            end
          end
        end
      end
    end
  end
end
