Coverage for /root/GitHubProjects/impacket/impacket/krb5/gssapi.py: 70%

# Coverage for /root/GitHubProjects/impacket/impacket/krb5/gssapi.py : 70%

## 163 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# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.

2#

3# This software is provided under under a slightly modified version

4# of the Apache Software License. See the accompanying LICENSE file

5# for more information.

6#

7# Author: Alberto Solino (@agsolino)

8#

9# Description:

10# RFC 1964 Partial Implementation

11# RFC 4757 Partial Implementation

12# RFC 4121 Partial Implementation

13# RFC 3962 Partial Implementation

14

15 import struct

16 import random

17 import string

18 from six import b

19

20 from Cryptodome.Hash import HMAC, MD5

21 from Cryptodome.Cipher import ARC4

22

23 from impacket.structure import Structure

24 from impacket.krb5 import constants, crypto

25

26# Our random number generator

27 try:

28 rand = random.SystemRandom()

29 except NotImplementedError:

30 rand = random

31 pass

32

33# Constants

34 GSS_C_DCE_STYLE = 0x1000

35 GSS_C_DELEG_FLAG = 1

36 GSS_C_MUTUAL_FLAG = 2

37 GSS_C_REPLAY_FLAG = 4

38 GSS_C_SEQUENCE_FLAG = 8

39 GSS_C_CONF_FLAG = 0x10

40 GSS_C_INTEG_FLAG = 0x20

41

42# Mic Semantics

43 GSS_HMAC = 0x11

44# Wrap Semantics

45 GSS_RC4 = 0x10

46

47# 2. Key Derivation for Per-Message Tokens

48 KG_USAGE_ACCEPTOR_SEAL = 22

49 KG_USAGE_ACCEPTOR_SIGN = 23

50 KG_USAGE_INITIATOR_SEAL = 24

51 KG_USAGE_INITIATOR_SIGN = 25

52

53 KRB5_AP_REQ = struct.pack('<H', 0x1)

54

55# 1.1.1. Initial Token - Checksum field

56 class CheckSumField(Structure):

57 structure = (

58 ('Lgth','<L=16'),

59 ('Bnd','16s=b""'),

60 ('Flags','<L=0'),

61 )

62

63 def GSSAPI(cipher):

64 if cipher.enctype == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value: 64 ↛ 65line 64 didn't jump to line 65, because the condition on line 64 was never true

65 return GSSAPI_AES256()

66 if cipher.enctype == constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value: 66 ↛ 67line 66 didn't jump to line 67, because the condition on line 66 was never true

67 return GSSAPI_AES128()

68 elif cipher.enctype == constants.EncryptionTypes.rc4_hmac.value: 68 ↛ 71line 68 didn't jump to line 71, because the condition on line 68 was never false

69 return GSSAPI_RC4()

70 else:

71 raise Exception('Unsupported etype 0x%x' % cipher.enctype)

72

73# 7.2. GSS-API MIC Semantics

74 class GSSAPI_RC4:

75 # 1.2.1. Per-message Tokens - MIC

76 class MIC(Structure):

77 structure = (

78 ('TOK_ID','<H=0x0101'),

79 ('SGN_ALG','<H=0'),

80 ('Filler','<L=0xffffffff'),

81 ('SND_SEQ','8s=b""'),

82 ('SGN_CKSUM','8s=b""'),

83 )

84

85 # 1.2.2. Per-message Tokens - Wrap

86 class WRAP(Structure):

87 structure = (

88 ('TOK_ID','<H=0x0102'),

89 ('SGN_ALG','<H=0'),

90 ('SEAL_ALG','<H=0'),

91 ('Filler','<H=0xffff'),

92 ('SND_SEQ','8s=b""'),

93 ('SGN_CKSUM','8s=b""'),

94 ('Confounder','8s=b""'),

95 )

96

97 def GSS_GetMIC(self, sessionKey, data, sequenceNumber, direction = 'init'):

98 GSS_GETMIC_HEADER = b'\x60\x23\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'

99 token = self.MIC()

100

101 # Let's pad the data

102 pad = (4 - (len(data) % 4)) & 0x3

103 padStr = b(chr(pad)) * pad

104 data += padStr

105

106 token['SGN_ALG'] = GSS_HMAC

