Hide keyboard shortcuts

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) 2021 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# Copyright and license note from types.py: 

10# 

11# Copyright (c) 2013, Marc Horowitz 

12# All rights reserved. 

13# 

14# Redistribution and use in source and binary forms, with or without 

15# modification, are permitted provided that the following conditions are 

16# met: 

17# 

18# Redistributions of source code must retain the above copyright notice, 

19# this list of conditions and the following disclaimer. 

20# 

21# Redistributions in binary form must reproduce the above copyright 

22# notice, this list of conditions and the following disclaimer in the 

23# documentation and/or other materials provided with the distribution. 

24# 

25# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 

26# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 

27# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 

28# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 

29# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 

30# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 

31# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 

32# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 

33# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 

34# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 

35# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

36# 

37import datetime 

38import socket 

39import re 

40import struct 

41 

42from pyasn1.codec.der import decoder 

43 

44from . import asn1 

45from . import constants 

46 

47 

48class KerberosException(Exception): 

49 pass 

50 

51def _asn1_decode(data, asn1Spec): 

52 if isinstance(data, str) or isinstance(data,bytes): 52 ↛ 53line 52 didn't jump to line 53, because the condition on line 52 was never true

53 data, substrate = decoder.decode(data, asn1Spec=asn1Spec) 

54 if substrate != b'': 

55 raise KerberosException("asn1 encoding invalid") 

56 return data 

57 

58# A principal can be represented as: 

59 

60class Principal(object): 

61 """The principal's value can be supplied as: 

62* a single string 

63* a sequence containing a sequence of component strings and a realm string 

64* a sequence whose first n-1 elemeents are component strings and whose last 

65 component is the realm 

66 

67If the value contains no realm, then default_realm will be used.""" 

68 def __init__(self, value=None, default_realm=None, type=None): 

69 self.type = constants.PrincipalNameType.NT_UNKNOWN 

70 self.components = [] 

71 self.realm = None 

72 

73 if value is None: 

74 return 

75 

76 try: # Python 2 

77 if isinstance(value, unicode): 77 ↛ 78line 77 didn't jump to line 78, because the condition on line 77 was never true

78 value = value.encode('utf-8') 

79 except NameError: # Python 3 

80 if isinstance(value, bytes): 

81 value = value.decode('utf-8') 

82 

83 if isinstance(value, Principal): 83 ↛ 84line 83 didn't jump to line 84, because the condition on line 83 was never true

84 self.type = value.type 

85 self.components = value.components[:] 

86 self.realm = value.realm 

87 elif isinstance(value, str): 87 ↛ 103line 87 didn't jump to line 103, because the condition on line 87 was never false

88 m = re.match(r'((?:[^\\]|\\.)+?)(@((?:[^\\@]|\\.)+))?$', value) 

89 if not m: 89 ↛ 90line 89 didn't jump to line 90, because the condition on line 89 was never true

90 raise KerberosException("invalid principal syntax") 

91 

92 def unquote_component(comp): 

93 return re.sub(r'\\(.)', r'\1', comp) 

94 

95 if m.group(2) is not None: 95 ↛ 96line 95 didn't jump to line 96, because the condition on line 95 was never true

96 self.realm = unquote_component(m.group(3)) 

97 else: 

98 self.realm = default_realm 

99 

100 self.components = [ 

101 unquote_component(qc) 

102 for qc in re.findall(r'(?:[^\\/]|\\.)+', m.group(1))] 

103 elif len(value) == 2: 

104 self.components = value[0] 

105 self.realm = value[-1] 

106 if isinstance(self.components, str): 

107 self.components = [self.components] 

108 elif len(value) >= 2: 

109 self.components = value[0:-1] 

110 self.realm = value[-1] 

111 else: 

112 raise KerberosException("invalid principal value") 

113 

114 if type is not None: 114 ↛ exitline 114 didn't return from function '__init__', because the condition on line 114 was never false

115 self.type = type 

116 

117 def __eq__(self, other): 

118 if isinstance (other, str): 

119 other = Principal (other) 

120 

121 return (self.type == constants.PrincipalNameType.NT_UNKNOWN.value or 

122 other.type == constants.PrincipalNameType.NT_UNKNOWN.value or 

123 self.type == other.type) and all (map (lambda a, b: a == b, self.components, other.components)) and \ 

124 self.realm == other.realm 

125 

126 def __str__(self): 

127 def quote_component(comp): 

128 return re.sub(r'([\\/@])', r'\\\1', comp) 

129 

130 ret = "/".join([quote_component(c) for c in self.components]) 

131 if self.realm is not None: 

132 ret += "@" + self.realm 

133 

134 return ret 

135 

136 def __repr__(self): 

137 return "Principal((" + repr(self.components) + ", " + \ 

138 repr(self.realm) + "), t=" + str(self.type) + ")" 

139 

140 def from_asn1(self, data, realm_component, name_component): 

141 name = data.getComponentByName(name_component) 

142 self.type = constants.PrincipalNameType( 

143 name.getComponentByName('name-type')).value 

144 self.components = [ 

145 str(c) for c in name.getComponentByName('name-string')] 

146 self.realm = str(data.getComponentByName(realm_component)) 

147 return self 

148 

149 def components_to_asn1(self, name): 

150 name.setComponentByName('name-type', int(self.type)) 

151 strings = name.setComponentByName('name-string' 

152 ).getComponentByName('name-string') 

153 for i, c in enumerate(self.components): 

