diff --git a/PKG-INFO b/PKG-INFO
index 49098c0..15774d1 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: minikerberos
-Version: 0.2.0
+Version: 0.2.14
 Summary: Kerberos manipulation library in pure Python
 Home-page: https://github.com/skelsec/minikerberos
 Author: Tamas Jos
diff --git a/debian/changelog b/debian/changelog
index 6d57f72..122f1da 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-minikerberos (0.2.14-0kali1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Kali Janitor <janitor@kali.org>  Tue, 24 Aug 2021 02:54:36 -0000
+
 python-minikerberos (0.2.0-0kali1) kali-dev; urgency=medium
 
   * Initial release
diff --git a/minikerberos.egg-info/PKG-INFO b/minikerberos.egg-info/PKG-INFO
index 49098c0..15774d1 100644
--- a/minikerberos.egg-info/PKG-INFO
+++ b/minikerberos.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: minikerberos
-Version: 0.2.0
+Version: 0.2.14
 Summary: Kerberos manipulation library in pure Python
 Home-page: https://github.com/skelsec/minikerberos
 Author: Tamas Jos
diff --git a/minikerberos.egg-info/SOURCES.txt b/minikerberos.egg-info/SOURCES.txt
index 94d8875..219bfa0 100644
--- a/minikerberos.egg-info/SOURCES.txt
+++ b/minikerberos.egg-info/SOURCES.txt
@@ -6,6 +6,7 @@ minikerberos/__init__.py
 minikerberos/_version.py
 minikerberos/aioclient.py
 minikerberos/client.py
+minikerberos/pkinit.py
 minikerberos/security.py
 minikerberos.egg-info/PKG-INFO
 minikerberos.egg-info/SOURCES.txt
@@ -24,7 +25,11 @@ minikerberos/common/spn.py
 minikerberos/common/target.py
 minikerberos/common/url.py
 minikerberos/common/utils.py
+minikerberos/common/windows/__init__.py
+minikerberos/common/windows/crypt32.py
+minikerberos/common/windows/defines.py
 minikerberos/crypto/BASE.py
+minikerberos/crypto/MD4.py
 minikerberos/crypto/RC4.py
 minikerberos/crypto/__init__.py
 minikerberos/crypto/hashing.py
@@ -48,10 +53,12 @@ minikerberos/examples/getTGS.py
 minikerberos/examples/getTGT.py
 minikerberos/examples/kirbi2ccache.py
 minikerberos/gssapi/__init__.py
+minikerberos/gssapi/channelbindings.py
 minikerberos/gssapi/gssapi.py
 minikerberos/network/__init__.py
 minikerberos/network/aioclientsocket.py
 minikerberos/network/aioclientsockssocket.py
+minikerberos/network/aioclientwsnetsocket.py
 minikerberos/network/clientsocket.py
 minikerberos/network/selector.py
 minikerberos/protocol/__init__.py
@@ -59,4 +66,7 @@ minikerberos/protocol/asn1_structs.py
 minikerberos/protocol/constants.py
 minikerberos/protocol/encryption.py
 minikerberos/protocol/errors.py
+minikerberos/protocol/mskile.py
+minikerberos/protocol/rfc4556.py
+minikerberos/protocol/rfc_iakerb.py
 minikerberos/protocol/structures.py
\ No newline at end of file
diff --git a/minikerberos.egg-info/requires.txt b/minikerberos.egg-info/requires.txt
index ceb1eaf..b404e52 100644
--- a/minikerberos.egg-info/requires.txt
+++ b/minikerberos.egg-info/requires.txt
@@ -1,2 +1,3 @@
 asn1crypto>=1.3.0
-asysocks>=0.0.2
+asysocks>=0.0.11
+oscrypto>=1.2.1
diff --git a/minikerberos/_version.py b/minikerberos/_version.py
index 92d8523..c3075ff 100644
--- a/minikerberos/_version.py
+++ b/minikerberos/_version.py
@@ -1,5 +1,5 @@
 
-__version__ = "0.2.0"
+__version__ = "0.2.14"
 __banner__ = \
 """
 # minikerberos %s 
diff --git a/minikerberos/aioclient.py b/minikerberos/aioclient.py
index 94fac4b..980b4b3 100644
--- a/minikerberos/aioclient.py
+++ b/minikerberos/aioclient.py
@@ -15,7 +15,7 @@ from minikerberos.common.ccache import CCACHE
 from minikerberos.network.aioclientsocket import AIOKerberosClientSocket
 from minikerberos.protocol.asn1_structs import METHOD_DATA, ETYPE_INFO, ETYPE_INFO2, \
 	PADATA_TYPE, PA_PAC_REQUEST, PA_ENC_TS_ENC, EncryptedData, krb5_pvno, KDC_REQ_BODY, \
-	AS_REQ, KDCOptions, PrincipalName, EncASRepPart, EncTGSRepPart, PrincipalName, Realm, \
+	AS_REQ, TGS_REP, KDCOptions, PrincipalName, EncASRepPart, EncTGSRepPart, PrincipalName, Realm, \
 	Checksum, APOptions, Authenticator, Ticket, AP_REQ, TGS_REQ, CKSUMTYPE, \
 	PA_FOR_USER_ENC, PA_PAC_OPTIONS, PA_PAC_OPTIONSTypes
 
@@ -26,13 +26,12 @@ from minikerberos.protocol.structures import AuthenticatorChecksum
 from minikerberos.gssapi.gssapi import GSSAPIFlags
 from minikerberos.network.selector import KerberosClientSocketSelector
 
-
 class AIOKerberosClient:
-	def __init__(self, ccred, target, ccache = None):
+	def __init__(self, ccred, target):
 		self.usercreds = ccred
 		self.target = target
 		self.ksoc = KerberosClientSocketSelector.select(self.target, True)
-		self.ccache = CCACHE() if ccache is None else ccache
+		self.ccache = CCACHE() if self.usercreds.ccache is None else self.usercreds.ccache
 		self.kerberos_session_key = None
 		self.kerberos_TGT = None
 		self.kerberos_TGT_encpart = None
@@ -119,9 +118,43 @@ class AIOKerberosClient:
 		logger.debug('Sending TGT request to server')
 		rep = await self.ksoc.sendrecv(req.dump())
 		if rep.name == 'KRB_ERROR':
-			raise Exception('Preauth failed! %s' % str(rep))
+			raise KerberosError(rep, 'Preauth failed!')
 		return rep
 
+	def tgt_from_ccache(self, override_etype = None):
+		try:
+			if self.ccache is None:
+				raise Exception('No CCACHE file found')
+			
+			for tgt, keystruct in self.ccache.get_all_tgt():
+				if self.usercreds.ccache_spn_strict_check is True:
+					our_user = str(self.usercreds.username) + '@' + self.usercreds.domain
+					ticket_for = tgt['cname']['name-string'][0] + '@' + tgt['crealm']
+					if ticket_for.upper() == our_user.upper():
+						logger.debug('Found TGT for user %s' % our_user)
+						self.kerberos_TGT = tgt
+						self.kerberos_TGT_encpart = tgt['enc-part']
+						self.kerberos_session_key = Key(keystruct['keytype'], keystruct['keyvalue'])
+						self.kerberos_cipher = _enctype_table[keystruct['keytype']]
+						self.kerberos_cipher_type = keystruct['keytype']
+										
+						return True, None
+				
+				else:
+					self.kerberos_TGT = tgt
+					self.kerberos_TGT_encpart = tgt['enc-part']
+					self.kerberos_session_key = Key(keystruct['keytype'], keystruct['keyvalue'])
+					self.kerberos_cipher = _enctype_table[keystruct['keytype']]
+					self.kerberos_cipher_type = keystruct['keytype']
+					return True, None
+
+			logger.debug('No TGT found for user %s' % our_user)
+			raise Exception('No TGT found for user %s' % our_user) 
+
+		except Exception as e:
+			return None, e
+
+
 	async def get_TGT(self, override_etype = None, decrypt_tgt = True):
 		"""
 		decrypt_tgt: used for asreproast attacks
@@ -130,6 +163,12 @@ class AIOKerberosClient:
 			2. Depending on the response (either error or AS_REP with TGT) we either send another AS_REQ with the encrypted data or return the TGT (or fail miserably)
 			3. PROFIT
 		"""
+
+		#first, let's check if CCACHE has the correct ticket already
+		_, err = self.tgt_from_ccache(override_etype)
+		if err is None:
+			return
+
 		logger.debug('Generating initial TGT without authentication data')
 		now = datetime.datetime.now(datetime.timezone.utc)
 		kdc_req_body = {}
@@ -202,7 +241,33 @@ class AIOKerberosClient:
 		logger.debug('Got valid TGT')
 		
 		return 
-		
+
+	def tgs_from_ccache(self, spn_user, override_etype):
+		try:
+			if self.ccache is None:
+				raise Exception('No CCACHE file found')
+			
+			for tgs, keystruct in self.ccache.get_all_tgs():
+				ticket_for = ('/'.join(tgs['ticket']['sname']['name-string'])) + '@' + tgs['ticket']['realm']
+				if self.usercreds.ccache_spn_strict_check is True:
+					if ticket_for.upper() == str(spn_user).upper():
+						logger.debug('Found TGS for user %s' % ticket_for)
+						key = Key(keystruct['keytype'], keystruct['keyvalue'])
+						tgs = TGS_REP(tgs).native
+						return tgs, tgs['enc-part'], key, None
+				else:
+					# I hope you know what you are doing at this point...
+					key = Key(keystruct['keytype'], keystruct['keyvalue'])
+					tgs = TGS_REP(tgs).native
+					return tgs, tgs['enc-part'], key, None
+
+			logger.debug('No TGS found for user %s' % ticket_for)
+			raise Exception('No TGS found for user %s' % ticket_for) 
+
+			
+		except Exception as e:
+			return None, None, None, e
+
 	async def get_TGS(self, spn_user, override_etype = None, is_linux = False):
 		"""
 		Requests a TGS ticket for the specified user.
@@ -211,7 +276,20 @@ class AIOKerberosClient:
 		spn_user: KerberosTarget: the service user you want to get TGS for.
 		override_etype: None or list of etype values (int) Used mostly for kerberoasting, will override the AP_REQ supported etype values (which is derived from the TGT) to be able to recieve whatever tgs tiecket 
 		"""
-		#construct tgs_req
+		
+		#first, let's check if CCACHE has the correct ticket already
+		tgs, encTGSRepPart, key, err = self.tgs_from_ccache(spn_user, override_etype)
+		if err is None:
+			return tgs, encTGSRepPart, key
+
+		
+		if self.kerberos_TGT is None:
+			#let's check if CCACHE has a TGT for us
+			_, err = self.tgt_from_ccache(override_etype=override_etype)
+			if err is not None:
+				raise Exception('No TGT found in CCACHE!')
+
+		#nope, we need to contact the server
 		logger.debug('Constructing TGS request for user %s' % spn_user.get_formatted_pname())
 		now = datetime.datetime.now(datetime.timezone.utc)
 		kdc_req_body = {}
@@ -240,8 +318,8 @@ class AIOKerberosClient:
 			chksum = {}
 			chksum['cksumtype'] = 0x8003
 			chksum['checksum'] = ac.to_bytes()
-			print(chksum['checksum'])
-			
+
+
 			authenticator_data['cksum'] = Checksum(chksum)
 			authenticator_data['seq-number'] = 0
 		
@@ -269,7 +347,7 @@ class AIOKerberosClient:
 		logger.debug('Constructing TGS request to server')
 		rep = await self.ksoc.sendrecv(req.dump())
 		if rep.name == 'KRB_ERROR':
-			raise Exception('get_TGS failed! %s' % str(rep))
+			raise KerberosError(rep, 'get_TGS failed!')
 		logger.debug('Got TGS reply, decrypting...')
 		tgs = rep.native
 		
@@ -289,7 +367,7 @@ class AIOKerberosClient:
 		"""
 		
 		if not self.kerberos_TGT:
-			logger.debug('S4U2self invoked, but TGT is not available! Fetching TGT...')
+			logger.debug('[S4U2self] TGT is not available! Fetching TGT...')
 			self.get_TGT()
 		
 		supp_enc = self.usercreds.get_preferred_enctype(supp_enc_methods)
@@ -324,11 +402,11 @@ class AIOKerberosClient:
 		S4UByteArray += user_to_impersonate.username.encode()
 		S4UByteArray += user_to_impersonate.domain.encode()
 		S4UByteArray += auth_package_name.encode()
-		logger.debug('S4U2self: S4UByteArray: %s' % S4UByteArray.hex())
-		logger.debug('S4U2self: S4UByteArray: %s' % S4UByteArray)
+		logger.debug('[S4U2self] S4UByteArray: %s' % S4UByteArray.hex())
+		logger.debug('[S4U2self] S4UByteArray: %s' % S4UByteArray)
 		
 		chksum_data = _HMACMD5.checksum(self.kerberos_session_key, 17, S4UByteArray)
-		logger.debug('S4U2self: chksum_data: %s' % chksum_data.hex())
+		logger.debug('[S4U2self] chksum_data: %s' % chksum_data.hex())
 		
 		
 		chksum = {}
@@ -366,29 +444,29 @@ class AIOKerberosClient:
 		
 		req = TGS_REQ(krb_tgs_req)
 		
-		logger.debug('Sending S4U2self request to server')
+		logger.debug('[S4U2self] Sending request to server')
 		
 		reply = await self.ksoc.sendrecv(req.dump())
 		if reply.name == 'KRB_ERROR':
+			emsg = 'S4U2self failed!'
 			if reply.native['error-code'] == 16:
-				logger.error('S4U2self: Failed to get S4U2self! Error code (16) indicates that delegation is not enabled for this account!')
-			
-			raise Exception('S4U2self failed! %s' % str(reply))
+				emsg = 'S4U2self: Failed to get S4U2self! Error code (16) indicates that delegation is not enabled for this account!'			
+			raise KerberosError(reply, emsg)
 		
-		logger.debug('Got S4U2self reply, decrypting...')
+		logger.debug('[S4U2self] Got reply, decrypting...')
 		tgs = reply.native
 		
 		encTGSRepPart = EncTGSRepPart.load(self.kerberos_cipher.decrypt(self.kerberos_session_key, 8, tgs['enc-part']['cipher'])).native
 		key = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'])
 		
 		self.ccache.add_tgs(tgs, encTGSRepPart)
-		logger.debug('Got valid TGS reply')
+		logger.debug('[S4U2self] Got valid TGS reply')
 		self.kerberos_TGS = tgs
 		return tgs, encTGSRepPart, key
-				
 		
 	# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/c920c148-8a9c-42e9-b8e9-db5755cd281b
 	async def S4U2proxy(self, s4uself_ticket, spn_user, supp_enc_methods = [EncryptionType.DES_CBC_CRC,EncryptionType.DES_CBC_MD4,EncryptionType.DES_CBC_MD5,EncryptionType.DES3_CBC_SHA1,EncryptionType.ARCFOUR_HMAC_MD5,EncryptionType.AES256_CTS_HMAC_SHA1_96,EncryptionType.AES128_CTS_HMAC_SHA1_96]):
+		logger.debug('[S4U2proxy] Impersonating %s' % '/'.join(spn_user.get_principalname()))
 		now = datetime.datetime.now(datetime.timezone.utc)
 		supp_enc = self.usercreds.get_preferred_enctype(supp_enc_methods)
 		
@@ -439,13 +517,24 @@ class AIOKerberosClient:
 		
 		reply = await self.ksoc.sendrecv(req.dump())
 		if reply.name == 'KRB_ERROR':
+			emsg = 'S4U2proxy failed!'
 			if reply.native['error-code'] == 16:
-				logger.error('S4U2proxy: Failed to get S4U2proxy! Error code (16) indicates that delegation is not enabled for this account!')
-			
-			raise Exception('S4U2proxy failed! %s' % str(reply))
+				emsg = 'S4U2proxy: Failed to get S4U2proxy! Error code (16) indicates that delegation is not enabled for this account!'
 			
+			raise KerberosError(reply, emsg)
+		
+		logger.debug('[S4U2proxy] Got server reply, decrypting...')
+		tgs = reply.native
 		
-	def construct_apreq(self, tgs, encTGSRepPart, sessionkey, flags = None, seq_number = 0, ap_opts = []):
+		encTGSRepPart = EncTGSRepPart.load(self.kerberos_cipher.decrypt(self.kerberos_session_key, 8, tgs['enc-part']['cipher'])).native
+		key = Key(encTGSRepPart['key']['keytype'], encTGSRepPart['key']['keyvalue'])
+		
+		self.ccache.add_tgs(tgs, encTGSRepPart)
+		logger.debug('[S4U2proxy] Got valid TGS reply')
+
+		return tgs, encTGSRepPart, key
+		
+	def construct_apreq(self, tgs, encTGSRepPart, sessionkey, flags = None, seq_number = 0, ap_opts = [], cb_data = None):
 		now = datetime.datetime.now(datetime.timezone.utc)
 		authenticator_data = {}
 		authenticator_data['authenticator-vno'] = krb5_pvno
@@ -456,7 +545,10 @@ class AIOKerberosClient:
 		if flags is not None:
 			ac = AuthenticatorChecksum()
 			ac.flags = flags
-			ac.channel_binding = b'\x00'*16
+
+			ac.channel_binding = cb_data
+			if cb_data is None:
+				ac.channel_binding = b'\x00'*16
 			
 			chksum = {}
 			chksum['cksumtype'] = 0x8003
@@ -478,7 +570,7 @@ class AIOKerberosClient:
 		return AP_REQ(ap_req).dump()
 		
 	@staticmethod
-	def construct_apreq_from_ticket(ticket_data, sessionkey, crealm, cname, flags = None, seq_number = 0, ap_opts = []):
+	def construct_apreq_from_ticket(ticket_data, sessionkey, crealm, cname, flags = None, seq_number = 0, ap_opts = [], cb_data = None):
 		"""
 		ticket: bytes of Ticket
 		"""
@@ -492,7 +584,9 @@ class AIOKerberosClient:
 		if flags is not None:
 			ac = AuthenticatorChecksum()
 			ac.flags = flags
-			ac.channel_binding = b'\x00'*16
+			ac.channel_binding = cb_data
+			if cb_data is None:
+				ac.channel_binding = b'\x00'*16
 			
 			chksum = {}
 			chksum['cksumtype'] = 0x8003
@@ -514,5 +608,5 @@ class AIOKerberosClient:
 		return AP_REQ(ap_req).dump()
 		
 	async def getST(self, target_user, service_spn):
-		tgs, encTGSRepPart, key = await self.S4U2self(target_user)
-		return self.S4U2proxy(tgs['ticket'], service_spn)
+		tgs, encTGSRepPart, key  = await self.S4U2self(target_user)
+		return await self.S4U2proxy(tgs['ticket'], service_spn)
diff --git a/minikerberos/client.py b/minikerberos/client.py
index d48adeb..303f7b5 100644
--- a/minikerberos/client.py
+++ b/minikerberos/client.py
@@ -234,7 +234,6 @@ class KerbrosClient:
 			chksum = {}
 			chksum['cksumtype'] = 0x8003
 			chksum['checksum'] = ac.to_bytes()
-			print(chksum['checksum'])
 			
 			authenticator_data['cksum'] = Checksum(chksum)
 			authenticator_data['seq-number'] = 0
diff --git a/minikerberos/common/ccache.py b/minikerberos/common/ccache.py
index 8c71c65..7931ad8 100644
--- a/minikerberos/common/ccache.py
+++ b/minikerberos/common/ccache.py
@@ -98,7 +98,7 @@ class Credential:
 		tgs_realm              = res['realm']
 		if tgs_encryption_type == EncryptionType.AES256_CTS_HMAC_SHA1_96.value:
 			tgs_checksum           = res['enc-part']['cipher'][-12:]
-			tgs_encrypted_data2    = res['enc-part']['cipher'][:-12:]
+			tgs_encrypted_data2    = res['enc-part']['cipher'][:-12]
 			return '$krb5tgs$%s$%s$%s$%s$%s' % (tgs_encryption_type,tgs_name_string,tgs_realm, tgs_checksum.hex(), tgs_encrypted_data2.hex() )
 		else:
 			tgs_checksum           = res['enc-part']['cipher'][:16]
@@ -122,6 +122,24 @@ class Credential:
 		t = EncryptionKey(self.key.to_asn1()).native
 		
 		return tgt_rep, t
+
+	def to_tgs(self):
+		"""
+		Returns the native format of an AS_REP message and the sessionkey in EncryptionKey native format
+		"""
+		enc_part = EncryptedData({'etype': 1, 'cipher': b''})
+		
+		tgt_rep = {}
+		tgt_rep['pvno'] = krb5_pvno
+		tgt_rep['msg-type'] = MESSAGE_TYPE.KRB_AS_REP.value
+		tgt_rep['crealm'] = self.server.realm.to_string()
+		tgt_rep['cname'] = self.client.to_asn1()[0]
+		tgt_rep['ticket'] = Ticket.load(self.ticket.to_asn1()).native
+		tgt_rep['enc-part'] = enc_part.native
+
+		t = EncryptionKey(self.key.to_asn1()).native
+		
+		return tgt_rep, t
 		
 	def to_kirbi(self):
 		filename = '%s@%s_%s' % (self.client.to_string() , self.server.to_string(), hashlib.sha1(self.ticket.to_asn1()).hexdigest()[:8])
@@ -131,11 +149,13 @@ class Credential:
 		krbcredinfo['pname'] = self.client.to_asn1()[0]
 		krbcredinfo['flags'] = core.IntegerBitString(self.tktflags).cast(TicketFlags)
 		if self.time.authtime != 0: #this parameter is not mandatory, and most of the time not present
-			krbcredinfo['authtime'] = datetime.datetime.fromtimestamp(self.time.authtime)
-		krbcredinfo['starttime'] = datetime.datetime.fromtimestamp(self.time.starttime)
-		krbcredinfo['endtime'] = datetime.datetime.fromtimestamp(self.time.endtime)
+			krbcredinfo['authtime'] = datetime.datetime.fromtimestamp(self.time.authtime, datetime.timezone.utc)
+		if self.time.starttime != 0:
+			krbcredinfo['starttime'] = datetime.datetime.fromtimestamp(self.time.starttime, datetime.timezone.utc)
+		if self.time.endtime != 0:
+			krbcredinfo['endtime'] = datetime.datetime.fromtimestamp(self.time.endtime, datetime.timezone.utc)
 		if self.time.renew_till != 0: #this parameter is not mandatory, and sometimes it's not present
-			krbcredinfo['renew-till'] = datetime.datetime.fromtimestamp(self.time.authtime)
+			krbcredinfo['renew-till'] = datetime.datetime.fromtimestamp(self.time.authtime, datetime.timezone.utc)
 		krbcredinfo['srealm'] = self.server.realm.to_string()
 		krbcredinfo['sname'] = self.server.to_asn1()[0]
 		
@@ -184,7 +204,7 @@ class Credential:
 		for _ in range(c.num_address):
 			c.addrs.append(Address.parse(reader))
 		c.num_authdata = int.from_bytes(reader.read(4), byteorder='big', signed=False)
-		for i in range(c.num_authdata):
+		for _ in range(c.num_authdata):
 			c.authdata.append(Authdata.parse(reader))
 		c.ticket = CCACHEOctetString.parse(reader)
 		c.second_ticket = CCACHEOctetString.parse(reader)
@@ -196,8 +216,8 @@ class Credential:
 		
 	def summary(self):
 		return [ 
-			'%s@%s' % 	(self.client.to_string(),self.client.realm.to_string()), 
-			'%s@%s' % 	(self.server.to_string(), self.server.realm.to_string()),
+			'%s@%s' % 	(self.client.to_string(separator='/'),self.client.realm.to_string()), 
+			'%s@%s' % 	(self.server.to_string(separator='/'), self.server.realm.to_string()),
 			datetime.datetime.fromtimestamp(self.time.starttime).isoformat() if self.time.starttime != 0 else 'N/A',
 			datetime.datetime.fromtimestamp(self.time.endtime).isoformat() if self.time.endtime != 0 else 'N/A',
 			datetime.datetime.fromtimestamp(self.time.renew_till).isoformat() if self.time.renew_till != 0 else 'N/A',
@@ -272,17 +292,14 @@ class Times:
 	@staticmethod
 	def from_asn1(enc_as_rep_part):
 		t = Times()
-		if 'authtime' in enc_as_rep_part and enc_as_rep_part['authtime']:
-			t.authtime = dt_to_kerbtime(enc_as_rep_part['authtime'])
-		else:
-			t.authtime = 0
-		if 'starttime' in enc_as_rep_part and enc_as_rep_part['starttime']:
-			t.starttime = dt_to_kerbtime(enc_as_rep_part['starttime'])
-		else:
-			t.starttime = 0
-		t.endtime = dt_to_kerbtime(enc_as_rep_part['endtime'])
-		t.renew_till = dt_to_kerbtime(enc_as_rep_part['renew-till'])
-		
+		t.authtime = dt_to_kerbtime(enc_as_rep_part['authtime']) \
+			if 'authtime' in enc_as_rep_part and enc_as_rep_part['authtime'] else 0
+		t.starttime = dt_to_kerbtime(enc_as_rep_part['starttime']) \
+			if 'starttime' in enc_as_rep_part and enc_as_rep_part['starttime'] else 0
+		t.endtime = dt_to_kerbtime(enc_as_rep_part['endtime']) \
+			if 'endtime' in enc_as_rep_part and enc_as_rep_part['endtime'] else 0
+		t.renew_till = dt_to_kerbtime(enc_as_rep_part['renew_till']) \
+			if 'renew_till' in enc_as_rep_part and enc_as_rep_part['renew_till'] else 0
 		return t
 	
 	@staticmethod
@@ -373,12 +390,15 @@ class CCACHEPrincipal:
 			
 		return p
 		
-	def to_string(self):
-		return '-'.join([c.to_string() for c in self.components])
+	def to_string(self, separator = '-'):
+		return separator.join([c.to_string() for c in self.components])
 		
 	def to_asn1(self):
 		t = {'name-type': self.name_type, 'name-string': [name.to_string() for name in self.components]}
-		return t, self.realm.to_string()		
+		return t, self.realm.to_string()
+
+	def to_spn(self):
+		return '/'.join([name.to_string() for name in self.components]) + '@' + self.realm.to_string()
 	
 	@staticmethod
 	def parse(reader):
@@ -595,11 +615,19 @@ class CCACHE:
 		"""
 		tgts = []
 		for cred in self.credentials:
-			if cred.server.to_string().lower().find('krbtgt') != -1:
+			if cred.server.to_string(separator = '/').lower().find('krbtgt') != -1:
 				tgts.append(cred.to_tgt())
 
 		return tgts
 
+	def get_all_tgs(self):
+		tgss = []
+		for cred in self.credentials:
+			if cred.server.to_string(separator = '/').lower().find('krbtgt') == -1:
+				tgss.append(cred.to_tgs())
+
+		return tgss
+
 	def get_hashes(self, all_hashes = False):
 		"""
 		Returns a list of hashes in hashcat-firendly format for tickets with encryption type 23 (which is RC4)
@@ -621,7 +649,6 @@ class CCACHE:
 		
 		hdr_size = int.from_bytes(reader.read(2), byteorder='big', signed=False)
 		c.headers = Header.parse(reader.read(hdr_size))
-		
 		#c.headerlen = 
 		#for i in range(c.headerlen):
 		#	c.headers.append(Header.parse(reader))
@@ -633,7 +660,10 @@ class CCACHE:
 		eof = reader.tell()
 		reader.seek(pos,0)
 		while reader.tell() < eof:
-			c.credentials.append(Credential.parse(reader))
+			cred = Credential.parse(reader)
+			if not (len(cred.server.components) > 0 and cred.server.components[0].to_string() == 'krb5_ccache_conf_data'
+			and cred.server.realm.to_string() == 'X-CACHECONF:'):
+				c.credentials.append(cred)
 		
 		return c
 		
@@ -690,6 +720,12 @@ class CCACHE:
 			filepath = os.path.join(kf_abs, filename)
 			with open(filepath, 'wb') as o:
 				o.write(kirbi.dump())
+
+	def list_targets(self):
+		for cred in self.credentials:
+			target = cred.server
+			yield target.to_spn()
+
 	
 	@staticmethod
 	def from_file(filename):
@@ -706,3 +742,6 @@ class CCACHE:
 		with open(filename, 'wb') as f:
 			f.write(self.to_bytes())
 		
+	@staticmethod
+	def from_bytes(data):
+		return CCACHE.parse(io.BytesIO(data))
\ No newline at end of file
diff --git a/minikerberos/common/constants.py b/minikerberos/common/constants.py
index 64ae24d..e217731 100644
--- a/minikerberos/common/constants.py
+++ b/minikerberos/common/constants.py
@@ -17,4 +17,5 @@ class KerberosSecretType(enum.Enum):
 	DES3 = 'DES3'
 	TDES = 'TDES'
 	CCACHE = 'CCACHE'
-	KEYTAB = 'KEYTAB'
\ No newline at end of file
+	KEYTAB = 'KEYTAB'
+	KIRBI = 'KIRBI'
\ No newline at end of file
diff --git a/minikerberos/common/creds.py b/minikerberos/common/creds.py
index 181bc53..e92bb6c 100644
--- a/minikerberos/common/creds.py
+++ b/minikerberos/common/creds.py
@@ -13,6 +13,7 @@ from minikerberos.protocol.encryption import string_to_key, Enctype
 from minikerberos.protocol.constants import EncryptionType
 from minikerberos.common.ccache import CCACHE
 from minikerberos.common.keytab import Keytab
+from minikerberos.crypto.hashing import md4
 
 
 class KerberosCredential:
@@ -28,6 +29,7 @@ class KerberosCredential:
 		self.kerberos_key_rc4 = None
 		self.kerberos_key_des3 = None
 		self.ccache = None
+		self.ccache_spn_strict_check = True
 
 	def get_preferred_enctype(self, server_enctypes):
 		client_enctypes = self.get_supported_enctypes(as_int=False)
@@ -69,7 +71,8 @@ class KerberosCredential:
 			if self.nt_hash:
 				return bytes.fromhex(self.nt_hash)
 			elif self.password:
-				self.nt_hash = hashlib.new('md4', self.password.encode('utf-16-le')).hexdigest().upper()
+				self.nt_hash = md4(self.password.encode('utf-16-le')).hexdigest().upper()
+				#self.nt_hash = hashlib.new('md4', self.password.encode('utf-16-le')).hexdigest().upper()
 				return bytes.fromhex(self.nt_hash)
 			else:
 				raise Exception('There is no key for RC4 encryption')
@@ -121,6 +124,19 @@ class KerberosCredential:
 		if as_int == True:
 			return [etype.value for etype in supp_enctypes]
 		return [etype for etype in supp_enctypes]
+	
+	@staticmethod
+	def from_krbcred(keytab_file_path: str, principal: str = None, realm: str = None):
+		return KerberosCredential.from_kirbi(keytab_file_path, principal, realm)
+
+	@staticmethod
+	def from_kirbi(keytab_file_path: str, principal: str = None, realm: str = None):
+		cred = KerberosCredential()
+		cred.username = principal
+		cred.domain = realm
+		cred.ccache = CCACHE.from_kirbifile(keytab_file_path)
+		cred.ccache_spn_strict_check = False
+		return cred
 
 	@staticmethod
 	def from_keytab(keytab_file_path: str, principal: str, realm: str):
@@ -156,6 +172,14 @@ class KerberosCredential:
 								cred.add_secret(enctype, keytab_entry.key_contents.hex())
 		return cred
 
+	@staticmethod
+	def from_ccache_file(filepath, principal: str = None, realm: str = None):
+		k = KerberosCredential()
+		k.username = principal
+		k.domain = realm
+		k.ccache = CCACHE.from_file(filepath)
+		return k
+
 	def add_secret(self, st: KerberosSecretType, secret: str):
 		if st == KerberosSecretType.PASSWORD or st == KerberosSecretType.PW or st == KerberosSecretType.PASS:
 			if secret == '' or secret is None:
diff --git a/minikerberos/common/proxy.py b/minikerberos/common/proxy.py
index bb9c950..dcb703a 100644
--- a/minikerberos/common/proxy.py
+++ b/minikerberos/common/proxy.py
@@ -1,8 +1,9 @@
 
 class KerberosProxy:
-	def __init__(self, target = None,creds = None):
+	def __init__(self, target = None,creds = None, type = None):
 		self.target = target
 		self.creds = creds
+		self.type = type
 
 	def __str__(self):
 		t = '===KerberosTarget===\r\n'
diff --git a/minikerberos/common/url.py b/minikerberos/common/url.py
index c99495c..2325992 100644
--- a/minikerberos/common/url.py
+++ b/minikerberos/common/url.py
@@ -1,5 +1,5 @@
 
-
+import getpass
 from minikerberos.common.target import KerberosTarget
 from minikerberos.common.creds import KerberosCredential
 from minikerberos.common.proxy import KerberosProxy
@@ -70,6 +70,8 @@ class KerberosClientURL:
 	def get_creds(self):
 		if self.secret_type == KerberosSecretType.KEYTAB:
 			return KerberosCredential.from_keytab(self.secret, self.username, self.domain)
+		if self.secret_type == KerberosSecretType.KIRBI:
+			return KerberosCredential.from_kirbi(self.secret)
 
 		res = KerberosCredential()
 		res.username = self.username
@@ -120,16 +122,21 @@ class KerberosClientURL:
 
 		res.dc_ip = url.hostname
 		schemes = url.scheme.upper().split('+')
-		if schemes[0] not in ['KERBEROS', 'KERBEROS-TCP, KERBEROS-UDP']:
+		
+		if schemes[0] not in ['KERBEROS', 'KERBEROS-TCP, KERBEROS-UDP', 'KRB5', 'KRB5-UDP', 'KRB5-TCP']:
 			raise Exception('Unknown protocol! %s' % schemes[0])
 
 		if schemes[0].endswith('UDP') is True:
 			res.protocol = KerberosSocketType.UDP
 		
+		ttype = schemes[1]
+		if ttype.find('-') != -1 and ttype.upper().endswith('-PROMPT'):
+			ttype = ttype.split('-')[0]
+			res.secret = getpass.getpass()
 		try:
-			res.secret_type = KerberosSecretType(schemes[1])
+			res.secret_type = KerberosSecretType(ttype)
 		except:
-			raise Exception('Unknown secret type! %s' % schemes[0])
+			raise Exception('Unknown secret type! %s' % ttype)
 		
 		if url.username is not None:
 			if url.username.find('\\') != -1:
@@ -138,16 +145,18 @@ class KerberosClientURL:
 				raise Exception('Domain missing from username!')
 		else:
 			raise Exception('Missing username!')
-
-		res.secret = url.password
+		
+		if res.secret is None:
+			res.secret = url.password
 		if url.port is not None:
 			res.port = int(url.port)
 		
 		query = parse_qs(url.query)
-		proxy_present = False
+		proxy_type = None
 		for k in query:
-			if k.startswith('proxy') is True:
-				proxy_present = True
+			if k == 'proxytype':
+				proxy_type = query[k][0]
+
 			
 			if k in kerberosclienturl_param2var:
 				data = query[k][0]
@@ -160,12 +169,13 @@ class KerberosClientURL:
 							data
 						)
 		
-		if proxy_present is True:
+		if proxy_type is not None:
 			cu = SocksClientURL.from_params(url_str)
-			cu.endpoint_ip = res.dc_ip
-			cu.endpoint_port = res.port
+			cu[-1].endpoint_ip = res.dc_ip
+			cu[-1].endpoint_port = res.port
+
+			res.proxy = KerberosProxy(cu, None, type='SOCKS')
 
-			res.proxy = KerberosProxy(cu.get_target(), cu.get_creds())
 
 		
 		if res.username is None:
diff --git a/minikerberos/common/utils.py b/minikerberos/common/utils.py
index fe09b96..3b37003 100644
--- a/minikerberos/common/utils.py
+++ b/minikerberos/common/utils.py
@@ -1,5 +1,6 @@
 
 import datetime
+from minikerberos.protocol.asn1_structs import KRB_CRED, KrbCredInfo, EncKrbCredPart, EncryptedData
 
 def print_table(lines, separate_head=True):
 	"""Prints a formatted table given a 2 dimensional array"""
@@ -36,12 +37,25 @@ def dt_to_kerbtime(dt):
 
 def TGSTicket2hashcat(res):		
 	tgs_encryption_type    = int(res['ticket']['enc-part']['etype'])
-	tgs_name_string        = res['ticket']['sname']['name-string'][0]
+	tgs_name_string        = '@'.join(res['ticket']['sname']['name-string']) #[0]
 	tgs_realm              = res['ticket']['realm']
-	tgs_checksum           = res['ticket']['enc-part']['cipher'][:16]
-	tgs_encrypted_data2    = res['ticket']['enc-part']['cipher'][16:]
+
+	if tgs_encryption_type == 23:
+		tgs_checksum           = res['ticket']['enc-part']['cipher'][:16]
+		tgs_encrypted_data2    = res['ticket']['enc-part']['cipher'][16:]
+		return '$krb5tgs$%s$*%s$%s$spn*$%s$%s' % (tgs_encryption_type,tgs_name_string,tgs_realm, tgs_checksum.hex(), tgs_encrypted_data2.hex() )
+	
+	elif tgs_encryption_type in [17,18]:
+		# $krb5tgs$17$user$realm$ae8434177efd09be5bc2eff8$90b4ce5b266821adc26c64f71958a475cf9348fce65096190be04f8430c4e0d554c86dd7ad29c275f9e8f15d2dab4565a3d6e21e449dc2f88e52ea0402c7170ba74f4af037c5d7f8db6d53018a564ab590fc23aa1134788bcc4a55f69ec13c0a083291a96b41bffb978f5a160b7edc828382d11aacd89b5a1bfa710b0e591b190bff9062eace4d26187777db358e70efd26df9c9312dbeef20b1ee0d823d4e71b8f1d00d91ea017459c27c32dc20e451ea6278be63cdd512ce656357c942b95438228e
+		# $krb5tgs$18$user$realm$8efd91bb01cc69dd07e46009$7352410d6aafd72c64972a66058b02aa1c28ac580ba41137d5a170467f06f17faf5dfb3f95ecf4fad74821fdc7e63a3195573f45f962f86942cb24255e544ad8d05178d560f683a3f59ce94e82c8e724a3af0160be549b472dd83e6b80733ad349973885e9082617294c6cbbea92349671883eaf068d7f5dcfc0405d97fda27435082b82b24f3be27f06c19354bf32066933312c770424eb6143674756243c1bde78ee3294792dcc49008a1b54f32ec5d5695f899946d42a67ce2fb1c227cb1d2004c0 
+
+		tgs_checksum           = res['ticket']['enc-part']['cipher'][-12:]
+		tgs_encrypted_data2    = res['ticket']['enc-part']['cipher'][:-12]
+		return '$krb5tgs$%s$%s$%s$%s$%s' % (tgs_encryption_type,tgs_name_string,tgs_realm, tgs_checksum.hex(), tgs_encrypted_data2.hex() )
+	
+	else:
+		return 'unknown$enctype$%s' % tgs_encryption_type
 		
-	return '$krb5tgs$%s$*%s$%s$spn*$%s$%s' % (tgs_encryption_type,tgs_name_string,tgs_realm, tgs_checksum.hex(), tgs_encrypted_data2.hex() )
 	
 def TGTTicket2hashcat(res):
 	tgt_encryption_type    = int(res['enc-part']['etype'])
@@ -51,4 +65,32 @@ def TGTTicket2hashcat(res):
 	tgt_encrypted_data2    = res['enc-part']['cipher'][16:]
 	
 	return '$krb5asrep$%s$%s@%s:%s$%s' % (tgt_encryption_type,tgt_name_string, tgt_realm, tgt_checksum.hex(), tgt_encrypted_data2.hex())
-	
+
+def tgt_to_kirbi(tgs, encTGSRepPart):
+	#encTGSRepPart is already decrypted at this point
+	ci = {}
+	ci['key'] = encTGSRepPart['key']
+	ci['prealm'] = tgs['crealm']
+	ci['pname'] = tgs['cname']
+	ci['flags'] = encTGSRepPart['flags']
+	ci['authtime'] = encTGSRepPart['authtime']
+	ci['starttime'] = encTGSRepPart['starttime']
+	ci['endtime'] = encTGSRepPart['endtime']
+	ci['renew-till'] = encTGSRepPart['renew-till']
+	ci['srealm'] = encTGSRepPart['srealm']
+	ci['sname'] = encTGSRepPart['sname']
+
+	ti = {}
+	ti['ticket-info'] = [KrbCredInfo(ci)]
+
+	te = {}
+	te['etype']  = 0
+	te['cipher'] = EncKrbCredPart(ti).dump()
+
+	t = {}
+	t['pvno'] = 5
+	t['msg-type'] = 22
+	t['enc-part'] = EncryptedData(te)
+	t['tickets'] = [tgs['ticket']]
+
+	return KRB_CRED(t)
\ No newline at end of file
diff --git a/minikerberos/common/windows/__init__.py b/minikerberos/common/windows/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/minikerberos/common/windows/crypt32.py b/minikerberos/common/windows/crypt32.py
new file mode 100644
index 0000000..063722f
--- /dev/null
+++ b/minikerberos/common/windows/crypt32.py
@@ -0,0 +1,418 @@
+
+# Kudos:
+# Parts of this code was inspired by the following project by @rubin_mor
+# https://github.com/morRubin/AzureADJoinedMachinePTC
+# 
+
+
+from ctypes import GetLastError
+from minikerberos.common.windows.defines import *
+
+CMSG_DATA = 1
+CMSG_SIGNED =  2
+CMSG_ENVELOPED =  3
+CMSG_SIGNED_AND_ENVELOPED = 4
+CMSG_HASHED = 5
+
+CMSG_TYPE_PARAM = 1
+CMSG_CONTENT_PARAM = 2
+CMSG_BARE_CONTENT_PARAM = 3
+CMSG_INNER_CONTENT_TYPE_PARAM = 4
+CMSG_SIGNER_COUNT_PARAM = 5
+CMSG_SIGNER_INFO_PARAM = 6
+CMSG_SIGNER_CERT_INFO_PARAM = 7
+CMSG_SIGNER_HASH_ALGORITHM_PARAM = 8
+CMSG_SIGNER_AUTH_ATTR_PARAM = 9
+CMSG_SIGNER_UNAUTH_ATTR_PARAM = 10
+CMSG_CERT_COUNT_PARAM = 11
+CMSG_CERT_PARAM = 12
+CMSG_CRL_COUNT_PARAM = 13
+CMSG_CRL_PARAM = 14
+CMSG_ENVELOPE_ALGORITHM_PARAM = 15
+CMSG_RECIPIENT_COUNT_PARAM = 17
+CMSG_RECIPIENT_INDEX_PARAM = 18
+CMSG_RECIPIENT_INFO_PARAM = 19
+CMSG_HASH_ALGORITHM_PARAM = 20
+CMSG_HASH_DATA_PARAM = 21
+CMSG_COMPUTED_HASH_PARAM = 22
+CMSG_ENCRYPT_PARAM = 26
+
+
+X509_ASN_ENCODING = 0x00000001
+X509_NDR_ENCODING = 0x00000002
+PKCS_7_ASN_ENCODING = 0x00010000
+PKCS_7_NDR_ENCODING = 0x00020000
+
+CMSG_CMS_ENCAPSULATED_CONTENT_FLAG = 0x00000040
+
+CRYPT_ACQUIRE_CACHE_FLAG = 0x00000001
+CRYPT_ACQUIRE_USE_PROV_INFO_FLAG = 0x00000002
+CRYPT_ACQUIRE_COMPARE_KEY_FLAG = 0x00000004
+CRYPT_ACQUIRE_NO_HEALING = 0x00000008
+CRYPT_ACQUIRE_SILENT_FLAG = 0x00000040
+CRYPT_ACQUIRE_NCRYPT_KEY_FLAGS_MASK = 0x00070000
+CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG = 0x00010000
+CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG = 0x00020000
+CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG = 0x00040000
+
+HCRYPTMSG = HANDLE
+HCERTSTORE = HANDLE
+HCRYPTPROV_OR_NCRYPT_KEY_HANDLE = HANDLE
+PHCRYPTPROV_OR_NCRYPT_KEY_HANDLE = POINTER(HCRYPTPROV_OR_NCRYPT_KEY_HANDLE)
+
+class FILETIME(Structure):
+	_fields_ = [
+		('dwLowDateTime', DWORD),
+		('dwHighDateTime', DWORD),
+	]
+
+
+class CRYPTOAPI_BLOB(Structure):
+	_fields_ = [
+		('cbData', DWORD),
+		('pbData', LPBYTE),
+	]
+
+	@staticmethod
+	def get_empty():
+		t = CRYPTOAPI_BLOB()
+		t.cbData = 0
+		t.pbData = None
+		return t
+
+CRYPT_INTEGER_BLOB = CRYPTOAPI_BLOB
+CERT_NAME_BLOB = CRYPTOAPI_BLOB
+CRL_BLOB = CRYPTOAPI_BLOB
+PCRL_BLOB = POINTER(CRL_BLOB)
+CERT_BLOB = CRYPTOAPI_BLOB
+PCERT_BLOB = POINTER(CERT_BLOB)
+CRYPT_BIT_BLOB = CRYPTOAPI_BLOB
+CRYPT_OBJID_BLOB = CRYPTOAPI_BLOB
+CRYPT_HASH_BLOB = CRYPTOAPI_BLOB
+PCRYPT_ATTR_BLOB = POINTER(CRYPTOAPI_BLOB)
+
+class CRYPT_ALGORITHM_IDENTIFIER(Structure):
+	_fields_ = [
+		('pszObjId', LPSTR),
+		('Parameters', CRYPT_OBJID_BLOB),
+	]
+
+class CRYPT_ATTRIBUTE(Structure):
+	_fields_ = [
+		("pszObjId", LPSTR),
+		("cValue",   DWORD),
+		("rgValue",  PCRYPT_ATTR_BLOB),
+	]
+PCRYPT_ATTRIBUTE = POINTER(CRYPT_ATTRIBUTE)
+
+class CERT_PUBLIC_KEY_INFO(Structure):
+	_fields_ = [
+		('Algorithm', CRYPT_ALGORITHM_IDENTIFIER),
+		('PublicKey', CRYPT_BIT_BLOB),
+	]
+
+class CERT_EXTENSION(Structure):
+	_fields_ = [
+		('pszObjId', LPSTR),
+		('fCritical', BOOL),
+		('Value', CRYPT_OBJID_BLOB),
+	]
+
+class CERT_ISSUER_SERIAL_NUMBER(Structure):
+	_fields_ = [
+		("Issuer", CERT_NAME_BLOB),
+		("SerialNumber", CRYPT_INTEGER_BLOB),
+	]
+PCERT_ISSUER_SERIAL_NUMBER = POINTER(CERT_ISSUER_SERIAL_NUMBER)
+
+class TMPUNION_CERT_ID(Union):
+	_fields_ = [
+		("IssuerSerialNumber", CERT_ISSUER_SERIAL_NUMBER),
+		("KeyId", CRYPT_HASH_BLOB),
+		("HashId", CRYPT_HASH_BLOB),
+	]
+PTMPUNION_CERT_ID = POINTER(TMPUNION_CERT_ID)
+
+class CERT_ID(Structure):
+	_fields_ = [
+		("dwIdChoice",     DWORD),
+		("DUMMYUNIONNAME", TMPUNION_CERT_ID),
+	]
+PCERT_ID = POINTER(CERT_ID)
+
+PCERT_EXTENSION  = POINTER(CERT_EXTENSION)
+
+class CERT_INFO(Structure):
+	_fields_ = [
+		('dwVersion',          DWORD),
+		('SerialNumber',       CRYPT_INTEGER_BLOB),
+		('SignatureAlgorithm', CRYPT_ALGORITHM_IDENTIFIER),
+		('Issuer',             CERT_NAME_BLOB),
+		('NotBefore',          FILETIME),
+		('NotAfter',           FILETIME),
+		('Subject',            CERT_NAME_BLOB),
+		('SubjectPublicKeyInfo',  CERT_PUBLIC_KEY_INFO),
+		('IssuerUniqueId',     CRYPT_BIT_BLOB),
+		('SubjectUniqueId',    CRYPT_BIT_BLOB),
+		('cExtension',         DWORD),
+		('rgExtension',        PCERT_EXTENSION),
+	]
+PCERT_INFO  = POINTER(CERT_INFO)
+
+
+HCRYPTPROV = HANDLE
+NCRYPT_KEY_HANDLE = HANDLE
+
+class CMSG_SIGNER_ENCODE_INFO_UNION(Union):
+	_fields_ = [
+		("hCryptProv", HCRYPTPROV),
+		("hNCryptKey", NCRYPT_KEY_HANDLE)
+]
+PCMSG_SIGNER_ENCODE_INFO_UNION = POINTER(CMSG_SIGNER_ENCODE_INFO_UNION)
+
+class DUMMYUNIONNAME(ctypes.Union):
+	_fields_ = [
+		("hCryptProv", HCRYPTPROV),
+		("hNCryptKey", NCRYPT_KEY_HANDLE)]
+
+class CMSG_SIGNER_ENCODE_INFO(Structure):
+	#_anonymous_ = ("u",)
+	_fields_ = [
+		('cbSize',        DWORD),
+		('pCertInfo',     PCERT_INFO),
+		('DUMMYUNIONNAME', LPVOID), # CMSG_SIGNER_ENCODE_INFO_UNION
+		('dwKeySpec',     DWORD),
+		('HashAlgorithm', CRYPT_ALGORITHM_IDENTIFIER ),
+		('pvHashAuxInfo', PVOID),
+		('cAuthAttr',     DWORD),
+		('rgAuthAttr',    PCRYPT_ATTRIBUTE),
+		('cUnauthAttr',   DWORD),
+		('rgUnauthAttr',  PCRYPT_ATTRIBUTE),
+		('SignerId',      CERT_ID),
+		#('HashEncryptionAlgorithm', CRYPT_ALGORITHM_IDENTIFIER),
+		#('pvHashEncryptionAuxInfo', PVOID),
+	]
+PCMSG_SIGNER_ENCODE_INFO  = POINTER(CMSG_SIGNER_ENCODE_INFO)
+
+
+class CMSG_SIGNED_ENCODE_INFO(Structure):
+	_fields_ = [
+		("cbSize", DWORD),
+		("cSigners", DWORD),
+		("rgSigners", PCMSG_SIGNER_ENCODE_INFO),
+		("cCertEncoded", DWORD),
+		("rgCertEncoded", PCERT_BLOB),
+		("cCrlEncoded", DWORD),
+		("rgCrlEncoded", PCRL_BLOB),
+		("cAttrCertEncoded", DWORD),
+		("rgAttrCertEncoded", PCERT_BLOB),
+	
+	]
+PCMSG_SIGNED_ENCODE_INFO = POINTER(CMSG_SIGNED_ENCODE_INFO)
+PPCMSG_SIGNED_ENCODE_INFO = POINTER(PCMSG_SIGNED_ENCODE_INFO)
+
+class CERT_CONTEXT(Structure):
+	_fields_ = [
+		('dwCertEncodingType', DWORD),
+		('pbCertEncoded',      LPBYTE),
+		('cbCertEncoded',      DWORD),
+		('pCertInfo',          PCERT_INFO),
+		('hCertStore',         HCERTSTORE),
+	]
+PCERT_CONTEXT  = POINTER(CERT_CONTEXT)
+PCCERT_CONTEXT = PCERT_CONTEXT
+
+
+class CMSG_STREAM_INFO(Structure):
+	_fields_ = [
+		("cbContent",       DWORD),
+		("pfnStreamOutput", PVOID),
+		("pvArg",           PVOID),
+	]
+PCMSG_STREAM_INFO = POINTER(CMSG_STREAM_INFO)
+
+
+# https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certopensystemstorew
+def CertOpenSystemStore(subSystemProtocol = 'MY'):
+	_CertOpenSystemStore = windll.crypt32.CertOpenSystemStoreW
+	_CertOpenSystemStore.argtypes = [PVOID, LPWSTR]
+	_CertOpenSystemStore.restype = HCERTSTORE
+	_CertOpenSystemStore.errcheck = RaiseIfZero
+	
+	handle = _CertOpenSystemStore(None, subSystemProtocol)
+
+	return handle
+
+# https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certclosestore
+def CertCloseStore(handle, dwFlags = 0):
+	_CertCloseStore = windll.crypt32.CertCloseStore
+	_CertCloseStore.argtypes = [HCERTSTORE, DWORD]
+	_CertCloseStore.restype = BOOL
+	_CertCloseStore.errcheck = RaiseIfZero
+	
+	handle = _CertCloseStore(handle, dwFlags)
+
+	return handle
+
+def CertFreeCertificateContext(handle):
+	_CertFreeCertificateContext = windll.crypt32.CertFreeCertificateContext
+	_CertFreeCertificateContext.argtypes = [PCCERT_CONTEXT]
+	_CertFreeCertificateContext.restype = BOOL
+	
+	_CertFreeCertificateContext(handle)
+
+def CertEnumCertificatesInStore(handle, prev_cert_handle = None):
+	_CertEnumCertificatesInStore = windll.crypt32.CertEnumCertificatesInStore
+	_CertEnumCertificatesInStore.argtypes = [HCERTSTORE, PCCERT_CONTEXT]
+	_CertEnumCertificatesInStore.restype = PCCERT_CONTEXT
+	#_CertEnumCertificatesInStore.errcheck = RaiseIfZero
+	
+	handle = _CertEnumCertificatesInStore(handle, prev_cert_handle)
+
+	return handle
+
+def CryptAcquireCertificatePrivateKey(handle, dwFlags = CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG):
+	_CryptAcquireCertificatePrivateKey = windll.crypt32.CryptAcquireCertificatePrivateKey
+	_CryptAcquireCertificatePrivateKey.argtypes = [PCCERT_CONTEXT, DWORD, PVOID, PHCRYPTPROV_OR_NCRYPT_KEY_HANDLE, LPDWORD, LPBOOL]
+	_CryptAcquireCertificatePrivateKey.restype = BOOL
+	_CryptAcquireCertificatePrivateKey.errcheck = RaiseIfZero
+
+	dwKeySpec = DWORD(0)
+	fCallerFreeProvOrNCryptKey = BOOL(0)
+	hprov = HANDLE(0)
+
+	_CryptAcquireCertificatePrivateKey(handle, dwFlags, None, byref(hprov), byref(dwKeySpec),  byref(fCallerFreeProvOrNCryptKey))
+
+	return hprov, dwKeySpec, fCallerFreeProvOrNCryptKey
+
+
+def CryptMsgOpenToEncode(MsgEncodeInfo, dwMsgEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, dwFlags = CMSG_CMS_ENCAPSULATED_CONTENT_FLAG, dwMsgType = CMSG_SIGNED, InnerContentObjID = b"1.3.6.1.5.2.3.1", StreamInfo = None):
+	_CryptMsgOpenToEncode = windll.crypt32.CryptMsgOpenToEncode
+	_CryptMsgOpenToEncode.argtypes = [DWORD, DWORD, DWORD, PCMSG_SIGNED_ENCODE_INFO, LPSTR, PCMSG_STREAM_INFO]
+	_CryptMsgOpenToEncode.restype = HCRYPTMSG
+	_CryptMsgOpenToEncode.errcheck = RaiseIfZero
+
+	if isinstance(InnerContentObjID, str):
+		InnerContentObjID = ctypes.create_string_buffer(InnerContentObjID)
+	res = _CryptMsgOpenToEncode(dwMsgEncodingType, dwFlags, dwMsgType, byref(MsgEncodeInfo), InnerContentObjID, StreamInfo)
+
+	return res
+
+def CryptMsgUpdate(hCryptMsg, data, fFinal = True):
+	_CryptMsgUpdate = windll.crypt32.CryptMsgUpdate
+	_CryptMsgUpdate.argtypes = [HCRYPTMSG, PVOID, DWORD, BOOL]
+	_CryptMsgUpdate.restype = BOOL
+	_CryptMsgUpdate.errcheck = RaiseIfZero
+
+	dlen = len(data)
+	data = ctypes.create_string_buffer(data, len(data))
+	res = _CryptMsgUpdate(hCryptMsg, data, dlen, fFinal)
+
+	return res
+
+def CryptMsgGetParam(hCryptMsg, ptype, dwIndex = 0):
+	_CryptMsgGetParam = windll.crypt32.CryptMsgGetParam
+	_CryptMsgGetParam.argtypes = [HCRYPTMSG, DWORD, DWORD, PVOID, LPDWORD]
+	_CryptMsgGetParam.restype = BOOL
+
+	
+	dlen = DWORD(0)
+	res = _CryptMsgGetParam(hCryptMsg, ptype, dwIndex, None, byref(dlen))
+	
+	data = ctypes.create_string_buffer(dlen.value)
+	res = _CryptMsgGetParam(hCryptMsg, ptype, dwIndex, byref(data), byref(dlen))
+	if res != True:
+		raise ctypes.WinError(GetLastError())
+
+	return data.raw
+
+
+def get_cert(pccert, native = False):
+	from asn1crypto.x509 import Certificate
+	cctx = pccert.contents
+	cert_data = ctypes.string_at(cctx.pbCertEncoded, cctx.cbCertEncoded)
+	if native is False:
+		return Certificate.load(cert_data)
+	return Certificate.load(cert_data).native
+
+def list_certstore(certstore_name = 'MY'):
+	chandle = CertOpenSystemStore(certstore_name)
+	hcert = None
+	while True:
+		hcert = CertEnumCertificatesInStore(chandle, hcert)
+		if bool(hcert) is False:
+			#null ptr means no more certs
+			break
+
+		certificate = get_cert(hcert, True)
+		if 'tbs_certificate' in certificate:
+			if 'subject' in certificate['tbs_certificate']:
+				subject = certificate['tbs_certificate']['subject']#['common_name']
+				if isinstance(subject, str):
+					subject = [subject]
+				print(subject)
+
+		else:
+			input('!')
+	if bool(hcert) is True:
+		CertFreeCertificateContext(hcert)
+
+def find_cert_by_cn(common_name, certstore_name = 'MY'):
+	chandle = CertOpenSystemStore(certstore_name)
+	hcert = None
+	while True:
+		hcert = CertEnumCertificatesInStore(chandle, hcert)
+		if bool(hcert) is False:
+			raise Exception('Couldnt find certificate for %s in certstore %s' % (common_name, certstore_name))
+		certificate = get_cert(hcert)
+		subject = certificate.subject.native['common_name']
+		if isinstance(subject, list):
+			for se in subject:
+				if se == common_name:
+					return certificate, hcert
+		else:
+			if subject == common_name:
+				return certificate, hcert
+
+	
+
+def pkcs7_sign(hcert, data):
+	hprov, keyspec, to_free = CryptAcquireCertificatePrivateKey(hcert)
+
+	hashalgo = CRYPT_ALGORITHM_IDENTIFIER()
+	hashalgo.pszObjId = b"1.3.14.3.2.26" #szOID_OIWSEC_sha1
+	hashalgo.Parameters = CRYPTOAPI_BLOB.get_empty()
+
+	Signers = CMSG_SIGNER_ENCODE_INFO()
+	Signers.cbSize = ctypes.sizeof(CMSG_SIGNER_ENCODE_INFO)
+	Signers.pCertInfo = hcert.contents.pCertInfo
+	Signers.DUMMYUNIONNAME = hprov
+	Signers.dwKeySpec = keyspec
+	Signers.HashAlgorithm = hashalgo
+	Signers.pvHashAuxInfo = None
+	Signers.cAuthAttr = 0
+	Signers.rgAuthAttr = None
+	Signers.cUnauthAttr = 0
+	Signers.rgUnauthAttr = None
+
+	Certificate = CERT_BLOB()
+	Certificate.cbData = hcert.contents.cbCertEncoded
+	Certificate.pbData = hcert.contents.pbCertEncoded
+
+	MsgEncodeInfo = CMSG_SIGNED_ENCODE_INFO()
+	MsgEncodeInfo.cbSize = ctypes.sizeof(CMSG_SIGNED_ENCODE_INFO)
+	MsgEncodeInfo.cSigners = 1
+	MsgEncodeInfo.rgSigners = ctypes.pointer(Signers)
+	MsgEncodeInfo.cCertEncoded = 1
+	MsgEncodeInfo.rgCertEncoded = ctypes.pointer(Certificate)
+	MsgEncodeInfo.cCrlEncoded = 0
+	MsgEncodeInfo.rgCrlEncoded = None
+	MsgEncodeInfo.cAttrCertEncoded  = 0
+	MsgEncodeInfo.rgAttrCertEncoded = None
+
+	hmsg = CryptMsgOpenToEncode(MsgEncodeInfo)
+
+	CryptMsgUpdate(hmsg, data, True)
+	res = CryptMsgGetParam(hmsg, CMSG_CONTENT_PARAM)
+	
+	return res
diff --git a/minikerberos/common/windows/defines.py b/minikerberos/common/windows/defines.py
new file mode 100644
index 0000000..b1548c2
--- /dev/null
+++ b/minikerberos/common/windows/defines.py
@@ -0,0 +1,740 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2009-2016, Mario Vilas
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright notice,
+#       this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice,this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the copyright holder nor the names of its
+#       contributors may be used to endorse or promote products derived from
+#       this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+"""
+Common definitions.
+"""
+
+# TODO
+# + add TCHAR and related types?
+
+import ctypes
+import functools
+
+#==============================================================================
+# This is used later on to calculate the list of exported symbols.
+_all = None
+_all = set(vars().keys())
+#==============================================================================
+
+# Cygwin compatibility.
+try:
+    WindowsError
+except NameError:
+    _gle = None
+    class WindowsError(OSError):
+        def __init__(self, *args, **kwargs):
+            OSError.__init__(self, *args, **kwargs)
+            global _gle
+            if _gle is None:
+                from kernel32 import GetLastError as _gle
+            self.winerror = _gle()
+
+    from os import getenv as _real_getenv
+    def getenv(key, default=None):
+        value = _real_getenv(key, None)
+        if value is None:
+            value = _real_getenv(key.upper(), default)
+        return value
+
+#------------------------------------------------------------------------------
+
+# Some stuff from ctypes we'll be using very frequently.
+addressof   = ctypes.addressof
+sizeof      = ctypes.sizeof
+SIZEOF      = ctypes.sizeof
+POINTER     = ctypes.POINTER
+WINFUNCTYPE = ctypes.WINFUNCTYPE
+windll      = ctypes.windll
+
+# Automatically disable padding of structs and unions on 32 bits.
+class Structure(ctypes.Structure):
+    if sizeof(ctypes.c_void_p) == 4:
+        _pack_ = 1
+class Union(ctypes.Union):
+    if sizeof(ctypes.c_void_p) == 4:
+        _pack_ = 1
+
+# The IronPython implementation of byref() was giving some problems,
+# so it's best to replace it with the slower pointer() function.
+try:
+    ctypes.c_void_p(ctypes.byref(ctypes.c_char()))  # this fails in IronPython
+    byref = ctypes.byref
+except TypeError:
+    byref = ctypes.pointer
+
+# XXX DEBUG
+# The following code can be enabled to make the Win32 API wrappers log to
+# standard output the dll and function names, the parameter values and the
+# return value for each call.
+
+##WIN32_VERBOSE_MODE = True
+WIN32_VERBOSE_MODE = False
+
+if WIN32_VERBOSE_MODE:
+
+    class WinDllHook(object):
+        def __getattr__(self, name):
+            if name.startswith('_'):
+                return object.__getattr__(self, name)
+            return WinFuncHook(name)
+
+    class WinFuncHook(object):
+        def __init__(self, name):
+            self.__name = name
+
+        def __getattr__(self, name):
+            if name.startswith('_'):
+                return object.__getattr__(self, name)
+            return WinCallHook(self.__name, name)
+
+    class WinCallHook(object):
+        def __init__(self, dllname, funcname):
+            self.__dllname = dllname
+            self.__funcname = funcname
+            self.__func = getattr(getattr(ctypes.windll, dllname), funcname)
+
+        def __copy_attribute(self, attribute):
+            try:
+                value = getattr(self, attribute)
+                setattr(self.__func, attribute, value)
+            except AttributeError:
+                try:
+                    delattr(self.__func, attribute)
+                except AttributeError:
+                    pass
+
+        def __call__(self, *argv):
+            self.__copy_attribute('argtypes')
+            self.__copy_attribute('restype')
+            self.__copy_attribute('errcheck')
+            print("-"*10)
+            print("%s ! %s %r" % (self.__dllname, self.__funcname, argv))
+            retval = self.__func(*argv)
+            print("== %r" % (retval,))
+            return retval
+
+    windll = WinDllHook()
+
+#------------------------------------------------------------------------------
+
+def RaiseIfZero(result, func = None, arguments = ()):
+    """
+    Error checking for most Win32 API calls.
+
+    The function is assumed to return an integer, which is C{0} on error.
+    In that case the C{WindowsError} exception is raised.
+    """
+    if not result:
+        raise ctypes.WinError()
+    return result
+
+def RaiseIfNotZero(result, func = None, arguments = ()):
+    """
+    Error checking for some odd Win32 API calls.
+
+    The function is assumed to return an integer, which is zero on success.
+    If the return value is nonzero the C{WindowsError} exception is raised.
+
+    This is mostly useful for free() like functions, where the return value is
+    the pointer to the memory block on failure or a C{NULL} pointer on success.
+    """
+    if result:
+        raise ctypes.WinError()
+    return result
+
+def RaiseIfNotErrorSuccess(result, func = None, arguments = ()):
+    """
+    Error checking for Win32 Registry API calls.
+
+    The function is assumed to return a Win32 error code. If the code is not
+    C{ERROR_SUCCESS} then a C{WindowsError} exception is raised.
+    """
+    if result != ERROR_SUCCESS:
+        raise ctypes.WinError(result)
+    return result
+
+class GuessStringType(object):
+    """
+    Decorator that guesses the correct version (A or W) to call
+    based on the types of the strings passed as parameters.
+
+    Calls the B{ANSI} version if the only string types are ANSI.
+
+    Calls the B{Unicode} version if Unicode or mixed string types are passed.
+
+    The default if no string arguments are passed depends on the value of the
+    L{t_default} class variable.
+
+    @type fn_ansi: function
+    @ivar fn_ansi: ANSI version of the API function to call.
+    @type fn_unicode: function
+    @ivar fn_unicode: Unicode (wide) version of the API function to call.
+
+    @type t_default: type
+    @cvar t_default: Default string type to use.
+        Possible values are:
+         - type('') for ANSI
+         - type(u'') for Unicode
+    """
+
+    # ANSI and Unicode types
+    t_ansi    = type('')
+    t_unicode = type(u'')
+
+    # Default is ANSI for Python 2.x
+    t_default = t_ansi
+
+    def __init__(self, fn_ansi, fn_unicode):
+        """
+        @type  fn_ansi: function
+        @param fn_ansi: ANSI version of the API function to call.
+        @type  fn_unicode: function
+        @param fn_unicode: Unicode (wide) version of the API function to call.
+        """
+        self.fn_ansi    = fn_ansi
+        self.fn_unicode = fn_unicode
+
+        # Copy the wrapped function attributes.
+        try:
+            self.__name__ = self.fn_ansi.__name__[:-1]  # remove the A or W
+        except AttributeError:
+            pass
+        try:
+            self.__module__ = self.fn_ansi.__module__
+        except AttributeError:
+            pass
+        try:
+            self.__doc__ = self.fn_ansi.__doc__
+        except AttributeError:
+            pass
+
+    def __call__(self, *argv, **argd):
+
+        # Shortcut to self.t_ansi
+        t_ansi    = self.t_ansi
+
+        # Get the types of all arguments for the function
+        v_types   = [ type(item) for item in argv ]
+        v_types.extend( [ type(value) for (key, value) in argd.items() ] )
+
+        # Get the appropriate function for the default type
+        if self.t_default == t_ansi:
+            fn = self.fn_ansi
+        else:
+            fn = self.fn_unicode
+
+        # If at least one argument is a Unicode string...
+        if self.t_unicode in v_types:
+
+            # If al least one argument is an ANSI string,
+            # convert all ANSI strings to Unicode
+            if t_ansi in v_types:
+                argv = list(argv)
+                for index in range(len(argv)):
+                    if v_types[index] == t_ansi:
+                        argv[index] = argv[index]
+                for (key, value) in argd.items():
+                    if type(value) == t_ansi:
+                        argd[key] = value
+
+            # Use the W version
+            fn = self.fn_unicode
+
+        # If at least one argument is an ANSI string,
+        # but there are no Unicode strings...
+        elif t_ansi in v_types:
+
+            # Use the A version
+            fn = self.fn_ansi
+
+        # Call the function and return the result
+        return fn(*argv, **argd)
+
+class DefaultStringType(object):
+    """
+    Decorator that uses the default version (A or W) to call
+    based on the configuration of the L{GuessStringType} decorator.
+
+    @see: L{GuessStringType.t_default}
+
+    @type fn_ansi: function
+    @ivar fn_ansi: ANSI version of the API function to call.
+    @type fn_unicode: function
+    @ivar fn_unicode: Unicode (wide) version of the API function to call.
+    """
+
+    def __init__(self, fn_ansi, fn_unicode):
+        """
+        @type  fn_ansi: function
+        @param fn_ansi: ANSI version of the API function to call.
+        @type  fn_unicode: function
+        @param fn_unicode: Unicode (wide) version of the API function to call.
+        """
+        self.fn_ansi    = fn_ansi
+        self.fn_unicode = fn_unicode
+
+        # Copy the wrapped function attributes.
+        try:
+            self.__name__ = self.fn_ansi.__name__[:-1]  # remove the A or W
+        except AttributeError:
+            pass
+        try:
+            self.__module__ = self.fn_ansi.__module__
+        except AttributeError:
+            pass
+        try:
+            self.__doc__ = self.fn_ansi.__doc__
+        except AttributeError:
+            pass
+
+    def __call__(self, *argv, **argd):
+
+        # Get the appropriate function based on the default.
+        if GuessStringType.t_default == GuessStringType.t_ansi:
+            fn = self.fn_ansi
+        else:
+            fn = self.fn_unicode
+
+        # Call the function and return the result
+        return fn(*argv, **argd)
+
+def MakeANSIVersion(fn):
+    """
+    Decorator that generates an ANSI version of a Unicode (wide) only API call.
+
+    @type  fn: callable
+    @param fn: Unicode (wide) version of the API function to call.
+    """
+    @functools.wraps(fn)
+    def wrapper(*argv, **argd):
+        t_ansi    = GuessStringType.t_ansi
+        t_unicode = GuessStringType.t_unicode
+        v_types   = [ type(item) for item in argv ]
+        v_types.extend( [ type(value) for (key, value) in argd.items() ] )
+        if t_ansi in v_types:
+            argv = list(argv)
+            for index in range(len(argv)):
+                if v_types[index] == t_ansi:
+                    argv[index] = t_unicode(argv[index])
+            for key, value in argd.items():
+                if type(value) == t_ansi:
+                    argd[key] = t_unicode(value)
+        return fn(*argv, **argd)
+    return wrapper
+
+def MakeWideVersion(fn):
+    """
+    Decorator that generates a Unicode (wide) version of an ANSI only API call.
+
+    @type  fn: callable
+    @param fn: ANSI version of the API function to call.
+    """
+    @functools.wraps(fn)
+    def wrapper(*argv, **argd):
+        t_ansi    = GuessStringType.t_ansi
+        t_unicode = GuessStringType.t_unicode
+        v_types   = [ type(item) for item in argv ]
+        v_types.extend( [ type(value) for (key, value) in argd.items() ] )
+        if t_unicode in v_types:
+            argv = list(argv)
+            for index in range(len(argv)):
+                if v_types[index] == t_unicode:
+                    argv[index] = t_ansi(argv[index])
+            for key, value in argd.items():
+                if type(value) == t_unicode:
+                    argd[key] = t_ansi(value)
+        return fn(*argv, **argd)
+    return wrapper
+
+#--- Types --------------------------------------------------------------------
+# http://msdn.microsoft.com/en-us/library/aa383751(v=vs.85).aspx
+
+# Map of basic C types to Win32 types
+LPVOID      = ctypes.c_void_p
+CHAR        = ctypes.c_char
+WCHAR       = ctypes.c_wchar
+BYTE        = ctypes.c_ubyte
+SBYTE       = ctypes.c_byte
+WORD        = ctypes.c_uint16
+SWORD       = ctypes.c_int16
+DWORD       = ctypes.c_uint32
+SDWORD      = ctypes.c_int32
+QWORD       = ctypes.c_uint64
+SQWORD      = ctypes.c_int64
+SHORT       = ctypes.c_int16
+USHORT      = ctypes.c_uint16
+INT         = ctypes.c_int32
+UINT        = ctypes.c_uint32
+LONG        = ctypes.c_int32
+ULONG       = ctypes.c_uint32
+LONGLONG    = ctypes.c_int64        # c_longlong
+ULONGLONG   = ctypes.c_uint64       # c_ulonglong
+LPSTR       = ctypes.c_char_p
+LPWSTR      = ctypes.c_wchar_p
+INT8        = ctypes.c_int8
+INT16       = ctypes.c_int16
+INT32       = ctypes.c_int32
+INT64       = ctypes.c_int64
+UINT8       = ctypes.c_uint8
+UINT16      = ctypes.c_uint16
+UINT32      = ctypes.c_uint32
+UINT64      = ctypes.c_uint64
+LONG32      = ctypes.c_int32
+LONG64      = ctypes.c_int64
+ULONG32     = ctypes.c_uint32
+ULONG64     = ctypes.c_uint64
+DWORD32     = ctypes.c_uint32
+DWORD64     = ctypes.c_uint64
+BOOL        = ctypes.c_int32
+FLOAT       = ctypes.c_float        # not sure on cygwin
+DOUBLE      = ctypes.c_double       # not sure on cygwin
+
+# Map size_t to SIZE_T
+try:
+    SIZE_T  = ctypes.c_size_t
+    SSIZE_T = ctypes.c_ssize_t
+except AttributeError:
+    # Size of a pointer
+    SIZE_T  = {1:BYTE, 2:WORD, 4:DWORD, 8:QWORD}[sizeof(LPVOID)]
+    SSIZE_T = {1:SBYTE, 2:SWORD, 4:SDWORD, 8:SQWORD}[sizeof(LPVOID)]
+PSIZE_T     = POINTER(SIZE_T)
+
+# Not really pointers but pointer-sized integers
+DWORD_PTR   = SIZE_T
+ULONG_PTR   = SIZE_T
+LONG_PTR    = SIZE_T
+
+# Other Win32 types, more may be added as needed
+PVOID       = LPVOID
+PPVOID      = POINTER(PVOID)
+PSTR        = LPSTR
+PWSTR       = LPWSTR
+PCHAR       = LPSTR
+PWCHAR      = LPWSTR
+LPBYTE      = POINTER(BYTE)
+LPSBYTE     = POINTER(SBYTE)
+LPWORD      = POINTER(WORD)
+LPSWORD     = POINTER(SWORD)
+LPDWORD     = POINTER(DWORD)
+LPSDWORD    = POINTER(SDWORD)
+LPULONG     = POINTER(ULONG)
+LPLONG      = POINTER(LONG)
+PDWORD      = LPDWORD
+PDWORD_PTR  = POINTER(DWORD_PTR)
+PULONG      = LPULONG
+PLONG       = LPLONG
+CCHAR       = CHAR
+BOOLEAN     = BYTE
+PBOOL       = POINTER(BOOL)
+LPBOOL      = PBOOL
+TCHAR       = CHAR      # XXX ANSI by default?
+UCHAR       = BYTE
+DWORDLONG   = ULONGLONG
+LPDWORD32   = POINTER(DWORD32)
+LPULONG32   = POINTER(ULONG32)
+LPDWORD64   = POINTER(DWORD64)
+LPULONG64   = POINTER(ULONG64)
+PDWORD32    = LPDWORD32
+PULONG32    = LPULONG32
+PDWORD64    = LPDWORD64
+PULONG64    = LPULONG64
+ATOM        = WORD
+HANDLE      = LPVOID
+PHANDLE     = POINTER(HANDLE)
+LPHANDLE    = PHANDLE
+HMODULE     = HANDLE
+HINSTANCE   = HANDLE
+HTASK       = HANDLE
+HKEY        = HANDLE
+PHKEY       = POINTER(HKEY)
+HDESK       = HANDLE
+HRSRC       = HANDLE
+HSTR        = HANDLE
+HWINSTA     = HANDLE
+HKL         = HANDLE
+HDWP        = HANDLE
+HFILE       = HANDLE
+HRESULT     = LONG
+HGLOBAL     = HANDLE
+HLOCAL      = HANDLE
+HGDIOBJ     = HANDLE
+HDC         = HGDIOBJ
+HRGN        = HGDIOBJ
+HBITMAP     = HGDIOBJ
+HPALETTE    = HGDIOBJ
+HPEN        = HGDIOBJ
+HBRUSH      = HGDIOBJ
+HMF         = HGDIOBJ
+HEMF        = HGDIOBJ
+HENHMETAFILE = HGDIOBJ
+HMETAFILE   = HGDIOBJ
+HMETAFILEPICT = HGDIOBJ
+HWND        = HANDLE
+NTSTATUS    = LONG
+PNTSTATUS   = POINTER(NTSTATUS)
+KAFFINITY   = ULONG_PTR
+RVA         = DWORD
+RVA64       = QWORD
+WPARAM      = DWORD
+LPARAM      = LPVOID
+LRESULT     = LPVOID
+ACCESS_MASK = DWORD
+REGSAM      = ACCESS_MASK
+PACCESS_MASK = POINTER(ACCESS_MASK)
+PREGSAM     = POINTER(REGSAM)
+
+# Since the SID is an opaque structure, let's treat its pointers as void*
+PSID = PVOID
+
+# typedef union _LARGE_INTEGER {
+#   struct {
+#     DWORD LowPart;
+#     LONG HighPart;
+#   } ;
+#   struct {
+#     DWORD LowPart;
+#     LONG HighPart;
+#   } u;
+#   LONGLONG QuadPart;
+# } LARGE_INTEGER,
+#  *PLARGE_INTEGER;
+
+# XXX TODO
+
+# typedef struct _FLOAT128 {
+#     __int64 LowPart;
+#     __int64 HighPart;
+# } FLOAT128;
+class FLOAT128 (Structure):
+    _fields_ = [
+        ("LowPart",     QWORD),
+        ("HighPart",    QWORD),
+    ]
+PFLOAT128 = POINTER(FLOAT128)
+
+# typedef struct DECLSPEC_ALIGN(16) _M128A {
+#     ULONGLONG Low;
+#     LONGLONG High;
+# } M128A, *PM128A;
+class M128A(Structure):
+    _fields_ = [
+        ("Low",     ULONGLONG),
+        ("High",    LONGLONG),
+    ]
+PM128A = POINTER(M128A)
+
+#--- Constants ----------------------------------------------------------------
+
+NULL        = None
+INFINITE    = -1
+TRUE        = 1
+FALSE       = 0
+
+# http://blogs.msdn.com/oldnewthing/archive/2004/08/26/220873.aspx
+ANYSIZE_ARRAY = 1
+
+# Invalid handle value is -1 casted to void pointer.
+try:
+    INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value #-1 #0xFFFFFFFF
+except TypeError:
+    if sizeof(ctypes.c_void_p) == 4:
+        INVALID_HANDLE_VALUE = 0xFFFFFFFF
+    elif sizeof(ctypes.c_void_p) == 8:
+        INVALID_HANDLE_VALUE = 0xFFFFFFFFFFFFFFFF
+    else:
+        raise
+
+MAX_MODULE_NAME32   = 255
+MAX_PATH            = 260
+
+# Error codes
+# TODO maybe add more error codes?
+# if they're too many they could be pickled instead,
+# or at the very least put in a new file
+ERROR_SUCCESS                       = 0
+ERROR_INVALID_FUNCTION              = 1
+ERROR_FILE_NOT_FOUND                = 2
+ERROR_PATH_NOT_FOUND                = 3
+ERROR_ACCESS_DENIED                 = 5
+ERROR_INVALID_HANDLE                = 6
+ERROR_NOT_ENOUGH_MEMORY             = 8
+ERROR_INVALID_DRIVE                 = 15
+ERROR_NO_MORE_FILES                 = 18
+ERROR_BAD_LENGTH                    = 24
+ERROR_HANDLE_EOF                    = 38
+ERROR_HANDLE_DISK_FULL              = 39
+ERROR_NOT_SUPPORTED                 = 50
+ERROR_FILE_EXISTS                   = 80
+ERROR_INVALID_PARAMETER             = 87
+ERROR_BUFFER_OVERFLOW               = 111
+ERROR_DISK_FULL                     = 112
+ERROR_CALL_NOT_IMPLEMENTED          = 120
+ERROR_SEM_TIMEOUT                   = 121
+ERROR_INSUFFICIENT_BUFFER           = 122
+ERROR_INVALID_NAME                  = 123
+ERROR_MOD_NOT_FOUND                 = 126
+ERROR_PROC_NOT_FOUND                = 127
+ERROR_DIR_NOT_EMPTY                 = 145
+ERROR_BAD_THREADID_ADDR             = 159
+ERROR_BAD_ARGUMENTS                 = 160
+ERROR_BAD_PATHNAME                  = 161
+ERROR_ALREADY_EXISTS                = 183
+ERROR_INVALID_FLAG_NUMBER           = 186
+ERROR_ENVVAR_NOT_FOUND              = 203
+ERROR_FILENAME_EXCED_RANGE          = 206
+ERROR_MORE_DATA                     = 234
+
+WAIT_TIMEOUT                        = 258
+
+ERROR_NO_MORE_ITEMS                 = 259
+ERROR_PARTIAL_COPY                  = 299
+ERROR_INVALID_ADDRESS               = 487
+ERROR_THREAD_NOT_IN_PROCESS         = 566
+ERROR_CONTROL_C_EXIT                = 572
+ERROR_UNHANDLED_EXCEPTION           = 574
+ERROR_ASSERTION_FAILURE             = 668
+ERROR_WOW_ASSERTION                 = 670
+
+ERROR_DBG_EXCEPTION_NOT_HANDLED     = 688
+ERROR_DBG_REPLY_LATER               = 689
+ERROR_DBG_UNABLE_TO_PROVIDE_HANDLE  = 690
+ERROR_DBG_TERMINATE_THREAD          = 691
+ERROR_DBG_TERMINATE_PROCESS         = 692
+ERROR_DBG_CONTROL_C                 = 693
+ERROR_DBG_PRINTEXCEPTION_C          = 694
+ERROR_DBG_RIPEXCEPTION              = 695
+ERROR_DBG_CONTROL_BREAK             = 696
+ERROR_DBG_COMMAND_EXCEPTION         = 697
+ERROR_DBG_EXCEPTION_HANDLED         = 766
+ERROR_DBG_CONTINUE                  = 767
+
+ERROR_ELEVATION_REQUIRED            = 740
+ERROR_NOACCESS                      = 998
+
+ERROR_CIRCULAR_DEPENDENCY           = 1059
+ERROR_SERVICE_DOES_NOT_EXIST        = 1060
+ERROR_SERVICE_CANNOT_ACCEPT_CTRL    = 1061
+ERROR_SERVICE_NOT_ACTIVE            = 1062
+ERROR_FAILED_SERVICE_CONTROLLER_CONNECT = 1063
+ERROR_EXCEPTION_IN_SERVICE          = 1064
+ERROR_DATABASE_DOES_NOT_EXIST       = 1065
+ERROR_SERVICE_SPECIFIC_ERROR        = 1066
+ERROR_PROCESS_ABORTED               = 1067
+ERROR_SERVICE_DEPENDENCY_FAIL       = 1068
+ERROR_SERVICE_LOGON_FAILED          = 1069
+ERROR_SERVICE_START_HANG            = 1070
+ERROR_INVALID_SERVICE_LOCK          = 1071
+ERROR_SERVICE_MARKED_FOR_DELETE     = 1072
+ERROR_SERVICE_EXISTS                = 1073
+ERROR_ALREADY_RUNNING_LKG           = 1074
+ERROR_SERVICE_DEPENDENCY_DELETED    = 1075
+ERROR_BOOT_ALREADY_ACCEPTED         = 1076
+ERROR_SERVICE_NEVER_STARTED         = 1077
+ERROR_DUPLICATE_SERVICE_NAME        = 1078
+ERROR_DIFFERENT_SERVICE_ACCOUNT     = 1079
+ERROR_CANNOT_DETECT_DRIVER_FAILURE  = 1080
+ERROR_CANNOT_DETECT_PROCESS_ABORT   = 1081
+ERROR_NO_RECOVERY_PROGRAM           = 1082
+ERROR_SERVICE_NOT_IN_EXE            = 1083
+ERROR_NOT_SAFEBOOT_SERVICE          = 1084
+
+ERROR_DEBUGGER_INACTIVE             = 1284
+
+ERROR_PRIVILEGE_NOT_HELD            = 1314
+
+ERROR_NONE_MAPPED                   = 1332
+
+RPC_S_SERVER_UNAVAILABLE            = 1722
+
+# Standard access rights
+DELETE                           = 0x00010000
+READ_CONTROL                     = 0x00020000
+WRITE_DAC                        = 0x00040000
+WRITE_OWNER                      = 0x00080000
+SYNCHRONIZE                      = 0x00100000
+STANDARD_RIGHTS_REQUIRED         = 0x000F0000
+STANDARD_RIGHTS_READ             = READ_CONTROL
+STANDARD_RIGHTS_WRITE            = READ_CONTROL
+STANDARD_RIGHTS_EXECUTE          = READ_CONTROL
+STANDARD_RIGHTS_ALL              = 0x001F0000
+SPECIFIC_RIGHTS_ALL              = 0x0000FFFF
+
+#--- Structures ---------------------------------------------------------------
+
+# typedef struct _LSA_UNICODE_STRING {
+#   USHORT Length;
+#   USHORT MaximumLength;
+#   PWSTR Buffer;
+# } LSA_UNICODE_STRING,
+#  *PLSA_UNICODE_STRING,
+#  UNICODE_STRING,
+#  *PUNICODE_STRING;
+class UNICODE_STRING(Structure):
+    _fields_ = [
+        ("Length",          USHORT),
+        ("MaximumLength",   USHORT),
+        ("Buffer",          PWSTR),
+    ]
+
+# From MSDN:
+#
+# typedef struct _GUID {
+#   DWORD Data1;
+#   WORD Data2;
+#   WORD Data3;
+#   BYTE Data4[8];
+# } GUID;
+class GUID(Structure):
+    _fields_ = [
+        ("Data1",   DWORD),
+        ("Data2",   WORD),
+        ("Data3",   WORD),
+        ("Data4",   BYTE * 8),
+]
+
+# From MSDN:
+#
+# typedef struct _LIST_ENTRY {
+#     struct _LIST_ENTRY *Flink;
+#     struct _LIST_ENTRY *Blink;
+# } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
+class LIST_ENTRY(Structure):
+    _fields_ = [
+        ("Flink",   PVOID),     # POINTER(LIST_ENTRY)
+        ("Blink",   PVOID),     # POINTER(LIST_ENTRY)
+]
+
+#==============================================================================
+# This calculates the list of exported symbols.
+_all = set(vars().keys()).difference(_all)
+##__all__ = [_x for _x in _all if not _x.startswith('_')]
+##__all__.sort()
+#==============================================================================
\ No newline at end of file
diff --git a/minikerberos/crypto/MD4.py b/minikerberos/crypto/MD4.py
new file mode 100644
index 0000000..3b64dda
--- /dev/null
+++ b/minikerberos/crypto/MD4.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright © 2019 James Seo <james@equiv.tech> (github.com/kangtastic).
+#
+# This file is released under the WTFPL, version 2 (wtfpl.net).
+#
+# md4.py: An implementation of the MD4 hash algorithm in pure Python 3.
+#
+# Description: Zounds! Yet another rendition of pseudocode from RFC1320!
+#              Bonus points for the algorithm literally being from 1992.
+#
+# Usage: Why would anybody use this? This is self-rolled crypto, and
+#        self-rolled *obsolete* crypto at that. DO NOT USE if you need
+#        something "performant" or "secure". :P
+#
+#        Anyway, from the command line:
+#
+#           $ ./md4.py [messages]
+#
+#        where [messages] are some strings to be hashed.
+#
+#        In Python, use similarly to hashlib (not that it even has MD4):
+#
+#           from .md4 import MD4
+#
+#           digest = MD4("BEES").hexdigest()
+#
+#           print(digest)  # "501af1ef4b68495b5b7e37b15b4cda68"
+#
+#
+# Sample console output:
+#
+#   Testing the MD4 class.
+#
+#   Message:  b''
+#   Expected: 31d6cfe0d16ae931b73c59d7e0c089c0
+#   Actual:   31d6cfe0d16ae931b73c59d7e0c089c0
+#
+#   Message:  b'The quick brown fox jumps over the lazy dog'
+#   Expected: 1bee69a46ba811185c194762abaeae90
+#   Actual:   1bee69a46ba811185c194762abaeae90
+#
+#   Message:  b'BEES'
+#   Expected: 501af1ef4b68495b5b7e37b15b4cda68
+#   Actual:   501af1ef4b68495b5b7e37b15b4cda68
+#
+import struct
+
+
+class MD4:
+	"""An implementation of the MD4 hash algorithm."""
+
+	width = 32
+	mask = 0xFFFFFFFF
+
+	# Unlike, say, SHA-1, MD4 uses little-endian. Fascinating!
+	h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476]
+
+	def __init__(self, msg=None):
+		""":param ByteString msg: The message to be hashed."""
+		if msg is None:
+			msg = b""
+
+		self.msg = msg
+
+		# Pre-processing: Total length is a multiple of 512 bits.
+		ml = len(msg) * 8
+		msg += b"\x80"
+		msg += b"\x00" * (-(len(msg) + 8) % 64)
+		msg += struct.pack("<Q", ml)
+
+		# Process the message in successive 512-bit chunks.
+		self._process([msg[i : i + 64] for i in range(0, len(msg), 64)])
+
+	def __repr__(self):
+		if self.msg:
+			return f"{self.__class__.__name__}({self.msg:s})"
+		return f"{self.__class__.__name__}()"
+
+	def __str__(self):
+		return self.hexdigest()
+
+	def __eq__(self, other):
+		return self.h == other.h
+
+	def bytes(self):
+		""":return: The final hash value as a `bytes` object."""
+		return struct.pack("<4L", *self.h)
+
+	def hexbytes(self):
+		""":return: The final hash value as hexbytes."""
+		return self.hexdigest().encode
+
+	def hexdigest(self):
+		""":return: The final hash value as a hexstring."""
+		return "".join(f"{value:02x}" for value in self.bytes())
+	
+	def digest(self):
+		return self.bytes()
+
+	def _process(self, chunks):
+		for chunk in chunks:
+			X, h = list(struct.unpack("<16I", chunk)), self.h.copy()
+
+			# Round 1.
+			Xi = [3, 7, 11, 19]
+			for n in range(16):
+				i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
+				K, S = n, Xi[n % 4]
+				hn = h[i] + MD4.F(h[j], h[k], h[l]) + X[K]
+				h[i] = MD4.lrot(hn & MD4.mask, S)
+
+			# Round 2.
+			Xi = [3, 5, 9, 13]
+			for n in range(16):
+				i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
+				K, S = n % 4 * 4 + n // 4, Xi[n % 4]
+				hn = h[i] + MD4.G(h[j], h[k], h[l]) + X[K] + 0x5A827999
+				h[i] = MD4.lrot(hn & MD4.mask, S)
+
+			# Round 3.
+			Xi = [3, 9, 11, 15]
+			Ki = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]
+			for n in range(16):
+				i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
+				K, S = Ki[n], Xi[n % 4]
+				hn = h[i] + MD4.H(h[j], h[k], h[l]) + X[K] + 0x6ED9EBA1
+				h[i] = MD4.lrot(hn & MD4.mask, S)
+
+			self.h = [((v + n) & MD4.mask) for v, n in zip(self.h, h)]
+
+	@staticmethod
+	def F(x, y, z):
+		return (x & y) | (~x & z)
+
+	@staticmethod
+	def G(x, y, z):
+		return (x & y) | (x & z) | (y & z)
+
+	@staticmethod
+	def H(x, y, z):
+		return x ^ y ^ z
+
+	@staticmethod
+	def lrot(value, n):
+		lbits, rbits = (value << n) & MD4.mask, value >> (MD4.width - n)
+		return lbits | rbits
+
+
+def main():
+	# Import is intentionally delayed.
+	import sys
+
+	if len(sys.argv) > 1:
+		messages = [msg.encode() for msg in sys.argv[1:]]
+		for message in messages:
+			print(MD4(message).hexdigest())
+	else:
+		messages = [b"", b"The quick brown fox jumps over the lazy dog", b"BEES"]
+		known_hashes = [
+			"31d6cfe0d16ae931b73c59d7e0c089c0",
+			"1bee69a46ba811185c194762abaeae90",
+			"501af1ef4b68495b5b7e37b15b4cda68",
+		]
+
+		print("Testing the MD4 class.")
+		print()
+
+		for message, expected in zip(messages, known_hashes):
+			print("Message: ", message)
+			print("Expected:", expected)
+			print("Actual:  ", MD4(message).hexdigest())
+			print()
+
+
+if __name__ == "__main__":
+	try:
+		main()
+	except KeyboardInterrupt:
+		pass
diff --git a/minikerberos/crypto/hashing.py b/minikerberos/crypto/hashing.py
index a2c5f2e..3cbf37f 100644
--- a/minikerberos/crypto/hashing.py
+++ b/minikerberos/crypto/hashing.py
@@ -2,6 +2,7 @@ import hashlib
 import hmac
 
 from minikerberos.crypto.BASE import hashBASE, hmacBASE
+from minikerberos.crypto.MD4 import MD4 
 
 class md5(hashBASE):
 	def __init__(self, data = None):
@@ -15,17 +16,19 @@ class md5(hashBASE):
 	def hexdigest(self):
 		return self._hash.hexdigest()
 
-class md4(hashBASE):
-	def __init__(self, data = None):
-		hashBASE.__init__(self, data)
-	def setup_hash(self):
-		self._hash = hashlib.new('md4')
-	def update(self, data):
-		return self._hash.update(data)
-	def digest(self):
-		return self._hash.digest()
-	def hexdigest(self):
-		return self._hash.hexdigest()
+#class md4(hashBASE):
+#	def __init__(self, data = None):
+#		hashBASE.__init__(self, data)
+#	def setup_hash(self):
+#		self._hash = hashlib.new('md4')
+#	def update(self, data):
+#		return self._hash.update(data)
+#	def digest(self):
+#		return self._hash.digest()
+#	def hexdigest(self):
+#		return self._hash.hexdigest()
+
+md4 = MD4
 
 class hmac_md5(hmacBASE):
 	def __init__(self, key):
diff --git a/minikerberos/examples/ccache_editor.py b/minikerberos/examples/ccache_editor.py
index adaf81c..2c4279e 100644
--- a/minikerberos/examples/ccache_editor.py
+++ b/minikerberos/examples/ccache_editor.py
@@ -60,7 +60,6 @@ def main():
 		id = args.id
 		temp_cc = CCACHE()
 		temp_cc.file_format_version = cc.file_format_version
-		temp_cc.headerlen = cc.headerlen
 		temp_cc.headers = cc.headers
 		temp_cc.primary_principal = cc.primary_principal
 		i = 0
diff --git a/minikerberos/examples/getTGS.py b/minikerberos/examples/getTGS.py
index 3201732..bfe6675 100644
--- a/minikerberos/examples/getTGS.py
+++ b/minikerberos/examples/getTGS.py
@@ -16,7 +16,7 @@ async def amain(args):
 		raise Exception('SPN must contain @')
 	t, domain = args.spn.split('@')
 	if t.find('/') != -1:
-		service, hostname = args.spn.split('/')
+		service, hostname = t.split('/')
 	else:
 		hostname = t
 		service = None
@@ -31,26 +31,12 @@ async def amain(args):
 	target = cu.get_target()
 	
 	logging.debug('Getting TGT')
-	
-	if not ccred.ccache:
-		client = AIOKerberosClient(ccred, target)
-		logging.debug('Getting TGT')
-		await client.get_TGT()
-		logging.debug('Getting TGS')
-		await client.get_TGS(spn)
-	else:
-		logging.debug('Getting TGS via TGT from CCACHE')
-		for tgt, key in ccred.ccache.get_all_tgt():
-			try:
-				logging.info('Trying to get SPN with %s' % '!'.join(tgt['cname']['name-string']))
-				client = AIOKerberosClient.from_tgt(target, tgt, key)
-				await client.get_TGS(spn)
-				logging.info('Sucsess!')
-			except Exception as e:
-				logging.debug('This ticket is not usable it seems Reason: %s' % e)
-				continue
-			else:
-				break
+
+	client = AIOKerberosClient(ccred, target)
+	logging.debug('Getting TGT')
+	await client.get_TGT()
+	logging.debug('Getting TGS')
+	await client.get_TGS(spn)
 				
 	client.ccache.to_file(args.ccache)
 	logging.info('Done!')
@@ -59,7 +45,7 @@ def main():
 	import argparse
 	
 	parser = argparse.ArgumentParser(description='Polls the kerberos service for a TGS for the sepcified user and specified service', formatter_class=argparse.RawDescriptionHelpFormatter, epilog = kerberos_url_help_epilog)
-	parser.add_argument('kerberos_connection_string', help='the kerberos target string in the following format <domain>/<username>/<secret_type>:<secret>@<domaincontroller-ip>')
+	parser.add_argument('kerberos_connection_url', help='the kerberos target string in the following format <domain>/<username>/<secret_type>:<secret>@<domaincontroller-ip>')
 	parser.add_argument('spn', help='the service principal in format <service>/<server-hostname>@<domain> Example: cifs/fileserver.test.corp@TEST.corp for a TGS ticket to be used for file access on server "fileserver". IMPORTANT: SERVER\'S HOSTNAME MUST BE USED, NOT IP!!!')
 	parser.add_argument('ccache', help='ccache file to store the TGT ticket in')
 	parser.add_argument('-u', action='store_true', help='Use UDP instead of TCP (not tested)')
diff --git a/minikerberos/gssapi/channelbindings.py b/minikerberos/gssapi/channelbindings.py
new file mode 100644
index 0000000..53baea0
--- /dev/null
+++ b/minikerberos/gssapi/channelbindings.py
@@ -0,0 +1,81 @@
+
+import enum
+import io
+
+# https://www.ietf.org/rfc/rfc2744.txt   3.11. Channel Bindings
+
+class GSS_C_AF(enum.Enum):
+	UNSPEC = 0     #Unspecified address type
+	LOCAL = 1      #Host-local address type
+	INET = 2       #Internet address type (e.g. IP)
+	IMPLINK = 3    #ARPAnet IMP address type
+	PUP = 4        #pup protocols (eg BSP) address type
+	CHAOS = 5      #MIT CHAOS protocol address type
+	NS = 6         #XEROX NS address type
+	NBS = 7        #nbs address type
+	ECMA  = 8      #ECMA address type
+	DATAKIT = 9    #datakit protocols address type
+	CCITT = 10     #CCITT protocols
+	SNA = 11       #IBM SNA address type
+	DECnet = 12    #DECnet address type
+	DLI = 13       #Direct data link interface address type
+	LAT = 14       #LAT address type
+	HYLINK = 15    #NSC Hyperchannel address type
+	APPLETALK= 16  #AppleTalk address type
+	BSC = 17       #BISYNC 2780/3780 address type
+	DSS = 18       #Distributed system services address type
+	OSI = 19       #OSI TP4 address type
+	X25 = 21       #X.25
+	NULLADDR = 255 #No address specified
+
+class ChannelBindingsStruct:
+	def __init__(self):
+		self.initiator_addrtype = None #GSS_C_AF
+		self.initiator_address = None #bytes
+		self.acceptor_addrtype = None #GSS_C_AF
+		self.acceptor_address = None #bytes
+		self.application_data = None #bytes
+
+	@staticmethod
+	def from_bytes(data):
+		return ChannelBindingsStruct.from_buffer(io.BytesIO(data))
+
+	@staticmethod
+	def from_buffer(buff):
+		# TODO: parse the addresses
+		cb = ChannelBindingsStruct()
+		t = buff.read(8)
+		if t != b'\x00' * 8:
+			cb.initiator_addrtype = GSS_C_AF(int.from_bytes(t, byteorder='little', signed = False))
+			initiator_address_length = int.from_bytes(buff.read(4), byteorder='little', signed = False)
+			cb.initiator_address  = buff.read(initiator_address_length)
+		t = buff.read(8)
+		if t != b'\x00' * 8:
+			cb.acceptor_addrtype  = GSS_C_AF(int.from_bytes(buff.read(4), byteorder='little', signed = False))
+			acceptor_address_length = int.from_bytes(buff.read(4), byteorder='little', signed = False)
+			cb.acceptor_address   = buff.read(acceptor_address_length)
+		t = buff.read(8)
+		if t != b'\x00' * 8:
+			application_data_length = int.from_bytes(buff.read(4), byteorder='little', signed = False)
+			cb.application_data   = buff.read(application_data_length)
+		return cb
+
+	def to_bytes(self):
+		if self.initiator_address is None:
+			t = b'\x00' * 8
+		else:
+			t = self.initiator_addrtype.value.to_bytes(4, byteorder='little', signed = False)
+			t += len(self.initiator_address).to_bytes(4, byteorder='little', signed = False)
+			t += self.initiator_address
+		if self.acceptor_address is None:
+			t += b'\x00' * 8
+		else:
+			t += self.acceptor_addrtype
+			t += len(self.acceptor_address).to_bytes(4, byteorder='little', signed = False)
+			t += self.acceptor_address
+		if self.application_data is None:
+			t += b'\x00' * 8
+		else:
+			t += len(self.application_data).to_bytes(4, byteorder='little', signed = False)
+			t += self.application_data
+		return t
\ No newline at end of file
diff --git a/minikerberos/gssapi/gssapi.py b/minikerberos/gssapi/gssapi.py
index d0344a5..c1d0d21 100644
--- a/minikerberos/gssapi/gssapi.py
+++ b/minikerberos/gssapi/gssapi.py
@@ -17,6 +17,8 @@ GSS_WRAP_HEADER = b'\x60\x2b\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'
 
 class GSSAPIFlags(enum.IntFlag):
 	GSS_C_DCE_STYLE     = 0x1000
+	GSS_C_EXTENDED_ERROR_FLAG = 0x4000
+	GSS_C_IDENTIFY_FLAG = 0x2000
 	GSS_C_DELEG_FLAG    = 1
 	GSS_C_MUTUAL_FLAG   = 2
 	GSS_C_REPLAY_FLAG   = 4
diff --git a/minikerberos/network/aioclientsockssocket.py b/minikerberos/network/aioclientsockssocket.py
index 059cb90..5684a84 100644
--- a/minikerberos/network/aioclientsockssocket.py
+++ b/minikerberos/network/aioclientsockssocket.py
@@ -29,7 +29,7 @@ class AIOKerberosClientSocksSocket:
 		self.in_queue = asyncio.Queue()
 		comms = SocksQueueComms(self.out_queue, self.in_queue)
 
-		self.client = SOCKSClient(comms, self.target.proxy.target, self.target.proxy.creds)
+		self.client = SOCKSClient(comms, self.target.proxy.target)
 		self.proxy_task = asyncio.create_task(self.client.run())
 
 		length = len(data).to_bytes(4, byteorder = 'big', signed = False)
diff --git a/minikerberos/network/aioclientwsnetsocket.py b/minikerberos/network/aioclientwsnetsocket.py
new file mode 100644
index 0000000..7706cf4
--- /dev/null
+++ b/minikerberos/network/aioclientwsnetsocket.py
@@ -0,0 +1,74 @@
+
+#!/usr/bin/env python3
+#
+# Author:
+#  Tamas Jos (@skelsec)
+#
+
+import asyncio
+
+from minikerberos.protocol.asn1_structs import KerberosResponse
+from minikerberos.common.constants import KerberosSocketType
+
+from pyodidewsnet.client import WSNetworkTCP
+
+class AIOKerberosClientWSNETSocket:
+	def __init__(self, target):
+		self.target = target
+		self.out_queue = None
+		self.in_queue = None
+
+		self.proxy_client = None
+		self.proxy_task = None
+		
+	def get_addr_str(self):
+		return '%s:%d' % (self.target.ip, self.target.port)
+
+	async def sendrecv(self, data):
+		self.out_queue = asyncio.Queue()
+		self.in_queue = asyncio.Queue()
+		self.proxy_client = WSNetworkTCP(self.target.ip, int(self.target.port), self.in_queue, self.out_queue)
+		_, err = await self.proxy_client.run()
+		if err is not None:
+			raise err
+
+		length = len(data).to_bytes(4, byteorder = 'big', signed = False)
+		await self.out_queue.put(length+data)
+
+		resp_data = b''
+		resp_data_len = -1
+		while True:
+			data, err = await self.in_queue.get()
+			if data is None:
+				break
+			if err is not None:
+				raise err
+			resp_data += data
+			if resp_data_len == -1:
+				if len(resp_data) > 4:
+					resp_data_len = int.from_bytes(resp_data[:4], byteorder = 'big', signed = False)
+					if resp_data_len == 0:
+						raise Exception('Returned data length is 0! This means the server did not understand our message')
+			
+			if resp_data_len != -1:
+				if len(resp_data) == resp_data_len + 4:
+					resp_data = resp_data[4:]
+					break
+				elif len(resp_data) > resp_data_len + 4:
+					raise Exception('Got too much data somehow')
+				else:
+					continue
+		
+		await self.out_queue.put(None)
+		if resp_data == b'':
+			raise Exception('Connection returned no data!')
+		
+		krb_message = KerberosResponse.load(resp_data)
+		return krb_message
+
+		
+	def __str__(self):
+		t = '===AIOKerberosClientProxySocket AIO===\r\n'
+		t += 'target: %s\r\n' % self.target
+		
+		return t
\ No newline at end of file
diff --git a/minikerberos/network/clientsocket.py b/minikerberos/network/clientsocket.py
index 54fedbf..2aeafdb 100644
--- a/minikerberos/network/clientsocket.py
+++ b/minikerberos/network/clientsocket.py
@@ -10,7 +10,7 @@ class KerberosClientSocket:
 	def __init__(self, target):
 		self.target = target
 		#ip, port = 88, soc_type = KerberosSocketType.TCP
-		self.soc_type = target.soc_type
+		self.soc_type = target.protocol
 		self.dst_ip = target.ip
 		self.dst_port = int(target.port)
 		self.soc = None
diff --git a/minikerberos/pkinit.py b/minikerberos/pkinit.py
new file mode 100644
index 0000000..0db43cf
--- /dev/null
+++ b/minikerberos/pkinit.py
@@ -0,0 +1,457 @@
+
+# Kudos:
+# Parts of this code was inspired by the following project by @rubin_mor
+# https://github.com/morRubin/AzureADJoinedMachinePTC
+# 
+
+# TODO: code currently supports RSA+DH+SHA1 , add support for other mechanisms
+
+
+import os
+import datetime
+import secrets
+import hashlib
+
+from oscrypto.keys import parse_pkcs12
+from oscrypto.asymmetric import rsa_pkcs1v15_sign, load_private_key
+from asn1crypto import cms
+from asn1crypto import algos
+from asn1crypto import core
+from asn1crypto import x509
+from asn1crypto import keys
+
+from minikerberos.protocol.constants import NAME_TYPE, MESSAGE_TYPE, PaDataType
+from minikerberos.protocol.encryption import Enctype, _checksum_table, _enctype_table, Key
+from minikerberos.protocol.structures import AuthenticatorChecksum
+from minikerberos.protocol.asn1_structs import KDC_REQ_BODY, PrincipalName, HostAddress, \
+	KDCOptions, EncASRepPart, AP_REQ, AuthorizationData, Checksum, krb5_pvno, Realm, \
+	EncryptionKey, Authenticator, Ticket, APOptions, EncryptedData, AS_REQ, AP_REP
+from minikerberos.protocol.rfc4556 import PKAuthenticator, AuthPack, Dunno1, Dunno2, MetaData, Info, CertIssuer, CertIssuers, PA_PK_AS_REP, KDCDHKeyInfo
+from minikerberos.protocol.rfc_iakerb import KRB_FINISHED
+from minikerberos.protocol.mskile import LSAP_TOKEN_INFO_INTEGRITY, KERB_AD_RESTRICTION_ENTRY, KERB_AD_RESTRICTION_ENTRYS
+from minikerberos.gssapi.gssapi import GSSAPIFlags
+
+def length_encode(x):
+	if x <= 127:
+		return x.to_bytes(1, 'big', signed = False)
+	else:
+		lb = x.to_bytes((x.bit_length() + 7) // 8, 'big')
+		t = (0x80 | len(lb)).to_bytes(1, 'big', signed = False)
+		return t+lb
+
+
+class DirtyDH:
+	def __init__(self):
+		self.p = None
+		self.g = None
+		self.shared_key = None
+		self.shared_key_int = None
+		self.private_key = os.urandom(32)
+		self.private_key_int = int(self.private_key.hex(), 16)
+		self.dh_nonce = os.urandom(32)
+
+	@staticmethod
+	def from_params(p, g):
+		dd = DirtyDH()
+		dd.p = p
+		dd.g = g
+		return dd
+
+	@staticmethod
+	def from_dict(dhp):
+		dd = DirtyDH()
+		dd.p = dhp['p']
+		dd.g = dhp['g']
+		return dd
+
+	@staticmethod
+	def from_asn1(asn1_bytes):
+		dhp = algos.DHParameters.load(asn1_bytes).native
+		return DirtyDH.from_dict(dhp)
+		
+	
+	def get_public_key(self):
+		#y = g^x mod p
+		return pow(self.g, self.private_key_int, self.p)
+	
+	def exchange(self, bob_int):
+		self.shared_key_int = pow(bob_int, self.private_key_int, self.p)
+		x = hex(self.shared_key_int)[2:]
+		if len(x) % 2 != 0:
+			x = '0' + x
+		self.shared_key = bytes.fromhex(x)
+		return self.shared_key
+
+
+class PKINIT:
+	def __init__(self):
+		self.privkeyinfo = None
+		self.certificate = None
+		self.extra_certs = None
+		self.user_sid = None
+		self.user_name = None
+		self.issuer = None
+		self.cname = None
+		self.diffie = None
+		self.__hcert = None
+
+
+	def init_windows_cert(self, username, certstore_name = 'MY', cert_serial = None):
+		from minikerberos.common.windows.crypt32 import find_cert_by_cn
+		self.certificate, self.__hcert = find_cert_by_cn(username, certstore_name)
+		
+
+	@staticmethod
+	def from_windows_certstore(username, certstore_name = 'MY', cert_serial = None, dh_params = None):
+		pkinit = PKINIT()
+		pkinit.init_windows_cert(username, certstore_name = certstore_name, cert_serial = cert_serial)
+		pkinit.setup(dh_params = dh_params)
+		return pkinit
+
+
+	@staticmethod
+	def from_pfx(pfxfile, pfxpass, dh_params = None):
+		pkinit = PKINIT()
+		#print('Loading pfx12')		
+		if isinstance(pfxpass, str):
+			pfxpass = pfxpass.encode()
+		with open(pfxfile, 'rb') as f:
+			pkinit.privkeyinfo, pkinit.certificate, pkinit.extra_certs = parse_pkcs12(f.read(), password = pfxpass)
+			pkinit.privkey = load_private_key(pkinit.privkeyinfo)
+		#print('pfx12 loaded!')
+		pkinit.setup(dh_params = dh_params)
+		return pkinit
+	
+	def setup(self, dh_params = None):
+		# parsing ceritficate to get basic info that will be needed to construct the asreq
+		# this has two components
+		for x in self.certificate.subject.native['common_name']:
+			if x.startswith("S-1-12"):
+				self.user_sid = x
+			elif x.find('@') != -1:
+				self.user_name = x
+
+		self.issuer = self.certificate.issuer.native['common_name']
+		self.cname = '\\'.join(['AzureAD', self.issuer, self.user_sid])
+
+		#print('cert issuer: %s' % pkinit.issuer)
+		#print('cert user_name: %s' % pkinit.user_name)
+		#print('cert user_sid: %s' % pkinit.user_sid)
+		#print('cert cname: %s' % pkinit.cname)
+
+		if dh_params is None:
+			print('Generating DH params...')
+			self.diffie = DirtyDH.from_dict( generate_dh_parameters(1024).native)
+			print('DH params generated.')
+		else:
+			#print('Loading default DH params...')
+			if isinstance(dh_params, dict):
+				self.diffie = DirtyDH.from_dict(dh_params)
+			elif isinstance(dh_params, bytes):
+				self.diffie = DirtyDH.from_asn1(dh_params)
+			elif isinstance(dh_params, DirtyDH):
+				self.diffie = dh_params
+			else:
+				raise Exception('DH params must be either a bytearray or a dict')
+
+
+	def build_asreq(self, target = None, cname = None, kdcopts = ['forwardable','renewable','proxiable', 'canonicalize']):
+		if isinstance(kdcopts, list):
+			kdcopts = set(kdcopts)
+		if cname is not None:
+			if isinstance(cname, str):
+				cname = [cname]
+		else:
+			cname = [self.cname]
+		
+		if target is not None:
+			if isinstance(target, str):
+				target = [target]
+		else:
+			target = ['127.0.0.1']
+
+		now = datetime.datetime.now(datetime.timezone.utc)
+
+		kdc_req_body_data = {}
+		kdc_req_body_data['kdc-options'] = KDCOptions(kdcopts)
+		kdc_req_body_data['cname'] = PrincipalName({'name-type': NAME_TYPE.MS_PRINCIPAL.value, 'name-string': cname})
+		kdc_req_body_data['realm'] = 'WELLKNOWN:PKU2U'
+		kdc_req_body_data['sname'] = PrincipalName({'name-type': NAME_TYPE.MS_PRINCIPAL.value, 'name-string': target})
+		kdc_req_body_data['till']  = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body_data['rtime'] = (now + datetime.timedelta(days=1)).replace(microsecond=0)
+		kdc_req_body_data['nonce'] = secrets.randbits(31)
+		kdc_req_body_data['etype'] = [18,17] # 23 breaks...
+		kdc_req_body_data['addresses'] = [HostAddress({'addr-type': 20, 'address': b'127.0.0.1'})] # not sure if this is needed
+		kdc_req_body = KDC_REQ_BODY(kdc_req_body_data)
+
+
+		checksum = hashlib.sha1(kdc_req_body.dump()).digest()
+		
+		authenticator = {}
+		authenticator['cusec'] = now.microsecond
+		authenticator['ctime'] = now.replace(microsecond=0)
+		authenticator['nonce'] = secrets.randbits(31)
+		authenticator['paChecksum'] = checksum
+		
+
+		dp = {}
+		dp['p'] = self.diffie.p
+		dp['g'] = self.diffie.g
+		dp['q'] = 0 # mandatory parameter, but it is not needed
+
+		pka = {}
+		pka['algorithm'] = '1.2.840.10046.2.1'
+		pka['parameters'] = keys.DomainParameters(dp)
+		
+		spki = {}
+		spki['algorithm'] = keys.PublicKeyAlgorithm(pka)
+		spki['public_key'] = self.diffie.get_public_key()
+
+		
+		authpack = {}
+		authpack['pkAuthenticator'] = PKAuthenticator(authenticator)
+		authpack['clientPublicValue'] = keys.PublicKeyInfo(spki)
+		authpack['clientDHNonce'] = self.diffie.dh_nonce
+		
+		authpack = AuthPack(authpack)
+		signed_authpack = self.sign_authpack(authpack.dump(), wrap_signed = False)
+		
+		# ??????? This is absolutely nonsense, 
+		payload = length_encode(len(signed_authpack)) + signed_authpack
+		payload = b'\x80' + payload
+		signed_authpack = b'\x30' + length_encode(len(payload)) + payload
+		
+		pa_data_1 = {}
+		pa_data_1['padata-type'] = PaDataType.PK_AS_REQ.value
+		pa_data_1['padata-value'] = signed_authpack 
+
+		asreq = {}
+		asreq['pvno'] = 5
+		asreq['msg-type'] = 10
+		asreq['padata'] = [pa_data_1]
+		asreq['req-body'] = kdc_req_body
+
+		return AS_REQ(asreq).dump()	
+
+	def build_apreq(self, asrep, session_key, cipher, subkey_data, krb_finished_data, flags = GSSAPIFlags.GSS_C_MUTUAL_FLAG | GSSAPIFlags.GSS_C_INTEG_FLAG  | GSSAPIFlags.GSS_C_EXTENDED_ERROR_FLAG):
+		
+
+		# TODO: https://www.ietf.org/rfc/rfc4757.txt
+		#subkey_data = {}
+		#subkey_data['keytype'] = Enctype.AES256
+		#subkey_data['keyvalue'] = os.urandom(32)
+
+		subkey_cipher = _enctype_table[subkey_data['keytype']]
+		subkey_key = Key(subkey_cipher.enctype, subkey_data['keyvalue'])
+		subkey_checksum = _checksum_table[16] # ChecksumTypes.hmac_sha1_96_aes256
+
+		krb_finished_checksum_data = {}
+		krb_finished_checksum_data['cksumtype'] = 16
+		krb_finished_checksum_data['checksum'] = subkey_checksum.checksum(subkey_key, 41, krb_finished_data)
+
+		krb_finished_data = {}
+		krb_finished_data['gss-mic'] = Checksum(krb_finished_checksum_data)
+
+		krb_finished = KRB_FINISHED(krb_finished_data).dump()
+
+		a = 2
+		extensions_data = a.to_bytes(4, byteorder='big', signed=True) + len(krb_finished).to_bytes(4, byteorder='big', signed=True) + krb_finished
+
+		ac = AuthenticatorChecksum()
+		ac.flags = flags
+		ac.channel_binding = b'\x00'*16
+		chksum = {}
+		chksum['cksumtype'] = 0x8003
+		chksum['checksum'] = ac.to_bytes() + extensions_data
+
+		tii = LSAP_TOKEN_INFO_INTEGRITY()
+		tii.Flags = 1
+		tii.TokenIL = 0x00002000 # Medium integrity
+		tii.MachineID = bytes.fromhex('7e303fffe6bff25146addca4fbddf1b94f1634178eb4528fb2731c669ca23cde')
+
+		restriction_data = {}
+		restriction_data['restriction-type'] = 0
+		restriction_data['restriction'] = tii.to_bytes()
+		restriction_data = KERB_AD_RESTRICTION_ENTRY(restriction_data)
+
+		x = KERB_AD_RESTRICTION_ENTRYS([restriction_data]).dump()
+		restrictions = AuthorizationData([{ 'ad-type' : 141, 'ad-data' : x}]).dump()
+
+		
+
+		now = datetime.datetime.now(datetime.timezone.utc)
+		authenticator_data = {}
+		authenticator_data['authenticator-vno'] = krb5_pvno 
+		authenticator_data['crealm'] = Realm(asrep['crealm'])
+		authenticator_data['cname'] = asrep['cname']
+		authenticator_data['cusec'] = now.microsecond
+		authenticator_data['ctime'] = now.replace(microsecond=0)
+		authenticator_data['subkey'] = EncryptionKey(subkey_data)
+		authenticator_data['seq-number'] = 682437742 #??? TODO: check this!
+		authenticator_data['authorization-data'] = AuthorizationData([{'ad-type': 1, 'ad-data' : restrictions}])
+		authenticator_data['cksum'] = Checksum(chksum)
+		
+		
+		#print('Authenticator(authenticator_data).dump()')
+		#print(Authenticator(authenticator_data).dump().hex())
+
+		authenticator_data_enc = cipher.encrypt(session_key, 11, Authenticator(authenticator_data).dump(), None)
+		
+		ap_opts = ['mutual-required']
+
+		ap_req = {}
+		ap_req['pvno'] = krb5_pvno
+		ap_req['msg-type'] = MESSAGE_TYPE.KRB_AP_REQ.value
+		ap_req['ticket'] = Ticket(asrep['ticket'])
+		ap_req['ap-options'] = APOptions(set(ap_opts))
+		ap_req['authenticator'] = EncryptedData({'etype': session_key.enctype, 'cipher': authenticator_data_enc})
+		
+		#pprint('AP_REQ \r\n%s' % AP_REQ(ap_req).native)
+		
+		#print(AP_REQ(ap_req).dump().hex())
+		#input()
+
+		return AP_REQ(ap_req).dump()
+
+	def sign_authpack(self, data, wrap_signed = False):
+		if self.__hcert is not None:
+			from minikerberos.common.windows.crypt32 import pkcs7_sign
+
+			return pkcs7_sign(self.__hcert, data)
+		return self.sign_authpack_native(self, data, wrap_signed)
+
+	def sign_authpack_native(self, data, wrap_signed = False):
+		"""
+		Creating PKCS7 blob which contains the following things:
+
+		1. 'data' blob which is an ASN1 encoded "AuthPack" structure
+		2. the certificate used to sign the data blob
+		3. the singed 'signed_attrs' structure (ASN1) which points to the "data" structure (in point 1)
+		"""
+		
+		da = {}
+		da['algorithm'] = algos.DigestAlgorithmId('1.3.14.3.2.26') # for sha1
+
+		si = {}
+		si['version'] = 'v1'
+		si['sid'] = cms.IssuerAndSerialNumber({
+			'issuer':  self.certificate.issuer,
+			'serial_number':  self.certificate.serial_number,
+		})
+
+
+		si['digest_algorithm'] = algos.DigestAlgorithm(da)
+		si['signed_attrs'] = [
+			cms.CMSAttribute({'type': 'content_type', 'values': ['1.3.6.1.5.2.3.1']}), # indicates that the encap_content_info's authdata struct (marked with OID '1.3.6.1.5.2.3.1' is signed )
+			cms.CMSAttribute({'type': 'message_digest', 'values': [hashlib.sha1(data).digest()]}), ### hash of the data, the data itself will not be signed, but this block of data will be.
+		]
+		si['signature_algorithm'] = algos.SignedDigestAlgorithm({'algorithm' : '1.2.840.113549.1.1.1'})
+		si['signature'] = rsa_pkcs1v15_sign(self.privkey,  cms.CMSAttributes(si['signed_attrs']).dump(), "sha1")
+
+		ec = {}
+		ec['content_type'] = '1.3.6.1.5.2.3.1'
+		ec['content'] = data
+
+		sd = {}
+		sd['version'] = 'v3'
+		sd['digest_algorithms'] = [algos.DigestAlgorithm(da)] # must have only one
+		sd['encap_content_info'] = cms.EncapsulatedContentInfo(ec)
+		sd['certificates'] = [self.certificate]
+		sd['signer_infos'] = cms.SignerInfos([cms.SignerInfo(si)])
+		
+		if wrap_signed is True:
+			ci = {}
+			ci['content_type'] = '1.2.840.113549.1.7.2' # signed data OID
+			ci['content'] = cms.SignedData(sd)
+			return cms.ContentInfo(ci).dump()
+
+		return cms.SignedData(sd).dump()
+		
+	
+	
+	
+	
+	def decrypt_asrep(self, as_rep):
+		
+
+		
+		def truncate_key(value, keysize):
+			output = b''
+			currentNum = 0
+			while len(output) < keysize:
+				currentDigest = hashlib.sha1(bytes([currentNum]) + value).digest()
+				if len(output) + len(currentDigest) > keysize:
+					output += currentDigest[:keysize - len(output)]
+					break
+				output += currentDigest
+				currentNum += 1
+			
+			return output
+
+		for pa in as_rep['padata']:
+			if pa['padata-type'] == 17:
+				pkasrep = PA_PK_AS_REP.load(pa['padata-value']).native
+				break
+		else:
+			raise Exception('PA_PK_AS_REP not found!')
+
+		sd = cms.SignedData.load(pkasrep['dhSignedData']).native
+		keyinfo = sd['encap_content_info']
+		if keyinfo['content_type'] != '1.3.6.1.5.2.3.2':
+			raise Exception('Keyinfo content type unexpected value')
+		authdata = KDCDHKeyInfo.load(keyinfo['content']).native
+		pubkey = int(''.join(['1'] + [str(x) for x in authdata['subjectPublicKey']]), 2)		
+
+		pubkey = int.from_bytes(core.BitString(authdata['subjectPublicKey']).dump()[7:], 'big', signed = False)
+		shared_key = self.diffie.exchange(pubkey)
+		
+		server_nonce = pkasrep['serverDHNonce']
+		fullKey = shared_key + self.diffie.dh_nonce + server_nonce
+
+		etype = as_rep['enc-part']['etype']
+		cipher = _enctype_table[etype]
+		if etype == Enctype.AES256:
+			t_key = truncate_key(fullKey, 32)
+		elif etype == Enctype.AES128:
+			t_key = truncate_key(fullKey, 16)
+		elif etype == Enctype.RC4:
+			raise NotImplementedError('RC4 key truncation documentation missing. it is different from AES')
+			#t_key = truncate_key(fullKey, 16)
+		
+
+		key = Key(cipher.enctype, t_key)
+		enc_data = as_rep['enc-part']['cipher']
+		dec_data = cipher.decrypt(key, 3, enc_data)
+		encasrep = EncASRepPart.load(dec_data).native
+		cipher = _enctype_table[ int(encasrep['key']['keytype'])]
+		session_key = Key(cipher.enctype, encasrep['key']['keyvalue'])
+		return encasrep, session_key, cipher
+
+	
+	def get_metadata(self, target = None):
+		
+
+
+		if target is not None:
+			if isinstance(target, str):
+				target = [target]
+		else:
+			target = ['127.0.0.1']
+
+		
+		ci = {}
+		ci['type'] = '2.5.4.3'
+		ci['value'] = self.issuer
+
+		a = Dunno1([ci])
+		ci = Dunno2([a])
+
+		info = {}
+		info['pku2u'] = 'WELLKNOWN:PKU2U'
+		info['clientInfo'] = PrincipalName({'name-type': NAME_TYPE.MS_PRINCIPAL.value, 'name-string': target})
+
+		md = {}
+		md['Info'] = Info(info)
+		md['1'] = [CertIssuer({'data' : ci.dump()})]
+
+		return MetaData(md).dump()
\ No newline at end of file
diff --git a/minikerberos/protocol/asn1_structs.py b/minikerberos/protocol/asn1_structs.py
index 17351ad..0345801 100644
--- a/minikerberos/protocol/asn1_structs.py
+++ b/minikerberos/protocol/asn1_structs.py
@@ -4,7 +4,13 @@
 #  Tamas Jos (@skelsec)
 #
 
+# Sources used:
 # https://zeroshell.org/kerberos/kerberos-operation/
+# https://tools.ietf.org/html/rfc4120
+# https://tools.ietf.org/html/rfc6113 (FAST extension)
+
+# TODO:
+# https://tools.ietf.org/html/rfc4556
 
 from asn1crypto import core
 import enum
@@ -19,16 +25,6 @@ APPLICATION = 1
 CONTEXT = 2
 krb5_pvno = 5 #-- current Kerberos protocol version number
 
-"""
-class NegotiationToken(core.Choice):
-	_alternatives = [
-		#('NegTokenInit2', NegTokenInit2, {'implicit': (0,16) }  ), #NegTokenInit2 the '2' in the name is because Microsoft added modifications to the original rfc :)
-		('NegTokenInit2', NegTokenInit2, {'implicit': (0,16) }  ), #NegTokenInit2 the '2' in the name is because Microsoft added modifications to the original rfc :)
-		('negTokenResp', negTokenResp, {'explicit': (2,1) } ),
-		
-]
-"""
-	
 class PADATA_TYPE(core.Enumerated):
 	_map = {
 		0   : 'NONE', #(0),
@@ -97,7 +93,7 @@ class PADATA_TYPE(core.Enumerated):
 class AUTHDATA_TYPE(core.Enumerated):
 	_map = {
 		1 : 'IF-RELEVANT', #1),
-		2 : 'INTENDED-FOR_SERVER', #2),
+		2 : 'INTENDED-FOR-SERVER', #2),
 		3 : 'INTENDED-FOR-APPLICATION-CLASS', #3),
 		4 : 'KDC-ISSUED', #4),
 		5 : 'AND-OR', #5),
@@ -108,6 +104,9 @@ class AUTHDATA_TYPE(core.Enumerated):
 		64 : 'OSF-DCE', #64),
 		65 : 'SESAME', #65),
 		66 : 'OSF-DCE-PKI-CERTID', #66),
+		70 : 'AD-authentication-strength',
+		71 : 'AD-fx-fast-armor',
+		72 : 'AD-fx-fast-used',
 		128 : 'WIN2K-PAC', #128),
 		129 : 'GSS-API-ETYPE-NEGOTIATION', #129), -- Authenticator only
 		-17 : 'SIGNTICKET-OLDER', #-17),
@@ -506,6 +505,86 @@ class AS_REQ(KDC_REQ):
 class TGS_REQ(KDC_REQ):
 	explicit = (APPLICATION, 12)
 
+class FastOptions(core.BitString):
+	_map = {
+		0:  'reserved',
+		1:  'hide-client-names',
+		2:  'critical_2', #forwarded',
+		3:  'critical_3', #proxiable',
+		4:  'critical_4', #proxy',
+		5:  'critical_5', #may-postdate',
+		6:  'critical_6', #postdated',
+		7:  'critical_7', #invalid',
+		8:  'critical_8', #renewable',
+		9:  'critical_9', #initial',
+		10: 'critical_10', #pre-authent',
+		11: 'critical_11', #hw-authent',
+		12: 'critical_12', #transited-policy-checked',
+		13: 'critical_13', #ok-as-delegate',
+		14: 'critical_14', #anonymous',
+		15: 'critical_15', #enc-pa-rep',
+		16: 'kdc-follow-referrals'
+	}
+	# error: KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTIONS   93
+#5.4.1.  FAST Armors
+
+class EncryptedChallenge(EncryptedData):
+	pass
+
+class AUTHENTICATION_SET_ELEM(core.SequenceOf):
+	_fields = [
+		('pa-type', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('pa-hint', core.OctetString, {'tag_type': TAG, 'tag': 1 , 'optional': True}),
+		('pa-value', core.OctetString, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+	]
+
+class AUTHENTICATION_SET(core.SequenceOf):
+	_child_spec = AUTHENTICATION_SET_ELEM
+
+class KrbFastArmor(core.Sequence):
+	_fields = [
+		('armor-type', krb5int32, {'tag_type': TAG, 'tag': 0}),
+		('armor-value', core.OctetString, {'tag_type': TAG, 'tag': 1}),
+	]
+
+
+class KrbFastArmoredReq(core.Sequence):
+	_fields = [
+		('armor', KrbFastArmor, {'tag_type': TAG, 'tag': 0, 'optional':True}),
+		('req-checksum', Checksum, {'tag_type': TAG, 'tag': 1}),
+		('enc-fast-req', EncryptedData, {'tag_type': TAG, 'tag': 2}), #KrbFastReq #KEY_USAGE_FAST_REQ_CHKSUM          50 #KEY_USAGE_FAST_ENC                 51
+	]
+
+class KrbFastReq(core.Sequence):
+	_fields = [
+		('fast-options', FastOptions, {'tag_type': TAG, 'tag': 0}),
+		('padata', METHOD_DATA, {'tag_type': TAG, 'tag': 1}),
+		('req-body', KDC_REQ_BODY, {'tag_type': TAG, 'tag': 2}),
+	]
+
+
+class KrbFastFinished(core.Sequence):
+	_fields = [
+		('timestamp', KerberosTime, {'tag_type': TAG, 'tag': 0}), 
+		('usec', Microseconds, {'tag_type': TAG, 'tag': 1}), 
+		('crealm', Realm, {'tag_type': TAG, 'tag': 2}), 
+		('cname', PrincipalName, {'tag_type': TAG, 'tag': 3}), 
+		('ticket-checksum', Checksum, {'tag_type': TAG, 'tag': 4}), #KEY_USAGE_FAST_FINISHED            53
+
+	]
+
+class KrbFastArmoredRep(core.Sequence):
+	_fields = [
+		('enc-fast-rep', EncryptedData, {'tag_type': TAG, 'tag': 0}), #KrbFastResponse KEY_USAGE_FAST_REP                 52
+	]
+
+class KrbFastResponse(core.Sequence):
+	_fields = [
+		('padata', METHOD_DATA, {'tag_type': TAG, 'tag': 0}),
+		('strengthen-key', EncryptionKey, {'tag_type': TAG, 'tag': 1, 'optional':True}),
+		('finished', KrbFastFinished, {'tag_type': TAG, 'tag': 2, 'optional':True}), #KrbFastReq #KEY_USAGE_FAST_REQ_CHKSUM          50 #KEY_USAGE_FAST_ENC                 51
+		('nonce', krb5uint32, {'tag_type': TAG, 'tag': 3}),
+	]
 
 #-- padata-type ::= PA-ENC-TIMESTAMP
 #-- padata-value ::= EncryptedData - PA-ENC-TS-ENC
@@ -518,6 +597,18 @@ class PA_PAC_OPTIONSTypes(core.BitString):
 			3: 'resource-based constrained delegation',
 		}
 
+class PA_FX_FAST_REQUEST(core.Choice):
+	_alternatives = [
+		('armored-data', KrbFastArmoredReq, {'explicit': (CONTEXT,0) }  ),
+	]
+
+class PA_FX_FAST_REPLY(core.Choice):
+	_alternatives = [
+		('armored-data', KrbFastArmoredRep, {'explicit': (CONTEXT,0) }  ),
+	]
+
+
+
 class PA_PAC_OPTIONS(core.Sequence):
 	_fields = [
 		('value', PA_PAC_OPTIONSTypes, {'tag_type': TAG, 'tag': 0}),
@@ -785,159 +876,18 @@ class AD_IF_RELEVANT(AuthorizationData):
 	pass
 
 
-#	
-#DOMAIN-X500-COMPRESS	krb5int32 ::= 1
-#
-#-- authorization data primitives
-#
-#AD-IF-RELEVANT ::= AuthorizationData
-#
-#AD-KDCIssued ::= SEQUENCE {
-#	ad-checksum[0]		Checksum,
-#	i-realm[1]		Realm OPTIONAL,
-#	i-sname[2]		PrincipalName OPTIONAL,
-#	elements[3]		AuthorizationData
-#}
-#
-#AD-AND-OR ::= SEQUENCE {
-#	condition-count[0]	INTEGER,
-#	elements[1]		AuthorizationData
-#}
-#
-#AD-MANDATORY-FOR-KDC ::= AuthorizationData
-#
-#-- PA-SAM-RESPONSE-2/PA-SAM-RESPONSE-2
-#
-#PA-SAM-TYPE ::= INTEGER {
-#	PA_SAM_TYPE_ENIGMA(1),		-- Enigma Logic
-#	PA_SAM_TYPE_DIGI_PATH(2),	-- Digital Pathways
-#	PA_SAM_TYPE_SKEY_K0(3),		-- S/key where  KDC has key 0
-#	PA_SAM_TYPE_SKEY(4),		-- Traditional S/Key
-#	PA_SAM_TYPE_SECURID(5),		-- Security Dynamics
-#	PA_SAM_TYPE_CRYPTOCARD(6)	-- CRYPTOCard
-#}
-#
-#PA-SAM-REDIRECT ::= HostAddresses
-#
-#SAMFlags ::= BIT STRING {
-#	use-sad-as-key(0),
-#	send-encrypted-sad(1),
-#	must-pk-encrypt-sad(2)
-#}
-#
-#PA-SAM-CHALLENGE-2-BODY ::= SEQUENCE {
-#	sam-type[0]		krb5int32,
-#	sam-flags[1]		SAMFlags,
-#	sam-type-name[2]	GeneralString OPTIONAL,
-#	sam-track-id[3]		GeneralString OPTIONAL,
-#	sam-challenge-label[4]	GeneralString OPTIONAL,
-#	sam-challenge[5]	GeneralString OPTIONAL,
-#	sam-response-prompt[6]	GeneralString OPTIONAL,
-#	sam-pk-for-sad[7]	EncryptionKey OPTIONAL,
-#	sam-nonce[8]		krb5int32,
-#	sam-etype[9]		krb5int32,
-#	...
-#}
-#
-#PA-SAM-CHALLENGE-2 ::= SEQUENCE {
-#	sam-body[0]		PA-SAM-CHALLENGE-2-BODY,
-#	sam-cksum[1]		SEQUENCE OF Checksum, -- (1..MAX)
-#	...
-#}
-#
-#PA-SAM-RESPONSE-2 ::= SEQUENCE {
-#	sam-type[0]		krb5int32,
-#	sam-flags[1]		SAMFlags,
-#	sam-track-id[2]		GeneralString OPTIONAL,
-#	sam-enc-nonce-or-sad[3]	EncryptedData, -- PA-ENC-SAM-RESPONSE-ENC
-#	sam-nonce[4]		krb5int32,
-#	...
-#}
-#
-#PA-ENC-SAM-RESPONSE-ENC ::= SEQUENCE {
-#	sam-nonce[0]		krb5int32,
-#	sam-sad[1]		GeneralString OPTIONAL,
-#	...
-#}
-#
-#PA-S4U2Self ::= SEQUENCE {
-#	name[0]		PrincipalName,
-#        realm[1]	Realm,
-#        cksum[2]	Checksum,
-#        auth[3]		GeneralString
-#}
-#	
-#	
-#	
-#	
-#	
-#
-#
-#
-#
-#
-#
-## https://github.com/tiran/kkdcpasn1/blob/asn1crypto/pykkdcpasn1.py
-#class EncryptedData(core.Sequence):
-#	"""EncryptedData
-#	* KDC-REQ-BODY
-#	* Ticket
-#	* AP-REQ
-#	* KRB-PRIV
-#	EncryptedData ::= SEQUENCE {
-#		etype		[0] Int32,
-#		kvno		 [1] UInt32 OPTIONAL,
-#		cipher	   [2] OCTET STRING
-#	}
-#	"""
-#	_fields = [
-#		('etype', Int32, {'tag_type': TAG, 'tag': 0}),
-#		('kvno', UInt32, {'tag_type': TAG, 'tag': 1, 'optional': True}),
-#		('cipher', core.OctetString, {'tag_type': TAG, 'tag': 2}),
-#]
-#
-#class EncryptionKey(core.Sequence):
-#	"""
-#	EncryptionKey ::= SEQUENCE {
-#	keytype[0]		krb5int32,
-#	keyvalue[1]		OCTET STRING
-#	}
-#	"""
-#	_fields = [
-#		('keytype', Int32, {'tag_type': TAG, 'tag': 0}),
-#		('keyvalue', core.OctetString, {'tag_type': TAG, 'tag': 1}),
-#]
-#
-#	
-#
-#
-#
-#
+class GSSAPIOID(core.ObjectIdentifier):
+	_map = {
+		'1.2.840.113554.1.2.2': 'krb5',
+	}
+
+	_reverse_map = {
+		'krb5': '1.2.840.113554.1.2.2',
+	}
+
+
+class GSSAPIToken(core.Asn1Value):
+	class_ = 1
+	tag = 0
+	method = 1
 
-#
-#
-#class SequenceOfInt32(core.SequenceOf):
-#	"""SEQUENCE OF Int32 for KDC-REQ-BODY
-#	"""
-#	_child_spec = Int32
-#
-#
-#	
-#class SequenceOfKrbCredInfo(core.SequenceOf):
-#	_child_spec = KrbCredInfo
-#	
-#	
-#class EncKrbCredPart(core.Sequence):
-#	explicit = (1, 29)
-#	
-#	_fields = [
-#		('ticket-info', SequenceOfKrbCredInfo, {'tag_type': TAG, 'tag': 0}),
-#		('nonce', Int32, {'tag_type': TAG, 'tag': 1, 'optional': True}),
-#		('timestamp', KerberosTime , {'tag_type': TAG, 'tag': 2, 'optional': True}),
-#		('usec', Microseconds , {'tag_type': TAG, 'tag': 3, 'optional': True}),
-#		('s-address', HostAddress , {'tag_type': TAG, 'tag': 4, 'optional': True}),
-#		('r-address', HostAddress , {'tag_type': TAG, 'tag': 5, 'optional': True}),
-#	]
-#	
-#
-#
diff --git a/minikerberos/protocol/constants.py b/minikerberos/protocol/constants.py
index a4a626e..2aabe97 100644
--- a/minikerberos/protocol/constants.py
+++ b/minikerberos/protocol/constants.py
@@ -99,6 +99,7 @@ class PaDataType(enum.Enum):
 	TD_APP_DEFINED_ERROR = 106#	__ application specific
 	TD_REQ_NONCE = 107#		__ INTEGER
 	TD_REQ_SEQ = 108#		__ INTEGER
+	TD_DH_PARAMETERS = 109 #__ PKINIT
 	PA_PAC_REQUEST = 128#	__ jbrezak@exchange.microsoft.com
 	FOR_USER = 129#		__ MS_KILE
 	FOR_X509_USER = 130#		__ MS_KILE
@@ -122,4 +123,56 @@ class PaDataType(enum.Enum):
 	PKU2U_NAME = 148#		__ zhu_pku2u
 	REQ_ENC_PA_REP = 149#	__
 	SPAKE = 151#	__https://datatracker.ietf.org/doc/draft-ietf-kitten-krb-spake-preauth/?include_text=1
-	SUPPORTED_ETYPES = 165 #)	__ MS_KILE
\ No newline at end of file
+	SUPPORTED_ETYPES = 165 #)	__ MS_KILE
+
+
+# Full list of key_usage numbers: https://tools.ietf.org/html/rfc4120#section-7.5.1
+# 
+class KEY_USAGE(enum.Enum):
+	AS_REQ_PA_ENC_TS = 1
+	KDC_REP_TICKET = 2
+	AS_REP_ENCPART = 3
+	TGS_REQ_AD_SESSKEY = 4
+	TGS_REQ_AD_SUBKEY = 5
+	TGS_REQ_AUTH_CKSUM = 6
+	TGS_REQ_AUTH = 7
+	TGS_REP_ENCPART_SESSKEY = 8
+	TGS_REP_ENCPART_SUBKEY = 9
+	AP_REQ_AUTH_CKSUM = 10
+	AP_REQ_AUTH = 11
+	AP_REP_ENCPART = 12
+	KRB_PRIV_ENCPART = 13
+	KRB_CRED_ENCPART = 14
+	KRB_SAFE_CKSUM = 15
+	APP_DATA_ENCRYPT = 16
+	APP_DATA_CKSUM = 17
+	KRB_ERROR_CKSUM = 18
+	AD_KDCISSUED_CKSUM = 19
+	AD_MTE = 20
+	AD_ITE = 21
+
+	GSS_TOK_MIC = 22
+	GSS_TOK_WRAP_INTEG = 23
+	GSS_TOK_WRAP_PRIV = 24
+
+	PA_SAM_CHALLENGE_CKSUM = 25
+	#PA_SAM_CHALLENGE_TRACKID  26 #/** Note conflict with @ref KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST */
+	#PA_SAM_RESPONSE           27 #/** Note conflict with @ref KRB5_KEYUSAGE_PA_S4U_X509_USER_REPLY */
+	
+	PA_S4U_X509_USER_REQUEST = 26 #/* Defined in [MS-SFU] *//** Note conflict with @ref KRB5_KEYUSAGE_PA_SAM_CHALLENGE_TRACKID */
+	PA_S4U_X509_USER_REPLY = 27 #/** Note conflict with @ref KRB5_KEYUSAGE_PA_SAM_RESPONSE */
+
+	AD_SIGNEDPATH = -21
+	IAKERB_FINISHED = 42
+	PA_PKINIT_KX = 44
+	PA_OTP_REQUEST = 45 
+
+	FAST_REQ_CHKSUM = 50
+	FAST_ENC = 51
+	FAST_REP = 52
+	FAST_FINISHED = 53
+	ENC_CHALLENGE_CLIENT = 54
+	ENC_CHALLENGE_KDC = 55
+	AS_REQ = 56
+	CAMMAC = 64
+	SPAKE = 65
\ No newline at end of file
diff --git a/minikerberos/protocol/encryption.py b/minikerberos/protocol/encryption.py
index 9edcf93..8fb7609 100644
--- a/minikerberos/protocol/encryption.py
+++ b/minikerberos/protocol/encryption.py
@@ -58,6 +58,7 @@ from minikerberos.crypto.AES import *
 from minikerberos.crypto.DES import DES3, DES_CBC, DES_ECB
 from minikerberos.crypto.RC4 import RC4 as ARC4
 from minikerberos.crypto.DES import DES
+from minikerberos.crypto.hashing import md4
 
 
 def get_random_bytes(lenBytes):
@@ -517,7 +518,9 @@ class _RC4(_EnctypeProfile):
 	@classmethod
 	def string_to_key(cls, string, salt, params):
 		utf16string = string.decode('UTF-8').encode('UTF-16LE')
-		return Key(cls.enctype, hashlib.new('md4', utf16string).digest())
+		#return Key(cls.enctype, hashlib.new('md4', utf16string).digest())
+		data = md4(utf16string).digest() #hashlib.new('md4', utf16string).digest()
+		return Key(cls.enctype, data)
 
 	@classmethod
 	def encrypt(cls, key, keyusage, plaintext, confounder):
diff --git a/minikerberos/protocol/errors.py b/minikerberos/protocol/errors.py
index 6833db4..8c4b78b 100644
--- a/minikerberos/protocol/errors.py
+++ b/minikerberos/protocol/errors.py
@@ -6,16 +6,24 @@
 import enum
 
 class KerberosError(Exception):
-	def __init__(self, krb_err_msg):
+	def __init__(self, krb_err_msg, extra_msg = ''):
 		self.krb_err_msg = krb_err_msg.native
-		self.errorcode = KerberosErrorCode(self.krb_err_msg['error-code'])
-		self.errormsg = KerberosErrorMessage[self.errorcode.name]
+		self.errorcode = KerberosErrorCode.ERR_NOT_FOUND
+		self.errormsg = 'Error code not found! Err code: %s' % self.krb_err_msg['error-code']
+		try:
+			self.errorcode = KerberosErrorCode(self.krb_err_msg['error-code'])
+			self.errormsg = KerberosErrorMessage[self.errorcode.name]
+		except:
+			pass
+		self.extra_msg = extra_msg
 		
-		super(Exception, self).__init__('%s Error Core: %d' % (self.errormsg.value, self.errorcode.value)) 
+		super(Exception, self).__init__('%s Error Code: %d Reason: %s ' % (extra_msg, self.errorcode.value, self.errormsg.value))
+	
 		
 
 # https://technet.microsoft.com/en-us/library/bb463166.aspx
 class KerberosErrorCode(enum.Enum):
+	ERR_NOT_FOUND = 0xFFFFFF
 	KDC_ERR_NONE = 0x0 #No error
 	KDC_ERR_NAME_EXP = 0x1 #Client's entry in KDC database has expired
 	KDC_ERR_SERVICE_EXP = 0x2 #Server's entry in KDC database has expired
@@ -70,10 +78,22 @@ class KerberosErrorCode(enum.Enum):
 	KDC_ERR_CLIENT_NOT_TRUSTED = 0x3E # The client trust failed or is not implemented
 	KDC_ERR_KDC_NOT_TRUSTED = 0x3F # The KDC server trust failed or could not be verified
 	KDC_ERR_INVALID_SIG = 0x40 # The signature is invalid
-	KDC_ERR_KEY_TOO_WEAK = 0x41 #A higher encryption level is needed
+	KDC_ERR_KEY_TOO_WEAK = 0x41 #A higher encryption level is needed, in PKINIT it's KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED
 	KRB_AP_ERR_USER_TO_USER_REQUIRED = 0x42# User-to-user authorization is required
 	KRB_AP_ERR_NO_TGT  = 0x43 # No TGT was presented or available
 	KDC_ERR_WRONG_REALM = 0x44 #Incorrect domain or principal
+	KDC_ERR_CANT_VERIFY_CERTIFICATE = 0x46
+	KDC_ERR_INVALID_CERTIFICATE = 0x47
+	KDC_ERR_REVOKED_CERTIFICATE = 0x48
+	KDC_ERR_REVOCATION_STATUS_UNKNOWN = 0x49
+	KDC_ERR_CLIENT_NAME_MISMATCH = 0x4B
+	KDC_ERR_INCONSISTENT_KEY_PURPOSE = 0x4D
+	KDC_ERR_DIGEST_IN_CERT_NOT_ACCEPTED = 0x4E
+	KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED = 0x4F
+	KDC_ERR_DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED = 0x50
+	KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED = 0x51
+	KRB_AP_ERR_IAKERB_KDC_NOT_FOUND = 0x55 #https://datatracker.ietf.org/doc/html/draft-ietf-kitten-iakerb
+	KRB_AP_ERR_IAKERB_KDC_NO_RESPONSE = 0x56 #https://datatracker.ietf.org/doc/html/draft-ietf-kitten-iakerb
 	
 class KerberosErrorMessage(enum.Enum):
 	KDC_ERR_NONE = 'No error'
@@ -133,4 +153,6 @@ class KerberosErrorMessage(enum.Enum):
 	KDC_ERR_KEY_TOO_WEAK = 'A higher encryption level is needed'
 	KRB_AP_ERR_USER_TO_USER_REQUIRED ='User-to-user authorization is required'
 	KRB_AP_ERR_NO_TGT  ='No TGT was presented or available'
-	KDC_ERR_WRONG_REALM = 'Incorrect domain or principal'
\ No newline at end of file
+	KDC_ERR_WRONG_REALM = 'Incorrect domain or principal'
+	KRB_AP_ERR_IAKERB_KDC_NOT_FOUND = 'The IAKERB proxy could not find a KDC'
+	KRB_AP_ERR_IAKERB_KDC_NO_RESPONSE = 'The KDC did not respond to the IAKERB proxy'
\ No newline at end of file
diff --git a/minikerberos/protocol/mskile.py b/minikerberos/protocol/mskile.py
new file mode 100644
index 0000000..d8ec188
--- /dev/null
+++ b/minikerberos/protocol/mskile.py
@@ -0,0 +1,93 @@
+import os
+from asn1crypto import core
+from minikerberos.protocol.asn1_structs import EncryptionKey, Checksum, KerberosTime, Realm
+
+TAG = 'explicit'
+
+# class
+UNIVERSAL = 0
+APPLICATION = 1
+CONTEXT = 2
+
+
+########
+# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/1aeca7fb-d6b4-4402-8fa4-6ec3e955c16e
+class KERB_AD_RESTRICTION_ENTRY(core.Sequence):
+    _fields = [
+        ('restriction-type', core.Integer, {'tag_type': TAG, 'tag': 0}),
+        ('restriction', core.OctetString, {'tag_type': TAG, 'tag': 1}),
+    ]
+    
+class KERB_AD_RESTRICTION_ENTRYS(core.SequenceOf):
+    _child_spec = KERB_AD_RESTRICTION_ENTRY
+
+
+# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/25fabd02-560d-4c1f-8f42-b32e9d97996a
+class KERB_ERROR_DATA(core.Sequence):
+    _fields = [
+        ('data-type', core.Integer, {'tag_type': TAG, 'tag': 1}),
+        ('data-value', core.OctetString, {'tag_type': TAG, 'tag': 2, 'optional': True}),
+    ]
+
+# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/765795ba-9e05-4220-9bd3-b34464e413a7
+class KERB_PA_PAC_REQUEST(core.Sequence):
+    _fields = [
+        ('include-pac', core.Boolean, {'tag_type': TAG, 'tag': 0}),
+    ]
+
+
+## implementation specific??????
+# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/2a01b297-c47f-4547-9268-cf589aedd063
+#class KERB_LOCAL(core.Sequence):
+#    _fields = [
+#    ]
+
+
+# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/ec551137-c5e5-476a-9c89-e0029473c41b
+class LSAP_TOKEN_INFO_INTEGRITY:
+	def __init__(self, machine_id = os.urandom(32)):
+		self.Flags = None # unsigned long
+		self.TokenIL = None # unsigned long
+		self.MachineID = machine_id # KILE implements a 32-byte binary random string machine ID.
+
+	def to_bytes(self):
+		t = self.Flags.to_bytes(4, byteorder='little', signed = False)
+		t += self.TokenIL.to_bytes(4, byteorder='little', signed = False)
+		t += self.MachineID
+		return t
+	
+	@staticmethod
+	def from_bytes(data):
+		return LSAP_TOKEN_INFO_INTEGRITY.from_buffer(io.BytesIO(data))
+	
+	@staticmethod
+	def from_buffer(buff):
+		msg = LSAP_TOKEN_INFO_INTEGRITY()
+		msg.Flags = int.from_bytes(buff.read(4), byteorder='little', signed = False)
+		msg.TokenIL = int.from_bytes(buff.read(4), byteorder='little', signed = False)
+		msg.MachineID = buff.read(32)
+		return msg
+
+
+class KERB_KEY_LIST_REP(core.SequenceOf):
+	_child_spec = EncryptionKey
+
+class KERB_KEY_LIST_REQ(core.SequenceOf):
+	_child_spec = core.Integer
+
+
+
+#### TODO
+"""
+# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/15fa77fb-deaa-487d-b685-1310afe45ca1
+ typedef struct KERB_EXT_ERROR {
+     unsigned long status;
+     unsigned long reserved;
+     unsigned long flags;
+ } KERB_EXT_ERROR;
+
+# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/25fabd02-560d-4c1f-8f42-b32e9d97996a
+
+
+
+"""
\ No newline at end of file
diff --git a/minikerberos/protocol/rfc4556.py b/minikerberos/protocol/rfc4556.py
new file mode 100644
index 0000000..4c30a7e
--- /dev/null
+++ b/minikerberos/protocol/rfc4556.py
@@ -0,0 +1,136 @@
+from asn1crypto import core
+from asn1crypto.keys import PublicKeyInfo as SubjectPublicKeyInfo
+from asn1crypto.x509 import AlgorithmIdentifier
+from minikerberos.protocol.asn1_structs import EncryptionKey, Checksum, KerberosTime, PrincipalName, Realm
+
+
+#### PKINIT ASN1 strructs
+#### RFC4556 # https://tools.ietf.org/html/rfc4556 ####
+
+# KerberosV5Spec2 DEFINITIONS EXPLICIT TAGS ::=
+TAG = 'explicit'
+
+# class
+UNIVERSAL = 0
+APPLICATION = 1
+CONTEXT = 2
+
+class DHNonce(core.OctetString):
+	pass
+
+class AlgorithmIdentifiers(core.SequenceOf):
+	_child_spec = AlgorithmIdentifier
+
+class TD_DH_PARAMETERS(core.SequenceOf):
+	_child_spec = AlgorithmIdentifier
+
+class ReplyKeyPack(core.Sequence):
+	_fields = [
+		('replyKey', EncryptionKey, {'tag_type': TAG, 'tag': 0}), 
+		('asChecksum', Checksum, {'tag_type': TAG, 'tag': 1}),
+		('dhKeyExpiration', KerberosTime, {'tag_type': TAG, 'tag': 2, 'optional': True}),
+	]
+
+class KDCDHKeyInfo(core.Sequence):
+	_fields = [
+		('subjectPublicKey', core.BitString, {'tag_type': TAG, 'tag': 0}), 
+		('nonce', core.Integer, {'tag_type': TAG, 'tag': 1}),
+		('dhKeyExpiration', KerberosTime, {'tag_type': TAG, 'tag': 2, 'optional': True}),
+	]
+
+class DHRepInfo(core.Sequence):
+	_fields = [
+		('dhSignedData', core.OctetString, {'tag_type': 'implicit', 'tag': 0}), 
+		('serverDHNonce', DHNonce, {'tag_type': TAG, 'tag': 1, 'optional': True}),
+
+	]
+
+class PA_PK_AS_REP(core.Choice):
+	_alternatives = [
+		('dhInfo', DHRepInfo, {'explicit': (CONTEXT,0) }  ),
+		('encKeyPack', core.OctetString, {'implicit': (CONTEXT,1) }  ),
+	]
+
+class ExternalPrincipalIdentifier(core.Sequence):
+	_fields = [
+		('subjectName', core.OctetString, {'tag_type': 'implicit', 'tag': 0, 'optional' : True}), 
+		('issuerAndSerialNumber', core.OctetString, {'tag_type': 'implicit', 'tag': 1, 'optional' : True}),
+		('subjectKeyIdentifier', core.OctetString, {'tag_type': 'implicit', 'tag': 2, 'optional' : True}), 
+	]
+
+class ExternalPrincipalIdentifiers(core.SequenceOf):
+	_child_spec = ExternalPrincipalIdentifier
+
+class AD_INITIAL_VERIFIED_CAS(core.SequenceOf):
+	_child_spec = ExternalPrincipalIdentifier
+
+class KRB5PrincipalName(core.Sequence):
+	_fields = [
+		('realm', Realm, {'tag_type': TAG, 'tag': 0}), 
+		('principalName', PrincipalName, {'tag_type': TAG, 'tag': 1}),
+	]
+
+class TD_INVALID_CERTIFICATES(core.SequenceOf):
+	_child_spec = ExternalPrincipalIdentifier
+
+class TD_TRUSTED_CERTIFIERS(core.SequenceOf):
+	_child_spec = ExternalPrincipalIdentifier
+
+class PKAuthenticator(core.Sequence):
+	_fields = [
+		('cusec', core.Integer, {'tag_type': TAG, 'tag': 0}), 
+		('ctime', KerberosTime, {'tag_type': TAG, 'tag': 1}),
+		('nonce', core.Integer, {'tag_type': TAG, 'tag': 2}),
+		('paChecksum', core.OctetString, {'tag_type': TAG, 'tag': 3, 'optional': True}),
+	]
+
+class AuthPack(core.Sequence):
+	_fields = [
+		('pkAuthenticator', PKAuthenticator, {'tag_type': TAG, 'tag': 0}), 
+		('clientPublicValue', SubjectPublicKeyInfo, {'tag_type': TAG, 'tag': 1, 'optional' : True}),
+		('supportedCMSTypes', AlgorithmIdentifiers, {'tag_type': TAG, 'tag': 2, 'optional' : True}), 
+		('clientDHNonce', DHNonce, {'tag_type': TAG, 'tag': 3, 'optional' : True}), 
+
+	]
+
+class PA_PK_AS_REQ(core.Sequence):
+	_fields = [
+		('signedAuthPack', core.OctetString, {'tag_type': 'implicit', 'tag': 0}), 
+		('trustedCertifiers', ExternalPrincipalIdentifiers, {'tag_type': TAG, 'tag': 1, 'optional' : True}),
+		('kdcPkId', core.OctetString, {'tag_type': 'implicit', 'tag': 2, 'optional' : True}), 
+	]
+
+
+# TODO: figure out what structs these are...
+
+class NameTypeAndValueBMP(core.Sequence):
+	_fields = [
+		('type', core.ObjectIdentifier),
+		('value', core.BMPString),
+	]
+
+class Dunno1(core.SetOf):
+	_child_spec = NameTypeAndValueBMP
+
+class Dunno2(core.SequenceOf):
+	_child_spec = Dunno1
+
+class Info(core.Sequence):
+	_fields = [
+		('pku2u', core.GeneralString, {'tag_type': TAG, 'tag': 0}),
+		('clientInfo', PrincipalName, {'tag_type': TAG, 'tag': 1}),
+	]
+
+class CertIssuer(core.Sequence):
+	_fields = [
+		('data', core.OctetString, {'tag_type': 'implicit', 'tag': 0}), # there is another ASN1 encoded blob here that contains the issuer. Classes X and Y deal with that. No documentation....
+	]
+
+class CertIssuers(core.SequenceOf):
+	_child_spec = CertIssuer
+
+class MetaData(core.Sequence):
+	_fields = [
+		('1', CertIssuers, {'tag_type': TAG, 'tag': 0}), 
+		('Info', Info, {'tag_type': TAG, 'tag': 1}),
+	]
\ No newline at end of file
diff --git a/minikerberos/protocol/rfc_iakerb.py b/minikerberos/protocol/rfc_iakerb.py
new file mode 100644
index 0000000..820e489
--- /dev/null
+++ b/minikerberos/protocol/rfc_iakerb.py
@@ -0,0 +1,29 @@
+
+from asn1crypto import core
+from minikerberos.protocol.asn1_structs import Checksum
+
+# GSS_EXTS_FINISHED             2 #Data type for the IAKERB checksum.
+# corresponding checksum type: KEY_USAGE_FINISHED            41
+# https://tools.ietf.org/html/draft-ietf-kitten-iakerb-03
+
+
+TAG = 'explicit'
+
+class KRB_FINISHED(core.Sequence):
+    _fields = [
+        ('gss-mic', Checksum, {'tag_type': TAG, 'tag': 1}),
+    ]
+
+	# Contains the checksum [RFC3961] of the GSS-API tokens
+	# exchanged between the initiator and the acceptor,
+	# and prior to the containing AP_REQ GSS-API token.
+	# The checksum is performed over the GSS-API tokens
+	# exactly as they were transmitted and received,
+	# in the order that the tokens were sent.
+
+
+class IAKERB_HEADER(core.Sequence):
+    _fields = [
+        ('target-realm', core.GeneralString, {'tag_type': TAG, 'tag': 1}),
+		('cookie', core.OctetString, {'tag_type': TAG, 'tag': 2, 'optional': True}),
+    ]
\ No newline at end of file
diff --git a/minikerberos/protocol/structures.py b/minikerberos/protocol/structures.py
index 78f4479..3f05ee5 100644
--- a/minikerberos/protocol/structures.py
+++ b/minikerberos/protocol/structures.py
@@ -1,5 +1,8 @@
 import io
 import enum
+import base64
+
+from minikerberos.protocol.asn1_structs import GSSAPIOID, GSSAPIToken
 
 #https://tools.ietf.org/html/rfc4121#section-4.1.1.1
 class ChecksumFlags(enum.IntFlag):
@@ -24,7 +27,7 @@ class AuthenticatorChecksum:
 		
 	@staticmethod
 	def from_bytes(data):
-		AuthenticatorChecksum.from_buffer(io.BytesIO(data))
+		return AuthenticatorChecksum.from_buffer(io.BytesIO(data))
 		
 	@staticmethod
 	def from_buffer(buffer):
@@ -33,9 +36,9 @@ class AuthenticatorChecksum:
 		ac.channel_binding = buffer.read(ac.length_of_binding) #according to the latest RFC this is 16 bytes long always
 		ac.flags = ChecksumFlags(int.from_bytes(buffer.read(4), byteorder = 'little', signed = False))
 		if ac.flags & ChecksumFlags.GSS_C_DELEG_FLAG:
-			ac.delegation = bool(int.from_bytes(buffer.read(1), byteorder = 'little', signed = False))
-			ac.delegation_length = int.from_bytes(2, byteorder = 'little', signed = False)
-			ac.delegation_data = int.from_bytes(ac.delegation_length, byteorder = 'little', signed = False)
+			ac.delegation = bool(int.from_bytes(buffer.read(2), byteorder = 'little', signed = False))
+			ac.delegation_length = int.from_bytes(buffer.read(2), byteorder = 'little', signed = False)
+			ac.delegation_data = buffer.read(ac.delegation_length)
 		ac.extensions = buffer.read()
 		return ac
 		
@@ -45,9 +48,32 @@ class AuthenticatorChecksum:
 		t += self.channel_binding
 		t += self.flags.to_bytes(4, byteorder = 'little', signed = False)
 		if self.flags & ChecksumFlags.GSS_C_DELEG_FLAG:
-			t += int(self.delegation).to_bytes(1, byteorder = 'little', signed = False)
+			t += int(self.delegation).to_bytes(2, byteorder = 'little', signed = False)
 			t += len(self.delegation_data.to_bytes()).to_bytes(2, byteorder = 'little', signed = False)
 			t += self.delegation_data.to_bytes()
 		if self.extensions:
 			t += self.extensions.to_bytes()
-		return t
\ No newline at end of file
+		return t
+
+
+# KRB5Token TOK_ID values.
+class KRB5TokenTokID(enum.IntFlag):
+	KRB_AP_REQ = 0x0100
+	KRB_AP_REP = 0x0200
+	KRB_ERROR = 0x0300
+
+	def get_bytes(self):
+		return self.value.to_bytes(2, byteorder='big')
+
+
+# https://tools.ietf.org/html/rfc4121#section-4.1
+class KRB5Token:
+	def __init__(self, inner_token):
+		self.oid = GSSAPIOID('krb5')
+		self.inner_token = inner_token
+
+	def get_apreq_token(self, encoding='utf-8'):
+		token = self.oid.dump()
+		token += KRB5TokenTokID.KRB_AP_REQ.get_bytes()
+		token += self.inner_token
+		return str(base64.b64encode(GSSAPIToken(contents=token).dump()), encoding)
diff --git a/minikerberos/security.py b/minikerberos/security.py
index b434224..981a454 100644
--- a/minikerberos/security.py
+++ b/minikerberos/security.py
@@ -88,7 +88,7 @@ class APREPRoast:
 			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)))
-
+			raise e
 
 class Kerberoast:
 	def __init__(self, target: KerberosTarget, cred: KerberosCredential):
@@ -102,6 +102,7 @@ class Kerberoast:
 		except Exception as e:
 			logger.exception('a')
 			logger.debug('Error logging in! Reason: %s' % (str(e)))
+			raise e
 
 		results = []
 		for spn in spns:
diff --git a/setup.py b/setup.py
index b14bed5..9686983 100644
--- a/setup.py
+++ b/setup.py
@@ -38,14 +38,16 @@ setup(
 
 	# long_description=open("README.txt").read(),
 	python_requires='>=3.6',
-	classifiers=(
+	classifiers=[
 		"Programming Language :: Python :: 3.6",
 		"License :: OSI Approved :: MIT License",
 		"Operating System :: OS Independent",
-	),
+	],
 	install_requires=[
 		'asn1crypto>=1.3.0',
-		'asysocks>=0.0.2',
+		'oscrypto>=1.2.1',
+		'asysocks>=0.0.11',
+
 	],
 
 	entry_points={