spnego.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. # Copyright (c) 2003-2016 CORE Security Technologies
  2. #
  3. # This software is provided under under a slightly modified version
  4. # of the Apache Software License. See the accompanying LICENSE file
  5. # for more information.
  6. #
  7. # Author: Alberto Solino (beto@coresecurity.com)
  8. #
  9. # Description:
  10. # SPNEGO functions used by SMB, SMB2/3 and DCERPC
  11. #
  12. from struct import pack, unpack, calcsize
  13. ############### GSS Stuff ################
  14. GSS_API_SPNEGO_UUID = '\x2b\x06\x01\x05\x05\x02'
  15. ASN1_SEQUENCE = 0x30
  16. ASN1_AID = 0x60
  17. ASN1_OID = 0x06
  18. ASN1_OCTET_STRING = 0x04
  19. ASN1_MECH_TYPE = 0xa0
  20. ASN1_MECH_TOKEN = 0xa2
  21. ASN1_SUPPORTED_MECH = 0xa1
  22. ASN1_RESPONSE_TOKEN = 0xa2
  23. ASN1_ENUMERATED = 0x0a
  24. MechTypes = {
  25. '+\x06\x01\x04\x01\x827\x02\x02\x1e': 'SNMPv2-SMI::enterprises.311.2.2.30',
  26. '+\x06\x01\x04\x01\x827\x02\x02\n': 'NTLMSSP - Microsoft NTLM Security Support Provider',
  27. '*\x86H\x82\xf7\x12\x01\x02\x02': 'MS KRB5 - Microsoft Kerberos 5',
  28. '*\x86H\x86\xf7\x12\x01\x02\x02': 'KRB5 - Kerberos 5',
  29. '*\x86H\x86\xf7\x12\x01\x02\x02\x03': 'KRB5 - Kerberos 5 - User to User'
  30. }
  31. TypesMech = dict((v,k) for k, v in MechTypes.iteritems())
  32. def asn1encode(data = ''):
  33. #res = asn1.SEQUENCE(str).encode()
  34. #import binascii
  35. #print '\nalex asn1encode str: %s\n' % binascii.hexlify(str)
  36. if 0 <= len(data) <= 0x7F:
  37. res = pack('B', len(data)) + data
  38. elif 0x80 <= len(data) <= 0xFF:
  39. res = pack('BB', 0x81, len(data)) + data
  40. elif 0x100 <= len(data) <= 0xFFFF:
  41. res = pack('!BH', 0x82, len(data)) + data
  42. elif 0x10000 <= len(data) <= 0xffffff:
  43. res = pack('!BBH', 0x83, len(data) >> 16, len(data) & 0xFFFF) + data
  44. elif 0x1000000 <= len(data) <= 0xffffffff:
  45. res = pack('!BL', 0x84, len(data)) + data
  46. else:
  47. raise Exception('Error in asn1encode')
  48. return str(res)
  49. def asn1decode(data = ''):
  50. len1 = unpack('B', data[:1])[0]
  51. data = data[1:]
  52. if len1 == 0x81:
  53. pad = calcsize('B')
  54. len2 = unpack('B',data[:pad])[0]
  55. data = data[pad:]
  56. ans = data[:len2]
  57. elif len1 == 0x82:
  58. pad = calcsize('H')
  59. len2 = unpack('!H', data[:pad])[0]
  60. data = data[pad:]
  61. ans = data[:len2]
  62. elif len1 == 0x83:
  63. pad = calcsize('B') + calcsize('!H')
  64. len2, len3 = unpack('!BH', data[:pad])
  65. data = data[pad:]
  66. ans = data[:len2 << 16 + len3]
  67. elif len1 == 0x84:
  68. pad = calcsize('!L')
  69. len2 = unpack('!L', data[:pad])[0]
  70. data = data[pad:]
  71. ans = data[:len2]
  72. # 1 byte length, string <= 0x7F
  73. else:
  74. pad = 0
  75. ans = data[:len1]
  76. return ans, len(ans)+pad+1
  77. class GSSAPI:
  78. # Generic GSSAPI Header Format
  79. def __init__(self, data = None):
  80. self.fields = {}
  81. self['UUID'] = GSS_API_SPNEGO_UUID
  82. if data:
  83. self.fromString(data)
  84. pass
  85. def __setitem__(self,key,value):
  86. self.fields[key] = value
  87. def __getitem__(self, key):
  88. return self.fields[key]
  89. def __delitem__(self, key):
  90. del self.fields[key]
  91. def __len__(self):
  92. return len(self.getData())
  93. def __str__(self):
  94. return len(self.getData())
  95. def fromString(self, data = None):
  96. # Manual parse of the GSSAPI Header Format
  97. # It should be something like
  98. # AID = 0x60 TAG, BER Length
  99. # OID = 0x06 TAG
  100. # GSSAPI OID
  101. # UUID data (BER Encoded)
  102. # Payload
  103. next_byte = unpack('B',data[:1])[0]
  104. if next_byte != ASN1_AID:
  105. raise Exception('Unknown AID=%x' % next_byte)
  106. data = data[1:]
  107. decode_data, total_bytes = asn1decode(data)
  108. # Now we should have a OID tag
  109. next_byte = unpack('B',decode_data[:1])[0]
  110. if next_byte != ASN1_OID:
  111. raise Exception('OID tag not found %x' % next_byte)
  112. decode_data = decode_data[1:]
  113. # Now the OID contents, should be SPNEGO UUID
  114. uuid, total_bytes = asn1decode(decode_data)
  115. self['OID'] = uuid
  116. # the rest should be the data
  117. self['Payload'] = decode_data[total_bytes:]
  118. #pass
  119. def dump(self):
  120. for i in self.fields.keys():
  121. print "%s: {%r}" % (i,self[i])
  122. def getData(self):
  123. ans = pack('B',ASN1_AID)
  124. ans += asn1encode(
  125. pack('B',ASN1_OID) +
  126. asn1encode(self['UUID']) +
  127. self['Payload'] )
  128. return ans
  129. class SPNEGO_NegTokenResp:
  130. # http://tools.ietf.org/html/rfc4178#page-9
  131. # NegTokenResp ::= SEQUENCE {
  132. # negState [0] ENUMERATED {
  133. # accept-completed (0),
  134. # accept-incomplete (1),
  135. # reject (2),
  136. # request-mic (3)
  137. # } OPTIONAL,
  138. # -- REQUIRED in the first reply from the target
  139. # supportedMech [1] MechType OPTIONAL,
  140. # -- present only in the first reply from the target
  141. # responseToken [2] OCTET STRING OPTIONAL,
  142. # mechListMIC [3] OCTET STRING OPTIONAL,
  143. # ...
  144. # }
  145. # This structure is not prepended by a GSS generic header!
  146. SPNEGO_NEG_TOKEN_RESP = 0xa1
  147. SPNEGO_NEG_TOKEN_TARG = 0xa0
  148. def __init__(self, data = None):
  149. self.fields = {}
  150. if data:
  151. self.fromString(data)
  152. pass
  153. def __setitem__(self,key,value):
  154. self.fields[key] = value
  155. def __getitem__(self, key):
  156. return self.fields[key]
  157. def __delitem__(self, key):
  158. del self.fields[key]
  159. def __len__(self):
  160. return len(self.getData())
  161. def __str__(self):
  162. return len(self.getData())
  163. def fromString(self, data = 0):
  164. payload = data
  165. next_byte = unpack('B', payload[:1])[0]
  166. if next_byte != SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP:
  167. raise Exception('NegTokenResp not found %x' % next_byte)
  168. payload = payload[1:]
  169. decode_data, total_bytes = asn1decode(payload)
  170. next_byte = unpack('B', decode_data[:1])[0]
  171. if next_byte != ASN1_SEQUENCE:
  172. raise Exception('SEQUENCE tag not found %x' % next_byte)
  173. decode_data = decode_data[1:]
  174. decode_data, total_bytes = asn1decode(decode_data)
  175. next_byte = unpack('B',decode_data[:1])[0]
  176. if next_byte != ASN1_MECH_TYPE:
  177. # MechType not found, could be an AUTH answer
  178. if next_byte != ASN1_RESPONSE_TOKEN:
  179. raise Exception('MechType/ResponseToken tag not found %x' % next_byte)
  180. else:
  181. decode_data2 = decode_data[1:]
  182. decode_data2, total_bytes = asn1decode(decode_data2)
  183. next_byte = unpack('B', decode_data2[:1])[0]
  184. if next_byte != ASN1_ENUMERATED:
  185. raise Exception('Enumerated tag not found %x' % next_byte)
  186. item, total_bytes2 = asn1decode(decode_data)
  187. self['NegResult'] = item
  188. decode_data = decode_data[1:]
  189. decode_data = decode_data[total_bytes:]
  190. # Do we have more data?
  191. if len(decode_data) == 0:
  192. return
  193. next_byte = unpack('B', decode_data[:1])[0]
  194. if next_byte != ASN1_SUPPORTED_MECH:
  195. if next_byte != ASN1_RESPONSE_TOKEN:
  196. raise Exception('Supported Mech/ResponseToken tag not found %x' % next_byte)
  197. else:
  198. decode_data2 = decode_data[1:]
  199. decode_data2, total_bytes = asn1decode(decode_data2)
  200. next_byte = unpack('B', decode_data2[:1])[0]
  201. if next_byte != ASN1_OID:
  202. raise Exception('OID tag not found %x' % next_byte)
  203. decode_data2 = decode_data2[1:]
  204. item, total_bytes2 = asn1decode(decode_data2)
  205. self['SupportedMech'] = item
  206. decode_data = decode_data[1:]
  207. decode_data = decode_data[total_bytes:]
  208. next_byte = unpack('B', decode_data[:1])[0]
  209. if next_byte != ASN1_RESPONSE_TOKEN:
  210. raise Exception('Response token tag not found %x' % next_byte)
  211. decode_data = decode_data[1:]
  212. decode_data, total_bytes = asn1decode(decode_data)
  213. next_byte = unpack('B', decode_data[:1])[0]
  214. if next_byte != ASN1_OCTET_STRING:
  215. raise Exception('Octet string token tag not found %x' % next_byte)
  216. decode_data = decode_data[1:]
  217. decode_data, total_bytes = asn1decode(decode_data)
  218. self['ResponseToken'] = decode_data
  219. def dump(self):
  220. for i in self.fields.keys():
  221. print "%s: {%r}" % (i,self[i])
  222. def getData(self):
  223. ans = pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP)
  224. if self.fields.has_key('NegResult') and self.fields.has_key('SupportedMech'):
  225. # Server resp
  226. ans += asn1encode(
  227. pack('B', ASN1_SEQUENCE) +
  228. asn1encode(
  229. pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) +
  230. asn1encode(
  231. pack('B',ASN1_ENUMERATED) +
  232. asn1encode( self['NegResult'] )) +
  233. pack('B',ASN1_SUPPORTED_MECH) +
  234. asn1encode(
  235. pack('B',ASN1_OID) +
  236. asn1encode(self['SupportedMech'])) +
  237. pack('B',ASN1_RESPONSE_TOKEN ) +
  238. asn1encode(
  239. pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken']))))
  240. elif self.fields.has_key('NegResult'):
  241. # Server resp
  242. ans += asn1encode(
  243. pack('B', ASN1_SEQUENCE) +
  244. asn1encode(
  245. pack('B', SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) +
  246. asn1encode(
  247. pack('B',ASN1_ENUMERATED) +
  248. asn1encode( self['NegResult'] ))))
  249. else:
  250. # Client resp
  251. ans += asn1encode(
  252. pack('B', ASN1_SEQUENCE) +
  253. asn1encode(
  254. pack('B', ASN1_RESPONSE_TOKEN) +
  255. asn1encode(
  256. pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken']))))
  257. return ans
  258. class SPNEGO_NegTokenInit(GSSAPI):
  259. # http://tools.ietf.org/html/rfc4178#page-8
  260. # NegTokeInit :: = SEQUENCE {
  261. # mechTypes [0] MechTypeList,
  262. # reqFlags [1] ContextFlags OPTIONAL,
  263. # mechToken [2] OCTET STRING OPTIONAL,
  264. # mechListMIC [3] OCTET STRING OPTIONAL,
  265. # }
  266. SPNEGO_NEG_TOKEN_INIT = 0xa0
  267. def fromString(self, data = 0):
  268. GSSAPI.fromString(self, data)
  269. payload = self['Payload']
  270. next_byte = unpack('B', payload[:1])[0]
  271. if next_byte != SPNEGO_NegTokenInit.SPNEGO_NEG_TOKEN_INIT:
  272. raise Exception('NegTokenInit not found %x' % next_byte)
  273. payload = payload[1:]
  274. decode_data, total_bytes = asn1decode(payload)
  275. # Now we should have a SEQUENCE Tag
  276. next_byte = unpack('B', decode_data[:1])[0]
  277. if next_byte != ASN1_SEQUENCE:
  278. raise Exception('SEQUENCE tag not found %x' % next_byte)
  279. decode_data = decode_data[1:]
  280. decode_data, total_bytes2 = asn1decode(decode_data)
  281. next_byte = unpack('B',decode_data[:1])[0]
  282. if next_byte != ASN1_MECH_TYPE:
  283. raise Exception('MechType tag not found %x' % next_byte)
  284. decode_data = decode_data[1:]
  285. remaining_data = decode_data
  286. decode_data, total_bytes3 = asn1decode(decode_data)
  287. next_byte = unpack('B', decode_data[:1])[0]
  288. if next_byte != ASN1_SEQUENCE:
  289. raise Exception('SEQUENCE tag not found %x' % next_byte)
  290. decode_data = decode_data[1:]
  291. decode_data, total_bytes4 = asn1decode(decode_data)
  292. # And finally we should have the MechTypes
  293. self['MechTypes'] = []
  294. while decode_data:
  295. next_byte = unpack('B', decode_data[:1])[0]
  296. if next_byte != ASN1_OID:
  297. # Not a valid OID, there must be something else we won't unpack
  298. break
  299. decode_data = decode_data[1:]
  300. item, total_bytes = asn1decode(decode_data)
  301. self['MechTypes'].append(item)
  302. decode_data = decode_data[total_bytes:]
  303. # Do we have MechTokens as well?
  304. decode_data = remaining_data[total_bytes3:]
  305. if len(decode_data) > 0:
  306. next_byte = unpack('B', decode_data[:1])[0]
  307. if next_byte == ASN1_MECH_TOKEN:
  308. # We have tokens in here!
  309. decode_data = decode_data[1:]
  310. decode_data, total_bytes = asn1decode(decode_data)
  311. next_byte = unpack('B', decode_data[:1])[0]
  312. if next_byte == ASN1_OCTET_STRING:
  313. decode_data = decode_data[1:]
  314. decode_data, total_bytes = asn1decode(decode_data)
  315. self['MechToken'] = decode_data
  316. def getData(self):
  317. mechTypes = ''
  318. for i in self['MechTypes']:
  319. mechTypes += pack('B', ASN1_OID)
  320. mechTypes += asn1encode(i)
  321. mechToken = ''
  322. # Do we have tokens to send?
  323. if self.fields.has_key('MechToken'):
  324. mechToken = pack('B', ASN1_MECH_TOKEN) + asn1encode(
  325. pack('B', ASN1_OCTET_STRING) + asn1encode(
  326. self['MechToken']))
  327. ans = pack('B',SPNEGO_NegTokenInit.SPNEGO_NEG_TOKEN_INIT)
  328. ans += asn1encode(
  329. pack('B', ASN1_SEQUENCE) +
  330. asn1encode(
  331. pack('B', ASN1_MECH_TYPE) +
  332. asn1encode(
  333. pack('B', ASN1_SEQUENCE) +
  334. asn1encode(mechTypes)) + mechToken ))
  335. self['Payload'] = ans
  336. return GSSAPI.getData(self)