Coverage for /root/GitHubProjects/impacket/impacket/krb5/keytab.py : 23%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Author: Patrick Welzel (@kcirtapw)
2#
3# Description:
4# Kerberos Keytab format implementation
5# based on file format described at:
6# https://repo.or.cz/w/krb5dissect.git/blob_plain/HEAD:/keytab.txt
7# As the ccache implementation, pretty lame and quick
8# Feel free to improve
9#
10from datetime import datetime
11from enum import Enum
12from six import b
14from struct import pack, unpack, calcsize
15from binascii import hexlify
17from impacket.structure import Structure
18from impacket import LOG
21class Enctype(Enum):
22 DES_CRC = 1
23 DES_MD4 = 2
24 DES_MD5 = 3
25 DES3 = 16
26 AES128 = 17
27 AES256 = 18
28 RC4 = 23
31class CountedOctetString(Structure):
32 """
33 Note: This is very similar to the CountedOctetString structure in ccache, except:
34 * `length` is uint16 instead of uint32
35 """
36 structure = (
37 ('length','!H=0'),
38 ('_data','_-data','self["length"]'),
39 ('data',':'),
40 )
42 def prettyPrint(self, indent=''):
43 return "%s%s" % (indent, hexlify(self['data']))
46class KeyBlock(Structure):
47 structure = (
48 ('keytype','!H=0'),
49 ('keyvalue',':', CountedOctetString),
50 )
52 def prettyKeytype(self):
53 try:
54 return Enctype(self['keytype']).name
55 except:
56 return "UNKNOWN:0x%x" % (self['keytype'])
58 def hexlifiedValue(self):
59 return hexlify(self['keyvalue']['data'])
61 def prettyPrint(self):
62 return "(%s)%s" % (self.prettyKeytype(), self.hexlifiedValue())
65class KeytabPrincipal:
66 """
67 Note: This is very similar to the principal structure in ccache, except:
68 * `num_components` is just uint16
69 * using other size type for CountedOctetString
70 * `name_type` field follows the other fields behind.
71 """
72 class PrincipalHeader1(Structure):
73 structure = (
74 ('num_components', '!H=0'),
75 )
77 class PrincipalHeader2(Structure):
78 structure = (
79 ('name_type', '!L=0'),
80 )
82 def __init__(self, data=None):
83 self.components = []
84 self.realm = None
85 if data is not None:
86 self.header1 = self.PrincipalHeader1(data)
87 data = data[len(self.header1):]
88 self.realm = CountedOctetString(data)
89 data = data[len(self.realm):]
90 self.components = []
91 for component in range(self.header1['num_components']):
92 comp = CountedOctetString(data)
93 data = data[len(comp):]
94 self.components.append(comp)
95 self.header2 = self.PrincipalHeader2(data)
96 else:
97 self.header1 = self.PrincipalHeader1()
98 self.header2 = self.PrincipalHeader2()
100 def __len__(self):
101 totalLen = len(self.header1) + len(self.header2) + len(self.realm)
102 for i in self.components:
103 totalLen += len(i)
104 return totalLen
106 def getData(self):
107 data = self.header1.getData() + self.realm.getData()
108 for component in self.components:
109 data += component.getData()
110 data += self.header2.getData()
111 return data
113 def __str__(self):
114 return self.getData()
116 def prettyPrint(self):
117 principal = b''
118 for component in self.components:
119 if isinstance(component['data'], bytes) is not True:
120 component = b(component['data'])
121 else:
122 component = component['data']
123 principal += component + b'/'
125 principal = principal[:-1]
126 if isinstance(self.realm['data'], bytes):
127 realm = self.realm['data']
128 else:
129 realm = b(self.realm['data'])
130 principal += b'@' + realm
131 return principal
134class KeytabEntry:
135 class KeytabEntryMainpart(Structure):
136 """
137 keytab_entry {
138 int32_t size; # wtf, signed size. what could possibly ...
139 uint16_t num_components; /* sub 1 if version 0x501 */ |\
140 counted_octet_string realm; | \ Keytab
141 counted_octet_string components[num_components]; | / Princial
142 uint32_t name_type; /* not present if version 0x501 */ |/
143 uint32_t timestamp;
144 uint8_t vno8;
145 keyblock key;
146 uint32_t vno; /* only present if >= 4 bytes left in entry */
147 };
148 """
149 structure = (
150 ('size', '!l=0'),
151 ('principal', ':', KeytabPrincipal),
152 ('timestamp', '!L=0'),
153 ('vno8', '!B=0'),
154 ('keyblock', ':', KeyBlock),
155 )
157 def __init__(self, data=None):
158 self.rest = b''
159 if data:
160 self.main_part = self.KeytabEntryMainpart(data)
161 self.size = abs(self.main_part['size']) + 4 # size field itself not included
162 self.kvno = self.main_part['vno8']
163 self.deleted = self.main_part['size'] < 0
164 len_main = len(self.main_part)
165 if self.size > len_main:
166 self.rest = data[len_main:self.size]
167 if len(self.rest) >= 4 and \
168 self.rest[:4] != [0, 0, 0, 0]: # if "field" is present but all 0, it seems to gets ignored
169 self.kvno = unpack('!L', self.rest[:4])[0]
170 else:
171 self.main_part = self.KeytabEntryMainpart()
172 self.deleted = True
173 self.size = len(self.main_part)
174 self.kvno = 0
176 def __len__(self):
177 return self.size
179 def getData(self):
180 data = self.main_part.getData()
181 if self.rest:
182 data += self.rest
183 return data
185 def prettyPrint(self, indent=''):
186 if self.deleted:
187 return "%s[DELETED]" % indent
188 else:
189 text = "%sPrincipal: %s\n" %(indent, self.main_part['principal'].prettyPrint())
190 text += "%sTimestamp: %s" % (indent, datetime.fromtimestamp(self.main_part['timestamp']).isoformat())
191 text += "\tKVNO: %i\n" % self.kvno
192 text += "%sKey: %s" % (indent, self.main_part['keyblock'].prettyPrint())
193 #if self.rest:
194 # text += "\n%sRest: %s" % (indent, self.rest)
195 return text
198class Keytab:
200 GetkeyEnctypePreference = (Enctype.AES256.value,
201 Enctype.AES128.value,
202 Enctype.RC4.value)
204 class MiniHeader(Structure):
205 structure = (
206 ('file_format_version', '!H=0x0502'),
207 )
209 def __init__(self, data=None):
210 self.miniHeader = None
211 self.entries = []
212 if data is not None:
213 self.miniHeader = self.MiniHeader(data)
214 data = data[len(self.miniHeader):]
215 while len(data):
216 entry = KeytabEntry(data)
217 self.entries.append(entry)
218 data = data[len(entry):]
220 def getData(self):
221 data = self.MiniHeader().getData()
222 for entry in self.entries:
223 data += entry.getData()
224 return data
226 def getKey(self, principal, specificEncType=None, ignoreRealm=True):
227 principal = b(principal.upper())
228 if ignoreRealm:
229 principal = principal.split(b'@')[0]
230 matching_keys = {}
231 for entry in self.entries:
232 entry_principal = entry.main_part['principal'].prettyPrint().upper()
233 if entry_principal == principal or (ignoreRealm and entry_principal.split(b'@')[0] == principal):
234 keytype = entry.main_part["keyblock"]["keytype"]
235 if keytype == specificEncType:
236 LOG.debug('Returning %s key for %s' % (entry.main_part['keyblock'].prettyKeytype(),
237 entry.main_part['principal'].prettyPrint()))
238 return entry.main_part["keyblock"]
239 elif specificEncType is None:
240 matching_keys[keytype] = entry
242 if specificEncType is None and matching_keys:
243 for preference in self.GetkeyEnctypePreference:
244 if preference in matching_keys:
245 entry = matching_keys[preference]
246 LOG.debug('Returning %s key for %s' % (entry.main_part['keyblock'].prettyKeytype(),
247 entry.main_part['principal'].prettyPrint()))
248 return entry.main_part["keyblock"]
250 LOG.debug('Principal %s not found in keytab' % principal)
251 return None
253 @classmethod
254 def loadFile(cls, fileName):
255 f = open(fileName, 'rb')
256 data = f.read()
257 f.close()
258 return cls(data)
260 @classmethod
261 def loadKeysFromKeytab(cls, fileName, username, domain, options):
262 keytab = Keytab.loadFile(fileName)
263 keyblock = keytab.getKey("%s@%s" % (username, domain))
264 if keyblock:
265 if keyblock["keytype"] == Enctype.AES256.value or keyblock["keytype"] == Enctype.AES128.value:
266 options.aesKey = keyblock.hexlifiedValue()
267 elif keyblock["keytype"] == Enctype.RC4.value:
268 options.hashes= ':' + keyblock.hexlifiedValue().decode('ascii')
269 else:
270 LOG.warning("No matching key for SPN '%s' in given keytab found!", username)
273 def saveFile(self, fileName):
274 f = open(fileName, 'wb+')
275 f.write(self.getData())
276 f.close()
278 def prettyPrint(self):
279 print("Keytab Entries:")
280 for i, entry in enumerate(self.entries):
281 print(("[%d]" % i))
282 print(entry.prettyPrint('\t'))
285if __name__ == '__main__': 285 ↛ 286line 285 didn't jump to line 286, because the condition on line 285 was never true
286 import sys
287 keytab = Keytab.loadFile(sys.argv[1])
288 keytab.prettyPrint()