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# Copyright (c) 2013, Marc Horowitz 

2# All rights reserved. 

3# 

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

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

6# met: 

7# 

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

9# this list of conditions and the following disclaimer. 

10# 

11# Redistributions in binary form must reproduce the above copyright 

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

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

14# 

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

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

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

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

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

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

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

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

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

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

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

26 

27import datetime 

28import socket 

29import re 

30import struct 

31 

32from pyasn1.codec.der import decoder 

33 

34from . import asn1 

35from . import constants 

36 

37 

38class KerberosException(Exception): 

39 pass 

40 

41def _asn1_decode(data, asn1Spec): 

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

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

44 if substrate != b'': 

45 raise KerberosException("asn1 encoding invalid") 

46 return data 

47 

48# A principal can be represented as: 

49 

50class Principal(object): 

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

52* a single string 

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

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

55 component is the realm 

56 

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

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

59 self.type = constants.PrincipalNameType.NT_UNKNOWN 

60 self.components = [] 

61 self.realm = None 

62 

63 if value is None: 

64 return 

65 

66 try: # Python 2 

67 if isinstance(value, unicode): 67 ↛ 68,   67 ↛ 732 missed branches: 1) line 67 didn't jump to line 68, because the condition on line 67 was never true, 2) line 67 didn't jump to line 73, because the condition on line 67 was never false

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

69 except NameError: # Python 3 

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

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

72 

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

74 self.type = value.type 

75 self.components = value.components[:] 

76 self.realm = value.realm 

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

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

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

80 raise KerberosException("invalid principal syntax") 

81 

82 def unquote_component(comp): 

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

84 

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

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

87 else: 

88 self.realm = default_realm 

89 

90 self.components = [ 

91 unquote_component(qc) 

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

93 elif len(value) == 2: 

94 self.components = value[0] 

95 self.realm = value[-1] 

96 if isinstance(self.components, str): 

97 self.components = [self.components] 

98 elif len(value) >= 2: 

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

100 self.realm = value[-1] 

101 else: 

102 raise KerberosException("invalid principal value") 

103 

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

105 self.type = type 

106 

107 def __eq__(self, other): 

108 if isinstance (other, str): 

109 other = Principal (other) 

110 

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

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

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

114 self.realm == other.realm 

115 

116 def __str__(self): 

117 def quote_component(comp): 

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

119 

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

121 if self.realm is not None: 

122 ret += "@" + self.realm 

123 

124 return ret 

125 

126 def __repr__(self): 

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

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

129 

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

131 name = data.getComponentByName(name_component) 

132 self.type = constants.PrincipalNameType( 

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

134 self.components = [ 

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

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

137 return self 

138 

139 def components_to_asn1(self, name): 

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

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

142 ).getComponentByName('name-string') 

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

144 strings.setComponentByPosition(i, c) 

145 

146 return name 

147 

148class Address(object): 

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

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

151 

152 def __init__(self): 

153 self.type = None 

154 self.data = None 

155 

156 def __str__(self): 

157 family = self.family 

158 

159 if family is not None: 

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

161 else: 

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

163 

164 @property 

165 def family(self): 

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

167 return socket.AF_INET 

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

169 return socket.AF_INET6 

170 else: 

171 return None 

172 

173 @property 

174 def address(self): 

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

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

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

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

179 else: 

180 return None 

181 

182 def encode(self): 

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

184 pass 

185 

186class EncryptedData(object): 

187 def __init__(self): 

188 self.etype = None 

189 self.kvno = None 

190 self.ciphertext = None 

191 

192 def from_asn1(self, data): 

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

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

195 kvno = data.getComponentByName('kvno') 

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

197 self.kvno = False 

198 else: 

199 self.kvno = kvno 

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

201 return self 

202 

203 def to_asn1(self, component): 

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

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

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

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

208 return component 

209 

210class Ticket(object): 

211 def __init__(self): 

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

213 # version number. 

214 self.tkt_vno = None 

215 self.service_principal = None 

216 self.encrypted_part = None 

217 

218 def from_asn1(self, data): 

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

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

221 self.service_principal = Principal() 

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

223 self.encrypted_part = EncryptedData() 

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

225 return self 

226 

227 def to_asn1(self, component): 

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

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

230 asn1.seq_set(component, 'sname', 

231 self.service_principal.components_to_asn1) 

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

233 return component 

234 

235 def __str__(self): 

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

237 

238class KerberosTime(object): 

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

240 

241 @staticmethod 

242 def to_asn1(dt): 

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

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

245 

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

247 

248 @staticmethod 

249 def from_asn1(data): 

250 data = str(data) 

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

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

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

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

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

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

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

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

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

260 

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

262 # TODO marc: turn this into a real test 

263 print(Principal("marc")) 

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

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

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

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

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

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

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

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

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

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

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