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

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

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

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