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

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
42from pyasn1.codec.der import decoder
44from . import asn1
45from . import constants
48class KerberosException(Exception):
49 pass
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
58# A principal can be represented as:
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
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
73 if value is None:
74 return
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')
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")
92 def unquote_component(comp):
93 return re.sub(r'\\(.)', r'\1', comp)
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
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")
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
117 def __eq__(self, other):
118 if isinstance (other, str):
119 other = Principal (other)
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
126 def __str__(self):
127 def quote_component(comp):
128 return re.sub(r'([\\/@])', r'\\\1', comp)
130 ret = "/".join([quote_component(c) for c in self.components])
131 if self.realm is not None:
132 ret += "@" + self.realm
134 return ret
136 def __repr__(self):
137 return "Principal((" + repr(self.components) + ", " + \
138 repr(self.realm) + "), t=" + str(self.type) + ")"
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
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)
156 return name
158class Address(object):
159 DIRECTIONAL_AP_REQ_SENDER = struct.pack('!I', 0)
160 DIRECTIONAL_AP_REQ_RECIPIENT = struct.pack('!I', 1)
162 def __init__(self):
163 self.type = None
164 self.data = None
166 def __str__(self):
167 family = self.family
169 if family is not None:
170 return str((family, self.address))
171 else:
172 return str((self.type, self.value))
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
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
192 def encode(self):
193 # ipv4-mapped ipv6 addresses must be encoded as ipv4.
194 pass
196class EncryptedData(object):
197 def __init__(self):
198 self.etype = None
199 self.kvno = None
200 self.ciphertext = None
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
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
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
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
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
245 def __str__(self):
246 return "<Ticket for %s vno %s>" % (str(self.service_principal), str(self.encrypted_part.kvno))
248class KerberosTime(object):
249 INDEFINITE = datetime.datetime(1970, 1, 1, 0, 0, 0)
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.
256 return "%04d%02d%02d%02d%02d%02dZ" % (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
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)
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"))