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 ↛ 78,   77 ↛ 832 missed branches: 1) line 77 didn't jump to line 78, because the condition on line 77 was never true, 2) line 77 didn't jump to line 83, because the condition on line 77 was never false

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

79 except NameError: # Python 3 

80 if isinstance(value, bytes): 80 ↛ 81line 80 didn't jump to line 81, because the condition on line 80 was never true

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