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# Impacket - Collection of Python classes for working with network protocols.
2#
3# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved.
4#
5# This software is provided under a slightly modified version
6# of the Apache Software License. See the accompanying LICENSE file
7# for more information.
8#
9# Description:
10# Kerberos Keytab format implementation
11# based on file format described at:
12# https://repo.or.cz/w/krb5dissect.git/blob_plain/HEAD:/keytab.txt
13# As the ccache implementation, pretty lame and quick
14# Feel free to improve
15#
16# Author:
17# Patrick Welzel (@kcirtapw)
18#
19from datetime import datetime
20from enum import Enum
21from six import b
23from struct import pack, unpack, calcsize
24from binascii import hexlify
26from impacket.structure import Structure
27from impacket import LOG
30class Enctype(Enum):
31 DES_CRC = 1
32 DES_MD4 = 2
33 DES_MD5 = 3
34 DES3 = 16
35 AES128 = 17
36 AES256 = 18
37 RC4 = 23
40class CountedOctetString(Structure):
41 """
42 Note: This is very similar to the CountedOctetString structure in ccache, except:
43 * `length` is uint16 instead of uint32
44 """
45 structure = (
46 ('length','!H=0'),
47 ('_data','_-data','self["length"]'),
48 ('data',':'),
49 )
51 def prettyPrint(self, indent=''):
52 return "%s%s" % (indent, hexlify(self['data']))
55class KeyBlock(Structure):
56 structure = (
57 ('keytype','!H=0'),
58 ('keyvalue',':', CountedOctetString),
59 )
61 def prettyKeytype(self):
62 try:
63 return Enctype(self['keytype']).name
64 except:
65 return "UNKNOWN:0x%x" % (self['keytype'])
67 def hexlifiedValue(self):
68 return hexlify(self['keyvalue']['data'])
70 def prettyPrint(self):
71 return "(%s)%s" % (self.prettyKeytype(), self.hexlifiedValue())
74class KeytabPrincipal:
75 """
76 Note: This is very similar to the principal structure in ccache, except:
77 * `num_components` is just uint16
78 * using other size type for CountedOctetString
79 * `name_type` field follows the other fields behind.
80 """
81 class PrincipalHeader1(Structure):
82 structure = (
83 ('num_components', '!H=0'),
84 )
86 class PrincipalHeader2(Structure):
87 structure = (
88 ('name_type', '!L=0'),
89 )
91 def __init__(self, data=None):
92 self.components = []
93 self.realm = None
94 if data is not None:
95 self.header1 = self.PrincipalHeader1(data)
96 data = data[len(self.header1):]
97 self.realm = CountedOctetString(data)
98 data = data[len(self.realm):]
99 self.components = []
100 for component in range(self.header1['num_components']):
101 comp = CountedOctetString(data)
102 data = data[len(comp):]
103 self.components.append(comp)
104 self.header2 = self.PrincipalHeader2(data)
105 else:
106 self.header1 = self.PrincipalHeader1()
107 self.header2 = self.PrincipalHeader2()
109 def __len__(self):
110 totalLen = len(self.header1) + len(self.header2) + len(self.realm)
111 for i in self.components:
112 totalLen += len(i)
113 return totalLen
115 def getData(self):
116 data = self.header1.getData() + self.realm.getData()
117 for component in self.components:
118 data += component.getData()
119 data += self.header2.getData()
120 return data
122 def __str__(self):
123 return self.getData()
125 def prettyPrint(self):
126 principal = b''
127 for component in self.components:
128 if isinstance(component['data'], bytes) is not True:
129 component = b(component['data'])
130 else:
131 component = component['data']
132 principal += component + b'/'
134 principal = principal[:-1]
135 if isinstance(self.realm['data'], bytes):
136 realm = self.realm['data']
137 else:
138 realm = b(self.realm['data'])
139 principal += b'@' + realm
140 return principal
143class KeytabEntry:
144 class KeytabEntryMainpart(Structure):
145 """
146 keytab_entry {
147 int32_t size; # wtf, signed size. what could possibly ...
148 uint16_t num_components; /* sub 1 if version 0x501 */ |\
149 counted_octet_string realm; | \\ Keytab
150 counted_octet_string components[num_components]; | / Princial
151 uint32_t name_type; /* not present if version 0x501 */ |/
152 uint32_t timestamp;
153 uint8_t vno8;
154 keyblock key;
155 uint32_t vno; /* only present if >= 4 bytes left in entry */
156 };
157 """
158 structure = (
159 ('size', '!l=0'),
160 ('principal', ':', KeytabPrincipal),
161 ('timestamp', '!L=0'),
162 ('vno8', '!B=0'),
163 ('keyblock', ':', KeyBlock),
164 )
166 def __init__(self, data=None):
167 self.rest = b''
168 if data:
169 self.main_part = self.KeytabEntryMainpart(data)
170 self.size = abs(self.main_part['size']) + 4 # size field itself not included
171 self.kvno = self.main_part['vno8']
172 self.deleted = self.main_part['size'] < 0
173 len_main = len(self.main_part)
174 if self.size > len_main:
175 self.rest = data[len_main:self.size]
176 if len(self.rest) >= 4 and \
177 self.rest[:4] != [0, 0, 0, 0]: # if "field" is present but all 0, it seems to gets ignored
178 self.kvno = unpack('!L', self.rest[:4])[0]
179 else:
180 self.main_part = self.KeytabEntryMainpart()
181 self.deleted = True
182 self.size = len(self.main_part)
183 self.kvno = 0
185 def __len__(self):
186 return self.size
188 def getData(self):
189 data = self.main_part.getData()
190 if self.rest:
191 data += self.rest
192 return data
194 def prettyPrint(self, indent=''):
195 if self.deleted:
196 return "%s[DELETED]" % indent
197 else:
198 text = "%sPrincipal: %s\n" %(indent, self.main_part['principal'].prettyPrint())
199 text += "%sTimestamp: %s" % (indent, datetime.fromtimestamp(self.main_part['timestamp']).isoformat())
200 text += "\tKVNO: %i\n" % self.kvno
201 text += "%sKey: %s" % (indent, self.main_part['keyblock'].prettyPrint())
202 #if self.rest:
203 # text += "\n%sRest: %s" % (indent, self.rest)
204 return text
207class Keytab:
209 GetkeyEnctypePreference = (Enctype.AES256.value,
210 Enctype.AES128.value,
211 Enctype.RC4.value)
213 class MiniHeader(Structure):
214 structure = (
215 ('file_format_version', '!H=0x0502'),
216 )
218 def __init__(self, data=None):
219 self.miniHeader = None
220 self.entries = []
221 if data is not None:
222 self.miniHeader = self.MiniHeader(data)
223 data = data[len(self.miniHeader):]
224 while len(data):
225 entry = KeytabEntry(data)
226 self.entries.append(entry)
227 data = data[len(entry):]
229 def getData(self):
230 data = self.MiniHeader().getData()
231 for entry in self.entries:
232 data += entry.getData()
233 return data
235 def getKey(self, principal, specificEncType=None, ignoreRealm=True):
236 principal = b(principal.upper())
237 if ignoreRealm:
238 principal = principal.split(b'@')[0]
239 matching_keys = {}
240 for entry in self.entries:
241 entry_principal = entry.main_part['principal'].prettyPrint().upper()
242 if entry_principal == principal or (ignoreRealm and entry_principal.split(b'@')[0] == principal):
243 keytype = entry.main_part["keyblock"]["keytype"]
244 if keytype == specificEncType:
245 LOG.debug('Returning %s key for %s' % (entry.main_part['keyblock'].prettyKeytype(),
246 entry.main_part['principal'].prettyPrint()))
247 return entry.main_part["keyblock"]
248 elif specificEncType is None:
249 matching_keys[keytype] = entry
251 if specificEncType is None and matching_keys:
252 for preference in self.GetkeyEnctypePreference:
253 if preference in matching_keys:
254 entry = matching_keys[preference]
255 LOG.debug('Returning %s key for %s' % (entry.main_part['keyblock'].prettyKeytype(),
256 entry.main_part['principal'].prettyPrint()))
257 return entry.main_part["keyblock"]
259 LOG.debug('Principal %s not found in keytab' % principal)
260 return None
262 @classmethod
263 def loadFile(cls, fileName):
264 f = open(fileName, 'rb')
265 data = f.read()
266 f.close()
267 return cls(data)
269 @classmethod
270 def loadKeysFromKeytab(cls, fileName, username, domain, options):
271 keytab = Keytab.loadFile(fileName)
272 keyblock = keytab.getKey("%s@%s" % (username, domain))
273 if keyblock:
274 if keyblock["keytype"] == Enctype.AES256.value or keyblock["keytype"] == Enctype.AES128.value:
275 options.aesKey = keyblock.hexlifiedValue()
276 elif keyblock["keytype"] == Enctype.RC4.value:
277 options.hashes= ':' + keyblock.hexlifiedValue().decode('ascii')
278 else:
279 LOG.warning("No matching key for SPN '%s' in given keytab found!", username)
282 def saveFile(self, fileName):
283 f = open(fileName, 'wb+')
284 f.write(self.getData())
285 f.close()
287 def prettyPrint(self):
288 print("Keytab Entries:")
289 for i, entry in enumerate(self.entries):
290 print(("[%d]" % i))
291 print(entry.prettyPrint('\t'))
294if __name__ == '__main__': 294 ↛ 295line 294 didn't jump to line 295, because the condition on line 294 was never true
295 import sys
296 keytab = Keytab.loadFile(sys.argv[1])
297 keytab.prettyPrint()