154 strings.setComponentByPosition(i, c) 

155 

156 return name 

157 

158class Address(object): 

159 DIRECTIONAL_AP_REQ_SENDER = struct.pack('!I', 0) 

160 DIRECTIONAL_AP_REQ_RECIPIENT = struct.pack('!I', 1) 

161 

162 def __init__(self): 

163 self.type = None 

164 self.data = None 

165 

166 def __str__(self): 

167 family = self.family 

168 

169 if family is not None: 

170 return str((family, self.address)) 

171 else: 

172 return str((self.type, self.value)) 

173 

174 @property 

175 def family(self): 

176 if self.type == constants.AddressType.IPv4.value: 

177 return socket.AF_INET 

178 elif self.type == constants.AddressType.IPv4.value: 

179 return socket.AF_INET6 

180 else: 

181 return None 

182 

183 @property 

184 def address(self): 

185 if self.type == constants.AddressType.IPv4.value: 

186 return socket.inet_pton(self.family, self.data) 

187 elif self.type == constants.AddressType.IPv4.value: 

188 return socket.inet_pton(self.family, self.data) 

189 else: 

190 return None 

191 

192 def encode(self): 

193 # ipv4-mapped ipv6 addresses must be encoded as ipv4. 

194 pass 

195 

196class EncryptedData(object): 

197 def __init__(self): 

198 self.etype = None 

199 self.kvno = None 

200 self.ciphertext = None 

201 

202 def from_asn1(self, data): 

203 data = _asn1_decode(data, asn1.EncryptedData()) 

204 self.etype = constants.EncryptionTypes(data.getComponentByName('etype')).value 

205 kvno = data.getComponentByName('kvno') 

206 if (kvno is None) or (kvno.hasValue() is False): 206 ↛ 207line 206 didn't jump to line 207, because the condition on line 206 was never true

207 self.kvno = False 

208 else: 

209 self.kvno = kvno 

210 self.ciphertext = str(data.getComponentByName('cipher')) 

211 return self 

212 

213 def to_asn1(self, component): 

214 component.setComponentByName('etype', int(self.etype)) 

215 if self.kvno: 215 ↛ 217line 215 didn't jump to line 217, because the condition on line 215 was never false

216 component.setComponentByName('kvno', self.kvno) 

217 component.setComponentByName('cipher', self.ciphertext) 

218 return component 

219 

220class Ticket(object): 

221 def __init__(self): 

222 # This is the kerberos version, not the service principal key 

223 # version number. 

224 self.tkt_vno = None 

225 self.service_principal = None 

226 self.encrypted_part = None 

227 

228 def from_asn1(self, data): 

229 data = _asn1_decode(data, asn1.Ticket()) 

230 self.tkt_vno = int(data.getComponentByName('tkt-vno')) 

231 self.service_principal = Principal() 

232 self.service_principal.from_asn1(data, 'realm', 'sname') 

233 self.encrypted_part = EncryptedData() 

234 self.encrypted_part.from_asn1(data.getComponentByName('enc-part')) 

235 return self 

236 

237 def to_asn1(self, component): 

238 component.setComponentByName('tkt-vno', 5) 

239 component.setComponentByName('realm', self.service_principal.realm) 

240 asn1.seq_set(component, 'sname', 

241 self.service_principal.components_to_asn1) 

242 asn1.seq_set(component, 'enc-part', self.encrypted_part.to_asn1) 

243 return component 

244 

245 def __str__(self): 

246 return "<Ticket for %s vno %s>" % (str(self.service_principal), str(self.encrypted_part.kvno)) 

247 

248class KerberosTime(object): 

249 INDEFINITE = datetime.datetime(1970, 1, 1, 0, 0, 0) 

250 

251 @staticmethod 

252 def to_asn1(dt): 

253 # A KerberosTime is really just a string, so we can return a 

254 # string here, and the asn1 library will convert it correctly. 

255 

256 return "%04d%02d%02d%02d%02d%02dZ" % (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) 

257 

258 @staticmethod 

259 def from_asn1(data): 

260 data = str(data) 

261 year = int(data[0:4]) 

262 month = int(data[4:6]) 

263 day = int(data[6:8]) 

264 hour = int(data[8:10]) 

265 minute = int(data[10:12]) 

266 second = int(data[12:14]) 

267 if data[14] != 'Z': 

268 raise KerberosException("timezone in KerberosTime is not Z") 

269 return datetime.datetime(year, month, day, hour, minute, second) 

270 

271if __name__ == '__main__': 271 ↛ 273line 271 didn't jump to line 273, because the condition on line 271 was never true

272 # TODO marc: turn this into a real test 

273 print(Principal("marc")) 

274 print(Principal(("marc", None))) 

275 print(Principal((("marc",), None))) 

276 print(Principal("marc@ATHENA.MIT.EDU")) 

277 print(Principal("marc", default_realm="ATHENA.MIT.EDU")) 

278 print(Principal("marc@ATHENA.MIT.EDU", default_realm="EXAMPLE.COM")) 

279 print(Principal(("marc", "ATHENA.MIT.EDU"))) 

280 print(Principal((("marc"), "ATHENA.MIT.EDU"))) 

281 print(Principal("marc/root")) 

282 print(Principal(("marc", "root", "ATHENA.MIT.EDU"))) 

283 print(Principal((("marc", "root"), "ATHENA.MIT.EDU"))) 

284 print(Principal("marc\\/root"))