107 if direction == 'init': 107 ↛ 110line 107 didn't jump to line 110, because the condition on line 107 was never false

108 token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\x00'*4

109 else:

110 token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\xff'*4

111

112 Ksign = HMAC.new(sessionKey.contents, b'signaturekey\0', MD5).digest()

113 Sgn_Cksum = MD5.new( struct.pack('<L',15) + token.getData()[:8] + data).digest()

114 Sgn_Cksum = HMAC.new(Ksign, Sgn_Cksum, MD5).digest()

115 token['SGN_CKSUM'] = Sgn_Cksum[:8]

116

117 Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest()

118 Kseq = HMAC.new(Kseq, token['SGN_CKSUM'], MD5).digest()

119 token['SND_SEQ'] = ARC4.new(Kseq).encrypt(token['SND_SEQ'])

120 finalData = GSS_GETMIC_HEADER + token.getData()

121 return finalData

122

123 def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, authData=None):

124 # Damn inacurate RFC, useful info from here

125 # https://social.msdn.microsoft.com/Forums/en-US/fb98e8f4-e697-4652-bcb7-604e027e14cc/gsswrap-token-size-kerberos-and-rc4hmac?forum=os_windowsprotocols

126 # and here

127 # http://www.rfc-editor.org/errata_search.php?rfc=4757

128 GSS_WRAP_HEADER = b'\x60\x2b\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'

129 token = self.WRAP()

130

131 # Let's pad the data

132 pad = (8 - (len(data) % 8)) & 0x7

133 padStr = b(chr(pad)) * pad

134 data += padStr

135

136 token['SGN_ALG'] = GSS_HMAC

137 token['SEAL_ALG'] = GSS_RC4

138

139 if direction == 'init': 139 ↛ 142line 139 didn't jump to line 142, because the condition on line 139 was never false

140 token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\x00'*4

141 else:

142 token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\xff'*4

143

144 # Random confounder :)

145 token['Confounder'] = b(''.join([rand.choice(string.ascii_letters) for _ in range(8)]))

146

147 Ksign = HMAC.new(sessionKey.contents, b'signaturekey\0', MD5).digest()

148 Sgn_Cksum = MD5.new(struct.pack('<L',13) + token.getData()[:8] + token['Confounder'] + data).digest()

149

150 Klocal = bytearray()

151 from builtins import bytes

152 for n in bytes(sessionKey.contents):

153 Klocal.append( n ^ 0xF0)

154

155 Kcrypt = HMAC.new(Klocal,struct.pack('<L',0), MD5).digest()

156 Kcrypt = HMAC.new(Kcrypt,struct.pack('>L', sequenceNumber), MD5).digest()

157

158 Sgn_Cksum = HMAC.new(Ksign, Sgn_Cksum, MD5).digest()

159

160 token['SGN_CKSUM'] = Sgn_Cksum[:8]

161

162 Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest()

163 Kseq = HMAC.new(Kseq, token['SGN_CKSUM'], MD5).digest()

164

165 token['SND_SEQ'] = ARC4.new(Kseq).encrypt(token['SND_SEQ'])

166

167 if authData is not None:

168 from impacket.dcerpc.v5.rpcrt import SEC_TRAILER

169 wrap = self.WRAP(authData[len(SEC_TRAILER()) + len(GSS_WRAP_HEADER):])

170 snd_seq = wrap['SND_SEQ']

171

172 Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest()

173 Kseq = HMAC.new(Kseq, wrap['SGN_CKSUM'], MD5).digest()

174

175 snd_seq = ARC4.new(Kseq).encrypt(wrap['SND_SEQ'])

176

177 Kcrypt = HMAC.new(Klocal,struct.pack('<L',0), MD5).digest()

178 Kcrypt = HMAC.new(Kcrypt,snd_seq[:4], MD5).digest()

179 rc4 = ARC4.new(Kcrypt)

180 cipherText = rc4.decrypt(token['Confounder'] + data)[8:]

181 elif encrypt is True: 181 ↛ 186line 181 didn't jump to line 186, because the condition on line 181 was never false

182 rc4 = ARC4.new(Kcrypt)

183 token['Confounder'] = rc4.encrypt(token['Confounder'])

184 cipherText = rc4.encrypt(data)

185 else:

186 cipherText = data

187

