# -*- coding: binary -*-

module Msf
  class Exploit
    class Remote
      module Kerberos
        module Client
          module TgsRequest

            # Builds the encrypted Kerberos TGS request
            #
            # @param opts [Hash{Symbol => <Rex::Proto::Kerberos::Model::Element>}]
            # @option opts [Rex::Proto::Kerberos::Model::AuthorizationData] :auth_data
            # @option opts [Rex::Proto::Kerberos::Model::EncryptedData] :enc_auth_data
            # @option opts [Rex::Proto::Kerberos::Model::EncryptionKey] :subkey
            # @option opts [Rex::Proto::Kerberos::Model::Checksum] :checksum
            # @option opts [Rex::Proto::Kerberos::Model::Authenticator] :auhtenticator
            # @option opts [Array<Rex::Proto::Kerberos::Model::PreAuthDataEntry>] :pa_data
            # @return [Rex::Proto::Kerberos::Model::KdcRequest]
            # @raise [RuntimeError] if ticket isn't available
            # @see Rex::Proto::Kerberos::Model::AuthorizationData
            # @see Rex::Proto::Kerberos::Model::EncryptedData
            # @see Rex::Proto::Kerberos::Model::EncryptionKey
            # @see Rex::Proto::Kerberos::Model::Checksum
            # @see Rex::Proto::Kerberos::Model::Authenticator
            # @see Rex::Proto::Kerberos::Model::PreAuthDataEntry
            # @see Rex::Proto::Kerberos::Model::KdcRequest
            def build_tgs_request(opts = {})
              subkey = opts.fetch(:subkey) { build_subkey(opts) }

              if opts[:enc_auth_data]
                enc_auth_data = opts[:enc_auth_data]
              elsif opts[:auth_data]
                enc_auth_data = build_enc_auth_data(
                  auth_data: opts[:auth_data],
                  subkey: subkey
                )
              else
                enc_auth_data = nil
              end

              body = opts.fetch(:body) do
                build_tgs_request_body(opts.merge(
                  enc_auth_data: enc_auth_data
                ))
              end

              checksum = opts.fetch(:checksum) do

                build_tgs_body_checksum(body: body,
                                        session_key: opts[:session_key],
                                        cksum_key_usage: Rex::Proto::Kerberos::Crypto::KeyUsage::TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR_CHKSUM)
              end

              authenticator = opts.fetch(:authenticator) do
                build_authenticator(opts.merge(
                  subkey: subkey,
                  checksum: checksum,
                  body: body,
                  authenticator_enc_key_usage: Rex::Proto::Kerberos::Crypto::KeyUsage::TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR
                ))
              end

              ap_req = opts.fetch(:ap_req) { build_ap_req(opts.merge(authenticator: authenticator)) }

              pa_ap_req = Rex::Proto::Kerberos::Model::PreAuthDataEntry.new(
                type: Rex::Proto::Kerberos::Model::PreAuthType::PA_TGS_REQ,
                value: ap_req.encode
              )

              pa_data = []
              pa_data.push(pa_ap_req)
              if opts[:pa_data]
                opts[:pa_data].each { |pa| pa_data.push(pa) }
              end

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

              request
            end

            # Builds the encrypted TGS authorization data
            #
            # @param opts [Hash{Symbol => <Rex::Proto::Kerberos::Model::AuthorizationData, Rex::Proto::Kerberos::Model::EncryptionKey>}]
            # @option opts [Rex::Proto::Kerberos::Model::AuthorizationData] :auth_data
            # @option opts [Rex::Proto::Kerberos::Model::EncryptionKey] :subkey
            # @return [Rex::Proto::Kerberos::Model::EncryptedData]
            # @raise [Rex::Proto::Kerberos::Model::Error::KerberosError] if auth_data option isn't provided
            # @see Rex::Proto::Kerberos::Model::AuthorizationData
            # @see Rex::Proto::Kerberos::Model::EncryptionKey
            # @see Rex::Proto::Kerberos::Model::EncryptedData
            def build_enc_auth_data(opts = {})
              auth_data = opts[:auth_data]

              if auth_data.nil?
                raise ::Rex::Proto::Kerberos::Model::Error::KerberosError, 'auth_data option required on #build_enc_auth_data'
              end

              subkey = opts[:subkey] || build_subkey(opts)

              encrypted = auth_data.encrypt(subkey.type, subkey.value)

              e_data = Rex::Proto::Kerberos::Model::EncryptedData.new(
                etype: subkey.type,
                cipher: encrypted
              )

              e_data
            end

            # Builds a KRB_AP_REQ message
            #
            # @param opts [Hash{Symbol => <Integer, Rex::Proto::Kerberos::Model::Ticket, Rex::Proto::Kerberos::Model::EncryptedData, Rex::Proto::Kerberos::Model::EncryptionKey>}]
            # @option opts [Integer] :pvno
            # @option opts [Integer] :msg_type
            # @option opts [Integer] :ap_req_options
            # @option opts [Rex::Proto::Kerberos::Model::Ticket] :ticket
            # @option opts [Rex::Proto::Kerberos::Model::EncryptedData] :authenticator
            # @option opts [Rex::Proto::Kerberos::Model::EncryptionKey] :session_key
            # @return [Rex::Proto::Kerberos::Model::EncryptionKey]
            # @raise [Rex::Proto::Kerberos::Model::Error::KerberosError] if ticket option isn't provided
            # @see Rex::Proto::Kerberos::Model::Ticket
            # @see Rex::Proto::Kerberos::Model::EncryptedData
            # @see Rex::Proto::Kerberos::Model::EncryptionKey
            def build_ap_req(opts = {})
              pvno = opts.fetch(:pvno) { Rex::Proto::Kerberos::Model::VERSION }
              msg_type = opts.fetch(:msg_type) { Rex::Proto::Kerberos::Model::AP_REQ }
              options = opts.fetch(:ap_req_options) { 0 }
              ticket = opts[:ticket]
              authenticator = opts.fetch(:authenticator) do
                build_authenticator(opts.merge(
                  authenticator_enc_key_usage: Rex::Proto::Kerberos::Crypto::KeyUsage::AP_REQ_AUTHENTICATOR
                ))
              end
              session_key = opts.fetch(:session_key) { build_subkey(opts) }

              if ticket.nil?
                raise ::Rex::Proto::Kerberos::Model::Error::KerberosError, 'Building a AP-REQ without ticket not supported'
              end

              enc_authenticator = Rex::Proto::Kerberos::Model::EncryptedData.new(
                etype: session_key.type,
                cipher: authenticator.encrypt(session_key.type, session_key.value)
              )

              ap_req = Rex::Proto::Kerberos::Model::ApReq.new(
                pvno: pvno,
                msg_type: msg_type,
                options: options,
                ticket: ticket,
                authenticator: enc_authenticator
              )

              ap_req
            end

            # Builds a kerberos authenticator for a TGS request
            #
            # @param opts [Hash{Symbol => <Rex::Proto::Kerberos::Model::PrincipalName, String, Time, Rex::Proto::Kerberos::Model::EncryptionKey>}]
            # @option opts [Rex::Proto::Kerberos::Model::PrincipalName] :cname
            # @option opts [String] :realm
            # @option opts [Time] :ctime
            # @option opts [Integer] :cusec
            # @option opts [Rex::Proto::Kerberos::Model::Checksum] :checksum
            # @option opts [Rex::Proto::Kerberos::Model::EncryptionKey] :subkey
            # @return [Rex::Proto::Kerberos::Model::Authenticator]
            # @see Rex::Proto::Kerberos::Model::PrincipalName
            # @see Rex::Proto::Kerberos::Model::Checksum
            # @see Rex::Proto::Kerberos::Model::EncryptionKey
            # @see Rex::Proto::Kerberos::Model::Authenticator
            def build_authenticator(opts = {})
              cname = opts.fetch(:cname) { build_client_name(opts) }
              realm = opts.fetch(:realm) { '' }
              ctime = opts.fetch(:ctime) { Time.now.utc }
              cusec = opts.fetch(:cusec) { ctime&.usec || 0 }
              sequence_number = opts.fetch(:sequence_number) { rand(1 << 32) }
              checksum = opts.fetch(:checksum) { build_tgs_body_checksum(opts) }
              subkey = opts.fetch(:subkey) { build_subkey(opts) }
              authenticator_enc_key_usage = opts.fetch(:authenticator_enc_key_usage) do
                Rex::Proto::Kerberos::Crypto::KeyUsage::TGS_REQ_PA_TGS_REQ_AP_REQ_AUTHENTICATOR
              end

              authenticator = Rex::Proto::Kerberos::Model::Authenticator.new(
                vno: 5,
                crealm: realm,
                cname: cname,
                checksum: checksum,
                cusec: cusec,
                ctime: ctime,
                subkey: subkey,
                enc_key_usage: authenticator_enc_key_usage,
                sequence_number: sequence_number
              )

              authenticator
            end

            # Builds an encryption key to protect the data sent in the TGS request.
            #
            # @param opts [Hash{Symbol => <Integer, String>}]
            # @option opts [Integer] :subkey_type
            # @option opts [String] :subkey_value
            # @return [Rex::Proto::Kerberos::Model::EncryptionKey]
            # @see Rex::Proto::Kerberos::Model::EncryptionKey
            def build_subkey(opts={})
              subkey_type = opts.fetch(:subkey_type) { Rex::Proto::Kerberos::Crypto::Encryption::DefaultEncryptionType }
              subkey_value = opts.fetch(:subkey_value) { Rex::Proto::Kerberos::Crypto::Encryption::from_etype(subkey_type).string_to_key(Rex::Text.rand_text_alphanumeric(16), '') }

              subkey = Rex::Proto::Kerberos::Model::EncryptionKey.new(
                type: subkey_type,
                value: subkey_value
              )

              subkey
            end

            # Builds a kerberos TGS request body
            #
            # @param opts [Hash{Symbol => <Integer, Time, String, Rex::Proto::Kerberos::Model::PrincipalName, Rex::Proto::Kerberos::Model::EncryptedData>}]
            # @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
            # @option opts [Rex::Proto::Kerberos::Model::EncryptedData] :enc_auth_data
            # @option opts [Array<Rex::Proto::Kerberos::Model::EncryptedData>] :additional_tickets
            # @return [Rex::Proto::Kerberos::Model::KdcRequestBody]
            # @see Rex::Proto::Kerberos::Model::PrincipalName
            # @see Rex::Proto::Kerberos::Model::KdcRequestBody
            def build_tgs_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) { Rex::Text.rand_text_numeric(6).to_i }
              etype = opts.fetch(:etype) { [Rex::Proto::Kerberos::Crypto::Encryption::DefaultEncryptionType] }
              cname = opts.fetch(:cname) { build_client_name(opts) }
              realm = opts.fetch(:realm) { '' }
              sname = opts.fetch(:sname) { build_server_name(opts) }
              enc_auth_data = opts[:enc_auth_data] || nil
              additional_tickets = opts[:additional_tickets] || nil

              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,
                enc_auth_data: enc_auth_data,
                additional_tickets: additional_tickets
              )

              body
            end

            # Builds a Kerberos TGS Request body checksum
            #
            # @param opts [Hash{Symbol => <Rex::Proto::Kerberos::Model::KdcRequestBody, Integer>}]
            # @option opts [Rex::Proto::Kerberos::Model::KdcRequestBody] :body
            # @return [Rex::Proto::Kerberos::Model::Checksum]
            # @see #build_tgs_request_body
            # @see Rex::Proto::Kerberos::Model::Checksum
            def build_tgs_body_checksum(opts = {})
              body = opts.fetch(:body) { build_tgs_request_body(opts) }
              checksum_type = Rex::Proto::Kerberos::Crypto::Checksum::RSA_MD5
              key = nil
              cksum_key_usage = opts.fetch(:cksum_key_usage)

              checksum_body = body.checksum(checksum_type, key, cksum_key_usage)
              checksum = Rex::Proto::Kerberos::Model::Checksum.new(
                type: checksum_type,
                checksum: checksum_body
              )

              checksum
            end

            # Builds a Kerberos PA-FOR-USER pa-data
            #
            # @param opts [Hash]
            # @option opts [String] :username The name of the user on whose
            #   behalf the service requests the service ticket
            # @option opts [String] :realm The realm in which the user account is located
            # @option opts [Rex::Proto::Kerberos::Model::EncryptionKey] :session_key The session key of the TGT
            # @return [Rex::Proto::Kerberos::Model::PreAuthDataEntry]
            # @see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/aceb70de-40f0-4409-87fa-df00ca145f5a
            def build_pa_for_user(opts = {})
              auth_package = 'Kerberos'.b

              checksum_data = [Rex::Proto::Kerberos::Model::NameType::NT_PRINCIPAL].pack('I<')
              checksum_data << opts[:username].b
              checksum_data << opts[:realm].b
              checksum_data << auth_package

              checksummer = Rex::Proto::Kerberos::Crypto::Checksum.from_checksum_type(
                Rex::Proto::Kerberos::Crypto::Checksum::HMAC_MD5
              )
              checksum = Rex::Proto::Kerberos::Model::Checksum.new
              checksum.type = Rex::Proto::Kerberos::Crypto::Checksum::HMAC_MD5
              checksum.checksum = checksummer.checksum(
                opts[:session_key].value,
                Rex::Proto::Kerberos::Crypto::KeyUsage::KERB_NON_KERB_CKSUM_SALT,
                checksum_data
              )

              pa_for_user = Rex::Proto::Kerberos::Model::PreAuthForUser.new
              pa_for_user.user_name = Rex::Proto::Kerberos::Model::PrincipalName.new(
                name_type: Rex::Proto::Kerberos::Model::NameType::NT_PRINCIPAL,
                name_string: [ opts[:username] ]
              )
              pa_for_user.user_realm = opts[:realm]
              pa_for_user.cksum = checksum
              pa_for_user.auth_package = auth_package

              Rex::Proto::Kerberos::Model::PreAuthDataEntry.new(
                type: Rex::Proto::Kerberos::Model::PreAuthType::PA_FOR_USER,
                value: pa_for_user.encode
              )
            end

          end
        end
      end
    end
  end
end
