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# 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#

19 from datetime import datetime

20 from enum import Enum

21 from six import b

22

23 from struct import pack, unpack, calcsize

24 from binascii import hexlify

25

26 from impacket.structure import Structure

27 from impacket import LOG

28

29

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

38

39

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

50

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

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

53

54

55 class KeyBlock(Structure):

56 structure = (

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

58 ('keyvalue',':', CountedOctetString),

59 )

60

61 def prettyKeytype(self):

62 try:

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

64 except:

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

66

67 def hexlifiedValue(self):

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

69

70 def prettyPrint(self):

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

72

73

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

85

86 class PrincipalHeader2(Structure):

87 structure = (

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

89 )

90

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

108

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

114

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

121

122 def __str__(self):

123 return self.getData()

124

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

133

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

141

142

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

165

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

184

185 def __len__(self):

186 return self.size

187

188 def getData(self):

189 data = self.main_part.getData()

190 if self.rest:

191 data += self.rest

192 return data

193

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

205

206

207 class Keytab:

208

209 GetkeyEnctypePreference = (Enctype.AES256.value,

210 Enctype.AES128.value,

211 Enctype.RC4.value)

212

213 class MiniHeader(Structure):

214 structure = (

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

216 )

217

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

228

229 def getData(self):

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

231 for entry in self.entries:

232 data += entry.getData()

233 return data

234

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

250

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

258

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

260 return None

261

262 @classmethod

263 def loadFile(cls, fileName):

264 f = open(fileName, 'rb')

265 data = f.read()

266 f.close()

267 return cls(data)

268

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)

280

281

282 def saveFile(self, fileName):

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

284 f.write(self.getData())

285 f.close()

286

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

292

293

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