188 finalData = GSS_WRAP_HEADER + token.getData()

189 return cipherText, finalData

190

191 def GSS_Unwrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, authData=None):

192 return self.GSS_Wrap(sessionKey, data, sequenceNumber, direction, encrypt, authData)

193

194 class GSSAPI_AES():

195 checkSumProfile = None

196 cipherType = None

197

198 class MIC(Structure):

199 structure = (

200 ('TOK_ID','>H=0x0404'),

201 ('Flags','B=0'),

202 ('Filler0','B=0xff'),

203 ('Filler','>L=0xffffffff'),

204 ('SND_SEQ','8s=b""'),

205 ('SGN_CKSUM','12s=b""'),

206 )

207

208 # 1.2.2. Per-message Tokens - Wrap

209 class WRAP(Structure):

210 structure = (

211 ('TOK_ID','>H=0x0504'),

212 ('Flags','B=0'),

213 ('Filler','B=0xff'),

214 ('EC','>H=0'),

215 ('RRC','>H=0'),

216 ('SND_SEQ','8s=b""'),

217 )

218

219 def GSS_GetMIC(self, sessionKey, data, sequenceNumber, direction = 'init'):

220 token = self.MIC()

221

222 # Let's pad the data

223 pad = (4 - (len(data) % 4)) & 0x3

224 padStr = chr(pad) * pad

225 data += padStr

226

227 checkSumProfile = self.checkSumProfile()

228

229 token['Flags'] = 4

230 token['SND_SEQ'] = struct.pack('>Q',sequenceNumber)

231 token['SGN_CKSUM'] = checkSumProfile.checksum(sessionKey, KG_USAGE_INITIATOR_SIGN, data + token.getData()[:16])

232

233 return token.getData()

234

235 def rotate(self, data, numBytes):

236 numBytes %= len(data)

237 left = len(data) - numBytes

238 result = data[left:] + data[:left]

239 return result

240

241 def unrotate(self, data, numBytes):

242 numBytes %= len(data)

243 result = data[numBytes:] + data[:numBytes]

244 return result

245

246 def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True):

247 token = self.WRAP()

248

249 cipher = self.cipherType()

250

251 # Let's pad the data

252 pad = (cipher.blocksize - (len(data) % cipher.blocksize)) & 15

253 padStr = b'\xFF' * pad

254 data += padStr

255

256 # The RRC field ([RFC4121] section 4.2.5) is 12 if no encryption is requested or 28 if encryption

257 # is requested. The RRC field is chosen such that all the data can be encrypted in place.

258 rrc = 28

259

260 token['Flags'] = 6

261 token['EC'] = pad

262 token['RRC'] = 0

263 token['SND_SEQ'] = struct.pack('>Q',sequenceNumber)

264

265 cipherText = cipher.encrypt(sessionKey, KG_USAGE_INITIATOR_SEAL, data + token.getData(), None)

266 token['RRC'] = rrc

267

268 cipherText = self.rotate(cipherText, token['RRC'] + token['EC'])

269

270 #nn = self.unrotate(cipherText, token['RRC'] + token['EC'])

271 ret1 = cipherText[len(self.WRAP()) + token['RRC'] + token['EC']:]

272 ret2 = token.getData() + cipherText[:len(self.WRAP()) + token['RRC'] + token['EC']]

273

274 return ret1, ret2

275

276 def GSS_Unwrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, authData=None):

277 from impacket.dcerpc.v5.rpcrt import SEC_TRAILER

278

279 cipher = self.cipherType()

280 token = self.WRAP(authData[len(SEC_TRAILER()):])

281

282 rotated = authData[len(self.WRAP())+len(SEC_TRAILER()):] + data

283

284 cipherText = self.unrotate(rotated, token['RRC'] + token['EC'])

285 plainText = cipher.decrypt(sessionKey, KG_USAGE_ACCEPTOR_SEAL, cipherText)

286

287 return plainText[:-(token['EC']+len(self.WRAP()))], None

288

289 class GSSAPI_AES256(GSSAPI_AES):

290 checkSumProfile = crypto._SHA1AES256

291 cipherType = crypto._AES256CTS

292

293 class GSSAPI_AES128(GSSAPI_AES):

294 checkSumProfile = crypto._SHA1AES128

295 cipherType = crypto._AES128CTS