Coverage for /root/GitHubProjects/impacket/impacket/krb5/types.py: 53%

# Coverage for /root/GitHubProjects/impacket/impacket/krb5/types.py : 53%

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

37 import datetime

38 import socket

39 import re

40 import struct

41

42 from pyasn1.codec.der import decoder

43

44 from . import asn1

45 from . import constants

46

47

48 class KerberosException(Exception):

49 pass

50

51 def _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

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

67 If 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

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

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

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

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

271 if __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"))