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

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

## 181 statements

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#

10 from datetime import datetime

11 from enum import Enum

12 from six import b

13

14 from struct import pack, unpack, calcsize

15 from binascii import hexlify

16

17 from impacket.structure import Structure

18 from impacket import LOG

19

20

21 class 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

29

30

31 class 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 )

41

42 def prettyPrint(self, indent=''):

43 return "%s%s" % (indent, hexlify(self['data']))

44

45

46 class KeyBlock(Structure):

47 structure = (

48 ('keytype','!H=0'),

49 ('keyvalue',':', CountedOctetString),

50 )

51

52 def prettyKeytype(self):

53 try:

54 return Enctype(self['keytype']).name

55 except:

56 return "UNKNOWN:0x%x" % (self['keytype'])

57

58 def hexlifiedValue(self):

59 return hexlify(self['keyvalue']['data'])

60

61 def prettyPrint(self):

62 return "(%s)%s" % (self.prettyKeytype(), self.hexlifiedValue())

63

64

65 class 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 )

76

77 class PrincipalHeader2(Structure):

78 structure = (

79 ('name_type', '!L=0'),

80 )

81

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()

99

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

105

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

112

113 def __str__(self):

114 return self.getData()

115

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'/'

124

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

132

133

134 class 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 )

156

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

175

176 def __len__(self):

177 return self.size

178

179 def getData(self):

180 data = self.main_part.getData()

181 if self.rest:

182 data += self.rest

183 return data

184

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

196

197

198 class Keytab:

199

200 GetkeyEnctypePreference = (Enctype.AES256.value,

201 Enctype.AES128.value,

202 Enctype.RC4.value)

203

204 class MiniHeader(Structure):

205 structure = (

206 ('file_format_version', '!H=0x0502'),

207 )

208

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):]

219

220 def getData(self):

221 data = self.MiniHeader().getData()

222 for entry in self.entries:

223 data += entry.getData()

224 return data

225

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

241

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"]

249

250 LOG.debug('Principal %s not found in keytab' % principal)

251 return None

252

253 @classmethod

254 def loadFile(cls, fileName):

255 f = open(fileName, 'rb')

256 data = f.read()

257 f.close()

258 return cls(data)

259

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)

271

272

273 def saveFile(self, fileName):

274 f = open(fileName, 'wb+')

275 f.write(self.getData())

276 f.close()

277

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'))

283

284

285 if __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()