Codebase list msldap / e7792b8
Import upstream version 0.4.1 Kali Janitor 1 year, 7 months ago
85 changed file(s) with 1571 addition(s) and 9126 deletion(s). Raw diff Collapse all Expand all
0 Metadata-Version: 1.2
0 Metadata-Version: 2.1
11 Name: msldap
2 Version: 0.3.30
2 Version: 0.4.1
33 Summary: Python library to play with MS LDAP
44 Home-page: https://github.com/skelsec/msldap
55 Author: Tamas Jos
66 Author-email: [email protected]
77 License: UNKNOWN
8 Description: Python library to play with MS LDAP
98 Platform: UNKNOWN
109 Classifier: Programming Language :: Python :: 3.7
1110 Classifier: Programming Language :: Python :: 3.8
1211 Classifier: License :: OSI Approved :: MIT License
1312 Classifier: Operating System :: OS Independent
1413 Requires-Python: >=3.7
14 License-File: LICENSE
15
16 Python library to play with MS LDAP
17
0 [![Documentation Status](https://readthedocs.org/projects/msldap/badge/?version=latest)](https://msldap.readthedocs.io/en/latest/?badge=latest)
0 ![Supported Python versions](https://img.shields.io/badge/python-3.6+-blue.svg) [![Documentation Status](https://readthedocs.org/projects/msldap/badge/?version=latest)](https://msldap.readthedocs.io/en/latest/?badge=latest) [![Twitter](https://img.shields.io/twitter/follow/skelsec?label=skelsec&style=social)](https://twitter.com/intent/follow?screen_name=skelsec)
11
2 # msldap client
3 ![Documentation Status](https://user-images.githubusercontent.com/19204702/81515211-3761e880-9333-11ea-837f-bcbe2a67ee48.gif )
2 :triangular_flag_on_post: This is the public repository of msldap, for latest version and updates please consider supporting us through https://porchetta.industries/
43
54 # msldap
65 LDAP library for MS AD
6 ![Documentation Status](https://user-images.githubusercontent.com/19204702/81515211-3761e880-9333-11ea-837f-bcbe2a67ee48.gif )
7
8 ## :triangular_flag_on_post: Sponsors
9
10 If you want to sponsors this project and have the latest updates on this project, latest issues fixed, latest features, please support us on https://porchetta.industries/
11
12 ## Official Discord Channel
13
14 Come hang out on Discord!
15
16 [![Porchetta Industries](https://discordapp.com/api/guilds/736724457258745996/widget.png?style=banner3)](https://discord.gg/ycGXUxy)
17
718
819 # Documentation
920 [Awesome documentation here!](https://msldap.readthedocs.io/en/latest/)
2839 `pip install msldap`
2940
3041 # Prerequisites
31 - `winsspi` module. For windows only. This supports SSPI based authentication.
3242 - `asn1crypto` module. Some LDAP queries incorporate ASN1 strucutres to be sent on top of the ASN1 transport XD
3343 - `asysocks` module. To support socks proxying.
3444 - `aiocmd` For the interactive client
5969 - kerberos-aes (dc option param must be used)
6070 - kerberos-keytab (dc option param must be used)
6171 - kerberos-ccache (dc option param must be used)
72 - kerberos-pfx (dc option param must be used)
73 - kerberos-pem (dc option param must be used)
74 - kerberos-certstore (dc option param must be used, windows only)
6275 - sspi-ntlm (windows only!)
6376 - sspi-kerberos (windows only!)
6477 - anonymous
95108 ```
96109
97110 # Kudos
98
111 Certificate services functionality was based on [certi](https://github.com/zer1t0/certi) created by @zer1t0
55
66 import logging
77 import csv
8 from msldap.connection import MSLDAPConnection
9 from msldap.commons.url import MSLDAPURLDecoder
8 from msldap.connection import MSLDAPClientConnection
9 from msldap.commons.factory import LDAPConnectionFactory
1010 from msldap.ldap_objects import MSADUser, MSADMachine
1111
1212
3838 else:
3939 logging.basicConfig(level=logging.DEBUG)
4040
41 url_dec = MSLDAPURLDecoder(args.connection)
41 url_dec = LDAPConnectionFactory.from_url(args.connection)
4242 creds = url_dec.get_credential()
4343 target = url_dec.get_target()
4444 print(str(creds))
4545 print(str(target))
46 connection = MSLDAPConnection(creds, target)
46 connection = MSLDAPClientConnection(creds, target)
4747
4848 if args.command == 'dsa':
4949 print(connection.get_server_info())
00
1 __version__ = "0.3.30"
1 __version__ = "0.4.1"
22 __banner__ = \
33 """
44 # msldap %s
+0
-0
msldap/authentication/__init__.py less more
(Empty file)
+0
-0
msldap/authentication/kerberos/__init__.py less more
(Empty file)
+0
-543
msldap/authentication/kerberos/gssapi.py less more
0 import enum
1 import io
2 import os
3
4 from asn1crypto.core import ObjectIdentifier
5
6 from minikerberos.protocol.constants import EncryptionType
7 from minikerberos.protocol import encryption
8 from minikerberos.crypto.hashing import md5, hmac_md5
9 from minikerberos.crypto.RC4 import RC4
10
11 #TODO: RC4 support!
12
13 # https://tools.ietf.org/html/draft-raeburn-krb-rijndael-krb-05
14 # https://tools.ietf.org/html/rfc2478
15 # https://tools.ietf.org/html/draft-ietf-krb-wg-gssapi-cfx-02
16 # https://tools.ietf.org/html/rfc4757
17 # https://www.rfc-editor.org/errata/rfc4757
18
19 GSS_WRAP_HEADER = b'\x60\x2b\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'
20 GSS_WRAP_HEADER_OID = b'\x60\x2b\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'
21
22 class KRB5_MECH_INDEP_TOKEN:
23 # https://tools.ietf.org/html/rfc2743#page-81
24 # Mechanism-Independent Token Format
25
26 def __init__(self, data, oid, remlen = None):
27 self.oid = oid
28 self.data = data
29
30 #dont set this
31 self.length = remlen
32
33 @staticmethod
34 def from_bytes(data):
35 return KRB5_MECH_INDEP_TOKEN.from_buffer(io.BytesIO(data))
36
37 @staticmethod
38 def from_buffer(buff):
39
40 start = buff.read(1)
41 if start != b'\x60':
42 raise Exception('Incorrect token data!')
43 remaining_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
44 token_data = buff.read(remaining_length)
45
46 buff = io.BytesIO(token_data)
47 pos = buff.tell()
48 buff.read(1)
49 oid_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
50 buff.seek(pos)
51 token_oid = ObjectIdentifier.load(buff.read(oid_length+2))
52
53 return KRB5_MECH_INDEP_TOKEN(buff.read(), str(token_oid), remlen = remaining_length)
54
55 @staticmethod
56 def decode_length_buffer(buff):
57 lf = buff.read(1)[0]
58 if lf <= 127:
59 length = lf
60 else:
61 bcount = lf - 128
62 length = int.from_bytes(buff.read(bcount), byteorder = 'big', signed = False)
63 return length
64
65 @staticmethod
66 def encode_length(length):
67 if length <= 127:
68 return length.to_bytes(1, byteorder = 'big', signed = False)
69 else:
70 lb = length.to_bytes((length.bit_length() + 7) // 8, 'big')
71 return (128+len(lb)).to_bytes(1, byteorder = 'big', signed = False) + lb
72
73
74 def to_bytes(self):
75 t = ObjectIdentifier(self.oid).dump() + self.data
76 t = b'\x60' + KRB5_MECH_INDEP_TOKEN.encode_length(len(t)) + t
77 return t[:-len(self.data)] , self.data
78
79
80 class GSSAPIFlags(enum.IntFlag):
81 GSS_C_DCE_STYLE = 0x1000
82 GSS_C_DELEG_FLAG = 1
83 GSS_C_MUTUAL_FLAG = 2
84 GSS_C_REPLAY_FLAG = 4
85 GSS_C_SEQUENCE_FLAG = 8
86 GSS_C_CONF_FLAG = 0x10
87 GSS_C_INTEG_FLAG = 0x20
88
89 class KG_USAGE(enum.Enum):
90 ACCEPTOR_SEAL = 22
91 ACCEPTOR_SIGN = 23
92 INITIATOR_SEAL = 24
93 INITIATOR_SIGN = 25
94
95 class FlagsField(enum.IntFlag):
96 SentByAcceptor = 0
97 Sealed = 2
98 AcceptorSubkey = 4
99
100 # https://tools.ietf.org/html/rfc4757 (7.2)
101 class GSSMIC_RC4:
102 def __init__(self):
103 self.TOK_ID = b'\x01\x01'
104 self.SGN_ALG = b'\x11\x00' #HMAC
105 self.Filler = b'\xff'*4
106 self.SND_SEQ = None
107 self.SGN_CKSUM = None
108
109 @staticmethod
110 def from_bytes(data):
111 return GSSMIC_RC4.from_buffer(io.BytesIO(data))
112
113 @staticmethod
114 def from_buffer(buff):
115 mic = GSSMIC_RC4()
116 mic.TOK_ID = buff.read(2)
117 mic.SGN_ALG = buff.read(2)
118 mic.Filler = buff.read(4)
119 mic.SND_SEQ = buff.read(8)
120 mic.SGN_CKSUM = buff.read(8)
121
122 return mic
123
124 def to_bytes(self):
125 t = self.TOK_ID
126 t += self.SGN_ALG
127 t += self.Filler
128 t += self.SND_SEQ
129 if self.SGN_CKSUM is not None:
130 t += self.SGN_CKSUM
131
132 return t
133
134 class GSSWRAP_RC4:
135 def __init__(self):
136 self.TOK_ID = b'\x02\x01'
137 self.SGN_ALG = b'\x11\x00' #HMAC
138 self.SEAL_ALG = None
139 self.Filler = b'\xFF' * 2
140 self.SND_SEQ = None
141 self.SGN_CKSUM = None
142 self.Confounder = None
143
144 def __str__(self):
145 t = 'GSSWRAP_RC4\r\n'
146 t += 'TOK_ID : %s\r\n' % self.TOK_ID.hex()
147 t += 'SGN_ALG : %s\r\n' % self.SGN_ALG.hex()
148 t += 'SEAL_ALG : %s\r\n' % self.SEAL_ALG.hex()
149 t += 'Filler : %s\r\n' % self.Filler.hex()
150 t += 'SND_SEQ : %s\r\n' % self.SND_SEQ.hex()
151 t += 'SGN_CKSUM : %s\r\n' % self.SGN_CKSUM.hex()
152 t += 'Confounder : %s\r\n' % self.Confounder.hex()
153 return t
154
155 @staticmethod
156 def from_bytes(data):
157 return GSSWRAP_RC4.from_buffer(io.BytesIO(data))
158
159 @staticmethod
160 def from_buffer(buff):
161 wrap = GSSWRAP_RC4()
162 wrap.TOK_ID = buff.read(2)
163 wrap.SGN_ALG = buff.read(2)
164 wrap.SEAL_ALG = buff.read(2)
165 wrap.Filler = buff.read(2)
166 wrap.SND_SEQ = buff.read(8)
167 wrap.SGN_CKSUM = buff.read(8)
168 wrap.Confounder = buff.read(8)
169
170 return wrap
171
172 def to_bytes(self):
173 t = self.TOK_ID
174 t += self.SGN_ALG
175 t += self.SEAL_ALG
176 t += self.Filler
177 t += self.SND_SEQ
178
179 if self.SGN_CKSUM:
180 t += self.SGN_CKSUM
181 if self.Confounder:
182 t += self.Confounder
183
184
185 return t
186
187 class GSSAPI_RC4:
188 def __init__(self, session_key):
189 self.session_key = session_key
190
191 def GSS_GetMIC(self, data, sequenceNumber, direction = 'init'):
192 raise Exception('Not tested! Sure it needs some changes')
193 GSS_GETMIC_HEADER = b'\x60\x23\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'
194
195 # Let's pad the data
196 pad = (4 - (len(data) % 4)) & 0x3
197 padStr = bytes([pad]) * pad
198 data += padStr
199
200 mic = GSSMIC_RC4()
201
202 if direction == 'init':
203 mic.SND_SEQ = sequenceNumber.to_bytes(4, 'big', signed = False) + b'\x00'*4
204 else:
205 mic.SND_SEQ = sequenceNumber.to_bytes(4, 'big', signed = False) + b'\xff'*4
206
207 Ksign_ctx = hmac_md5(self.session_key.contents)
208 Ksign_ctx.update(b'signaturekey\0')
209 Ksign = Ksign_ctx.digest()
210
211 id = 15
212 temp = md5( id.to_bytes(4, 'little', signed = False) + mic.to_bytes()[:8] ).digest()
213 chksum_ctx = hmac_md5(Ksign)
214 chksum_ctx.update(temp)
215 mic.SGN_CKSUM = chksum_ctx.digest()[:8]
216
217 id = 0
218 temp = hmac_md5(self.session_key.contents)
219 temp.update(id.to_bytes(4, 'little', signed = False))
220
221 Kseq_ctx = hmac_md5(temp.digest())
222 Kseq_ctx.update(mic.SGN_CKSUM)
223 Kseq = Kseq_ctx.digest()
224
225 mic.SGN_CKSUM = RC4(Kseq).encrypt(mic.SND_SEQ)
226
227 return GSS_GETMIC_HEADER + mic.to_bytes()
228
229
230 def GSS_Wrap(self, data, seq_num, direction = 'init', encrypt=True, cofounder = None):
231 #direction = 'a'
232 #seq_num = 0
233 #print('[GSS_Wrap] data: %s' % data)
234 #print('[GSS_Wrap] seq_num: %s' % seq_num.to_bytes(4, 'big', signed = False).hex())
235 #print('[GSS_Wrap] direction: %s' % direction)
236 #print('[GSS_Wrap] encrypt: %s' % encrypt)
237 #
238 #print('[GSS_Wrap] auth_data: %s' % auth_data)
239
240 #pad = 0
241 if encrypt is True:
242 data += b'\x01'
243 #pad = (8 - (len(data) % 8)) & 0x7
244 #padStr = bytes([pad]) * pad
245 #data += padStr
246 #
247 ##data += b'\x08' * 8
248 #print('[GSS_Wrap] pad: %s' % pad)
249 #print('[GSS_Wrap] data padded: %s' % data)
250
251
252 token = GSSWRAP_RC4()
253 token.SEAL_ALG = b'\x10\x00' # RC4
254
255 if direction == 'init':
256 token.SND_SEQ = seq_num.to_bytes(4, 'big', signed = False) + b'\x00'*4
257 else:
258 token.SND_SEQ = seq_num.to_bytes(4, 'big', signed = False) + b'\xff'*4
259
260 token.Confounder = os.urandom(8)
261 #if cofounder is not None:
262 # token.Confounder = cofounder
263 # #testing purposes only, pls remove
264
265
266 temp = hmac_md5(self.session_key.contents)
267 temp.update(b'signaturekey\0')
268 Ksign = temp.digest()
269
270 id = 13
271 Sgn_Cksum = md5(id.to_bytes(4, 'little', signed = False) + token.to_bytes()[:8] + token.Confounder + data).digest()
272
273 klocal = b''
274 for b in self.session_key.contents:
275 klocal += bytes([b ^ 0xf0])
276
277 id = 0
278 temp = hmac_md5(klocal)
279 temp.update(id.to_bytes(4, 'little', signed = False))
280 temp = hmac_md5(temp.digest())
281 temp.update(seq_num.to_bytes(4, 'big', signed = False))
282 Kcrypt = temp.digest()
283
284 temp = hmac_md5(Ksign)
285 temp.update(Sgn_Cksum)
286 token.SGN_CKSUM = temp.digest()[:8]
287
288 id = 0
289 temp = hmac_md5(self.session_key.contents)
290 temp.update(id.to_bytes(4, 'little', signed = False))
291 temp = hmac_md5(temp.digest())
292 temp.update(token.SGN_CKSUM)
293 Kseq = temp.digest()
294
295 token.SND_SEQ = RC4(Kseq).encrypt(token.SND_SEQ)
296
297
298 #if auth_data is not None:
299 if encrypt is False:
300 #print('Unwrap sessionkey: %s' % self.session_key.contents.hex())
301 #print('Unwrap data : %s' % data.hex())
302
303 sspi_wrap = KRB5_MECH_INDEP_TOKEN.from_bytes(data)
304
305 hdr = sspi_wrap.data[:32]
306 data = sspi_wrap.data[32:]
307
308 wrap = GSSWRAP_RC4.from_bytes(hdr)
309
310 id = 0
311 temp = hmac_md5(self.session_key.contents)
312 temp.update(id.to_bytes(4, 'little', signed = False))
313 temp = hmac_md5(temp.digest())
314 temp.update(wrap.SGN_CKSUM)
315 Kseq = temp.digest()
316
317 snd_seq = RC4(Kseq).encrypt(wrap.SND_SEQ)
318
319 id = 0
320 temp = hmac_md5(klocal)
321 temp.update(id.to_bytes(4, 'little', signed = False))
322 temp = hmac_md5(temp.digest())
323 temp.update(snd_seq[:4])
324 Kcrypt = temp.digest()
325
326 rc4 = RC4(Kcrypt)
327 dec_cofounder = rc4.decrypt(wrap.Confounder)
328 dec_data = rc4.decrypt(data)
329
330 id = 13
331 Sgn_Cksum_calc = md5(id.to_bytes(4, 'little', signed = False) + wrap.to_bytes()[:8] + dec_cofounder + dec_data).digest()
332
333 temp = hmac_md5(Ksign)
334 temp.update(Sgn_Cksum_calc)
335 Sgn_Cksum_calc = temp.digest()[:8]
336
337 if wrap.SGN_CKSUM != Sgn_Cksum_calc[:8]:
338 return None, Exception('Integrity verification failed')
339
340 pad = 1
341 return dec_data[:-pad], None
342
343 elif encrypt is True:
344 rc4 = RC4(Kcrypt)
345 token.Confounder = rc4.encrypt(token.Confounder)
346 cipherText = rc4.encrypt(data)
347 finalData, cipherText = KRB5_MECH_INDEP_TOKEN( token.to_bytes() + cipherText, '1.2.840.113554.1.2.2' ).to_bytes()
348
349
350 #print('cipherText %s' % cipherText.hex())
351 #print('finalData %s' % finalData.hex())
352 #print('sessionkey %s' % self.session_key.contents.hex())
353 return cipherText, finalData
354
355
356 def GSS_Unwrap(self, data, seq_num, direction='init'):
357 #print('GSS_Unwrap data : %s' % data)
358 dec_data, err = self.GSS_Wrap(data, seq_num, direction=direction, encrypt = False)
359 #print('GSS_Unwrap decrypted data : %s' % dec_data)
360 return dec_data, err
361
362 # 4.2.6.1. MIC Tokens
363 class GSSMIC:
364 def __init__(self):
365 self.TOK_ID = b'\x04\x04'
366 self.Flags = None
367 self.Filler = b'\xFF' * 5
368 self.SND_SEQ = None
369 self.SGN_CKSUM = None
370
371 @staticmethod
372 def from_bytes(data):
373 return GSSMIC.from_buffer(io.BytesIO(data))
374
375 @staticmethod
376 def from_buffer(buff):
377 m = GSSMIC()
378 m.TOK_ID = buff.read(2)
379 m.Flags = FlagsField(int.from_bytes(buff.read(1), 'big', signed = False))
380 m.Filler = buff.read(5)
381 m.SND_SEQ = int.from_bytes(buff.read(8), 'big', signed = False)
382 m.SGN_CKSUM = buff.read() #should know the size based on the algo!
383 return m
384
385 def to_bytes(self):
386 t = self.TOK_ID
387 t += self.Flags.to_bytes(1, 'big', signed = False)
388 t += self.Filler
389 t += self.SND_SEQ.to_bytes(8, 'big', signed = False)
390 if self.SGN_CKSUM is not None:
391 t += self.SGN_CKSUM
392
393 return t
394
395 # 4.2.6.2. Wrap Tokens
396 class GSSWrapToken:
397 def __init__(self):
398 self.TOK_ID = b'\x05\x04'
399 self.Flags = None
400 self.Filler = b'\xFF'
401 self.EC = None
402 self.RRC = None
403 self.SND_SEQ = None
404 self.Data = None
405
406 @staticmethod
407 def from_bytes(data):
408 return GSSWrapToken.from_buffer(io.BytesIO(data))
409
410 @staticmethod
411 def from_buffer(buff):
412 m = GSSWrapToken()
413 m.TOK_ID = buff.read(2)
414 m.Flags = FlagsField(int.from_bytes(buff.read(1), 'big', signed = False))
415 m.Filler = buff.read(1)
416 m.EC = int.from_bytes(buff.read(2), 'big', signed = False)
417 m.RRC = int.from_bytes(buff.read(2), 'big', signed = False)
418 m.SND_SEQ = int.from_bytes(buff.read(8), 'big', signed = False)
419 return m
420
421 def to_bytes(self):
422 t = self.TOK_ID
423 t += self.Flags.to_bytes(1, 'big', signed = False)
424 t += self.Filler
425 t += self.EC.to_bytes(2, 'big', signed = False)
426 t += self.RRC.to_bytes(2, 'big', signed = False)
427 t += self.SND_SEQ.to_bytes(8, 'big', signed = False)
428 if self.Data is not None:
429 t += self.Data
430
431 return t
432
433 class GSSAPI_AES:
434 def __init__(self, session_key, cipher_type, checksum_profile):
435 self.session_key = session_key
436 self.checksum_profile = checksum_profile
437 self.cipher_type = cipher_type
438 self.cipher = None
439
440 def rotate(self, data, numBytes):
441 numBytes %= len(data)
442 left = len(data) - numBytes
443 result = data[left:] + data[:left]
444 return result
445
446 def unrotate(self, data, numBytes):
447 numBytes %= len(data)
448 result = data[numBytes:] + data[:numBytes]
449 return result
450
451 def GSS_GetMIC(self, data, seq_num):
452 pad = (4 - (len(data) % 4)) & 0x3
453 padStr = bytes([pad]) * pad
454 data += padStr
455
456 m = GSSMIC()
457 m.Flags = FlagsField.AcceptorSubkey
458 m.SND_SEQ = seq_num
459 checksum_profile = self.checksum_profile()
460 m.checksum = checksum_profile.checksum(self.session_key, KG_USAGE.INITIATOR_SIGN.value, data + m.to_bytes()[:16])
461
462 return m.to_bytes()
463
464 def GSS_Wrap(self, data, seq_num, use_padding = False):
465 #print('[GSS_Wrap] seq_num: %s' % seq_num.to_bytes(4, 'big', signed = False).hex())
466 cipher = self.cipher_type()
467 pad = 0
468 if use_padding is True:
469 pad = ((cipher.blocksize - len(data)) % cipher.blocksize) #(cipher.blocksize - (len(data) % cipher.blocksize)) & 15
470 padStr = b'\xFF' * pad
471 data += padStr
472
473 t = GSSWrapToken()
474 t.Flags = FlagsField.AcceptorSubkey | FlagsField.Sealed
475 t.EC = pad
476 t.RRC = 0
477 t.SND_SEQ = seq_num
478
479 #print('Wrap data: %s' % (data + t.to_bytes()))
480 cipher_text = cipher.encrypt(self.session_key, KG_USAGE.INITIATOR_SEAL.value, data + t.to_bytes(), None)
481 t.RRC = 28 #[RFC4121] section 4.2.5
482 cipher_text = self.rotate(cipher_text, t.RRC + t.EC)
483
484 ret1 = cipher_text
485 ret2 = t.to_bytes()
486
487 return ret1, ret2
488
489 def GSS_Unwrap(self, data, seq_num, direction='init', auth_data = None, use_padding = False):
490 #print('')
491 #print('Unwrap data %s' % data[16:])
492 #print('Unwrap hdr %s' % data[:16])
493
494 cipher = self.cipher_type()
495 original_hdr = GSSWrapToken.from_bytes(data[:16])
496 rotated = data[16:]
497
498 cipher_text = self.unrotate(rotated, original_hdr.RRC + original_hdr.EC)
499 plain_text = cipher.decrypt(self.session_key, KG_USAGE.ACCEPTOR_SEAL.value, cipher_text)
500 new_hdr = GSSWrapToken.from_bytes(plain_text[-16:])
501
502 #signature checking
503 new_hdr.RRC = 28
504 if data[:16] != new_hdr.to_bytes():
505 return None, Exception('GSS_Unwrap signature mismatch!')
506
507
508 #print('Unwrap checksum: %s' % plain_text[-(original_hdr.EC + 16):])
509 #print('Unwrap orig chk: %s' % original_hdr.to_bytes())
510 #print('Unwrap result 1: %s' % plain_text)
511 #print('Unwrap result : %s' % plain_text[:-(original_hdr.EC + 16)])
512 return plain_text[:-(original_hdr.EC + 16)], None
513
514 def get_gssapi(session_key):
515 if session_key.enctype == encryption.Enctype.AES256:
516 return GSSAPI_AES(session_key, encryption._AES256CTS, encryption._SHA1AES256)
517 if session_key.enctype == encryption.Enctype.AES128:
518 return GSSAPI_AES(session_key, encryption._AES128CTS, encryption._SHA1AES128)
519 elif session_key.enctype == encryption.Enctype.RC4:
520 return GSSAPI_RC4(session_key)
521 else:
522 raise Exception('Unsupported etype %s' % session_key.enctype)
523
524
525 def test():
526 data = b'\xAF' * 1024
527 session_key = encryption.Key( encryption.Enctype.AES256 , bytes.fromhex('3e242e91996aadd513ecb1bc2369e44183e08e08c51550fa4b681e77f75ed8e1'))
528 sequenceNumber = 0
529 gssapi = get_gssapi(session_key)
530
531 r1, r2 = gssapi.GSS_Wrap(data, sequenceNumber)
532 print(len(r2))
533 sent = r2 + r1
534 print(r1)
535 ret1, ret2 = gssapi.GSS_Unwrap(sent, sequenceNumber)
536
537 print(r1.hex())
538 print(ret1.hex())
539
540
541 if __name__ == '__main__':
542 test()
+0
-155
msldap/authentication/kerberos/multiplexor.py less more
0
1 ##
2 ##
3 ## Interface to allow remote kerberos authentication via Multiplexor
4 ##
5 ##
6 ##
7 ##
8 ##
9 ## TODO: RPC auth type is not implemented or tested!!!!
10
11 from msldap.authentication.spnego.asn1_structs import KRB5Token
12 from msldap.authentication.kerberos.gssapi import get_gssapi, GSSWrapToken, KRB5_MECH_INDEP_TOKEN
13 from minikerberos.protocol.asn1_structs import AP_REQ, AP_REP, TGS_REP
14 from minikerberos.protocol.encryption import Enctype, Key, _enctype_table
15
16 from multiplexor.operator.external.sspi import KerberosSSPIClient
17 from multiplexor.operator import MultiplexorOperator
18 import enum
19
20 # mutual auth not supported
21 # encryption is always on
22 # we dont get the output flags back (lack of time to do the multiplexor protocol... TODO
23
24 class ISC_REQ(enum.IntFlag):
25 DELEGATE = 1
26 MUTUAL_AUTH = 2
27 REPLAY_DETECT = 4
28 SEQUENCE_DETECT = 8
29 CONFIDENTIALITY = 16
30 USE_SESSION_KEY = 32
31 PROMPT_FOR_CREDS = 64
32 USE_SUPPLIED_CREDS = 128
33 ALLOCATE_MEMORY = 256
34 USE_DCE_STYLE = 512
35 DATAGRAM = 1024
36 CONNECTION = 2048
37 CALL_LEVEL = 4096
38 FRAGMENT_SUPPLIED = 8192
39 EXTENDED_ERROR = 16384
40 STREAM = 32768
41 INTEGRITY = 65536
42 IDENTIFY = 131072
43 NULL_SESSION = 262144
44 MANUAL_CRED_VALIDATION = 524288
45 RESERVED1 = 1048576
46 FRAGMENT_TO_FIT = 2097152
47 HTTP = 0x10000000
48
49 class MSLDAPKerberosMultiplexor:
50 def __init__(self, settings):
51 self.iterations = 0
52 self.settings = settings
53 self.mode = 'CLIENT'
54 self.ksspi = None
55 self.client = None
56 self.target = None
57 self.gssapi = None
58 self.etype = None
59 self.session_key = None
60 self.seq_number = 0
61 self.flags = ISC_REQ.CONNECTION
62
63 self.setup()
64
65 def setup(self):
66 if self.settings.encrypt is True:
67 self.flags = \
68 ISC_REQ.CONFIDENTIALITY |\
69 ISC_REQ.INTEGRITY |\
70 ISC_REQ.REPLAY_DETECT |\
71 ISC_REQ.SEQUENCE_DETECT
72
73 def get_seq_number(self):
74 """
75 Fetches the starting sequence number. This is either zero or can be found in the authenticator field of the
76 AP_REQ structure. As windows uses a random seq number AND a subkey as well, we can't obtain it by decrypting the
77 AP_REQ structure. Insead under the hood we perform an encryption operation via EncryptMessage API which will
78 yield the start sequence number
79 """
80 return self.seq_number
81
82 async def encrypt(self, data, message_no):
83 return self.gssapi.GSS_Wrap(data, message_no)
84
85 async def decrypt(self, data, message_no, direction='init', auth_data=None):
86 return self.gssapi.GSS_Unwrap(data, message_no, direction=direction, auth_data=auth_data)
87
88 def signing_needed(self):
89 """
90 Checks if integrity protection was negotiated
91 """
92 return ISC_REQ.INTEGRITY in self.flags
93
94 def encryption_needed(self):
95 """
96 Checks if confidentiality flag was negotiated
97 """
98 return ISC_REQ.CONFIDENTIALITY in self.flags
99
100 def get_session_key(self):
101 return self.session_key
102
103 async def authenticate(self, authData = None, flags = None, seq_number = 0, cb_data=None):
104 #authdata is only for api compatibility reasons
105 if self.ksspi is None:
106 await self.start_remote_kerberos()
107 try:
108 apreq, res = await self.ksspi.authenticate(self.settings.target.to_target_string(), flags=str(self.flags.value))
109 #print('MULTIPLEXOR KERBEROS SSPI, APREQ: %s ERROR: %s' % (apreq, res))
110 if res is not None:
111 return None, None, res
112
113 # here it seems like we get the full token not just the apreq data...
114 # so we need to discard the layers
115
116 self.session_key, err = await self.ksspi.get_session_key()
117 if err is not None:
118 return None, None, err
119
120 unwrap = KRB5_MECH_INDEP_TOKEN.from_bytes(apreq)
121 aprep = AP_REQ.load(unwrap.data[2:]).native
122 subkey = Key(aprep['ticket']['enc-part']['etype'], self.session_key)
123 self.gssapi = get_gssapi(subkey)
124
125 if aprep['ticket']['enc-part']['etype'] != 23:
126 raw_seq_data, err = await self.ksspi.get_seq_number()
127 if err is not None:
128 return None, None, err
129 self.seq_number = GSSWrapToken.from_bytes(raw_seq_data[16:]).SND_SEQ
130
131 return unwrap.data[2:], False, res
132 except Exception as e:
133 return None, None, e
134
135 async def start_remote_kerberos(self):
136 try:
137 #print(self.settings.get_url())
138 #print(self.settings.agent_id)
139 self.operator = MultiplexorOperator(self.settings.get_url())
140 await self.operator.connect()
141 #creating virtual sspi server
142 server_info = await self.operator.start_sspi(self.settings.agent_id)
143 #print(server_info)
144
145 sspi_url = 'ws://%s:%s' % (server_info['listen_ip'], server_info['listen_port'])
146
147 #print(sspi_url)
148 self.ksspi = KerberosSSPIClient(sspi_url)
149 await self.ksspi.connect()
150 except Exception as e:
151 import traceback
152 traceback.print_exc()
153 return None
154
+0
-220
msldap/authentication/kerberos/native.py less more
0 #
1 #
2 # This is just a simple interface to the minikerberos library to support SPNEGO
3 #
4 #
5 # - Links -
6 # 1. See minikerberos library
7
8 import datetime
9
10 import os
11 from minikerberos.common import *
12
13
14 from minikerberos.protocol.asn1_structs import AP_REP, EncAPRepPart, EncryptedData, AP_REQ, Ticket
15 from msldap.authentication.kerberos.gssapi import get_gssapi, KRB5_MECH_INDEP_TOKEN
16 from msldap.commons.proxy import MSLDAPProxyType
17 from minikerberos.protocol.structures import ChecksumFlags
18 from minikerberos.protocol.encryption import Enctype, Key, _enctype_table
19 from minikerberos.protocol.constants import MESSAGE_TYPE
20 from minikerberos.aioclient import AIOKerberosClient
21 from minikerberos.network.aioclientsockssocket import AIOKerberosClientSocksSocket
22 from msldap import logger
23
24 # SMBKerberosCredential
25
26 MSLDAP_SOCKS_PROXY_TYPES = [
27 MSLDAPProxyType.SOCKS4,
28 MSLDAPProxyType.SOCKS4_SSL,
29 MSLDAPProxyType.SOCKS5,
30 MSLDAPProxyType.SOCKS5_SSL,
31 MSLDAPProxyType.WSNET,
32 ]
33
34 class MSLDAPKerberos:
35 def __init__(self, settings):
36 self.settings = settings
37 self.signing_preferred = None
38 self.encryption_preferred = None
39 self.ccred = None
40 self.target = None
41 self.spn = None
42 self.kc = None
43 self.flags = None
44 self.preferred_etypes = [23,17,18]
45
46 self.session_key = None
47 self.gssapi = None
48 self.iterations = 0
49 self.etype = None
50 self.seq_number = 0
51 self.expected_server_seq_number = None
52 self.from_ccache = False
53
54 self.setup()
55
56 def get_seq_number(self):
57 """
58 Returns the initial sequence number. It is 0 by default, but can be adjusted during authentication,
59 by passing the 'seq_number' parameter in the 'authenticate' function
60 """
61 return self.seq_number
62
63 def signing_needed(self):
64 """
65 Checks if integrity protection was negotiated
66 """
67 return ChecksumFlags.GSS_C_INTEG_FLAG in self.flags
68
69 def encryption_needed(self):
70 """
71 Checks if confidentiality flag was negotiated
72 """
73 return ChecksumFlags.GSS_C_CONF_FLAG in self.flags
74
75 async def sign(self, data, message_no, direction = 'init'):
76 """
77 Signs a message.
78 """
79 return self.gssapi.GSS_GetMIC(data, message_no, direction = direction)
80
81 async def encrypt(self, data, message_no):
82 """
83 Encrypts a message.
84 """
85
86 return self.gssapi.GSS_Wrap(data, message_no)
87
88 async def decrypt(self, data, message_no, direction='init'):
89 """
90 Decrypts message. Also performs integrity checking.
91 """
92
93 return self.gssapi.GSS_Unwrap(data, message_no, direction=direction)
94
95 def setup(self):
96 self.ccred = self.settings.ccred
97 self.spn = self.settings.spn
98 self.target = self.settings.target
99 if self.settings.enctypes is not None:
100 self.preferred_etypes = self.settings.enctypes
101
102 self.flags = ChecksumFlags.GSS_C_MUTUAL_FLAG
103 if self.settings.encrypt is True:
104 self.flags = \
105 ChecksumFlags.GSS_C_CONF_FLAG |\
106 ChecksumFlags.GSS_C_INTEG_FLAG |\
107 ChecksumFlags.GSS_C_REPLAY_FLAG |\
108 ChecksumFlags.GSS_C_SEQUENCE_FLAG
109
110
111 def get_session_key(self):
112 return self.session_key.contents, None
113
114
115 async def setup_kc(self):
116 try:
117 # sockst/wsnet proxying is handled by the minikerberos&asysocks modules
118 if self.target.proxy is None or self.target.proxy.type in MSLDAP_SOCKS_PROXY_TYPES:
119 self.kc = AIOKerberosClient(self.ccred, self.target)
120
121 elif self.target.proxy.type in [MSLDAPProxyType.MULTIPLEXOR, MSLDAPProxyType.MULTIPLEXOR_SSL]:
122 from msldap.network.multiplexor import MultiplexorProxyConnection
123 mpc = MultiplexorProxyConnection(self.target)
124 socks_proxy = await mpc.connect(is_kerberos = True)
125
126 self.kc = AIOKerberosClient(self.ccred, socks_proxy)
127
128 else:
129 raise Exception('Unknown proxy type %s' % self.target.proxy.type)
130
131 return None, None
132 except Exception as e:
133 return None, e
134
135 async def authenticate(self, authData, flags = None, seq_number = 0, cb_data = None):
136 """
137 This function is called (multiple times depending on the flags) to perform authentication.
138 """
139 try:
140 if self.kc is None:
141 _, err = await self.setup_kc()
142 if err is not None:
143 return None, None, err
144
145 if self.iterations == 0:
146 self.seq_number = 0
147 self.iterations += 1
148
149 try:
150 #check TGS first, maybe ccache already has what we need
151 for target in self.ccred.ccache.list_targets():
152 # just printing this to debug...
153 logger.debug('CCACHE SPN record: %s' % target)
154 tgs, encpart, self.session_key = await self.kc.get_TGS(self.spn)
155
156 self.from_ccache = True
157 except:
158 tgt = await self.kc.get_TGT(override_etype = self.preferred_etypes)
159 tgs, encpart, self.session_key = await self.kc.get_TGS(self.spn)#, override_etype = self.preferred_etypes)
160
161 #self.expected_server_seq_number = encpart.get('nonce', seq_number)
162
163 ap_opts = []
164 if ChecksumFlags.GSS_C_MUTUAL_FLAG in self.flags or ChecksumFlags.GSS_C_DCE_STYLE in self.flags:
165 if ChecksumFlags.GSS_C_MUTUAL_FLAG in self.flags:
166 ap_opts.append('mutual-required')
167 if self.from_ccache is False:
168 apreq = self.kc.construct_apreq(tgs, encpart, self.session_key, flags = self.flags, seq_number = self.seq_number, ap_opts=ap_opts, cb_data = cb_data)
169 else:
170 apreq = self.kc.construct_apreq_from_ticket(Ticket(tgs['ticket']).dump(), self.session_key, tgs['crealm'], tgs['cname']['name-string'][0], flags = self.flags, seq_number = self.seq_number, ap_opts = ap_opts, cb_data = cb_data)
171 return apreq, True, None
172
173 else:
174 #no mutual or dce auth will take one step only
175 if self.from_ccache is False:
176 apreq = self.kc.construct_apreq(tgs, encpart, self.session_key, flags = self.flags, seq_number = self.seq_number, ap_opts=[], cb_data = cb_data)
177 else:
178 apreq = self.kc.construct_apreq_from_ticket(Ticket(tgs['ticket']).dump(), self.session_key, tgs['crealm'], tgs['cname']['name-string'][0], flags = self.flags, seq_number = self.seq_number, ap_opts = ap_opts, cb_data = cb_data)
179
180
181 self.gssapi = get_gssapi(self.session_key)
182 return apreq, False, None
183
184 else:
185 self.iterations += 1
186 if ChecksumFlags.GSS_C_DCE_STYLE in self.flags:
187 # adata = authData[16:]
188 # if ChecksumFlags.GSS_C_DCE_STYLE in self.flags:
189 # adata = authData
190 raise Exception('DCE auth Not implemented!')
191
192 # at this point we are dealing with mutual authentication
193 # This means that the server sent back an AP-rep wrapped in a token
194 # The APREP contains a new session key we'd need to update and a seq-number
195 # that is expected the server will use for future communication.
196 # For mutual auth we dont need to reply anything after this step,
197 # but for DCE auth a reply is expected. TODO
198
199 # converting the token to aprep
200 token = KRB5_MECH_INDEP_TOKEN.from_bytes(authData)
201 if token.data[:2] != b'\x02\x00':
202 raise Exception('Unexpected token type! %s' % token.data[:2].hex() )
203 aprep = AP_REP.load(token.data[2:]).native
204
205 # decrypting aprep
206 cipher = _enctype_table[int(aprep['enc-part']['etype'])]()
207 cipher_text = aprep['enc-part']['cipher']
208 temp = cipher.decrypt(self.session_key, 12, cipher_text)
209 enc_part = EncAPRepPart.load(temp).native
210
211 #updating session key, gssapi
212 self.session_key = Key(int(enc_part['subkey']['keytype']), enc_part['subkey']['keyvalue'])
213 #self.seq_number = enc_part.get('seq-number', 0)
214 self.gssapi = get_gssapi(self.session_key)
215
216 return b'', False, None
217
218 except Exception as e:
219 return None, None, e
+0
-148
msldap/authentication/kerberos/sspi.py less more
0 #
1 # This is just a simple interface to the winsspi library to support Kerberos
2 # Will only work on windows, ovbiously
3 #
4 #
5 #
6
7 from msldap.authentication.spnego.asn1_structs import KRB5Token
8 from winsspi.sspi import KerberosMSLDAPSSPI
9 from winsspi.common.function_defs import ISC_REQ, GetSequenceNumberFromEncryptdataKerberos
10 from msldap.authentication.kerberos.gssapi import get_gssapi, GSSWrapToken
11 from minikerberos.protocol.asn1_structs import AP_REQ, AP_REP
12 from minikerberos.protocol.encryption import Enctype, Key, _enctype_table
13
14 class MSLDAPKerberosSSPI:
15 def __init__(self, settings):
16 self.iterations = 0
17 self.settings = settings
18 self.username = settings.username
19 self.password = settings.password
20 self.domain = settings.domain
21 self.actual_ctx_flags = None #this will be popilated by the output of get_ticket_for_spn
22 self.flags = ISC_REQ.CONNECTION
23 if settings.encrypt is True:
24 self.flags = ISC_REQ.CONFIDENTIALITY| ISC_REQ.INTEGRITY | ISC_REQ.CONNECTION #| ISC_REQ.MUTUAL_AUTH #| ISC_REQ.USE_DCE_STYLE
25 self.ksspi = None
26 self.spn = settings.spn
27 self.gssapi = None
28 self.etype = None
29 self.session_key = None
30 self.seq_number = None
31
32 def get_seq_number(self):
33 """
34 Fetches the starting sequence number. This is either zero or can be found in the authenticator field of the
35 AP_REQ structure. As windows uses a random seq number AND a subkey as well, we can't obtain it by decrypting the
36 AP_REQ structure. Insead under the hood we perform an encryption operation via EncryptMessage API which will
37 yield the start sequence number
38 """
39 if self.seq_number is not None:
40 return self.seq_number
41 if ISC_REQ.CONFIDENTIALITY in self.actual_ctx_flags:
42 self.seq_number = GetSequenceNumberFromEncryptdataKerberos(self.ksspi.context)
43 if self.seq_number is None:
44 self.seq_number = 0
45
46 return self.seq_number
47
48 def signing_needed(self):
49 """
50 Checks if integrity protection was enabled
51 """
52 return ISC_REQ.INTEGRITY in self.actual_ctx_flags
53
54 def encryption_needed(self):
55 """
56 Checks if confidentiality was enabled
57 """
58 return ISC_REQ.CONFIDENTIALITY in self.actual_ctx_flags
59
60 async def sign(self, data, message_no, direction = 'init'):
61 """
62 Signs a message.
63 """
64 return self.gssapi.GSS_GetMIC(data, message_no, direction = direction)
65
66 async def encrypt(self, data, message_no):
67 """
68 Encrypts a message.
69 """
70 return self.gssapi.GSS_Wrap(data, message_no)
71
72 async def decrypt(self, data, message_no, direction='init'):
73 """
74 Decrypts message. Also performs integrity checking.
75 """
76 return self.gssapi.GSS_Unwrap(data, message_no, direction=direction)
77
78 def get_session_key(self):
79 """
80 Fetches the session key. Under the hood this uses QueryContextAttributes API call.
81 This will fail if the authentication is not yet finished!
82 """
83 err = None
84 if self.session_key is None:
85 self.session_key, err = self.ksspi.get_session_key()
86 return self.session_key, err
87
88 async def authenticate(self, authData = None, flags = None, seq_number = 0, cb_data = None):
89 """
90 This function is called (multiple times depending on the flags) to perform authentication.
91 """
92 try:
93 if self.iterations == 0:
94 self.ksspi = KerberosMSLDAPSSPI(domain = self.domain, username=self.username, password=self.password)
95 token, self.actual_ctx_flags = self.ksspi.get_ticket_for_spn(self.spn, ctx_flags = self.flags)
96 self.iterations += 1
97
98
99 if ISC_REQ.MUTUAL_AUTH in self.actual_ctx_flags or ISC_REQ.USE_DCE_STYLE in self.actual_ctx_flags:
100 #in these cases continuation is needed
101 return token, True, None
102
103 else:
104 #no mutual or dce auth will take one step only
105 _, err = self.get_session_key()
106 if err is not None:
107 return None, None, err
108 apreq = AP_REQ.load(token).native
109 subkey = Key(apreq['ticket']['enc-part']['etype'], self.session_key)
110 self.gssapi = get_gssapi(subkey)
111 self.get_seq_number()
112
113 return token, False, None
114
115
116 else:
117 adata = authData[16:]
118 if ISC_REQ.USE_DCE_STYLE in self.actual_ctx_flags:
119 adata = authData
120 token, self.actual_ctx_flags = self.ksspi.get_ticket_for_spn(self.spn, ctx_flags = self.actual_ctx_flags, token_data = adata)
121
122
123
124 if ISC_REQ.USE_DCE_STYLE in self.actual_ctx_flags:
125 #Using DCE style 3-legged auth
126 aprep = AP_REP.load(token).native
127 else:
128 aprep = AP_REP.load(adata).native
129 subkey = Key(aprep['enc-part']['etype'], self.get_session_key())
130
131 _, err = self.get_session_key()
132 if err is not None:
133 return None, None, err
134
135 _, err = self.get_seq_number()
136 if err is not None:
137 return None, None, err
138
139 subkey = Key(token['enc-part']['etype'], self.session_key)
140 self.gssapi = get_gssapi(subkey)
141
142 self.iterations += 1
143 return token, False, None
144
145 except Exception as e:
146 return None, None, e
147
+0
-126
msldap/authentication/kerberos/sspiproxyws.py less more
0
1 ##
2 ##
3 ## Interface to allow remote kerberos authentication via Multiplexor
4 ##
5 ##
6 ##
7 ##
8 ##
9 ## TODO: RPC auth type is not implemented or tested!!!!
10
11 import enum
12
13 from msldap.authentication.spnego.asn1_structs import KRB5Token
14 from msldap.authentication.kerberos.gssapi import get_gssapi, GSSWrapToken, KRB5_MECH_INDEP_TOKEN
15 from minikerberos.protocol.asn1_structs import AP_REQ, AP_REP, TGS_REP
16 from minikerberos.protocol.encryption import Enctype, Key, _enctype_table
17 from pyodidewsnet.sspiproxyws import SSPIProxyWS
18
19
20 # mutual auth not supported
21 # encryption is always on
22
23 class ISC_REQ(enum.IntFlag):
24 DELEGATE = 1
25 MUTUAL_AUTH = 2
26 REPLAY_DETECT = 4
27 SEQUENCE_DETECT = 8
28 CONFIDENTIALITY = 16
29 USE_SESSION_KEY = 32
30 PROMPT_FOR_CREDS = 64
31 USE_SUPPLIED_CREDS = 128
32 ALLOCATE_MEMORY = 256
33 USE_DCE_STYLE = 512
34 DATAGRAM = 1024
35 CONNECTION = 2048
36 CALL_LEVEL = 4096
37 FRAGMENT_SUPPLIED = 8192
38 EXTENDED_ERROR = 16384
39 STREAM = 32768
40 INTEGRITY = 65536
41 IDENTIFY = 131072
42 NULL_SESSION = 262144
43 MANUAL_CRED_VALIDATION = 524288
44 RESERVED1 = 1048576
45 FRAGMENT_TO_FIT = 2097152
46 HTTP = 0x10000000
47
48 class MSLDAPSSPIProxyKerberosAuth:
49 def __init__(self, settings):
50 self.iterations = 0
51 self.settings = settings
52 self.mode = 'CLIENT'
53 url = '%s://%s:%s' % (self.settings.proto, self.settings.host, self.settings.port)
54 self.sspi = SSPIProxyWS(url, self.settings.agent_id)
55 self.client = None
56 self.target = None
57 self.gssapi = None
58 self.etype = None
59 self.session_key = None
60 self.seq_number = 0
61 self.flags = ISC_REQ.CONNECTION
62
63 self.setup()
64
65 def setup(self):
66 if self.settings.encrypt is True:
67 self.flags = \
68 ISC_REQ.CONFIDENTIALITY |\
69 ISC_REQ.INTEGRITY |\
70 ISC_REQ.REPLAY_DETECT |\
71 ISC_REQ.SEQUENCE_DETECT
72
73 def get_seq_number(self):
74 return self.seq_number
75
76 async def encrypt(self, data, message_no):
77 return self.gssapi.GSS_Wrap(data, message_no)
78
79 async def decrypt(self, data, message_no, direction='init', auth_data=None):
80 return self.gssapi.GSS_Unwrap(data, message_no, direction=direction, auth_data=auth_data)
81
82 def signing_needed(self):
83 """
84 Checks if integrity protection was negotiated
85 """
86 return ISC_REQ.INTEGRITY in self.flags
87
88 def encryption_needed(self):
89 """
90 Checks if confidentiality flag was negotiated
91 """
92 return ISC_REQ.CONFIDENTIALITY in self.flags
93
94 def get_session_key(self):
95 return self.session_key
96
97 async def authenticate(self, authData = None, flags = None, seq_number = 0, cb_data=None):
98 try:
99 status, ctxattr, apreq, err = await self.sspi.authenticate('KERBEROS', '', self.settings.target.to_target_string(), 3, self.flags.value, authdata = b'')
100 if err is not None:
101 raise err
102
103 self.flags = ISC_REQ(ctxattr)
104
105 self.session_key, err = await self.sspi.get_sessionkey()
106 if err is not None:
107 return None, None, err
108
109 unwrap = KRB5_MECH_INDEP_TOKEN.from_bytes(apreq)
110 aprep = AP_REQ.load(unwrap.data[2:]).native
111 subkey = Key(aprep['ticket']['enc-part']['etype'], self.session_key)
112 self.gssapi = get_gssapi(subkey)
113
114 if aprep['ticket']['enc-part']['etype'] != 23:
115 if ISC_REQ.CONFIDENTIALITY in self.flags:
116 raw_seq_data, err = await self.sspi.get_sequenceno()
117 if err is not None:
118 return None, None, err
119 self.seq_number = GSSWrapToken.from_bytes(raw_seq_data[16:]).SND_SEQ
120
121 return unwrap.data[2:], False, None
122 except Exception as e:
123 return None, None, e
124
125
+0
-125
msldap/authentication/kerberos/wsnet.py less more
0
1 ##
2 ##
3 ## Interface to allow remote kerberos authentication via Multiplexor
4 ##
5 ##
6 ##
7 ##
8 ##
9 ## TODO: RPC auth type is not implemented or tested!!!!
10
11 import enum
12
13 from msldap.authentication.spnego.asn1_structs import KRB5Token
14 from msldap.authentication.kerberos.gssapi import get_gssapi, GSSWrapToken, KRB5_MECH_INDEP_TOKEN
15 from minikerberos.protocol.asn1_structs import AP_REQ, AP_REP, TGS_REP
16 from minikerberos.protocol.encryption import Enctype, Key, _enctype_table
17 from pyodidewsnet.clientauth import WSNETAuth
18
19
20 # mutual auth not supported
21 # encryption is always on
22
23 class ISC_REQ(enum.IntFlag):
24 DELEGATE = 1
25 MUTUAL_AUTH = 2
26 REPLAY_DETECT = 4
27 SEQUENCE_DETECT = 8
28 CONFIDENTIALITY = 16
29 USE_SESSION_KEY = 32
30 PROMPT_FOR_CREDS = 64
31 USE_SUPPLIED_CREDS = 128
32 ALLOCATE_MEMORY = 256
33 USE_DCE_STYLE = 512
34 DATAGRAM = 1024
35 CONNECTION = 2048
36 CALL_LEVEL = 4096
37 FRAGMENT_SUPPLIED = 8192
38 EXTENDED_ERROR = 16384
39 STREAM = 32768
40 INTEGRITY = 65536
41 IDENTIFY = 131072
42 NULL_SESSION = 262144
43 MANUAL_CRED_VALIDATION = 524288
44 RESERVED1 = 1048576
45 FRAGMENT_TO_FIT = 2097152
46 HTTP = 0x10000000
47
48 class MSLDAPWSNetKerberosAuth:
49 def __init__(self, settings):
50 self.iterations = 0
51 self.settings = settings
52 self.mode = 'CLIENT'
53 self.sspi = WSNETAuth()
54 self.client = None
55 self.target = None
56 self.gssapi = None
57 self.etype = None
58 self.session_key = None
59 self.seq_number = 0
60 self.flags = ISC_REQ.CONNECTION
61
62 self.setup()
63
64 def setup(self):
65 if self.settings.encrypt is True:
66 self.flags = \
67 ISC_REQ.CONFIDENTIALITY |\
68 ISC_REQ.INTEGRITY |\
69 ISC_REQ.REPLAY_DETECT |\
70 ISC_REQ.SEQUENCE_DETECT
71
72 def get_seq_number(self):
73 return self.seq_number
74
75 async def encrypt(self, data, message_no):
76 return self.gssapi.GSS_Wrap(data, message_no)
77
78 async def decrypt(self, data, message_no, direction='init', auth_data=None):
79 return self.gssapi.GSS_Unwrap(data, message_no, direction=direction, auth_data=auth_data)
80
81 def signing_needed(self):
82 """
83 Checks if integrity protection was negotiated
84 """
85 return ISC_REQ.INTEGRITY in self.flags
86
87 def encryption_needed(self):
88 """
89 Checks if confidentiality flag was negotiated
90 """
91 return ISC_REQ.CONFIDENTIALITY in self.flags
92
93 def get_session_key(self):
94 return self.session_key
95
96 async def authenticate(self, authData = None, flags = None, seq_number = 0, cb_data=None):
97 try:
98 status, ctxattr, apreq, err = await self.sspi.authenticate('KERBEROS', '', self.settings.target.to_target_string(), 3, self.flags.value, authdata = b'')
99 if err is not None:
100 raise err
101
102 self.flags = ISC_REQ(ctxattr)
103
104 self.session_key, err = await self.sspi.get_sessionkey()
105 if err is not None:
106 return None, None, err
107
108 unwrap = KRB5_MECH_INDEP_TOKEN.from_bytes(apreq)
109 aprep = AP_REQ.load(unwrap.data[2:]).native
110 subkey = Key(aprep['ticket']['enc-part']['etype'], self.session_key)
111 self.gssapi = get_gssapi(subkey)
112
113 if aprep['ticket']['enc-part']['etype'] != 23:
114 if ISC_REQ.CONFIDENTIALITY in self.flags:
115 raw_seq_data, err = await self.sspi.get_sequenceno()
116 if err is not None:
117 return None, None, err
118 self.seq_number = GSSWrapToken.from_bytes(raw_seq_data[16:]).SND_SEQ
119
120 return unwrap.data[2:], False, None
121 except Exception as e:
122 return None, None, e
123
124
+0
-0
msldap/authentication/ntlm/__init__.py less more
(Empty file)
+0
-540
msldap/authentication/ntlm/creds_calc.py less more
0 import io
1 import os
2 import hmac
3 import datetime
4
5 from msldap.crypto.symmetric import DES
6 from msldap.crypto.hashing import *
7 from msldap.authentication.ntlm.structures.challenge_response import *
8
9
10 class NTLMCredentials:
11 @staticmethod
12 def construct(ntlmNegotiate, ntlmChallenge, ntlmAuthenticate):
13 # now the guessing-game begins
14
15 if isinstance(ntlmAuthenticate.NTChallenge, NTLMv2Response):
16 #if ntlmAuthenticate._use_NTLMv2:
17 # this is a netNTLMv2 then, otherwise auth would have failed on protocol level
18 creds = netntlmv2()
19 creds.username = ntlmAuthenticate.UserName
20 creds.domain = ntlmAuthenticate.DomainName
21 creds.ServerChallenge = ntlmChallenge.ServerChallenge
22 creds.ClientResponse = ntlmAuthenticate.NTChallenge.Response
23 creds.ChallengeFromClinet = ntlmAuthenticate.NTChallenge.ChallengeFromClinet_hex
24
25 creds2 = netlmv2()
26 creds2.username = ntlmAuthenticate.UserName
27 creds2.domain = ntlmAuthenticate.DomainName
28 creds2.ServerChallenge = ntlmChallenge.ServerChallenge
29 creds2.ClientResponse = ntlmAuthenticate.LMChallenge.Response
30 creds2.ChallengeFromClinet = ntlmAuthenticate.LMChallenge.ChallengeFromClinet
31 return [creds, creds2]
32
33 else:
34 if ntlmAuthenticate.NegotiateFlags & NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY:
35 # extended security is used, this means that the LMresponse actually contains client challenge data
36 # and the LM and NT respondses need to be combined to form the cred data
37 creds = netntlm_ess()
38 creds.username = ntlmAuthenticate.UserName
39 creds.domain = ntlmAuthenticate.DomainName
40 creds.ServerChallenge = ntlmChallenge.ServerChallenge
41 creds.ClientResponse = ntlmAuthenticate.NTChallenge.Response
42 creds.ChallengeFromClinet = ntlmAuthenticate.LMChallenge.Response
43
44 return [creds]
45
46 else:
47 creds = netntlm()
48 creds.username = ntlmAuthenticate.UserName
49 creds.domain = ntlmAuthenticate.DomainName
50 creds.ServerChallenge = ntlmChallenge.ServerChallenge
51 creds.ClientResponse = ntlmAuthenticate.NTChallenge.Response
52
53 if ntlmAuthenticate.NTChallenge.Response == ntlmAuthenticate.LMChallenge.Response:
54 # the the two responses are the same, then the client did not send encrypted LM hashes, only NT
55 return [creds]
56
57
58 # CAME FOR COPPER, FOUND GOLD!!!!!
59 # HOW OUTDATED IS YOUR CLIENT ANYHOW???
60 creds2 = netlm()
61 creds2.username = ntlmAuthenticate.UserName
62 creds2.domain = ntlmAuthenticate.DomainName
63 creds2.ServerChallenge = ntlmChallenge.ServerChallenge
64 creds2.ClientResponse = ntlmAuthenticate.LMChallenge.Response
65 return [creds2, creds]
66
67 class netlm:
68 # not supported by hashcat?
69 def __init__(self):
70 # this part comes from the NTLMAuthenticate class
71 self.username = None
72 self.domain = None
73 # this comes from the NTLMChallenge class
74 self.ServerChallenge = None
75
76 # this is from the LMv1Response class (that is a member of NTLMAuthenticate class)
77 self.ClientResponse = None
78
79 def to_credential(self):
80 cred = Credential('netLM',
81 username = self.username,
82 fullhash = '%s:$NETLM$%s$%s' % (self.username, self.ServerChallenge, self.ClientResponse)
83 )
84 return cred
85
86 def verify(self, creds, credtype='plain'):
87 """
88 Verifies the authentication data against the user credentials
89 Be careful! If the credtype is 'hash' then LM hash is expected!
90 :param creds: dictionary containing the domain, user, hash/password
91 :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform
92 :return: bool
93 """
94
95 # print('Creds: %s' % creds)
96 if creds is None:
97 return True
98
99 if self.domain not in creds:
100 return False
101 if self.username not in creds[self.domain]:
102 return False
103
104 if credtype == 'plain':
105 lm_hash = LMOWFv1(creds[self.domain][self.username])
106 elif credtype == 'hash':
107 lm_hash = bytes.fromhex(creds[self.domain][self.username])
108 else:
109 raise Exception('Unknown cred type!')
110
111 calc_response = DESL(lm_hash, self.ServerChallenge)
112
113 return self.ClientResponse == calc_response.hex()
114
115
116 class netlmv2:
117 # not supported by hashcat?
118 def __init__(self):
119 # this part comes from the NTLMAuthenticate class
120 self.username = None
121 self.domain = None
122 # this comes from the NTLMChallenge class
123 self.ServerChallenge = None
124
125 # this is from the LMv2Response class (that is a member of NTLMAuthenticate class)
126 self.ClientResponse = None
127 self.ChallengeFromClinet = None
128
129 def to_credential(self):
130 cred = Credential(
131 'netLMv2',
132 username = self.username,
133 fullhash = '$NETLMv2$%s$%s$%s$%s' % (self.username, self.ServerChallenge, self.ClientResponse, self.ChallengeFromClinet)
134 )
135 return cred
136
137 def verify(self, creds, credtype='plain'):
138 """
139 Verifies the authentication data against the user credentials
140 :param creds: dictionary containing the domain, user, hash/password
141 :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform
142 :return: bool
143 """
144
145 # print('Creds: %s' % creds)
146 if creds is None:
147 return True
148
149 if self.domain not in creds:
150 return False
151 if self.username not in creds[self.domain]:
152 return False
153
154 if credtype == 'plain':
155 lm_hash = LMOWFv2(creds[self.domain][self.username], self.username, self.domain)
156 elif credtype == 'hash':
157 lm_hash = LMOWFv2(None, self.username, self.domain, bytes.fromhex(creds[self.domain][self.username]))
158 else:
159 raise Exception('Unknown cred type!')
160
161 hm = hmac_md5(lm_hash)
162 hm.update(bytes.fromhex(self.ServerChallenge))
163 hm.update(bytes.fromhex(self.ChallengeFromClinet))
164
165 return self.ClientResponse == hm.hexdigest()
166
167
168 class netntlm_ess:
169 def __init__(self):
170 # this part comes from the NTLMAuthenticate class
171 self.credentials = None
172 # this comes from the NTLMChallenge class
173 self.ServerChallenge = None
174
175 self.LMResponse = None
176 self.NTResponse = None
177
178 self.SessionBaseKey = None
179
180
181 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/d86303b5-b29e-4fb9-b119-77579c761370
182 def calc_key_exchange_key(self):
183 if not self.credentials.nt_hash:
184 nt_hash = NTOWFv1(self.credentials.password)
185 else:
186 nt_hash = bytes.fromhex(self.credentials.nt_hash)
187
188 hm = hmac_md5(self.SessionBaseKey)
189 hm.update(self.ServerChallenge)
190 hm.update(self.LMResponse.to_bytes()[:8])
191
192 return hm.digest()
193
194 @staticmethod
195 def construct(server_challenge, client_challenge, credentials):
196 ntlm_creds = netntlm_ess()
197 ntlm_creds.credentials = credentials
198 ntlm_creds.ServerChallenge = server_challenge
199
200 if credentials.password:
201 nt_hash = NTOWFv1(credentials.password)
202 lm_hash = LMOWFv1(credentials.password)
203 else:
204 nt_hash = bytes.fromhex(credentials.nt_hash)
205 lm_hash = bytes.fromhex(credentials.lm_hash) if credentials.lm_hash else None
206
207
208 ntlm_creds.LMResponse = LMResponse()
209 ntlm_creds.LMResponse.Response = client_challenge + b'\x00' * 16
210
211 temp_1 = md5(server_challenge + client_challenge[:8]).digest()
212 data = DESL(nt_hash, temp_1[:8])
213
214 ntlm_creds.NTResponse = NTLMv1Response()
215 ntlm_creds.NTResponse.Response = data
216
217 ntlm_creds.SessionBaseKey = md4(nt_hash).digest()
218
219 return ntlm_creds
220
221 def to_credential(self):
222 cred = Credential(
223 'netNTLMv1-ESS',
224 username = self.username,
225 fullhash = '%s::%s:%s:%s:%s' % (self.credentials.username, self.credentials.domain, ntlm_creds.LMResponse.Response, ntlm_creds.NTResponse.Response, self.ServerChallenge)
226 )
227 return cred
228 # u4-netntlm::kNS:338d08f8e26de93300000000000000000000000000000000:9526fb8c23a90751cdd619b6cea564742e1e4bf33006ba41:cb8086049ec4736c
229
230 def calc_session_base_key(self, creds, credtype = 'plain'):
231 if credtype == 'plain':
232 nt_hash = NTOWFv1(creds[self.domain][self.username])
233 elif credtype == 'hash':
234 nt_hash = bytes.fromhex(creds[self.domain][self.username])
235 else:
236 raise Exception('Unknown cred type!')
237
238 session_base_key = md4(nt_hash).digest()
239 return session_base_key
240
241 def verify(self, creds, credtype='plain'):
242 """
243 Verifies the authentication data against the user credentials
244 :param creds: dictionary containing the domain, user, hash/password
245 :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform
246 :return: bool
247 """
248 if creds is None:
249 return True
250 if self.domain not in creds:
251 return False
252 if self.username not in creds[self.domain]:
253 return False
254
255 if credtype == 'plain':
256 nt_hash = NTOWFv1(creds[self.domain][self.username])
257 elif credtype == 'hash':
258 nt_hash = bytes.fromhex(creds[self.domain][self.username])
259 else:
260 raise Exception('Unknown cred type!')
261
262 # print('Server chall: %s' % self.ServerChallenge)
263 # print('Client chall: %s' % self.ChallengeFromClinet)
264
265 temp_1 = md5(bytes.fromhex(self.ServerChallenge) + bytes.fromhex(self.ChallengeFromClinet)[:8]).digest()
266 calc_response = DESL(nt_hash, temp_1[:8])
267 # print('calc_response: %s' % calc_response.hex())
268 # print('ClientResponse: %s' % self.ClientResponse)
269
270 return calc_response == bytes.fromhex(self.ClientResponse)
271
272
273 class netntlm:
274 # not supported by hashcat?
275 def __init__(self):
276 # this part comes from the NTLMAuthenticate class
277 self.credentials = None
278 # this comes from the NTLMChallenge class
279 self.ServerChallenge = None
280
281 self.LMResponse = None
282 self.NTResponse = None
283
284
285 self.SessionBaseKey = None
286
287 def calc_key_exchange_key(self, with_lm = False, non_nt_session_key = False):
288
289 if self.credentials.password:
290 lm_hash = LMOWFv1(self.credentials.password)
291 else:
292 lm_hash = self.credentials.lm_hash
293
294 if with_lm:
295 temp1 = DES(lm_hash[:7]).encrypt(self.LMResponse.to_bytes()[:8])
296 temp2 = DES(lm_hash[7:8] + b'\xBD\xBD\xBD\xBD\xBD\xBD').encrypt(self.LMResponse.to_bytes()[:8])
297 kex = temp1 + temp2
298
299 else:
300 if non_nt_session_key:
301 kex = lm_hash[:8] + b'\x00' * 8
302 else:
303 kex = self.SessionBaseKey
304
305 return kex
306
307 @staticmethod
308 def construct(server_challenge, credentials):
309 ntlm_creds = netntlm()
310 ntlm_creds.credentials = credentials
311 ntlm_creds.ServerChallenge = server_challenge
312
313 if credentials.password:
314 nt_hash = NTOWFv1(credentials.password)
315 lm_hash = LMOWFv1(credentials.password)
316 else:
317 nt_hash = bytes.fromhex(credentials.nt_hash)
318 lm_hash = bytes.fromhex(credentials.lm_hash) if credentials.lm_hash else None
319
320 ntlm_creds.NTResponse = NTLMv1Response()
321 ntlm_creds.NTResponse.Response = DESL(nt_hash, server_challenge)
322
323 if lm_hash:
324 ntlm_creds.LMResponse = LMResponse()
325 ntlm_creds.LMResponse.Response = DESL(lm_hash, server_challenge)
326 else:
327 ntlm_creds.LMResponse = ntresponse
328
329 ntlm_creds.SessionBaseKey = md4(nt_hash).digest()
330
331 return ntlm_creds
332
333 def to_credential(self):
334 cred = Credential('netNTLMv1',
335 username = self.username,
336 fullhash = '%s:$NETNTLM$%s$%s' % (self.username, self.ServerChallenge, self.NTResponse.Response)
337 )
338 return cred
339 #username:$NETNTLM$1122334455667788$B2B2220790F40C88BCFF347C652F67A7C4A70D3BEBD70233
340
341 def calc_session_base_key(self, creds, credtype = 'plain'):
342 if credtype == 'plain':
343 nt_hash = NTOWFv1(creds[self.domain][self.username])
344 elif credtype == 'hash':
345 nt_hash = bytes.fromhex(creds[self.domain][self.username])
346 else:
347 raise Exception('Unknown cred type!')
348
349 session_base_key = md4(nt_hash).digest()
350 return session_base_key
351
352 def verify(self, creds, credtype='plain'):
353 """
354 Verifies the authentication data against the user credentials
355 :param creds: dictionary containing the domain, user, hash/password
356 :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform
357 :return: bool
358 """
359 if creds is None:
360 return True
361 if self.domain not in creds:
362 return False
363 if self.username not in creds[self.domain]:
364 return False
365
366 if credtype == 'plain':
367 nt_hash = NTOWFv1(creds[self.domain][self.username])
368 elif credtype == 'hash':
369 nt_hash = bytes.fromhex(creds[self.domain][self.username])
370 else:
371 raise Exception('Unknown cred type!')
372
373 return DESL(nt_hash, self.ServerChallenge) == bytes.fromhex(self.ClientResponse)
374
375
376 class netntlmv2:
377 def __init__(self):
378 self.credentials = None
379
380 # this comes from the NTLMChallenge class
381 self.ServerChallenge = None
382
383 # this is from the NTLMv2Response class (that is a member of NTLMAuthenticate class)
384 #self.ClientResponse = None
385 #self.ChallengeFromClinet = None
386
387 self.LMResponse = None
388 self.NTResponse = None
389
390
391 self.SessionBaseKey = None
392
393 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/d86303b5-b29e-4fb9-b119-77579c761370
394 def calc_key_exchange_key(self):
395 return self.SessionBaseKey
396
397 @staticmethod
398 def construct(server_challenge, client_challenge, server_details, credentials, timestamp = None):
399 ntlm_creds = netntlmv2()
400 ntlm_creds.credentials = credentials
401 ntlm_creds.ServerChallenge = server_challenge
402
403 if not credentials.nt_hash and not credentials.password:
404 raise Exception('Password or NT hash must be supplied!')
405
406 if credentials.password:
407 nt_hash_v2 = NTOWFv2(credentials.password, credentials.username, credentials.domain)
408 else:
409 nt_hash_v2 = NTOWFv2(None, credentials.username, credentials.domain, bytes.fromhex(credentials.nt_hash))
410
411 if not timestamp:
412 timestamp = datetime.datetime.utcnow()
413
414 cc = NTLMv2ClientChallenge.construct(timestamp, client_challenge, server_details)
415 temp = cc.to_bytes()
416
417 hm = hmac_md5(nt_hash_v2)
418 hm.update(server_challenge)
419 hm.update(temp)
420
421 NTProofStr = hm.digest()
422
423 ntlm_creds.NTResponse = NTLMv2Response()
424 ntlm_creds.NTResponse.Response = NTProofStr
425 ntlm_creds.NTResponse.ChallengeFromClinet = cc
426
427
428 hm = hmac_md5(nt_hash_v2)
429 hm.update(server_challenge)
430 hm.update(client_challenge)
431
432 ntlm_creds.LMResponse = LMv2Response()
433 ntlm_creds.LMResponse.Response = hm.digest()
434 ntlm_creds.LMResponse.ChallengeFromClinet = client_challenge
435
436
437 hm = hmac_md5(nt_hash_v2)
438 hm.update(NTProofStr)
439 ntlm_creds.SessionBaseKey = hm.digest()
440
441 return ntlm_creds
442
443 def to_credential(self):
444 cred = Credential(
445 'netNTLMv2',
446 username = self.username,
447 domain = self.domain,
448 fullhash = '%s::%s:%s:%s:%s' % (self.credentials.username, self.credentials.domain, self.ServerChallenge, self.NTResponse.Response, self.NTResponse.ChallengeFromClinet)
449 )
450 return cred
451
452 def verify(self, creds, credtype = 'plain'):
453 """
454 Verifies the authentication data against the user credentials
455 :param creds: dictionary containing the domain, user, hash/password
456 :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform
457 :return: bool
458 """
459
460 # print('Creds: %s' % creds)
461 if creds is None:
462 return True
463
464 if self.domain not in creds:
465 return False
466 if self.username not in creds[self.domain]:
467 return False
468
469 if credtype == 'plain':
470 nt_hash = NTOWFv2(creds[self.domain][self.username], self.username, self.domain)
471 elif credtype == 'hash':
472 nt_hash = NTOWFv2(None, self.username, self.domain, bytes.fromhex(creds[self.domain][self.username]))
473 else:
474 raise Exception('Unknown cred type!')
475
476 # print(self.ServerChallenge)
477 # print(self.ChallengeFromClinet)
478
479 hm = hmac_md5(nt_hash)
480 hm.update(bytes.fromhex(self.ServerChallenge))
481 hm.update(bytes.fromhex(self.ChallengeFromClinet))
482
483 # print('M_nthash: %s' % nthash.hex())
484 # print('M_temp: %s' % self.ChallengeFromClinet)
485 # print('M_nthash: %s' % nthash.hex())
486 # print('M_server_chall: %s' % self.ServerChallenge)
487 # print('M_ntproof_string: %s' % self.ClientResponse)
488 # print('M_ntproof_string_calc: %s' % hm.hexdigest())
489
490 return self.ClientResponse == hm.hexdigest()
491
492
493 def LMOWFv1(password):
494 LM_SECRET = b'KGS!@#$%'
495 t1 = password[:14].ljust(14, '\x00').upper()
496 d = DES(t1[:7].encode('ascii'))
497 r1 = d.encrypt(LM_SECRET)
498 d = DES(t1[7:].encode('ascii'))
499 r2 = d.encrypt(LM_SECRET)
500
501 return r1+r2
502
503
504 def NTOWFv1(password):
505 return md4(password.encode('utf-16le')).digest()
506
507
508 def LMOWFv2(Passwd, User, UserDom, PasswdHash = None):
509 return NTOWFv2(Passwd, User, UserDom, PasswdHash)
510
511
512 def NTOWFv2(Passwd, User, UserDom, PasswdHash = None):
513 if PasswdHash is not None:
514 fp = hmac_md5(PasswdHash)
515 else:
516 fp = hmac_md5(NTOWFv1(Passwd))
517 fp.update((User.upper() + UserDom).encode('utf-16le'))
518 return fp.digest()
519
520
521 def DESL(K, D):
522 """
523 Indicates the encryption of an 8-byte data item D with the 16-byte key K
524 using the Data Encryption Standard Long (DESL) algorithm.
525 The result is 24 bytes in length.
526 :param K:
527 :param D:
528 :return:
529 """
530 if len(K) != 16:
531 raise Exception("K MUST be 16 bytes long")
532 if len(D) != 8:
533 raise Exception("D MUST be 8 bytes long")
534
535 res = b''
536 res += DES(K[:7]).encrypt(D)
537 res += DES(K[7:14]).encrypt(D)
538 res += DES(K[14:] + b'\x00'*5).encrypt(D)
539 return res
+0
-0
msldap/authentication/ntlm/messages/__init__.py less more
(Empty file)
+0
-231
msldap/authentication/ntlm/messages/authenticate.py less more
0 import io
1
2 from msldap.authentication.ntlm.structures.fields import Fields
3 from msldap.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
4 from msldap.authentication.ntlm.structures.version import Version
5 from msldap.authentication.ntlm.structures.challenge_response import *
6
7 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/033d32cc-88f9-4483-9bf2-b273055038ce
8 class NTLMAuthenticate:
9 def __init__(self, _use_NTLMv2 = True):
10 self.Signature = b'NTLMSSP\x00'
11 self.MessageType = 3
12 self.LmChallengeResponseFields = None
13 self.NtChallengeResponseFields = None
14 self.DomainNameFields = None
15 self.UserNameFields = None
16 self.WorkstationFields = None
17 self.EncryptedRandomSessionKeyFields = None
18 self.NegotiateFlags = None
19 self.Version = None
20 self.MIC = None
21 self.Payload = None
22
23 # high level
24 self.LMChallenge = None
25 self.NTChallenge = None
26 self.DomainName = None
27 self.UserName = None
28 self.Workstation = None
29 self.EncryptedRandomSession = None
30
31 # this is a global variable that needs to be indicated
32 self._use_NTLMv2 = _use_NTLMv2
33
34 @staticmethod
35 def construct(flags, domainname= None, workstationname= None, username= None, encrypted_session= None, lm_response= None, nt_response= None, version = None, mic = b'\x00'*16):
36 auth = NTLMAuthenticate()
37 auth.Payload = b''
38
39 payload_pos = 8+4+8+8+8+8+8+8+4
40 if flags & NegotiateFlags.NEGOTIATE_VERSION:
41 if not version:
42 raise Exception('NEGOTIATE_VERSION set but no Version supplied!')
43
44 auth.Version = version
45
46 payload_pos += 8
47
48 if mic is not None:
49 auth.MIC = mic
50 payload_pos += 16
51
52 if lm_response:
53 data = lm_response.to_bytes()
54 auth.Payload += data
55 auth.LmChallengeResponseFields = Fields(len(data), payload_pos)
56 payload_pos += len(data)
57 auth.LMChallenge = lm_response
58 else:
59 auth.LmChallengeResponseFields = Fields(0,0)
60
61 if nt_response:
62 data = nt_response.to_bytes()
63 auth.Payload += data
64 auth.NtChallengeResponseFields = Fields(len(data), payload_pos)
65 payload_pos += len(data)
66 auth.NTChallenge = nt_response
67 else:
68 auth.NtChallengeResponseFields = Fields(0,0)
69
70
71 if domainname:
72 data = domainname.encode('utf-16le')
73 auth.Payload += data
74 auth.DomainNameFields = Fields(len(data), payload_pos)
75 payload_pos += len(data)
76 auth.DomainName = domainname
77 else:
78 auth.DomainNameFields = Fields(0,0)
79
80 if username:
81 data = username.encode('utf-16le')
82 auth.Payload += data
83 auth.UserNameFields = Fields(len(data), payload_pos)
84 payload_pos += len(data)
85 auth.UserName = username
86 else:
87 auth.UserNameFields = Fields(0,0)
88
89 if workstationname:
90 data = workstationname.encode('utf-16le')
91 auth.Payload += data
92 auth.WorkstationFields = Fields(len(data), payload_pos)
93 payload_pos += len(data)
94 auth.Workstation = workstationname
95 else:
96 auth.WorkstationFields = Fields(0,0)
97
98 if encrypted_session:
99 data = encrypted_session
100 auth.Payload += data
101 auth.EncryptedRandomSessionKeyFields = Fields(len(data), payload_pos)
102 payload_pos += len(data)
103 auth.EncryptedRandomSession = encrypted_session
104 else:
105 auth.EncryptedRandomSessionKeyFields = Fields(0,0)
106
107 auth.NegotiateFlags = flags
108 return auth
109
110 def to_bytes(self):
111 t = b''
112 t += self.Signature
113 t += self.MessageType.to_bytes(4, byteorder = 'little', signed = False)
114
115 t += self.LmChallengeResponseFields.to_bytes()
116 t += self.NtChallengeResponseFields.to_bytes()
117 t += self.DomainNameFields.to_bytes()
118 t += self.UserNameFields.to_bytes()
119 t += self.WorkstationFields.to_bytes()
120 t += self.EncryptedRandomSessionKeyFields.to_bytes()
121 t += self.NegotiateFlags.to_bytes(4, byteorder = 'little', signed = False)
122 if self.Version:
123 t += self.Version.to_bytes()
124 if self.MIC is not None:
125 t += self.MIC
126 t += self.Payload
127 return t
128
129
130 @staticmethod
131 def from_bytes(bbuff,_use_NTLMv2 = True):
132 return NTLMAuthenticate.from_buffer(io.BytesIO(bbuff), _use_NTLMv2 = _use_NTLMv2)
133
134 @staticmethod
135 def from_buffer(buff, _use_NTLMv2 = True):
136 auth = NTLMAuthenticate(_use_NTLMv2)
137 auth.Signature = buff.read(8)
138 auth.MessageType = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
139 auth.LmChallengeResponseFields = Fields.from_buffer(buff)
140 auth.NtChallengeResponseFields = Fields.from_buffer(buff)
141 auth.DomainNameFields = Fields.from_buffer(buff)
142 auth.UserNameFields = Fields.from_buffer(buff)
143 auth.WorkstationFields = Fields.from_buffer(buff)
144 auth.EncryptedRandomSessionKeyFields = Fields.from_buffer(buff)
145 auth.NegotiateFlags = NegotiateFlags(int.from_bytes(buff.read(4), byteorder = 'little', signed = False))
146 if auth.NegotiateFlags & NegotiateFlags.NEGOTIATE_VERSION:
147 auth.Version = Version.from_buffer(buff)
148
149 # TODO: I'm not sure about this condition!!! Need to test this!
150 if auth.NegotiateFlags & NegotiateFlags.NEGOTIATE_ALWAYS_SIGN:
151 auth.MIC = buff.read(16)
152
153 currPos = buff.tell()
154 auth.Payload = buff.read()
155
156 if auth._use_NTLMv2 and auth.NtChallengeResponseFields.length > 24:
157 buff.seek(auth.LmChallengeResponseFields.offset, io.SEEK_SET)
158 auth.LMChallenge = LMv2Response.from_buffer(buff)
159
160
161 buff.seek(auth.NtChallengeResponseFields.offset, io.SEEK_SET)
162 auth.NTChallenge = NTLMv2Response.from_buffer(buff)
163
164 else:
165 buff.seek(auth.LmChallengeResponseFields.offset, io.SEEK_SET)
166 auth.LMChallenge = LMResponse.from_buffer(buff)
167
168 buff.seek(auth.NtChallengeResponseFields.offset, io.SEEK_SET)
169 auth.NTChallenge = NTLMv1Response.from_buffer(buff)
170
171 buff.seek(auth.DomainNameFields.offset,io.SEEK_SET)
172 auth.DomainName = buff.read(auth.DomainNameFields.length).decode('utf-16le')
173
174 buff.seek(auth.UserNameFields.offset,io.SEEK_SET)
175 auth.UserName = buff.read(auth.UserNameFields.length).decode('utf-16le')
176
177 buff.seek(auth.WorkstationFields.offset,io.SEEK_SET)
178 auth.Workstation = buff.read(auth.WorkstationFields.length).decode('utf-16le')
179
180 buff.seek(auth.EncryptedRandomSessionKeyFields.offset,io.SEEK_SET)
181 auth.EncryptedRandomSession = buff.read(auth.EncryptedRandomSessionKeyFields.length)
182
183 buff.seek(currPos, io.SEEK_SET)
184
185 return auth
186
187 def __repr__(self):
188 t = '== NTLMAuthenticate ==\r\n'
189 t += 'Signature : %s\r\n' % repr(self.Signature)
190 t += 'MessageType : %s\r\n' % repr(self.MessageType)
191 t += 'NegotiateFlags: %s\r\n' % repr(self.NegotiateFlags)
192 t += 'Version : %s\r\n' % repr(self.Version)
193 t += 'MIC : %s\r\n' % repr(self.MIC.hex() if self.MIC else 'None')
194 t += 'LMChallenge : %s\r\n' % repr(self.LMChallenge)
195 t += 'NTChallenge : %s\r\n' % repr(self.NTChallenge)
196 t += 'DomainName : %s\r\n' % repr(self.DomainName)
197 t += 'UserName : %s\r\n' % repr(self.UserName)
198 t += 'Workstation : %s\r\n' % repr(self.Workstation)
199 t += 'EncryptedRandomSession: %s\r\n' % repr(self.EncryptedRandomSession.hex())
200 return t
201
202 def test():
203 test_reconstrut()
204 test_construct()
205 test_2()
206
207 def test_2():
208 data = bytes.fromhex('4e 54 4c 4d 53 53 50 00 03 00 00 00 18 00 18 006c 00 00 00 54 00 54 00 84 00 00 00 0c 00 0c 0048 00 00 00 08 00 08 00 54 00 00 00 10 00 10 005c 00 00 00 10 00 10 00 d8 00 00 00 35 82 88 e205 01 28 0a 00 00 00 0f 44 00 6f 00 6d 00 61 0069 00 6e 00 55 00 73 00 65 00 72 00 43 00 4f 004d 00 50 00 55 00 54 00 45 00 52 00 86 c3 50 97ac 9c ec 10 25 54 76 4a 57 cc cc 19 aa aa aa aaaa aa aa aa 68 cd 0a b8 51 e5 1c 96 aa bc 92 7beb ef 6a 1c 01 01 00 00 00 00 00 00 00 00 00 0000 00 00 00 aa aa aa aa aa aa aa aa 00 00 00 0002 00 0c 00 44 00 6f 00 6d 00 61 00 69 00 6e 0001 00 0c 00 53 00 65 00 72 00 76 00 65 00 72 0000 00 00 00 00 00 00 00 c5 da d2 54 4f c9 79 9094 ce 1c e9 0b c9 d0 3e')
209 challenge = NTLMAuthenticate.from_bytes(data)
210 print(repr(challenge))
211
212 def test_reconstrut(data = None):
213 print('=== reconstruct===')
214 if not data:
215 auth_test_data = bytes.fromhex('4e544c4d5353500003000000180018007c000000180118019400000008000800580000000c000c0060000000100010006c00000010001000ac010000158288e20a00d73a0000000f0d98eb57e9c52820709c99b98ca321a15400450053005400760069006300740069006d00570049004e0031003000580036003400000000000000000000000000000000000000000000000000fade3940b9381c53c91ddcdd0d44000b0101000000000000aec600bfc5fdd4011bfa20699d7628730000000002000800540045005300540001001200570049004e003200300031003900410044000400120074006500730074002e0063006f007200700003002600570049004e003200300031003900410044002e0074006500730074002e0063006f007200700007000800aec600bfc5fdd40106000400020000000800300030000000000000000000000000200000527d27f234de743760966384d36f61ae2aa4fc2a380699f8caa600011b486d890a0010000000000000000000000000000000000009001e0063006900660073002f00310030002e00310030002e00310030002e003200000000000000000000000000fd67edfb41c09465a91fd733deb0b55b')
216 else:
217 auth_test_data = data
218 challenge = NTLMAuthenticate.from_bytes(auth_test_data)
219 print(repr(challenge))
220 auth_test_data_verify = challenge.to_bytes()
221 print('====== reconstructed ====')
222 print(hexdump(auth_test_data_verify))
223 print('====== original ====')
224 print(hexdump(auth_test_data))
225 assert auth_test_data == auth_test_data_verify
226
227
228 def test_construct():
229 pass
230
+0
-162
msldap/authentication/ntlm/messages/challenge.py less more
0 import os
1 import io
2 import base64
3
4 from msldap.authentication.ntlm.structures.fields import Fields
5 from msldap.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
6 from msldap.authentication.ntlm.structures.version import Version
7 from msldap.authentication.ntlm.structures.avpair import AVPairs
8
9 from msldap.authentication.ntlm.templates.server import NTLMServerTemplates
10
11 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/801a4681-8809-4be9-ab0d-61dcfe762786
12 class NTLMChallenge:
13 def __init__(self):
14 self.Signature = b'NTLMSSP\x00'
15 self.MessageType = 2
16 self.TargetNameFields = None
17 self.NegotiateFlags = None
18 self.ServerChallenge = None
19 self.Reserved = b'\x00'*8
20 self.TargetInfoFields = None
21 self.Version = None
22 self.Payload = None
23
24 self.TargetName = None
25 self.TargetInfo = None
26
27
28 @staticmethod
29 def from_bytes(bbuff):
30 return NTLMChallenge.from_buffer(io.BytesIO(bbuff))
31
32 @staticmethod
33 def from_buffer(buff):
34 t = NTLMChallenge()
35 t.Signature = buff.read(8)
36 t.MessageType = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
37 t.TargetNameFields = Fields.from_buffer(buff)
38 t.NegotiateFlags = NegotiateFlags(int.from_bytes(buff.read(4), byteorder = 'little', signed = False))
39 t.ServerChallenge = buff.read(8)
40 t.Reserved = buff.read(8)
41 t.TargetInfoFields = Fields.from_buffer(buff)
42
43 if t.NegotiateFlags & NegotiateFlags.NEGOTIATE_VERSION:
44 t.Version = Version.from_buffer(buff)
45
46 currPos = buff.tell()
47 t.Payload = buff.read()
48
49 if t.TargetNameFields.length != 0:
50 buff.seek(t.TargetNameFields.offset, io.SEEK_SET)
51 raw_data = buff.read(t.TargetNameFields.length)
52 try:
53 t.TargetName = raw_data.decode('utf-16le')
54 except UnicodeDecodeError:
55 # yet another cool bug.
56 t.TargetName = raw_data.decode('utf-8')
57
58 if t.TargetInfoFields.length != 0:
59 buff.seek(t.TargetInfoFields.offset, io.SEEK_SET)
60 raw_data = buff.read(t.TargetInfoFields.length)
61 t.TargetInfo = AVPairs.from_bytes(raw_data)
62
63
64
65 return t
66
67 @staticmethod
68 def construct_from_template(templateName, challenge = os.urandom(8), ess = True):
69 version = NTLMServerTemplates[templateName]['version']
70 challenge = challenge
71 targetName = NTLMServerTemplates[templateName]['targetname']
72 targetInfo = NTLMServerTemplates[templateName]['targetinfo']
73 targetInfo = NTLMServerTemplates[templateName]['targetinfo']
74 flags = NTLMServerTemplates[templateName]['flags']
75 if ess:
76 flags |= NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY
77 else:
78 flags &= ~NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY
79
80 return NTLMChallenge.construct(challenge=challenge, targetName = targetName, targetInfo = targetInfo, version = version, flags= flags)
81
82
83 # TODO: needs some clearning up (like re-calculating flags when needed)
84 @staticmethod
85 def construct(challenge = os.urandom(8), targetName = None, targetInfo = None, version = None, flags = None):
86 pos = 48
87 if version:
88 pos += 8
89 t = NTLMChallenge()
90 t.NegotiateFlags = flags
91 t.Version = version
92 t.ServerChallenge = challenge
93 t.TargetName = targetName
94 t.TargetInfo = targetInfo
95
96 t.TargetNameFields = Fields(len(t.TargetName.encode('utf-16le')),pos)
97 t.TargetInfoFields = Fields(len(t.TargetInfo.to_bytes()), pos + len(t.TargetName.encode('utf-16le')))
98
99 t.Payload = t.TargetName.encode('utf-16le')
100 t.Payload += t.TargetInfo.to_bytes()
101
102 return t
103
104 def to_bytes(self):
105 tn = self.TargetName.encode('utf-16le')
106 ti = self.TargetInfo.to_bytes()
107
108 buff = self.Signature
109 buff += self.MessageType.to_bytes(4, byteorder = 'little', signed = False)
110 buff += self.TargetNameFields.to_bytes()
111 buff += self.NegotiateFlags.to_bytes(4, byteorder = 'little', signed = False)
112 buff += self.ServerChallenge
113 buff += self.Reserved
114 buff += self.TargetInfoFields.to_bytes()
115 if self.Version:
116 buff += self.Version.to_bytes()
117 buff += self.Payload
118
119 return buff
120
121 def __repr__(self):
122 t = '== NTLMChallenge ==\r\n'
123 t += 'Signature : %s\r\n' % repr(self.Signature)
124 t += 'MessageType : %s\r\n' % repr(self.MessageType)
125 t += 'ServerChallenge: %s\r\n' % repr(self.ServerChallenge)
126 t += 'TargetName : %s\r\n' % repr(self.TargetName)
127 t += 'TargetInfo : %s\r\n' % repr(self.TargetInfo)
128 return t
129
130 def toBase64(self):
131 return base64.b64encode(self.to_bytes()).decode('ascii')
132
133
134 def test():
135 test_reconstrut()
136 test_construct()
137 test_template()
138
139 def test_reconstrut(data = None):
140 print('=== reconstruct===')
141 if not data:
142 challenge_test_data = bytes.fromhex('4e544c4d53535000020000000800080038000000158289e2a7314a557bdb11bf000000000000000072007200400000000a0063450000000f540045005300540002000800540045005300540001001200570049004e003200300031003900410044000400120074006500730074002e0063006f007200700003002600570049004e003200300031003900410044002e0074006500730074002e0063006f007200700007000800aec600bfc5fdd40100000000')
143 else:
144 challenge_test_data = data
145 challenge = NTLMChallenge.from_bytes(challenge_test_data)
146 print(repr(challenge))
147 challenge_test_data_verify = challenge.to_bytes()
148 print('====== reconstructed ====')
149 print(hexdump(challenge_test_data_verify))
150 print('====== original ====')
151 print(hexdump(challenge_test_data))
152 assert challenge_test_data == challenge_test_data_verify
153
154 def test_template():
155
156 challenge = NTLMChallenge.construct_from_template('Windows2003')
157 test_reconstrut(challenge.to_bytes())
158
159 def test_construct():
160 pass
161
+0
-169
msldap/authentication/ntlm/messages/negotiate.py less more
0 import io
1
2
3 from msldap.authentication.ntlm.structures.fields import Fields
4 from msldap.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
5 from msldap.authentication.ntlm.structures.version import Version
6
7 # https://msdn.microsoft.com/en-us/library/cc236641.aspx
8 class NTLMNegotiate:
9 def __init__(self):
10 self.Signature = b'NTLMSSP\x00'
11 self.MessageType = 1
12 self.NegotiateFlags = None
13 self.DomainNameFields = None
14 self.WorkstationFields = None
15 self.Version = None
16 self.Payload = None
17
18 ####High-level variables
19 self.Domain = None
20 self.Workstation = None
21
22 @staticmethod
23 def from_bytes(bbuff):
24 return NTLMNegotiate.from_buffer(io.BytesIO(bbuff))
25
26 @staticmethod
27 def from_buffer(buff):
28 t = NTLMNegotiate()
29 t.Signature = buff.read(8)
30 t.MessageType = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
31 t.NegotiateFlags = NegotiateFlags(int.from_bytes(buff.read(4), byteorder = 'little', signed = False))
32 t.DomainNameFields = Fields.from_buffer(buff)
33 t.WorkstationFields = Fields.from_buffer(buff)
34
35 if t.NegotiateFlags & NegotiateFlags.NEGOTIATE_VERSION:
36 t.Version = Version.from_buffer(buff)
37
38
39 currPos = buff.tell()
40 t.Payload = buff.read()
41
42 #currPos = buff.tell()
43
44 if t.DomainNameFields.length != 0:
45 buff.seek(t.DomainNameFields.offset, io.SEEK_SET)
46 raw_data = buff.read(t.WorkstationFields.length)
47 #print(raw_data)
48 #print(t.DomainNameFields.length)
49 try:
50 t.Domain = raw_data.decode('utf-16le')
51 except UnicodeDecodeError:
52 # yet another cool bug.
53 t.Domain = raw_data.decode('utf-8')
54
55 if t.WorkstationFields.length != 0:
56 buff.seek(t.WorkstationFields.offset, io.SEEK_SET)
57 raw_data = buff.read(t.WorkstationFields.length)
58 try:
59 t.Workstation = raw_data.decode('utf-16le')
60 except UnicodeDecodeError:
61 # yet another cool bug.
62 t.Workstation = raw_data.decode('utf-8')
63
64 #buff.seek(currPos, io.SEEK_SET)
65
66 return t
67
68 @staticmethod
69 def construct(flags, domainname = None, workstationname = None, version = None):
70 nego = NTLMNegotiate()
71 nego.NegotiateFlags = flags
72 nego.Payload = b''
73
74 payload_pos = 32
75 if flags & NegotiateFlags.NEGOTIATE_VERSION:
76 if not version:
77 raise Exception('NEGOTIATE_VERSION set but no Version supplied!')
78 payload_pos += 8
79 nego.Version = version
80
81
82
83 if nego.NegotiateFlags & NegotiateFlags.NEGOTIATE_OEM_DOMAIN_SUPPLIED and domainname:
84 data = domainname.encode('utf-16le')
85 nego.Payload += data
86 nego.DomainNameFields = Fields(len(data), payload_pos)
87 payload_pos += len(data)
88 nego.Domain = data
89
90 else:
91 nego.DomainNameFields = Fields(0,0)
92
93 if nego.NegotiateFlags & NegotiateFlags.NEGOTIATE_OEM_WORKSTATION_SUPPLIED and workstationname:
94 data = workstationname.encode('utf-16le')
95 nego.Payload += data
96 nego.WorkstationFields = Fields(len(data), payload_pos)
97 payload_pos += len(data)
98 nego.Workstation = data
99
100 else:
101 nego.WorkstationFields = Fields(0,0)
102
103 return nego
104
105 def to_bytes(self):
106 t = b''
107 t += self.Signature
108 t += self.MessageType.to_bytes(4, byteorder = 'little', signed = False)
109 t += self.NegotiateFlags.to_bytes(4, byteorder = 'little', signed = False)
110 t += self.DomainNameFields.to_bytes()
111 t += self.WorkstationFields.to_bytes()
112 if self.Version:
113 t += self.Version.to_bytes()
114 t += self.Payload
115 return t
116
117 def __repr__(self):
118 t = '== NTLMNegotiate ==\r\n'
119 t += 'Signature : %s\r\n' % repr(self.Signature)
120 t += 'MessageType: %s\r\n' % repr(self.MessageType)
121 t += 'NegotiateFlags: %s\r\n' % repr(self.NegotiateFlags)
122 t += 'Version : %s\r\n' % repr(self.Version)
123 t += 'Domain : %s\r\n' % repr(self.Domain)
124 t += 'Workstation: %s\r\n' % repr(self.Workstation)
125
126 return t
127
128 def test():
129 test_reconstrut()
130 test_construct()
131
132 def test_reconstrut(data = None):
133 print('=== reconstruct===')
134 if not data:
135 nego_test_data = bytes.fromhex('4e544c4d5353500001000000978208e2000000000000000000000000000000000a00d73a0000000f')
136 else:
137 nego_test_data = data
138 nego = NTLMNegotiate.from_bytes(nego_test_data)
139 print(repr(nego))
140 nego_test_data_verify = nego.to_bytes()
141 assert nego_test_data == nego_test_data_verify
142
143 def test_construct():
144 flags = NegotiateFlags.NEGOTIATE_56|NegotiateFlags.NEGOTIATE_KEY_EXCH|NegotiateFlags.NEGOTIATE_128|\
145 NegotiateFlags.NEGOTIATE_VERSION|\
146 NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|\
147 NegotiateFlags.NEGOTIATE_ALWAYS_SIGN|NegotiateFlags.NEGOTIATE_NTLM|NegotiateFlags.NEGOTIATE_LM_KEY|\
148 NegotiateFlags.NEGOTIATE_SIGN|NegotiateFlags.REQUEST_TARGET|NegotiateFlags.NTLM_NEGOTIATE_OEM|NegotiateFlags.NEGOTIATE_UNICODE|\
149 NegotiateFlags.NEGOTIATE_OEM_WORKSTATION_SUPPLIED|NegotiateFlags.NEGOTIATE_OEM_DOMAIN_SUPPLIED
150 nego = NTLMNegotiate.construct(flags, domainname = "alma.com", workstationname = "testjoe", version = Version.construct())
151 nego.to_bytes()
152 print(repr(nego))
153
154 test_reconstrut(nego.to_bytes())
155
156 flags = NegotiateFlags.NEGOTIATE_56|NegotiateFlags.NEGOTIATE_KEY_EXCH|NegotiateFlags.NEGOTIATE_128|\
157 NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|\
158 NegotiateFlags.NEGOTIATE_ALWAYS_SIGN|NegotiateFlags.NEGOTIATE_NTLM|NegotiateFlags.NEGOTIATE_LM_KEY|\
159 NegotiateFlags.NEGOTIATE_SIGN|NegotiateFlags.REQUEST_TARGET|NegotiateFlags.NTLM_NEGOTIATE_OEM|NegotiateFlags.NEGOTIATE_UNICODE|\
160 NegotiateFlags.NEGOTIATE_OEM_WORKSTATION_SUPPLIED|NegotiateFlags.NEGOTIATE_OEM_DOMAIN_SUPPLIED
161 nego = NTLMNegotiate.construct(flags, domainname = "alma.com", workstationname = "testjoe2")
162 print(nego.to_bytes())
163 print(repr(nego))
164
165 test_reconstrut(nego.to_bytes())
166
167 if __name__ == '__main__':
168 test()
+0
-154
msldap/authentication/ntlm/multiplexor.py less more
0 #
1 #
2 # Interface to support remote authentication via multiplexor
3 #
4 # Connects to the multiplexor server, and starts an SSPI server locally for the specific agentid
5 # SSPI server will be used to perform NTLM authentication remotely,
6 # while constructing a local NTLM authentication object
7 # After the auth finishes, it also grabs the sessionkey.
8 # The NTLM object can be used in future operations (encrypt/decrypt/sign) locally
9 # without the need of future remote calls
10 #
11
12 from msldap import logger
13 from msldap.authentication.ntlm.native import NTLMAUTHHandler, NTLMHandlerSettings
14 from multiplexor.operator.external.sspi import SSPINTLMClient
15 from multiplexor.operator import MultiplexorOperator
16 import enum
17
18 class ISC_REQ(enum.IntFlag):
19 DELEGATE = 1
20 MUTUAL_AUTH = 2
21 REPLAY_DETECT = 4
22 SEQUENCE_DETECT = 8
23 CONFIDENTIALITY = 16
24 USE_SESSION_KEY = 32
25 PROMPT_FOR_CREDS = 64
26 USE_SUPPLIED_CREDS = 128
27 ALLOCATE_MEMORY = 256
28 USE_DCE_STYLE = 512
29 DATAGRAM = 1024
30 CONNECTION = 2048
31 CALL_LEVEL = 4096
32 FRAGMENT_SUPPLIED = 8192
33 EXTENDED_ERROR = 16384
34 STREAM = 32768
35 INTEGRITY = 65536
36 IDENTIFY = 131072
37 NULL_SESSION = 262144
38 MANUAL_CRED_VALIDATION = 524288
39 RESERVED1 = 1048576
40 FRAGMENT_TO_FIT = 2097152
41 HTTP = 0x10000000
42
43 #
44 #
45 # Interface to support remote authentication via multiplexor
46 #
47 # Connects to the multiplexor server, and starts an SSPI server locally for the specific agentid
48 # SSPI server will be used to perform NTLM authentication remotely,
49 # while constructing a local NTLM authentication object
50 # After the auth finishes, it also grabs the sessionkey.
51 # The NTLM object can be used in future operations (encrypt/decrypt/sign) locally
52 # without the need of future remote calls
53 #
54
55 class MSLDAPNTLMMultiplexor:
56 def __init__(self, settings):
57 self.settings = settings
58 self.mode = None #'CLIENT'
59 self.sspi = None
60 self.operator = None
61 self.client = None
62 self.target = None
63 self.seq_number = 0
64
65 self.session_key = None
66 self.ntlm_ctx = NTLMAUTHHandler(NTLMHandlerSettings(None, 'MANUAL'))
67
68 def setup(self):
69 return
70
71 @property
72 def ntlmChallenge(self):
73 return self.ntlm_ctx.ntlmChallenge
74
75 def get_sealkey(self, mode = 'Client'):
76 return self.ntlm_ctx.get_sealkey(mode = mode)
77
78 def get_signkey(self, mode = 'Client'):
79 return self.ntlm_ctx.get_signkey(mode = mode)
80
81 def get_session_key(self):
82 return self.session_key
83
84 def is_extended_security(self):
85 return self.ntlm_ctx.is_extended_security()
86
87 def get_seq_number(self):
88 return self.seq_number
89
90 def signing_needed(self):
91 return self.ntlm_ctx.signing_needed()
92
93 def encryption_needed(self):
94 return self.ntlm_ctx.encryption_needed()
95
96 async def encrypt(self, data, message_no):
97 return await self.ntlm_ctx.encrypt(data, message_no)
98
99 async def decrypt(self, data, sequence_no, direction='init', auth_data=None):
100 return await self.ntlm_ctx.decrypt(data, sequence_no, direction=direction, auth_data=auth_data)
101
102 async def sign(self, data, message_no, direction=None, reset_cipher = False):
103 return await self.ntlm_ctx.sign(data, message_no, direction=None, reset_cipher = reset_cipher)
104
105 async def authenticate(self, authData = None, flags = None, seq_number = 0, cb_data=None):
106 is_rpc = False
107 if self.sspi is None:
108 res, err = await self.start_remote_sspi()
109 if err is not None:
110 return None, None, err
111
112 if is_rpc is True and flags is None:
113 flags = ISC_REQ.REPLAY_DETECT | ISC_REQ.CONFIDENTIALITY| ISC_REQ.USE_SESSION_KEY| ISC_REQ.INTEGRITY| ISC_REQ.SEQUENCE_DETECT| ISC_REQ.CONNECTION
114 flags = int(flags)
115
116 if authData is None:
117 data, res = await self.sspi.authenticate(flags = flags)
118 if res is None:
119 self.ntlm_ctx.load_negotiate(data)
120 return data, res, None
121 else:
122 self.ntlm_ctx.load_challenge( authData)
123 data, res = await self.sspi.challenge(authData, flags = flags)
124 if res is None:
125 self.ntlm_ctx.load_authenticate( data)
126 self.session_key, res = await self.sspi.get_session_key()
127 if res is None:
128 self.ntlm_ctx.load_sessionkey(self.get_session_key())
129
130 return data, res, None
131
132
133 async def start_remote_sspi(self):
134 try:
135 #print(self.settings.get_url())
136 self.operator = MultiplexorOperator(self.settings.get_url(), logging_sink=logger)
137 await self.operator.connect()
138 #creating virtual sspi server
139 server_info = await self.operator.start_sspi(self.settings.agent_id)
140 #print(server_info)
141
142 sspi_url = 'ws://%s:%s' % (server_info['listen_ip'], server_info['listen_port'])
143
144 #print(sspi_url)
145 self.sspi = SSPINTLMClient(sspi_url)
146 await self.sspi.connect()
147 return True, None
148 except Exception as e:
149 import traceback
150 traceback.print_exc()
151 return None, e
152
153
+0
-450
msldap/authentication/ntlm/native.py less more
0 import os
1 import struct
2 import hmac
3 import copy
4 import hashlib
5
6 #from aiosmb.commons.connection.credential import SMBNTLMCredential
7 #from aiosmb.commons.serverinfo import NTLMServerInfo
8 from msldap.authentication.ntlm.templates.server import NTLMServerTemplates
9 from msldap.authentication.ntlm.templates.client import NTLMClientTemplates
10 from msldap.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
11 from msldap.authentication.ntlm.structures.version import Version
12 from msldap.authentication.ntlm.structures.ntlmssp_message_signature import NTLMSSP_MESSAGE_SIGNATURE
13 from msldap.authentication.ntlm.structures.ntlmssp_message_signature_noext import NTLMSSP_MESSAGE_SIGNATURE_NOEXT
14 from msldap.authentication.ntlm.messages.negotiate import NTLMNegotiate
15 from msldap.authentication.ntlm.messages.challenge import NTLMChallenge
16 from msldap.authentication.ntlm.messages.authenticate import NTLMAuthenticate
17 from msldap.authentication.ntlm.creds_calc import *
18 from msldap.crypto.symmetric import RC4
19
20
21 class NTLMHandlerSettings:
22 def __init__(self, credential, mode = 'CLIENT', template_name = 'Windows10_15063', custom_template = None):
23 self.credential = credential
24 self.mode = mode
25 self.template_name = template_name
26 self.custom_template = custom_template #for custom templates, must be dict
27
28 self.encrypt = False
29
30 self.template = None
31 self.ntlm_downgrade = False
32
33 self.construct_message_template()
34
35 def construct_message_template(self):
36 if self.mode.upper() == 'MANUAL':
37 return
38
39 if not self.template_name:
40 if not self.custom_template:
41 raise Exception('No NTLM tamplate specified!')
42
43 self.template = self.custom_template
44
45 self.encrypt = self.credential.encrypt
46
47 if self.encrypt is True:
48 self.template_name = 'Windows10_15063_channel'
49
50 if self.mode.upper() == 'SERVER':
51 if self.template_name in NTLMServerTemplates:
52 self.template = NTLMServerTemplates[self.template_name]
53 else:
54 raise Exception('No NTLM server template found with name %s' % self.template_name)
55
56 else:
57 if self.template_name in NTLMClientTemplates:
58 self.template = NTLMClientTemplates[self.template_name]
59 if 'ntlm_downgrade' in self.template:
60 self.ntlm_downgrade = self.template['ntlm_downgrade']
61 else:
62 raise Exception('No NTLM server template found with name %s' % self.template_name)
63
64
65 class NTLMAUTHHandler:
66 def __init__(self, settings):
67 self.settings = settings #NTLMHandlerSettings
68
69 self.mode = None
70 self.flags = None
71 self.challenge = None
72
73 self.ntlmNegotiate = None #ntlm Negotiate message from client
74 self.ntlmChallenge = None #ntlm Challenge message to client
75 self.ntlmAuthenticate = None #ntlm Authenticate message from client
76
77 self.ntlmNegotiate_raw = None #message as bytes, as it's recieved/sent
78 self.ntlmChallenge_raw = None #message as bytes, as it's recieved/sent
79 self.ntlmAuthenticate_raw = None #message as bytes, as it's recieved/sent
80
81
82 self.EncryptedRandomSessionKey = None
83 self.RandomSessionKey = None
84 self.SessionBaseKey = None
85 self.KeyExchangeKey = None
86
87 self.SignKey_client = None
88 self.SealKey_client = None
89 self.SignKey_server = None
90 self.SealKey_server = None
91
92 self.crypthandle_client = None
93 self.crypthandle_server = None
94 #self.signhandle_server = None doesnt exists, only crypthandle
95 #self.signhandle_client = None doesnt exists, only crypthandle
96
97 self.seq_number = 0
98 self.iteration_cnt = 0
99 self.ntlm_credentials = None
100 self.timestamp = None #used in unittest only!
101 self.extra_info = None
102 self.setup()
103
104 def setup(self):
105 self.mode = self.settings.mode
106 if self.mode.upper() == 'MANUAL':
107 #for passign the messages automatically with the sessionbasekey, the using this class for sign and seal
108 return
109
110 if 'challenge' not in self.settings.template:
111 self.challenge = os.urandom(8)
112 else:
113 self.challenge = self.settings.template['challenge']
114 self.flags = self.settings.template['flags']
115 if 'session_key' in self.settings.template:
116 self.RandomSessionKey = self.settings.template['session_key']
117
118 self.timestamp = self.settings.template.get('timestamp') #used in unittest only!
119
120 def load_negotiate(self, data):
121 self.ntlmNegotiate = NTLMNegotiate.from_bytes(data)
122
123 def load_challenge(self, data):
124 self.ntlmChallenge = NTLMChallenge.from_bytes(data)
125
126 def load_authenticate(self, data):
127 self.ntlmAuthenticate = NTLMAuthenticate.from_bytes(data)
128
129 def load_sessionkey(self, data):
130 self.RandomSessionKey = data
131 self.setup_crypto()
132
133 def get_seq_number(self):
134 return self.seq_number
135
136 def set_sign(self, tf = True):
137 if tf == True:
138 self.flags |= NegotiateFlags.NEGOTIATE_SIGN
139 else:
140 self.flags &= ~NegotiateFlags.NEGOTIATE_SIGN
141
142 def set_seal(self, tf = True):
143 if tf == True:
144 self.flags |= NegotiateFlags.NEGOTIATE_SEAL
145 else:
146 self.flags &= ~NegotiateFlags.NEGOTIATE_SEAL
147
148 def set_version(self, tf = True):
149 if tf == True:
150 self.flags |= NegotiateFlags.NEGOTIATE_VERSION
151 else:
152 self.flags &= ~NegotiateFlags.NEGOTIATE_VERSION
153
154 def is_extended_security(self):
155 return NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY in self.ntlmChallenge.NegotiateFlags
156
157 #def get_extra_info(self):
158 # self.extra_info = NTLMServerInfo.from_challenge(self.ntlmChallenge)
159 # return self.extra_info
160
161 def MAC(self, handle, signingKey, seqNum, message):
162 if self.is_extended_security() == True:
163 msg = NTLMSSP_MESSAGE_SIGNATURE()
164 if NegotiateFlags.NEGOTIATE_KEY_EXCH in self.ntlmChallenge.NegotiateFlags:
165 tt = struct.pack('<i', seqNum) + message
166 t = hmac_md5(signingKey)
167 t.update(tt)
168
169 msg.Checksum = handle(t.digest()[:8])
170 msg.SeqNum = seqNum
171 seqNum += 1
172 else:
173 t = hmac_md5(signingKey)
174 t.update(struct.pack('<i',seqNum)+message)
175 msg.Checksum = t.digest()[:8]
176 msg.SeqNum = seqNum
177 seqNum += 1
178
179 else:
180 raise Exception('Not implemented!')
181 #t = struct.pack('<I',binascii.crc32(message)& 0xFFFFFFFF)
182 #randompad = 0
183 #msg = NTLMSSP_MESSAGE_SIGNATURE_NOEXT()
184 #msg.RandomPad = handle(struct.pack('<I',randompad))
185 #msg.Checksum = struct.unpack('<I',handle(messageSignature['Checksum']))[0]
186
187 return msg.to_bytes()
188
189 async def encrypt(self, data, sequence_no):
190 """
191 This function is to support SSPI encryption.
192 """
193 return self.SEAL(
194 #self.SignKey_client,
195 self.SignKey_client,
196 self.SealKey_client,
197 data,
198 data,
199 sequence_no,
200 self.crypthandle_client.encrypt
201 )
202
203 async def decrypt(self, data, sequence_no, direction='init', auth_data=None):
204 """
205 This function is to support SSPI decryption.
206 """
207 edata = data[16:]
208 srv_sig = NTLMSSP_MESSAGE_SIGNATURE.from_bytes(data[:16])
209 sealedMessage = self.crypthandle_server.encrypt(edata)
210 signature = self.MAC(self.crypthandle_server.encrypt, self.SignKey_server, srv_sig.SeqNum, sealedMessage)
211 #print('seqno %s' % sequence_no)
212 #print('Srv sig: %s' % data[:16])
213 #print('Calc sig: %s' % signature)
214
215 return sealedMessage, None
216
217 async def sign(self, data, message_no, direction=None, reset_cipher = False):
218 """
219 Singing outgoing messages. The reset_cipher parameter is needed for calculating mechListMIC.
220 """
221 #print('sign data : %s' % data)
222 #print('sign message_no : %s' % message_no)
223 #print('sign direction : %s' % direction)
224 signature = self.MAC(self.crypthandle_client.encrypt, self.SignKey_client, message_no, data)
225 if reset_cipher is True:
226 self.crypthandle_client = RC4(self.SealKey_client)
227 self.crypthandle_server = RC4(self.SealKey_server)
228 self.seq_number += 1
229 return signature
230
231 async def verify(self, data, signature):
232 """
233 Verifying incoming server message
234 """
235 signature_struct = NTLMSSP_MESSAGE_SIGNATURE.from_bytes(signature)
236 calc_sig = self.MAC(self.crypthandle_server.encrypt, self.SignKey_server, signature_struct.SeqNum, data)
237 #print('server signature : %s' % signature)
238 #print('calculates signature: %s' % calc_sig)
239 return signature == calc_sig
240
241 def SEAL(self, signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt):
242 """
243 This is the official SEAL function.
244 """
245 sealedMessage = cipher_encrypt(messageToEncrypt)
246 signature = self.MAC(cipher_encrypt, signingKey, seqNum, messageToSign)
247 return sealedMessage, signature
248
249 def SIGN(self, signingKey, message, seqNum, cipher_encrypt):
250 """
251 This is the official SIGN function.
252 """
253 return self.MAC(cipher_encrypt, signingKey, seqNum, message)
254
255 def signing_needed(self):
256 return (
257 NegotiateFlags.NEGOTIATE_SIGN in self.ntlmChallenge.NegotiateFlags or \
258 NegotiateFlags.NEGOTIATE_SEAL in self.ntlmChallenge.NegotiateFlags
259 )
260
261 def encryption_needed(self):
262 return NegotiateFlags.NEGOTIATE_SEAL in self.ntlmChallenge.NegotiateFlags
263
264 def calc_sealkey(self, mode = 'Client'):
265 if NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY in self.ntlmChallenge.NegotiateFlags:
266 if NegotiateFlags.NEGOTIATE_128 in self.ntlmChallenge.NegotiateFlags:
267 sealkey = self.RandomSessionKey
268 elif NegotiateFlags.NEGOTIATE_56 in self.ntlmChallenge.NegotiateFlags:
269 sealkey = self.RandomSessionKey[:7]
270 else:
271 sealkey = self.RandomSessionKey[:5]
272
273 if mode == 'Client':
274 md5 = hashlib.new('md5')
275 md5.update(sealkey + b'session key to client-to-server sealing key magic constant\x00')
276 sealkey = md5.digest()
277 else:
278 md5 = hashlib.new('md5')
279 md5.update(sealkey + b'session key to server-to-client sealing key magic constant\x00')
280 sealkey = md5.digest()
281
282 elif NegotiateFlags.NEGOTIATE_56 in self.ntlmChallenge.NegotiateFlags:
283 sealkey = self.RandomSessionKey[:7] + b'\xa0'
284 else:
285 sealkey = self.RandomSessionKey[:5] + b'\xe5\x38\xb0'
286
287 if mode == 'Client':
288 self.SealKey_client = sealkey
289 if sealkey is not None:
290 self.crypthandle_client = RC4(self.SealKey_client)
291 else:
292 self.SealKey_server = sealkey
293 if sealkey is not None:
294 self.crypthandle_server = RC4(self.SealKey_server)
295
296 return sealkey
297
298 def calc_signkey(self, mode = 'Client'):
299 if NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY in self.ntlmChallenge.NegotiateFlags:
300 if mode == 'Client':
301 md5 = hashlib.new('md5')
302 md5.update(self.RandomSessionKey + b"session key to client-to-server signing key magic constant\x00")
303 signkey = md5.digest()
304 else:
305 md5 = hashlib.new('md5')
306 md5.update(self.RandomSessionKey + b"session key to server-to-client signing key magic constant\x00")
307 signkey = md5.digest()
308 else:
309 signkey = None
310
311 if mode == 'Client':
312 self.SignKey_client = signkey
313
314 else:
315 self.SignKey_server = signkey
316
317 return signkey
318
319 def get_session_key(self):
320 return self.RandomSessionKey
321
322 def get_sealkey(self, mode = 'Client'):
323 if mode == 'Client':
324 return self.SealKey_client
325 else:
326 return self.SealKey_server
327
328 def get_signkey(self, mode = 'Client'):
329 if mode == 'Client':
330 return self.SignKey_client
331 else:
332 return self.SignKey_server
333
334 def setup_crypto(self):
335 if not self.RandomSessionKey:
336 self.RandomSessionKey = os.urandom(16)
337
338 if self.mode.upper() != 'MANUAL':
339 #this check is here to provide the option to load the messages + the sessionbasekey manually
340 #then you will be able to use the sign and seal functions provided by this class
341 self.SessionBaseKey = self.ntlm_credentials.SessionBaseKey
342
343 rc4 = RC4(self.KeyExchangeKey)
344 self.EncryptedRandomSessionKey = rc4.encrypt(self.RandomSessionKey)
345
346 self.calc_sealkey('Client')
347 self.calc_sealkey('Server')
348 self.calc_signkey('Client')
349 self.calc_signkey('Server')
350
351 async def authenticate(self, authData, flags = None, seq_number = 0, cb_data = None):
352 if self.mode.upper() == 'CLIENT':
353 if self.iteration_cnt == 0:
354 if authData is not None:
355 raise Exception('First call as client MUST be with empty data!')
356
357 self.iteration_cnt += 1
358 #negotiate message was already calulcated in setup
359 self.ntlmNegotiate = NTLMNegotiate.construct(self.flags, domainname = self.settings.template['domain_name'], workstationname = self.settings.template['workstation_name'], version = self.settings.template.get('version'))
360 self.ntlmNegotiate_raw = self.ntlmNegotiate.to_bytes()
361 return self.ntlmNegotiate_raw, True, None
362
363 else:
364 #server challenge incoming
365 self.ntlmChallenge_raw = authData
366 self.ntlmChallenge = NTLMChallenge.from_bytes(authData)
367
368 ##################self.flags = self.ntlmChallenge.NegotiateFlags
369
370 #we need to calculate the response based on the credential and the settings flags
371 if self.settings.ntlm_downgrade == True:
372 #NTLMv1 authentication
373 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/464551a8-9fc4-428e-b3d3-bc5bfb2e73a5
374
375 #check if we authenticate as guest
376 if self.settings.credential.is_guest == True:
377 lmresp = LMResponse()
378 lmresp.Response = b'\x00'
379 self.ntlmAuthenticate = NTLMAuthenticate.construct(self.flags, lm_response= lmresp)
380 return self.ntlmAuthenticate.to_bytes(), False, None
381
382 if self.flags & NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY:
383 #Extended auth!
384 self.ntlm_credentials = netntlm_ess.construct(self.ntlmChallenge.ServerChallenge, self.challenge, self.settings.credential)
385
386 self.KeyExchangeKey = self.ntlm_credentials.calc_key_exchange_key()
387 self.setup_crypto()
388
389 self.ntlmAuthenticate = NTLMAuthenticate.construct(self.flags, lm_response= self.ntlm_credentials.LMResponse, nt_response = self.ntlm_credentials.NTResponse, version = self.ntlmNegotiate.Version, encrypted_session = self.EncryptedRandomSessionKey)
390 else:
391 self.ntlm_credentials = netntlm.construct(self.ntlmChallenge.ServerChallenge, self.settings.credential)
392
393 self.KeyExchangeKey = self.ntlm_credentials.calc_key_exchange_key(with_lm = self.flags & NegotiateFlags.NEGOTIATE_LM_KEY, non_nt_session_key = self.flags & NegotiateFlags.REQUEST_NON_NT_SESSION_KEY)
394 self.setup_crypto()
395 self.ntlmAuthenticate = NTLMAuthenticate.construct(self.flags, lm_response= self.ntlm_credentials.LMResponse, nt_response = self.ntlm_credentials.NTResponse, version = self.ntlmNegotiate.Version, encrypted_session = self.EncryptedRandomSessionKey)
396
397
398
399 else:
400 #NTLMv2
401 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/5e550938-91d4-459f-b67d-75d70009e3f3
402 if self.settings.credential.is_guest == True:
403 lmresp = LMResponse()
404 lmresp.Response = b'\x00'
405 self.ntlmAuthenticate = NTLMAuthenticate.construct(self.flags, lm_response= lmresp)
406 return self.ntlmAuthenticate.to_bytes(), False, None
407
408 else:
409 #comment this out for testing!
410 ti = self.ntlmChallenge.TargetInfo
411 ti[AVPAIRType.MsvAvTargetName] = 'ldaps/%s' % ti[AVPAIRType.MsvAvDnsComputerName]
412 if cb_data is not None:
413 md5_ctx = hashlib.new('md5')
414 md5_ctx.update(cb_data)
415 ti[AVPAIRType.MsvChannelBindings] = md5_ctx.digest()
416 ###
417
418 self.ntlm_credentials = netntlmv2.construct(self.ntlmChallenge.ServerChallenge, self.challenge, ti, self.settings.credential, timestamp = self.timestamp)
419 self.KeyExchangeKey = self.ntlm_credentials.calc_key_exchange_key()
420 self.setup_crypto()
421
422 #TODO: if "ti" / targetinfo in the challenge message has "MsvAvFlags" type and the bit for MIC is set (0x00000002) we need to send a MIC. probably...
423 mic = None
424
425 self.ntlmAuthenticate = NTLMAuthenticate.construct(self.flags, domainname= self.settings.credential.domain, workstationname= self.settings.credential.workstation, username= self.settings.credential.username, lm_response= self.ntlm_credentials.LMResponse, nt_response= self.ntlm_credentials.NTResponse, version = self.ntlmNegotiate.Version, encrypted_session = self.EncryptedRandomSessionKey, mic = mic)
426
427
428 self.ntlmAuthenticate_raw = self.ntlmAuthenticate.to_bytes()
429 return self.ntlmAuthenticate_raw, False, None
430
431 elif self.mode.upper() == 'RELAY':
432 if self.iteration_cnt == 0:
433 self.ntlmNegotiate_raw = authData
434 self.ntlmNegotiate = NTLMNegotiate.from_bytes(authData)
435 self.iteration_cnt += 1
436
437 elif self.iteration_cnt == 1:
438 self.ntlmChallenge_raw = authData
439 self.ntlmChallenge = NTLMChallenge.from_bytes(authData)
440 self.iteration_cnt += 1
441
442 elif self.iteration_cnt == 2:
443 self.ntlmChallenge_raw = authData
444 self.ntlmChallenge = NTLMChallenge.from_bytes(authData)
445 self.iteration_cnt += 1
446
447 else:
448 raise Exception('Too many iterations for relay mode!')
449
+0
-99
msldap/authentication/ntlm/sspi.py less more
0 #
1 #
2 # This is just a simple interface to the winsspi library to support NTLM
3 #
4 from winsspi.sspi import NTLMMSLDAPSSPI
5 from winsspi.common.function_defs import ISC_REQ
6 from msldap.authentication.ntlm.native import NTLMAUTHHandler, NTLMHandlerSettings
7
8 class MSLDAPNTLMSSPI:
9 def __init__(self, settings):
10 self.settings = settings
11 self.mode = 'CLIENT'
12 self.username = settings.username
13 self.password = settings.password
14 self.domain = settings.domain
15 self.actual_ctx_flags = None
16 self.flags = ISC_REQ.CONNECTION
17 if settings.encrypt is True:
18 #self.flags = ISC_REQ.REPLAY_DETECT | ISC_REQ.CONFIDENTIALITY| ISC_REQ.USE_SESSION_KEY| ISC_REQ.INTEGRITY| ISC_REQ.SEQUENCE_DETECT| ISC_REQ.CONNECTION
19 self.flags = ISC_REQ.CONNECTION | ISC_REQ.CONFIDENTIALITY
20 self.sspi = NTLMMSLDAPSSPI()
21
22 self.seq_number = 0
23 self.session_key = None
24 self.ntlm_ctx = NTLMAUTHHandler(NTLMHandlerSettings(None, 'MANUAL'))
25
26 @property
27 def ntlmChallenge(self):
28 return self.ntlm_ctx.ntlmChallenge
29
30 def get_seq_number(self):
31 return self.ntlm_ctx.get_seq_number()
32
33 def signing_needed(self):
34 return self.ntlm_ctx.signing_needed()
35
36 def encryption_needed(self):
37 return self.ntlm_ctx.encryption_needed()
38
39 def get_sealkey(self, mode = 'Client'):
40 return self.ntlm_ctx.get_sealkey(mode = mode)
41
42 def get_signkey(self, mode = 'Client'):
43 return self.ntlm_ctx.get_signkey(mode = mode)
44
45 #def wrap(self, data, sequence_no):
46 # self.ntlm_ctx.wrap()
47
48 def unwrap(self, data):
49 return self.ntlm_ctx.unwrap(data)
50
51 def SEAL(self, signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt):
52 return self.ntlm_ctx.SEAL(signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt)
53
54 def SIGN(self, signingKey, message, seqNum, cipher_encrypt):
55 return self.ntlm_ctx.SIGN(signingKey, message, seqNum, cipher_encrypt)
56
57 def sign(self, data, message_no = 0, direction = 'init', reset_cipher = False):
58 return self.ntlm_ctx.sign(data, message_no = message_no, reset_cipher = reset_cipher)
59
60 def verify(self, data, signature):
61 return self.ntlm_ctx.verify(data, signature)
62
63 def get_session_key(self):
64 if not self.session_key:
65 self.session_key = self.sspi.get_session_key()
66
67 return self.session_key
68
69 #def get_extra_info(self):
70 # return self.ntlm_ctx.get_extra_info()
71
72 def is_extended_security(self):
73 return self.ntlm_ctx.is_extended_security()
74
75 def encrypt(self, data, message_no):
76 return self.ntlm_ctx.encrypt(data, message_no)
77
78 def decrypt(self, data, message_no, direction='init', auth_data=None):
79 return self.ntlm_ctx.decrypt(data, message_no, direction=direction, auth_data=auth_data)
80
81 async def authenticate(self, authData = None, flags = None, seq_number = 0, cb_data = None):
82 if authData is None:
83 try:
84 data, res = self.sspi.negotiate(ctx_flags = self.flags)
85 self.actual_ctx_flags = self.sspi.ctx_outflags
86 self.ntlm_ctx.load_negotiate(data)
87 return data, res, None
88 except Exception as e:
89 return None, None, e
90 else:
91 self.ntlm_ctx.load_challenge(authData)
92 data, res = self.sspi.authenticate(authData, ctx_flags = self.flags)
93 self.ntlm_ctx.load_authenticate( data)
94 self.ntlm_ctx.load_sessionkey(self.get_session_key())
95
96 return data, res, None
97
98
+0
-117
msldap/authentication/ntlm/sspiproxy.py less more
0 from msldap import logger
1 from msldap.authentication.ntlm.native import NTLMAUTHHandler, NTLMHandlerSettings
2 from pyodidewsnet.sspiproxyws import SSPIProxyWS
3 import enum
4
5 class ISC_REQ(enum.IntFlag):
6 DELEGATE = 1
7 MUTUAL_AUTH = 2
8 REPLAY_DETECT = 4
9 SEQUENCE_DETECT = 8
10 CONFIDENTIALITY = 16
11 USE_SESSION_KEY = 32
12 PROMPT_FOR_CREDS = 64
13 USE_SUPPLIED_CREDS = 128
14 ALLOCATE_MEMORY = 256
15 USE_DCE_STYLE = 512
16 DATAGRAM = 1024
17 CONNECTION = 2048
18 CALL_LEVEL = 4096
19 FRAGMENT_SUPPLIED = 8192
20 EXTENDED_ERROR = 16384
21 STREAM = 32768
22 INTEGRITY = 65536
23 IDENTIFY = 131072
24 NULL_SESSION = 262144
25 MANUAL_CRED_VALIDATION = 524288
26 RESERVED1 = 1048576
27 FRAGMENT_TO_FIT = 2097152
28 HTTP = 0x10000000
29
30 class MSLDAPSSPIProxyNTLMAuth:
31 def __init__(self, settings):
32 self.settings = settings
33 self.mode = None #'CLIENT'
34 url = '%s://%s:%s' % (self.settings.proto, self.settings.host, self.settings.port)
35 self.sspi = SSPIProxyWS(url, self.settings.agent_id)
36 self.operator = None
37 self.client = None
38 self.target = None
39 self.seq_number = 0
40
41 self.session_key = None
42 self.ntlm_ctx = NTLMAUTHHandler(NTLMHandlerSettings(None, 'MANUAL'))
43
44 def setup(self):
45 return
46
47 @property
48 def ntlmChallenge(self):
49 return self.ntlm_ctx.ntlmChallenge
50
51 def get_sealkey(self, mode = 'Client'):
52 return self.ntlm_ctx.get_sealkey(mode = mode)
53
54 def get_signkey(self, mode = 'Client'):
55 return self.ntlm_ctx.get_signkey(mode = mode)
56
57 def signing_needed(self):
58 return self.ntlm_ctx.signing_needed()
59
60 def encryption_needed(self):
61 return self.ntlm_ctx.encryption_needed()
62
63 async def encrypt(self, data, message_no):
64 return await self.ntlm_ctx.encrypt(data, message_no)
65
66 async def decrypt(self, data, sequence_no, direction='init', auth_data=None):
67 return await self.ntlm_ctx.decrypt(data, sequence_no, direction=direction, auth_data=auth_data)
68
69 async def sign(self, data, message_no, direction=None, reset_cipher = False):
70 return await self.ntlm_ctx.sign(data, message_no, direction=None, reset_cipher = reset_cipher)
71
72
73 def SEAL(self, signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt):
74 return self.ntlm_ctx.SEAL(signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt)
75
76 def SIGN(self, signingKey, message, seqNum, cipher_encrypt):
77 return self.ntlm_ctx.SIGN(signingKey, message, seqNum, cipher_encrypt)
78
79 def get_session_key(self):
80 return self.session_key
81
82 def get_seq_number(self):
83 return self.seq_number
84
85 def is_extended_security(self):
86 return self.ntlm_ctx.is_extended_security()
87
88 async def authenticate(self, authData = b'', flags = None, seq_number = 0, cb_data = None):
89 try:
90 if flags is None:
91 flags = ISC_REQ.CONNECTION
92
93 if authData is None:
94 status, ctxattr, data, err = await self.sspi.authenticate('NTLM', '', '', 3, flags.value, authdata = b'')
95 if err is not None:
96 raise err
97 self.ntlm_ctx.load_negotiate(data)
98 return data, True, None
99 else:
100 self.ntlm_ctx.load_challenge(authData)
101 status, ctxattr, data, err = await self.sspi.authenticate('NTLM', '', '', 3, flags.value, authdata = authData)
102 if err is not None:
103 raise err
104 if err is None:
105 self.ntlm_ctx.load_authenticate(data)
106 self.session_key, err = await self.sspi.get_sessionkey()
107 if err is not None:
108 raise err
109 self.ntlm_ctx.load_sessionkey(self.get_session_key())
110
111 await self.sspi.disconnect()
112 return data, False, None
113 except Exception as e:
114 return None, None, e
115
116
+0
-0
msldap/authentication/ntlm/structures/__init__.py less more
(Empty file)
+0
-96
msldap/authentication/ntlm/structures/avpair.py less more
0 import enum
1 import io
2 import collections
3
4 class MsvAvFlags(enum.IntFlag):
5 CONSTRAINED_AUTH = 0x00000001
6 MIC_PRESENT = 0x00000002
7 SPN_UNTRUSTED = 0x00000004
8
9 class AVPAIRType(enum.Enum):
10 MsvAvEOL = 0x0000 #Indicates that this is the last AV_PAIR in the list. AvLen MUST be 0. This type of information MUST be present in the AV pair list.
11 MsvAvNbComputerName = 0x0001 #The server's NetBIOS computer name. The name MUST be in Unicode, and is not null-terminated. This type of information MUST be present in the AV_pair list.
12 MsvAvNbDomainName = 0x0002 #The server's NetBIOS domain name. The name MUST be in Unicode, and is not null-terminated. This type of information MUST be present in the AV_pair list.
13 MsvAvDnsComputerName = 0x0003 #The fully qualified domain name (FQDN) of the computer. The name MUST be in Unicode, and is not null-terminated.
14 MsvAvDnsDomainName = 0x0004 #The FQDN of the domain. The name MUST be in Unicode, and is not null-terminated.
15 MsvAvDnsTreeName = 0x0005 #The FQDN of the forest. The name MUST be in Unicode, and is not null-terminated.<13>
16 MsvAvFlags = 0x0006 #A 32-bit value indicating server or client configuration.
17 MsvAvTimestamp = 0x0007 #A FILETIME structure ([MS-DTYP] section 2.3.3) in little-endian byte order that contains the server local time. This structure is always sent in the CHALLENGE_MESSAGE.<16>
18 MsvAvSingleHost = 0x0008 #A Single_Host_Data (section 2.2.2.2) structure. The Value field contains a platform-specific blob, as well as a MachineID created at computer startup to identify the calling machine.<17>
19 MsvAvTargetName = 0x0009 #The SPN of the target server. The name MUST be in Unicode and is not null-terminated.<18>
20 MsvChannelBindings = 0x000A #A channel bindings hash. The Value field contains an MD5 hash ([RFC4121] section 4.1.1.2) of a gss_channel_bindings_struct ([RFC2744] section 3.11). An all-zero value of the hash is used to indicate absence of channel bindings.<19>
21
22
23 # ???? https://msdn.microsoft.com/en-us/library/windows/desktop/aa374793(v=vs.85).aspx
24 # https://msdn.microsoft.com/en-us/library/cc236646.aspx
25 class AVPairs(collections.UserDict):
26 """
27 AVPairs is a dictionary-like object that stores the "AVPair list" in a key -value format where key is an AVPAIRType object and value is the corresponding object defined by the MSDN documentation. Usually it's string but can be other object as well
28 """
29 def __init__(self, data = None):
30 collections.UserDict.__init__(self, data)
31
32 @staticmethod
33 def from_bytes(bbuff):
34 return AVPairs.from_buffer(io.BytesIO(bbuff))
35
36 @staticmethod
37 def from_buffer(buff):
38 avp = AVPairs()
39 while True:
40 avId = AVPAIRType(int.from_bytes(buff.read(2), byteorder = 'little', signed = False))
41 AvLen = int.from_bytes(buff.read(2), byteorder = 'little', signed = False)
42 if avId == AVPAIRType.MsvAvEOL:
43 break
44
45 elif avId in [AVPAIRType.MsvAvNbComputerName,
46 AVPAIRType.MsvAvNbDomainName,
47 AVPAIRType.MsvAvDnsComputerName,
48 AVPAIRType.MsvAvDnsDomainName,
49 AVPAIRType.MsvAvDnsTreeName,
50 AVPAIRType.MsvAvTargetName,
51 ]:
52 avp[avId] = buff.read(AvLen).decode('utf-16le')
53
54 elif avId == AVPAIRType.MsvAvFlags:
55 avp[avId] = MsvAvFlags(int.from_bytes(buff.read(4), byteorder = 'little', signed = False))
56
57 # TODO IMPLEMENT PARSING OFR OTHER TYPES!!!!
58 else:
59 avp[avId] = buff.read(AvLen)
60
61 return avp
62
63 def to_bytes(self):
64 t = b''
65 for av in self.data:
66 t += AVPair(data = self.data[av], type = av).to_bytes()
67
68 t += AVPair(data = '', type = AVPAIRType.MsvAvEOL).to_bytes()
69 return t
70
71
72 class AVPair:
73 def __init__(self, data = None, type = None):
74 self.type = type
75 self.data = data
76
77 def to_bytes(self):
78 t = self.type.value.to_bytes(2, byteorder = 'little', signed = False)
79 raw_data = None
80 if self.type in [AVPAIRType.MsvAvNbComputerName,
81 AVPAIRType.MsvAvNbDomainName,
82 AVPAIRType.MsvAvDnsComputerName,
83 AVPAIRType.MsvAvDnsDomainName,
84 AVPAIRType.MsvAvDnsTreeName,
85 AVPAIRType.MsvAvTargetName,
86 AVPAIRType.MsvAvEOL
87 ]:
88 raw_data = self.data.encode('utf-16le')
89 else:
90 raw_data = self.data
91 t += len(raw_data).to_bytes(2, byteorder = 'little', signed = False)
92 t += raw_data
93 return t
94
95
+0
-204
msldap/authentication/ntlm/structures/challenge_response.py less more
0 import io
1 import datetime
2
3 from msldap.commons.utils import *
4 from msldap.authentication.ntlm.structures.avpair import AVPairs, AVPAIRType
5
6 # https://msdn.microsoft.com/en-us/library/cc236648.aspx
7 class LMResponse:
8 def __init__(self):
9 self.Response = None
10
11 def to_bytes(self):
12 return self.Response
13
14 @staticmethod
15 def from_bytes(bbuff):
16 return LMResponse.from_buffer(io.BytesIO(bbuff))
17
18 @staticmethod
19 def from_buffer(buff):
20 t = LMResponse()
21 t.Response = buff.read(24)
22 return t
23
24 def __repr__(self):
25 t = '== LMResponse ==\r\n'
26 t += 'Response: %s\r\n' % repr(self.Response.hex())
27 return t
28
29
30 # https://msdn.microsoft.com/en-us/library/cc236649.aspx
31 class LMv2Response:
32 def __init__(self):
33 self.Response = None
34 self.ChallengeFromClinet = None
35
36
37 def to_bytes(self):
38 return self.Response + self.ChallengeFromClinet
39
40 @staticmethod
41 def from_bytes(bbuff):
42 return LMv2Response.from_buffer(io.BytesIO(bbuff))
43
44 @staticmethod
45 def from_buffer(buff):
46 t = LMv2Response()
47 t.Response = buff.read(16).hex()
48 t.ChallengeFromClinet = buff.read(8).hex()
49 return t
50
51 def __repr__(self):
52 t = '== LMv2Response ==\r\n'
53 t += 'Response: %s\r\n' % repr(self.Response)
54 t += 'ChallengeFromClinet: %s\r\n' % repr(self.ChallengeFromClinet)
55 return t
56
57
58 # https://msdn.microsoft.com/en-us/library/cc236651.aspx
59 class NTLMv1Response:
60 def __init__(self):
61 self.Response = None
62
63 def to_bytes(self):
64 return self.Response
65
66 @staticmethod
67 def from_bytes(bbuff):
68 return NTLMv1Response.from_buffer(io.BytesIO(bbuff))
69
70 @staticmethod
71 def from_buffer(buff):
72 t = NTLMv1Response()
73 t.Response = buff.read(24).hex()
74 return t
75
76 def __repr__(self):
77 t = '== NTLMv1Response ==\r\n'
78 t += 'Response: %s\r\n' % repr(self.Response)
79 return t
80
81
82 # https://msdn.microsoft.com/en-us/library/cc236653.aspx
83 class NTLMv2Response:
84 def __init__(self):
85 self.Response = None
86 self.ChallengeFromClinet = None
87
88 def to_bytes(self):
89 return self.Response + self.ChallengeFromClinet.to_bytes()
90
91 @staticmethod
92 def from_bytes(bbuff):
93 return NTLMv2Response.from_buffer(io.BytesIO(bbuff))
94
95 @staticmethod
96 def from_buffer(buff):
97 t = NTLMv2Response()
98 t.Response = buff.read(16).hex()
99 pos = buff.tell()
100 t.ChallengeFromClinet = NTLMv2ClientChallenge.from_buffer(buff)
101
102 return t
103
104 def __repr__(self):
105 t = '== NTLMv2Response ==\r\n'
106 t += 'Response : %s\r\n' % repr(self.Response)
107 t += 'ChallengeFromClinet: %s\r\n' % repr(self.ChallengeFromClinet)
108 return t
109
110
111 class NTLMv2ClientChallenge:
112 def __init__(self):
113 self.RespType = 1
114 self.HiRespType = 1
115 self.Reserved1 = 0
116 self.TimeStamp = None #bytes! because of conversion error :(
117 self.Reserved2 = 0
118 self.ChallengeFromClient = None
119 self.Reserved3 = 0
120 self.Details = None #named AVPairs in the documentation
121
122 self.timestamp_dt = None
123 self.raw_data = b''
124
125 @staticmethod
126 def construct(timestamp, client_challenge, details):
127 """
128 timestamp: datetime.datetime
129 client_challenge: 8 bytes
130 details: AVPairs object
131 """
132 cc = NTLMv2ClientChallenge()
133 cc.TimeStamp = datetime2timestamp(timestamp)
134 cc.ChallengeFromClient = client_challenge
135 cc.Details = details
136 cc.timestamp_dt = timestamp
137 return cc
138
139 def to_bytes(self):
140 t = self.RespType.to_bytes(1 , byteorder = 'little', signed = False)
141 t += self.HiRespType.to_bytes(1 , byteorder = 'little', signed = False)
142 t += self.Reserved1.to_bytes(6, byteorder = 'little', signed = False)
143 t += self.TimeStamp
144 t += self.ChallengeFromClient
145 t += self.Reserved2.to_bytes(4, byteorder = 'little', signed = False)
146 t += self.Details.to_bytes()
147 t += self.Reserved3.to_bytes(4, byteorder = 'little', signed = False)
148
149 return t
150
151 @staticmethod
152 def from_bytes(bbuff):
153 return NTLMv2ClientChallenge.from_buffer(io.BytesIO(bbuff))
154
155 @staticmethod
156 def from_buffer(buff):
157 cc = NTLMv2ClientChallenge()
158 cc.RespType = int.from_bytes(buff.read(1), byteorder = 'little', signed = False)
159 cc.HiRespType = int.from_bytes(buff.read(1), byteorder = 'little', signed = False)
160 cc.Reserved1 = int.from_bytes(buff.read(6), byteorder = 'little', signed = False)
161 cc.TimeStamp = buff.read(8)
162 cc.ChallengeFromClient = buff.read(8)
163 cc.Reserved2 = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
164 cc.Details = AVPairs.from_buffer(buff) #referred to as ServerName in the documentation
165 cc.Reserved3 = int.from_bytes(buff.read(4), byteorder='little', signed=False)
166
167 cc.timestamp_dt = timestamp2datetime(cc.TimeStamp)
168
169 return cc
170
171 def __repr__(self):
172 t = '== NTLMv2ClientChallenge ==\r\n'
173 t += 'RespType : %s\r\n' % repr(self.RespType)
174 t += 'TimeStamp : %s\r\n' % repr(self.timestamp_dt)
175 t += 'ChallengeFromClient: %s\r\n' % repr(self.ChallengeFromClient.hex())
176 t += 'Details : %s\r\n' % repr(self.Details)
177 return t
178
179 def test():
180 test_data = bytes.fromhex('0101000000000000aec600bfc5fdd4011bfa20699d7628730000000002000800540045005300540001001200570049004e003200300031003900410044000400120074006500730074002e0063006f007200700003002600570049004e003200300031003900410044002e0074006500730074002e0063006f007200700007000800aec600bfc5fdd40106000400020000000800300030000000000000000000000000200000527d27f234de743760966384d36f61ae2aa4fc2a380699f8caa600011b486d890a0010000000000000000000000000000000000009001e0063006900660073002f00310030002e00310030002e00310030002e0032000000000000000000')
181
182 cc = NTLMv2ClientChallenge.from_bytes(test_data)
183 print(repr(cc))
184
185 cc2 = NTLMv2ClientChallenge.from_bytes(cc.to_bytes())
186 print(repr(cc2))
187 print('=== Original ===')
188 print(hexdump(test_data))
189 print('=== CC ===')
190 print(hexdump(cc.to_bytes()))
191
192 ### assertions here fail because of the timestamp re-conversion loosing info (float-int conversion)
193 #assert cc.to_bytes() == test_data
194 #assert cc2.to_bytes() == test_data
195
196 details = AVPairs({AVPAIRType.MsvAvNbDomainName: 'TEST', AVPAIRType.MsvAvNbComputerName: 'WIN2019AD', AVPAIRType.MsvAvDnsDomainName: 'test.corp', AVPAIRType.MsvAvDnsComputerName: 'WIN2019AD.test.corp', AVPAIRType.MsvAvTimestamp: b'\xae\xc6\x00\xbf\xc5\xfd\xd4\x01', AVPAIRType.MsvAvFlags: b'\x02\x00\x00\x00', AVPAIRType.MsvAvSingleHost: b"0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00R}'\xf24\xdet7`\x96c\x84\xd3oa\xae*\xa4\xfc*8\x06\x99\xf8\xca\xa6\x00\x01\x1bHm\x89", AVPAIRType.MsvChannelBindings: b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', AVPAIRType.MsvAvTargetName: 'cifs/10.10.10.2'})
197 timestamp = datetime.datetime(2019,1,1)
198 client_challenge = os.urandom(8)
199
200 cc3 = NTLMv2ClientChallenge.construct(timestamp, client_challenge, details)
201 print(repr(cc3))
202 cc4 = NTLMv2ClientChallenge.from_bytes(cc3.to_bytes())
203
+0
-23
msldap/authentication/ntlm/structures/fields.py less more
0
1 class Fields:
2 def __init__(self, length, offset, maxLength = None):
3 self.length = length
4 self.maxLength = length if maxLength is None else maxLength
5 self.offset = offset
6
7 @staticmethod
8 def from_bytes(bbuff):
9 return Fields.from_buffer(io.BytesIO(bbuff))
10
11 @staticmethod
12 def from_buffer( buff):
13 length = int.from_bytes(buff.read(2), byteorder = 'little', signed = False)
14 maxLength = int.from_bytes(buff.read(2), byteorder = 'little', signed = False)
15 offset = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
16
17 return Fields(length, offset, maxLength = maxLength)
18
19 def to_bytes(self):
20 return self.length.to_bytes(2, byteorder = 'little', signed = False) + \
21 self.maxLength.to_bytes(2, byteorder = 'little', signed = False) + \
22 self.offset.to_bytes(4, byteorder = 'little', signed = False)
+0
-72
msldap/authentication/ntlm/structures/negotiate_flags.py less more
0 import enum
1
2 # https://msdn.microsoft.com/en-us/library/cc236650.aspx
3 class NegotiateFlags(enum.IntFlag):
4 NEGOTIATE_56 = 0x80000000
5 NEGOTIATE_KEY_EXCH = 0x40000000
6 NEGOTIATE_128 = 0x20000000
7 r1 = 0x10000000
8 r2 = 0x8000000
9 r3 = 0x4000000
10 NEGOTIATE_VERSION = 0x2000000
11 r4 = 0x1000000
12 NEGOTIATE_TARGET_INFO = 0x800000
13 REQUEST_NON_NT_SESSION_KEY = 0x400000
14 r5 = 0x200000
15 NEGOTIATE_IDENTIFY = 0x100000
16 NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x80000
17 r6 = 0x40000
18 TARGET_TYPE_SERVER = 0x20000
19 TARGET_TYPE_DOMAIN = 0x10000
20 NEGOTIATE_ALWAYS_SIGN = 0x8000
21 r7 = 0x4000
22 NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x2000
23 NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x1000
24 J = 0x800
25 r8 = 0x400
26 NEGOTIATE_NTLM = 0x200
27 r9 = 0x100
28 NEGOTIATE_LM_KEY = 0x80
29 NEGOTIATE_DATAGRAM = 0x40
30 NEGOTIATE_SEAL = 0x20
31 NEGOTIATE_SIGN = 0x10
32 r10 = 0x8
33 REQUEST_TARGET = 0x4
34 NTLM_NEGOTIATE_OEM = 0x2
35 NEGOTIATE_UNICODE = 0x1
36
37 NegotiateFlagExp = {
38 NegotiateFlags.NEGOTIATE_56 : 'requests 56-bit encryption. If the client sends NTLMSSP_NEGOTIATE_SEAL or NTLMSSP_NEGOTIATE_SIGN with NTLMSSP_NEGOTIATE_56 to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_56 to the client in the CHALLENGE_MESSAGE. Otherwise it is ignored. If both NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 are requested and supported by the client and server, NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 will both be returned to the client. Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD set NTLMSSP_NEGOTIATE_56 if it is supported. An alternate name for this field is NTLMSSP_NEGOTIATE_56.',
39 NegotiateFlags.NEGOTIATE_KEY_EXCH : 'requests an explicit key exchange. This capability SHOULD be used because it improves security for message integrity or confidentiality. See sections 3.2.5.1.2, 3.2.5.2.1, and 3.2.5.2.2 for details. An alternate name for this field is NTLMSSP_NEGOTIATE_KEY_EXCH.',
40 NegotiateFlags.NEGOTIATE_128 : 'requests 128-bit session key negotiation. An alternate name for this field is NTLMSSP_NEGOTIATE_128. If the client sends NTLMSSP_NEGOTIATE_128 to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_128 to the client in the CHALLENGE_MESSAGE only if the client sets NTLMSSP_NEGOTIATE_SEAL or NTLMSSP_NEGOTIATE_SIGN. Otherwise it is ignored. If both NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 are requested and supported by the client and server, NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 will both be returned to the client. Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD set NTLMSSP_NEGOTIATE_128 if it is supported. An alternate name for this field is NTLMSSP_NEGOTIATE_128.<23>',
41 NegotiateFlags.r1 : 'This bit is unused and MUST be zero.',
42 NegotiateFlags.r2 : 'This bit is unused and MUST be zero.',
43 NegotiateFlags.r3 : 'This bit is unused and MUST be zero.',
44 NegotiateFlags.NEGOTIATE_VERSION : 'requests the protocol version number. The data corresponding to this flag is provided in the Version field of the NEGOTIATE_MESSAGE, the CHALLENGE_MESSAGE, and the AUTHENTICATE_MESSAGE.<24> An alternate name for this field is NTLMSSP_NEGOTIATE_VERSION.',
45 NegotiateFlags.r4 : 'This bit is unused and MUST be zero.',
46 NegotiateFlags.NEGOTIATE_TARGET_INFO : 'indicates that the TargetInfo fields in the CHALLENGE_MESSAGE (section 2.2.1.2) are populated. An alternate name for this field is NTLMSSP_NEGOTIATE_TARGET_INFO.',
47 NegotiateFlags.REQUEST_NON_NT_SESSION_KEY : ' requests the usage of the LMOWF. An alternate name for this field is NTLMSSP_REQUEST_NON_NT_SESSION_KEY.',
48 NegotiateFlags.r5 : 'This bit is unused and MUST be zero.',
49 NegotiateFlags.NEGOTIATE_IDENTIFY : 'requests an identify level token. An alternate name for this field is NTLMSSP_NEGOTIATE_IDENTIFY.',
50 NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY : 'requests usage of the NTLM v2 session security. NTLM v2 session security is a misnomer because it is not NTLM v2. It is NTLM v1 using the extended session security that is also in NTLM v2. NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are mutually exclusive. If both NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and NTLMSSP_NEGOTIATE_LM_KEY are requested, NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY alone MUST be returned to the client. NTLM v2 authentication session key generation MUST be supported by both the client and the DC in order to be used, and extended session security signing and sealing requires support from the client and the server in order to be used.<25> An alternate name for this field is NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.',
51 NegotiateFlags.r6 : 'This bit is unused and MUST be zero.',
52 NegotiateFlags.TARGET_TYPE_SERVER : 'TargetName MUST be a server name. The data corresponding to this flag is provided by the server in the TargetName field of the CHALLENGE_MESSAGE. If this bit is set, then NTLMSSP_TARGET_TYPE_DOMAIN MUST NOT be set. This flag MUST be ignored in the NEGOTIATE_MESSAGE and the AUTHENTICATE_MESSAGE. An alternate name for this field is NTLMSSP_TARGET_TYPE_SERVER.',
53 NegotiateFlags.TARGET_TYPE_DOMAIN : 'TargetName MUST be a domain name. The data corresponding to this flag is provided by the server in the TargetName field of the CHALLENGE_MESSAGE. then NTLMSSP_TARGET_TYPE_SERVER MUST NOT be set. This flag MUST be ignored in the NEGOTIATE_MESSAGE and the AUTHENTICATE_MESSAGE. An alternate name for this field is NTLMSSP_TARGET_TYPE_DOMAIN.',
54 NegotiateFlags.NEGOTIATE_ALWAYS_SIGN : ' requests the presence of a signature block on all messages. NTLMSSP_NEGOTIATE_ALWAYS_SIGN MUST be set in the NEGOTIATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. NTLMSSP_NEGOTIATE_ALWAYS_SIGN is overridden by NTLMSSP_NEGOTIATE_SIGN and NTLMSSP_NEGOTIATE_SEAL, if they are supported. An alternate name for this field is NTLMSSP_NEGOTIATE_ALWAYS_SIGN.',
55 NegotiateFlags.r7 : 'This bit is unused and MUST be zero.',
56 NegotiateFlags.NEGOTIATE_OEM_WORKSTATION_SUPPLIED : 'This flag indicates whether the Workstation field is present. If this flag is not set, the Workstation field MUST be ignored. If this flag is set, the length of the Workstation field specifies whether the workstation name is nonempty or not.<26> An alternate name for this field is NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED.',
57 NegotiateFlags.NEGOTIATE_OEM_DOMAIN_SUPPLIED : 'the domain name is provided (section 2.2.1.1).<27> An alternate name for this field is NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED.',
58 NegotiateFlags.J : 'the connection SHOULD be anonymous.<28>',
59 NegotiateFlags.r8 : 'This bit is unused and MUST be zero.',
60 NegotiateFlags.NEGOTIATE_NTLM : 'requests usage of the NTLM v1 session security protocol. NTLMSSP_NEGOTIATE_NTLM MUST be set in the NEGOTIATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. An alternate name for this field is NTLMSSP_NEGOTIATE_NTLM.',
61 NegotiateFlags.r9 : 'This bit is unused and MUST be zero.',
62 NegotiateFlags.NEGOTIATE_LM_KEY : 'requests LAN Manager (LM) session key computation. NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are mutually exclusive. If both NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are requested, NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY alone MUST be returned to the client. NTLM v2 authentication session key generation MUST be supported by both the client and the DC in order to be used, and extended session security signing and sealing requires support from the client and the server to be used. An alternate name for this field is NTLMSSP_NEGOTIATE_LM_KEY.',
63 NegotiateFlags.NEGOTIATE_DATAGRAM : 'requests connectionless authentication. If NTLMSSP_NEGOTIATE_DATAGRAM is set, then NTLMSSP_NEGOTIATE_KEY_EXCH MUST always be set in the AUTHENTICATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. An alternate name for this field is NTLMSSP_NEGOTIATE_DATAGRAM',
64 NegotiateFlags.NEGOTIATE_SEAL : 'requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE. Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD always set NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128, if they are supported. An alternate name for this field is NTLMSSP_NEGOTIATE_SEAL.',
65 NegotiateFlags.NEGOTIATE_SIGN : 'requests session key negotiation for message signatures. If the client sends NTLMSSP_NEGOTIATE_SIGN to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SIGN to the client in the CHALLENGE_MESSAGE. An alternate name for this field is NTLMSSP_NEGOTIATE_SIGN.',
66 NegotiateFlags.r10 : 'This bit is unused and MUST be zero.',
67 NegotiateFlags.REQUEST_TARGET : 'TargetName field of the CHALLENGE_MESSAGE (section 2.2.1.2) MUST be supplied. An alternate name for this field is NTLMSSP_REQUEST_TARGET.',
68 NegotiateFlags.NTLM_NEGOTIATE_OEM : 'requests OEM character set encoding. An alternate name for this field is NTLM_NEGOTIATE_OEM. See bit A for details.',
69 NegotiateFlags.NEGOTIATE_UNICODE : 'requests Unicode character set encoding. An alternate name for this field is NTLMSSP_NEGOTIATE_UNICODE.',
70
71 }
+0
-33
msldap/authentication/ntlm/structures/ntlmssp_message_signature.py less more
0 import io
1
2 class NTLMSSP_MESSAGE_SIGNATURE:
3 def __init__(self):
4 self.Version = 1
5 self.Checksum = None
6 self.SeqNum = None
7
8 def to_bytes(self):
9 t = self.Version.to_bytes(4, byteorder = 'little', signed = False)
10 t += self.Checksum
11 t += self.SeqNum.to_bytes(4, byteorder = 'little', signed = False)
12 return t
13
14 @staticmethod
15 def from_bytes(bbuff):
16 return NTLMSSP_MESSAGE_SIGNATURE.from_buffer(io.BytesIO(bbuff))
17
18 @staticmethod
19 def from_buffer(buff):
20 v = NTLMSSP_MESSAGE_SIGNATURE()
21 v.Version = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
22 v.Checksum = buff.read(8)
23 v.SeqNum = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
24
25 return v
26
27 def __repr__(self):
28 t = '== NTLMSSP_MESSAGE_SIGNATURE ==\r\n'
29 t += 'Version : %s\r\n' % self.Version
30 t += 'Checksum : %s\r\n' % self.Checksum
31 t += 'SeqNum : %s\r\n' % self.SeqNum
32 return t
+0
-37
msldap/authentication/ntlm/structures/ntlmssp_message_signature_noext.py less more
0 import io
1
2 class NTLMSSP_MESSAGE_SIGNATURE_NOEXT:
3 def __init__(self):
4 self.Version = 1
5 self.RandomPad = None
6 self.Checksum = None
7 self.SeqNum = None
8
9 def to_bytes(self):
10 t = self.Version.to_bytes(4, byteorder = 'little', signed = False)
11 t += self.RandomPad
12 t += self.Checksum
13 t += self.SeqNum.to_bytes(4, byteorder = 'little', signed = False)
14 return t
15
16 @staticmethod
17 def from_bytes(bbuff):
18 return NTLMSSP_MESSAGE_SIGNATURE_NOEXT.from_buffer(io.BytesIO(bbuff))
19
20 @staticmethod
21 def from_buffer(buff):
22 v = NTLMSSP_MESSAGE_SIGNATURE_NOEXT()
23 v.Version = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
24 v.RandomPad = buff.read(4)
25 v.Checksum = buff.read(4)
26 v.SeqNum = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
27
28 return v
29
30 def __repr__(self):
31 t = '== NTLMSSP_MESSAGE_SIGNATURE_NOEXT ==\r\n'
32 t += 'Version : %s\r\n' % self.Version
33 t += 'RandomPad : %s\r\n' % self.RandomPad
34 t += 'Checksum : %s\r\n' % self.Checksum
35 t += 'SeqNum : %s\r\n' % self.SeqNum
36 return t
+0
-89
msldap/authentication/ntlm/structures/version.py less more
0 import enum
1 import io
2
3 class NTLMRevisionCurrent(enum.Enum):
4 NTLMSSP_REVISION_W2K3 = 0x0F
5
6
7 # https://msdn.microsoft.com/en-us/library/cc236722.aspx#Appendix_A_33
8 class WindowsMajorVersion(enum.Enum):
9 WINDOWS_MAJOR_VERSION_5 = 0x05
10 WINDOWS_MAJOR_VERSION_6 = 0x06
11 WINDOWS_MAJOR_VERSION_10 = 0x0A
12
13
14 # https://msdn.microsoft.com/en-us/library/cc236722.aspx#Appendix_A_33
15 class WindowsMinorVersion(enum.Enum):
16 WINDOWS_MINOR_VERSION_0 = 0x00
17 WINDOWS_MINOR_VERSION_1 = 0x01
18 WINDOWS_MINOR_VERSION_2 = 0x02
19 WINDOWS_MINOR_VERSION_3 = 0x03
20
21 # https://msdn.microsoft.com/en-us/library/cc236722.aspx#Appendix_A_33
22 WindowsProduct = {
23 (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_5, WindowsMinorVersion.WINDOWS_MINOR_VERSION_1) : 'Windows XP operating system Service Pack 2 (SP2)',
24 (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_5, WindowsMinorVersion.WINDOWS_MINOR_VERSION_2) : 'Windows Server 2003',
25 (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_6, WindowsMinorVersion.WINDOWS_MINOR_VERSION_0) : 'Windows Vista or Windows Server 2008',
26 (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_6, WindowsMinorVersion.WINDOWS_MINOR_VERSION_1) : 'Windows 7 or Windows Server 2008 R2',
27 (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_6, WindowsMinorVersion.WINDOWS_MINOR_VERSION_2) : 'Windows 8 or Windows Server 2012 operating system',
28 (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_6, WindowsMinorVersion.WINDOWS_MINOR_VERSION_3) : 'Windows 8.1 or Windows Server 2012 R2',
29 (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10,WindowsMinorVersion.WINDOWS_MINOR_VERSION_0) : 'Windows 10 or Windows Server 2016',
30 }
31
32 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b1a6ceb2-f8ad-462b-b5af-f18527c48175
33 class Version:
34 def __init__(self):
35 self.ProductMajorVersion = None
36 self.ProductMinorVersion = None
37 self.ProductBuild = None
38 self.Reserved = 0
39 self.NTLMRevisionCurrent = None
40
41 # higher level
42 self.WindowsProduct = None
43
44 @staticmethod
45 def construct(major = WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 1555 ):
46 v = Version()
47 v.ProductMajorVersion = major
48 v.ProductMinorVersion = minor
49 v.ProductBuild = build
50 v.NTLMRevisionCurrent = NTLMRevisionCurrent.NTLMSSP_REVISION_W2K3
51
52 return v
53
54 def to_bytes(self):
55 t = self.ProductMajorVersion.value.to_bytes(1, byteorder = 'little', signed = False)
56 t += self.ProductMinorVersion.value.to_bytes(1, byteorder = 'little', signed = False)
57 t += self.ProductBuild.to_bytes(2, byteorder = 'little', signed = False)
58 t += self.Reserved.to_bytes(3, byteorder = 'little', signed = False)
59 t += self.NTLMRevisionCurrent.value.to_bytes(1, byteorder = 'little', signed = False)
60 return t
61
62 @staticmethod
63 def from_bytes(bbuff):
64 return Version.from_buffer(io.BytesIO(bbuff))
65
66 @staticmethod
67 def from_buffer(buff):
68 v = Version()
69 v.ProductMajorVersion = WindowsMajorVersion(int.from_bytes(buff.read(1), byteorder = 'little', signed = False))
70 v.ProductMinorVersion = WindowsMinorVersion(int.from_bytes(buff.read(1), byteorder = 'little', signed = False))
71 v.ProductBuild = int.from_bytes(buff.read(2), byteorder = 'little', signed = False)
72 v.Reserved = int.from_bytes(buff.read(3), byteorder = 'little', signed = False)
73 v.NTLMRevisionCurrent = NTLMRevisionCurrent(int.from_bytes(buff.read(1), byteorder = 'little', signed = False))
74
75 try:
76 v.WindowsProduct = WindowsProduct[(v.ProductMajorVersion, v.ProductMinorVersion)]
77 except:
78 pass
79
80 return v
81
82 def __repr__(self):
83 t = '== NTLMVersion ==\r\n'
84 t += 'ProductMajorVersion : %s\r\n' % repr(self.ProductMajorVersion.name)
85 t += 'ProductMinorVersion : %s\r\n' % repr(self.ProductMinorVersion.name)
86 t += 'ProductBuild : %s\r\n' % repr(self.ProductBuild)
87 t += 'WindowsProduct : %s\r\n' % repr(self.WindowsProduct)
88 return t
+0
-0
msldap/authentication/ntlm/templates/__init__.py less more
(Empty file)
+0
-89
msldap/authentication/ntlm/templates/client.py less more
0 from msldap.authentication.ntlm.structures.fields import Fields
1 from msldap.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
2 from msldap.authentication.ntlm.structures.version import Version, WindowsMajorVersion, WindowsMinorVersion
3
4 # LDAP doesnt seem to support sign-only. either no seal nor sign nor always_sign OR include seal.
5 NTLMClientTemplates = {
6 "Windows10_15063" : {
7 'flags' : NegotiateFlags.NEGOTIATE_KEY_EXCH|
8 NegotiateFlags.NEGOTIATE_128|
9 NegotiateFlags.NEGOTIATE_VERSION|
10 NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
11 NegotiateFlags.NEGOTIATE_NTLM|
12 NegotiateFlags.REQUEST_TARGET|
13 NegotiateFlags.NEGOTIATE_UNICODE,
14 'version' : Version.construct(WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 15063 ),
15 'domain_name' : None,
16 'workstation_name' : None,
17 'ntlm_downgrade' : False,
18 },
19 "Windows10_15063_channel" : {
20 'flags' : NegotiateFlags.NEGOTIATE_KEY_EXCH|
21 NegotiateFlags.NEGOTIATE_128|
22 NegotiateFlags.NEGOTIATE_VERSION|
23 NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
24 NegotiateFlags.NEGOTIATE_ALWAYS_SIGN|
25 NegotiateFlags.NEGOTIATE_NTLM|
26 NegotiateFlags.NEGOTIATE_SIGN|
27 NegotiateFlags.NEGOTIATE_SEAL|
28 NegotiateFlags.REQUEST_TARGET|
29 NegotiateFlags.NEGOTIATE_UNICODE,
30 'version' : Version.construct(WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 15063 ),
31 'domain_name' : None,
32 'workstation_name' : None,
33 'ntlm_downgrade' : False,
34 },
35 "Windows10_15063_old" : {
36 'flags' : NegotiateFlags.NEGOTIATE_56|
37 NegotiateFlags.NEGOTIATE_KEY_EXCH|
38 NegotiateFlags.NEGOTIATE_128|
39 NegotiateFlags.NEGOTIATE_VERSION|
40 NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
41 NegotiateFlags.NEGOTIATE_ALWAYS_SIGN|
42 NegotiateFlags.NEGOTIATE_NTLM|
43 NegotiateFlags.NEGOTIATE_LM_KEY|
44 NegotiateFlags.NEGOTIATE_SIGN|
45 NegotiateFlags.REQUEST_TARGET|
46 NegotiateFlags.NTLM_NEGOTIATE_OEM|
47 NegotiateFlags.NEGOTIATE_UNICODE,
48 'version' : Version.construct(WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 15063 ),
49 'domain_name' : None,
50 'workstation_name' : None,
51 'ntlm_downgrade' : False,
52 },
53 "Windows10_15063_knowkey" : {
54 'flags' : NegotiateFlags.NEGOTIATE_56|
55 NegotiateFlags.NEGOTIATE_KEY_EXCH|
56 NegotiateFlags.NEGOTIATE_128|
57 NegotiateFlags.NEGOTIATE_VERSION|
58 NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
59 NegotiateFlags.NEGOTIATE_ALWAYS_SIGN|
60 NegotiateFlags.NEGOTIATE_NTLM|
61 NegotiateFlags.NEGOTIATE_LM_KEY|
62 NegotiateFlags.NEGOTIATE_SIGN|
63 NegotiateFlags.REQUEST_TARGET|
64 NegotiateFlags.NTLM_NEGOTIATE_OEM|
65 NegotiateFlags.NEGOTIATE_UNICODE,
66 'version' : Version.construct(WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 15063 ),
67 'domain_name' : None,
68 'workstation_name' : None,
69 'ntlm_downgrade' : False,
70 'session_key' : b'A'*16,
71 },
72 "Windows10_15063_nosign" : {
73 'flags' : NegotiateFlags.NEGOTIATE_56|
74 NegotiateFlags.NEGOTIATE_KEY_EXCH|
75 NegotiateFlags.NEGOTIATE_128|
76 NegotiateFlags.NEGOTIATE_VERSION|
77 NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
78 NegotiateFlags.NEGOTIATE_NTLM|
79 NegotiateFlags.NEGOTIATE_LM_KEY|
80 NegotiateFlags.REQUEST_TARGET|
81 NegotiateFlags.NTLM_NEGOTIATE_OEM|
82 NegotiateFlags.NEGOTIATE_UNICODE,
83 'version' : Version.construct(WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 15063 ),
84 'domain_name' : None,
85 'workstation_name' : None,
86 'ntlm_downgrade' : False,
87 },
88 }
+0
-23
msldap/authentication/ntlm/templates/server.py less more
0 from msldap.authentication.ntlm.structures.fields import Fields
1 from msldap.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
2 from msldap.authentication.ntlm.structures.version import Version
3 from msldap.authentication.ntlm.structures.avpair import AVPairs, AVPAIRType
4
5 NTLMServerTemplates = {
6 "Windows2003" : {
7 'flags' : NegotiateFlags.NEGOTIATE_56|NegotiateFlags.NEGOTIATE_128|
8 NegotiateFlags.NEGOTIATE_VERSION|NegotiateFlags.NEGOTIATE_TARGET_INFO|
9 NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
10 NegotiateFlags.TARGET_TYPE_DOMAIN|NegotiateFlags.NEGOTIATE_NTLM|
11 NegotiateFlags.REQUEST_TARGET|NegotiateFlags.NEGOTIATE_UNICODE ,
12 'version' : Version.from_bytes(b"\x05\x02\xce\x0e\x00\x00\x00\x0f"),
13 'targetinfo' : AVPairs({ AVPAIRType.MsvAvNbDomainName : 'SMB',
14 AVPAIRType.MsvAvNbComputerName : 'SMB-TOOLKIT',
15 AVPAIRType.MsvAvDnsDomainName : 'smb.local',
16 AVPAIRType.MsvAvDnsComputerName : 'server2003.smb.local',
17 AVPAIRType.MsvAvDnsTreeName : 'smb.local',
18 }),
19
20 'targetname' : 'SMB',
21 },
22 }
+0
-116
msldap/authentication/ntlm/wsnet.py less more
0 from msldap import logger
1 from msldap.authentication.ntlm.native import NTLMAUTHHandler, NTLMHandlerSettings
2 from pyodidewsnet.clientauth import WSNETAuth
3 import enum
4
5 class ISC_REQ(enum.IntFlag):
6 DELEGATE = 1
7 MUTUAL_AUTH = 2
8 REPLAY_DETECT = 4
9 SEQUENCE_DETECT = 8
10 CONFIDENTIALITY = 16
11 USE_SESSION_KEY = 32
12 PROMPT_FOR_CREDS = 64
13 USE_SUPPLIED_CREDS = 128
14 ALLOCATE_MEMORY = 256
15 USE_DCE_STYLE = 512
16 DATAGRAM = 1024
17 CONNECTION = 2048
18 CALL_LEVEL = 4096
19 FRAGMENT_SUPPLIED = 8192
20 EXTENDED_ERROR = 16384
21 STREAM = 32768
22 INTEGRITY = 65536
23 IDENTIFY = 131072
24 NULL_SESSION = 262144
25 MANUAL_CRED_VALIDATION = 524288
26 RESERVED1 = 1048576
27 FRAGMENT_TO_FIT = 2097152
28 HTTP = 0x10000000
29
30 class MSLDAPWSNetNTLMAuth:
31 def __init__(self, settings):
32 self.settings = settings
33 self.mode = None #'CLIENT'
34 self.sspi = WSNETAuth()
35 self.operator = None
36 self.client = None
37 self.target = None
38 self.seq_number = 0
39
40 self.session_key = None
41 self.ntlm_ctx = NTLMAUTHHandler(NTLMHandlerSettings(None, 'MANUAL'))
42
43 def setup(self):
44 return
45
46 @property
47 def ntlmChallenge(self):
48 return self.ntlm_ctx.ntlmChallenge
49
50 def get_sealkey(self, mode = 'Client'):
51 return self.ntlm_ctx.get_sealkey(mode = mode)
52
53 def get_signkey(self, mode = 'Client'):
54 return self.ntlm_ctx.get_signkey(mode = mode)
55
56 def signing_needed(self):
57 return self.ntlm_ctx.signing_needed()
58
59 def encryption_needed(self):
60 return self.ntlm_ctx.encryption_needed()
61
62 async def encrypt(self, data, message_no):
63 return await self.ntlm_ctx.encrypt(data, message_no)
64
65 async def decrypt(self, data, sequence_no, direction='init', auth_data=None):
66 return await self.ntlm_ctx.decrypt(data, sequence_no, direction=direction, auth_data=auth_data)
67
68 async def sign(self, data, message_no, direction=None, reset_cipher = False):
69 return await self.ntlm_ctx.sign(data, message_no, direction=None, reset_cipher = reset_cipher)
70
71
72 def SEAL(self, signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt):
73 return self.ntlm_ctx.SEAL(signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt)
74
75 def SIGN(self, signingKey, message, seqNum, cipher_encrypt):
76 return self.ntlm_ctx.SIGN(signingKey, message, seqNum, cipher_encrypt)
77
78 def get_session_key(self):
79 return self.session_key
80
81 def get_seq_number(self):
82 return self.seq_number
83
84 def is_extended_security(self):
85 return self.ntlm_ctx.is_extended_security()
86
87 async def authenticate(self, authData = b'', flags = None, seq_number = 0, cb_data = None):
88 try:
89 if flags is None:
90 flags = ISC_REQ.CONNECTION
91
92 if authData is None:
93 status, ctxattr, data, err = await self.sspi.authenticate('NTLM', '', '', 3, flags.value, authdata = b'')
94 if err is not None:
95 raise err
96 self.ntlm_ctx.load_negotiate(data)
97 return data, True, None
98 else:
99 self.ntlm_ctx.load_challenge(authData)
100 status, ctxattr, data, err = await self.sspi.authenticate('NTLM', '', '', 3, flags.value, authdata = authData)
101 if err is not None:
102 raise err
103 if err is None:
104 self.ntlm_ctx.load_authenticate(data)
105 self.session_key, err = await self.sspi.get_sessionkey()
106 if err is not None:
107 raise err
108 self.ntlm_ctx.load_sessionkey(self.get_session_key())
109
110 await self.sspi.disconnect()
111 return data, False, None
112 except Exception as e:
113 return None, None, e
114
115
+0
-0
msldap/authentication/spnego/__init__.py less more
(Empty file)
+0
-168
msldap/authentication/spnego/asn1_structs.py less more
0 #!/usr/bin/env python3
1 #
2 # Author:
3 # Tamas Jos (@skelsec)
4 #
5
6 # https://www.rfc-editor.org/rfc/rfc4178.txt
7
8 from asn1crypto.core import ObjectIdentifier, Sequence, SequenceOf, Enumerated, GeneralString, OctetString, BitString, Choice, Any, Boolean
9 import enum
10 import os
11 import io
12
13 TAG = 'explicit'
14
15 # class
16 UNIVERSAL = 0
17 APPLICATION = 1
18 CONTEXT = 2
19
20
21 class MechType(ObjectIdentifier):
22 _map = {
23 '1.3.6.1.4.1.311.2.2.10': 'NTLMSSP - Microsoft NTLM Security Support Provider',
24 '1.2.840.48018.1.2.2' : 'MS KRB5 - Microsoft Kerberos 5',
25 '1.2.840.113554.1.2.2' : 'KRB5 - Kerberos 5',
26 '1.2.840.113554.1.2.2.3': 'KRB5 - Kerberos 5 - User to User',
27 '1.3.6.1.4.1.311.2.2.30': 'NEGOEX - SPNEGO Extended Negotiation Security Mechanism',
28 }
29
30 class MechTypes(SequenceOf):
31 _child_spec = MechType
32
33 class ContextFlags(BitString):
34 _map = {
35 0: 'delegFlag',
36 1: 'mutualFlag',
37 2: 'replayFlag',
38 3: 'sequenceFlag',
39 4: 'anonFlag',
40 5: 'confFlag',
41 6: 'integFlag',
42 }
43
44 class NegState(Enumerated):
45 _map = {
46 0: 'accept-completed',
47 1: 'accept-incomplete',
48 2: 'reject',
49 3: 'request-mic',
50 }
51
52 class NegHints(Sequence):
53 _fields = [
54 ('hintName', GeneralString, {'explicit': 0, 'optional': True}),
55 ('hintAddress', OctetString, {'explicit': 1, 'optional': True}),
56 ]
57
58 # https://www.rfc-editor.org/rfc/rfc4178.txt 4.2.1
59 # EXTENDED IN: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-spng/8e71cf53-e867-4b79-b5b5-38c92be3d472
60 class NegTokenInit2(Sequence):
61 #explicit = (APPLICATION, 0)
62
63 _fields = [
64 ('mechTypes', MechTypes, {'tag_type': TAG, 'tag': 0}),
65 ('reqFlags', ContextFlags, {'tag_type': TAG, 'tag': 1, 'optional': True}),
66 ('mechToken', OctetString, {'tag_type': TAG, 'tag': 2, 'optional': True}),
67 ('negHints', NegHints, {'tag_type': TAG, 'tag': 3, 'optional': True}),
68 ('mechListMIC', OctetString, {'tag_type': TAG, 'tag': 4, 'optional': True}),
69 ]
70
71 # https://www.rfc-editor.org/rfc/rfc4178.txt 4.2.2
72
73 class NegTokenResp(Sequence):
74 #explicit = (APPLICATION, 1)
75
76 _fields = [
77 ('negState', NegState, {'tag_type': TAG, 'tag': 0, 'optional': True}),
78 ('supportedMech', MechType, {'tag_type': TAG, 'tag': 1, 'optional': True}),
79 ('responseToken', OctetString, {'tag_type': TAG, 'tag': 2, 'optional': True}),
80 ('mechListMIC', OctetString, {'tag_type': TAG, 'tag': 3, 'optional': True}),
81 ]
82
83
84 class NegotiationToken(Choice):
85 _alternatives = [
86 ('negTokenInit', NegTokenInit2, {'explicit': (CONTEXT, 0) } ),
87 ('negTokenResp', NegTokenResp, {'explicit': (CONTEXT, 1) } ),
88 ]
89
90
91 class GSS_SPNEGO(Sequence):
92 class_ = 2
93 tag = 0
94
95 _fields = [
96 ('NegotiationToken', NegotiationToken),
97 ]
98
99 ### I have 0 idea where this is tandardized :(
100 class GSSType(ObjectIdentifier):
101 _map = {
102 #'': 'SNMPv2-SMI::enterprises.311.2.2.30',
103 '1.3.6.1.5.5.2': 'SPNEGO',
104 }
105
106 class GSSAPI(Sequence):
107 class_ = 1
108 tag = 0
109
110 _fields = [
111 ('type', GSSType, {'optional': False}),
112 ('value', Any, {'optional': False}),
113 ]
114
115 _oid_pair = ('type', 'value')
116 _oid_specs = {
117 'SPNEGO': NegotiationToken,
118 }
119
120 # https://tools.ietf.org/html/rfc2743#page-81
121 # You may think this is ASN1. But it truth, it's not.
122 # Below is a fucking disgrace of a protocol design.
123 class KRB5Token:
124 def __init__(self, data = None, tok_id = b'\x01\x00'):
125 self.tok_id = tok_id
126 self.data = data
127
128
129 @staticmethod
130 def from_bytes(data):
131 return KRB5Token.from_buffer(io.BytesIO(data))
132
133 @staticmethod
134 def from_buffer(buff):
135 t = KRB5Token()
136 buff.read(1)
137 length = -1
138 x = int.from_bytes(buff.read(1), 'big', signed = False)
139 input(x)
140 if x <= 127:
141 length = x
142 else:
143 x &= ~0x80
144 input(x)
145 length = int.from_bytes(buff.read(x), 'big', signed = False)
146 input('length: %s' % length)
147 oid_asn1 = buff.read(11)
148 t.tok_id = int.from_bytes(buff.read(2), 'big', signed = False)
149 t.data = buff.read(length-13)
150 input(t.tok_id )
151 return t
152
153 def length_encode(self, x):
154 if x <= 127:
155 return x.to_bytes(1, 'big', signed = False)
156 else:
157 lb = x.to_bytes((x.bit_length() + 7) // 8, 'big')
158 t = (0x80 | len(lb)).to_bytes(1, 'big', signed = False)
159 return t+lb
160
161 def to_bytes(self):
162 t = b'\x60' #
163 t += self.length_encode(11 + 2 + len(self.data))
164 t += bytes.fromhex('06092a864886f712010202') #OID length + OID for kerberos
165 t += self.tok_id
166 t += self.data
167 return t
+0
-258
msldap/authentication/spnego/native.py less more
0 #!/usr/bin/env python3
1 #
2 # Author:
3 # Tamas Jos (@skelsec)
4 #
5
6 import copy
7 from msldap.authentication.spnego.asn1_structs import *
8 from asn1crypto.core import OctetString
9
10 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-spng/d4f2b41c-5f9e-4e11-98d0-ade76467095d
11
12
13 # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-spng/94ccc4f8-d224-495f-8d31-4f58d1af598e
14 ## SPNEGO has been assigned the following object identifier (OID): so.org.dod.internet.security.mechanism.snego (1.3.6.1.5.5.2)
15
16 class SPNEGO:
17 def __init__(self, mode = 'CLIENT'):
18 self.mode = mode
19 self.authentication_contexts = {}
20 self.original_authentication_contexts = {}
21 self.selected_authentication_context = None
22 self.selected_mechtype = None
23 self.iteration_ctr = 0
24
25 def get_copy(self):
26 spnego = SPNEGO()
27 for ctx_name in self.list_original_conexts():
28 spnego.add_auth_context(ctx_name, self.get_original_context(ctx_name))
29 return spnego
30
31 def list_original_conexts(self):
32 """
33 Returns a list of authentication context names available to the SPNEGO authentication.
34 """
35 return list(self.original_authentication_contexts.keys())
36
37 def get_original_context(self, ctx_name):
38 """
39 Returns a copy of the original (not used) authentication context sp[ecified by name.
40 You may use this ctx to perform future authentication, as it has the user credentials
41 """
42 return copy.deepcopy(self.original_authentication_contexts[ctx_name])
43
44 def signing_needed(self):
45 return self.selected_authentication_context.signing_needed()
46
47 def encryption_needed(self):
48 return self.selected_authentication_context.encryption_needed()
49
50 def get_seq_number(self):
51 return self.selected_authentication_context.get_seq_number()
52
53 async def unsign(self, data):
54 #TODO: IMPLEMENT THIS
55 return data
56
57 async def verify(self, data, signature):
58 return await self.selected_authentication_context.verify(data, signature)
59
60 async def sign(self, data, message_no, direction='init', reset_cipher = False):
61 return await self.selected_authentication_context.sign(data, message_no, direction=direction, reset_cipher = reset_cipher)
62
63 async def encrypt(self, data, message_no):
64 return await self.selected_authentication_context.encrypt(data, message_no)
65
66 async def decrypt(self, data, message_no, direction='init', auth_data=None):
67 return await self.selected_authentication_context.decrypt(data, message_no, direction=direction)
68
69 def add_auth_context(self, name, ctx):
70 """
71 Add an authentication context to the given authentication context name.
72 Valid names are:
73 'NTLMSSP - Microsoft NTLM Security Support Provider'
74 'MS KRB5 - Microsoft Kerberos 5'
75 'KRB5 - Kerberos 5'
76 'KRB5 - Kerberos 5 - User to User'
77 'NEGOEX - SPNEGO Extended Negotiation Security Mechanism'
78
79 Context MUST be already set up!
80 """
81 self.authentication_contexts[name] = ctx
82 self.original_authentication_contexts[name] = copy.deepcopy(ctx)
83
84 def select_common_athentication_type(self, mech_types):
85 for auth_type_name in self.authentication_contexts:
86 if auth_type_name in mech_types:
87 return auth_type_name, self.authentication_contexts[auth_type_name]
88
89 return None, None
90
91 async def process_ctx_authenticate(self, token_data, include_negstate = False, flags = None, seq_number = 0, cb_data = None):
92 result, to_continue, err = await self.selected_authentication_context.authenticate(token_data, flags = flags, seq_number = seq_number, cb_data = cb_data)
93 if err is not None:
94 return None, None, err
95 if not result:
96 return None, False, None
97 response = {}
98 if include_negstate == True:
99 if to_continue == True:
100 response['negState'] = NegState('accept-incomplete')
101 else:
102 response['negState'] = NegState('accept-completed')
103
104 response['responseToken'] = result
105 return response, to_continue, None
106
107 def get_extra_info(self):
108 if hasattr(self.selected_authentication_context, 'get_extra_info'):
109 return self.selected_authentication_context.get_extra_info()
110 return None
111
112 def get_session_key(self):
113 return self.selected_authentication_context.get_session_key()
114
115 def get_mechtypes_list(self):
116 neghint = {'hintName':'not_defined_in_RFC4178@please_ignore'}
117 tokinit = {
118 'mechTypes': [MechType(mt) for mt in self.authentication_contexts],
119 'negHints': NegHints(neghint),
120 }
121
122 negtoken = NegotiationToken({'negTokenInit':NegTokenInit2(tokinit)})
123 #spnego = GSS_SPNEGO({'NegotiationToken':negtoken})
124 return GSSAPI({'type': GSSType('1.3.6.1.5.5.2'), 'value':negtoken}).dump()
125
126 async def authenticate(self, token, flags = None, seq_number = 0, cb_data = None):
127 """
128 This function is called (multiple times) during negotiation phase of a protocol to determine hich auth mechanism to be used
129 Token is a byte array that is an ASN1 NegotiationToken structure.
130 """
131 if self.selected_mechtype is None:
132 if token is None:
133 #first call to auth, we need to create NegTokenInit2
134 #we must list all available auth types, if only one is present then generate initial auth data with it
135
136 selected_name = None
137 mechtypes = []
138 for mechname in self.authentication_contexts:
139 selected_name = mechname #only used if there is one!
140 mechtypes.append(MechType(mechname))
141
142 response = {}
143 response['mechTypes'] = MechTypes(mechtypes)
144
145 self.negtypes_store = MechTypes(mechtypes).dump()
146
147
148 if len(mechtypes) == 1:
149 self.selected_authentication_context = self.authentication_contexts[selected_name]
150 self.selected_mechtype = selected_name
151 result, to_continue, err = await self.selected_authentication_context.authenticate(None, cb_data=cb_data)
152 if err is not None:
153 return None, None, err
154
155 if not result:
156 return None, False, None
157 if str(response['mechTypes'][0]) == '1.2.840.48018.1.2.2':
158 response['mechToken'] = KRB5Token(result).to_bytes()
159
160 #response['mechToken'] = bytes.fromhex('2a864886f712010202') + #???????
161 else:
162 response['mechToken'] = result
163 #raise Exception('NTLM as RPC GSSAPI not implemented!')
164
165 ### First message and ONLY the first message goes out with additional wrapping
166
167 negtoken = NegotiationToken({'negTokenInit':NegTokenInit2(response)})
168
169
170 #spnego = GSS_SPNEGO({'NegotiationToken':negtoken})
171 return GSSAPI({'type': GSSType('1.3.6.1.5.5.2'), 'value':negtoken}).dump(), True, None
172
173
174 else:
175 #we have already send the NegTokenInit2, but it contained multiple auth types,
176 #at this point server is replying which auth type to use
177 neg_token_raw = NegotiationToken.load(token)
178
179 neg_token = neg_token_raw.native
180
181 if not isinstance(neg_token_raw, NegTokenResp):
182 raise Exception('Server send init???')
183
184
185 self.selected_authentication_context = self.authentication_contexts[neg_token.mechTypes[0]]
186 self.selected_mechtype = neg_token['supportedMech']
187
188
189 response, to_continue, err = await self.process_ctx_authenticate(neg_token['responseToken'], flags = flags, seq_number = seq_number, cb_data = cb_data)
190 if err is not None:
191 return None, None, err
192 return NegTokenResp(response).dump(), to_continue, None
193
194 else:
195 #everything is netotiated, but authentication needs more setps
196 neg_token_raw = NegotiationToken.load(token)
197 neg_token = neg_token_raw.native
198 if neg_token['negState'] == 'accept-completed':
199 return None, True, None
200 if neg_token['responseToken'] is None:
201 # https://tools.ietf.org/html/rfc4178#section-5
202 # mechlistmic exchange happening at the end of the authentication
203 return None, True, None
204 #raise Exception('Should not be here....')
205 #print('server mechListMIC: %s' % neg_token['mechListMIC'])
206 #res = await self.verify(self.negtypes_store, neg_token['mechListMIC'])
207 #print('res %s' % res)
208 #print(self.negtypes_store)
209 #print(self.negtypes_store.hex())
210 #ret = await self.sign(self.negtypes_store, 0)
211 #print(ret)
212 #print(ret.hex())
213 #res = {
214 # 'mechListMIC' : ret,
215 # 'negState': NegState('accept-completed')
216 #}
217 #return NegotiationToken({'negTokenResp':NegTokenResp(res)}).dump(), True, None
218
219 else:
220 response, to_continue, err = await self.process_ctx_authenticate(neg_token['responseToken'], flags = flags, seq_number = seq_number, cb_data = cb_data)
221 if err is not None:
222 return None, None, err
223 if not response:
224 return None, False, None
225
226 if self.selected_mechtype.startswith('NTLM'):
227 response['mechListMIC'] = await self.sign(self.negtypes_store, 0, reset_cipher = True)
228 #self.selected_authentication_context.
229 #print(response)
230 res = NegotiationToken({'negTokenResp':NegTokenResp(response)}).dump()
231
232 return res, to_continue, None
233
234 def test():
235 test_data = bytes.fromhex('a03e303ca00e300c060a2b06010401823702020aa22a04284e544c4d5353500001000000978208e2000000000000000000000000000000000a00d73a0000000f')
236 neg_token = NegotiationToken.load(test_data)
237 print(neg_token.native)
238
239
240 test_data_2 = bytes.fromhex('a181ce3081cba0030a0101a10c060a2b06010401823702020aa281b50481b24e544c4d53535000020000000800080038000000158289e2a7314a557bdb11bf000000000000000072007200400000000a0063450000000f540045005300540002000800540045005300540001001200570049004e003200300031003900410044000400120074006500730074002e0063006f007200700003002600570049004e003200300031003900410044002e0074006500730074002e0063006f007200700007000800aec600bfc5fdd40100000000')
241 neg_token = NegotiationToken.load(test_data_2)
242 print(neg_token.native)
243
244 test_data_3 = bytes.fromhex('a11b3019a0030a0100a3120410010000006b65125a00bb9ab400000000')
245 neg_token = NegotiationToken.load(test_data_3)
246 print(neg_token.native)
247
248 mt = MechType('NTLMSSP - Microsoft NTLM Security Support Provider')
249 print(mt)
250
251 print(MechType.map('1.3.6.1.4.1.311.2.2.10'))
252 print(MechType.unmap('1.3.6.1.4.1.311.2.2.10'))
253
254 #spnego_test = SPNEGO()
255 #spnego_test.authenticate(test_data_2)
256 if __name__ == '__main__':
257 test()
+0
-63
msldap/authentication/spnego/sspi.py less more
0 #!/usr/bin/env python3
1 #
2 # Author:
3 # Tamas Jos (@skelsec)
4 #
5 # This is a failed attempt to simplify the SSPI integration.
6 # Sadly this doesnt work, as windows doesnt give the session key for some reason?
7 #
8
9 from winsspi.sspi import NegotiateSSPI, SSPIResult
10
11 class SPNEGO_SSPI:
12 def __init__(self, settings):
13 self.mode = 'CLIENT'
14 self.settings = settings
15 self.sspi = None
16 self.username = None
17 self.password = None
18 self.target = None
19
20 self.setup()
21
22 def setup(self):
23 if 'mode' in self.settings:
24 self.mode = self.settings['mode']
25
26 if 'username' in self.settings:
27 self.username = self.settings['username']
28 if 'password' in self.settings:
29 self.password = self.settings['password']
30
31 if 'target' in self.settings:
32 self.username = self.settings['target']
33
34 self.sspi = NegotiateSSPI()
35 self.sspi.authGSSClientInit(self.target, client_name = self.username)
36
37 def get_session_key(self):
38 return self.sspi.get_session_key()
39
40 async def encrypt(self, data, message_no):
41 return await self.sspi.encrypt(data, message_no)
42
43 async def decrypt(self, data, message_no):
44 return await self.sspi.decrypt(data, message_no)
45
46 async def authenticate(self, token, flags = None, seq_number = 0):
47 try:
48 if self.mode.upper() == 'CLIENT':
49 res, data = self.sspi.authGSSClientStep(token)
50 if res == SSPIResult.OK:
51 return data[0][1], True
52 elif res == SSPIResult.CONTINUE:
53 return data[0][1], False
54 else:
55 raise Exception('SSPI errors')
56
57 else:
58 raise Exception('SERVER is not supported now')
59 except Exception as e:
60 import traceback
61 traceback.print_exc()
62
33 # Tamas Jos (@skelsec)
44 #
55
6 from codecs import lookup
67 import copy
78 import asyncio
89
1516 from msldap.connection import MSLDAPClientConnection
1617 from msldap.protocol.messages import Control
1718 from msldap.ldap_objects import *
19 from msldap.commons.utils import KNOWN_SIDS
1820
1921 from winacl.dtyp.security_descriptor import SECURITY_DESCRIPTOR
2022 from winacl.dtyp.ace import ACCESS_ALLOWED_OBJECT_ACE, ADS_ACCESS_MASK
3739 :rtype: dict
3840
3941 """
40 def __init__(self, target, creds):
42 def __init__(self, target, creds, connection = None, keepalive = False):
4143 self.creds = creds
4244 self.target = target
43
44 self.ldap_query_page_size = self.target.ldap_query_page_size
45 self.keepalive = keepalive
46 self.ldap_query_page_size = 1000
47 if self.target is not None:
48 self.ldap_query_page_size = self.target.ldap_query_page_size
49
50 self.ldap_query_ratelimit = 0
51 if self.target is not None:
52 self.ldap_query_ratelimit = self.target.ldap_query_ratelimit
53
4554 self._tree = None
4655 self._ldapinfo = None
47 self._con = None
56 self._con = connection
57 self.__keepalive_task = None
58 self.keepalive_period = 10
59 self.disconnected_evt = None
60 self._sid_cache = {} #SID -> (domain, user)
61 self._domainsid_cache = {} # SID -> domain
4862
4963 async def __aenter__(self):
5064 return self
5266 async def __aexit__(self, exc_type, exc, traceback):
5367 await asyncio.wait_for(self.disconnect(), timeout = 1)
5468
69 async def __keepalive(self):
70 try:
71 while not self.disconnected_evt.is_set():
72 if self._con is not None:
73 ldap_filter = r'(distinguishedName=%s)' % self._tree
74 async for entry, err in self.pagedsearch(ldap_filter, MSADInfo_ATTRS):
75 if err is not None:
76 return None, err
77 await asyncio.sleep(self.keepalive_period)
78
79
80 except asyncio.CancelledError:
81 return
82
83 except Exception as e:
84 print('Keepalive exception: %s' % e)
85 await self.disconnect()
86
5587 async def disconnect(self):
5688 try:
89 if self.__keepalive_task is not None:
90 self.__keepalive_task.cancel()
5791 if self._con is not None:
5892 await self._con.disconnect()
93
94 self.disconnected_evt.set()
5995
6096 except Exception as e:
6197 return False, e
6298
6399 async def connect(self):
64100 try:
65 self._con = MSLDAPClientConnection(self.target, self.creds)
66 _, err = await self._con.connect()
67 if err is not None:
68 raise err
69 res, err = await self._con.bind()
70 if err is not None:
71 return False, err
101 self.disconnected_evt = asyncio.Event()
102 if self._con is None:
103 self._con = MSLDAPClientConnection(self.target, self.creds)
104 _, err = await self._con.connect()
105 if err is not None:
106 raise err
107 res, err = await self._con.bind()
108 if err is not None:
109 return False, err
72110 res, err = await self._con.get_serverinfo()
73111 if err is not None:
74112 raise err
75113 self._serverinfo = res
76114 self._tree = res['defaultNamingContext']
77115 self._ldapinfo, err = await self.get_ad_info()
116 self._domainsid_cache[self._ldapinfo.objectSid] = self._ldapinfo.name
117 if self.keepalive is True:
118 self.__keepalive_task = asyncio.create_task(self.__keepalive())
78119 if err is not None:
79120 raise err
80121 return True, None
84125 def get_server_info(self):
85126 return self._serverinfo
86127
87 async def pagedsearch(self, query, attributes, controls = None):
128 async def pagedsearch(self, query, attributes, controls = None, tree = None):
88129 """
89130 Performs a paged search on the AD, using the filter and attributes as a normal query does.
90131 !The LDAP connection MUST be active before invoking this function!
111152 print('Theconnection is in stopped state!')
112153 return
113154
114 if self._tree is None:
155 if tree is None:
156 tree = self._tree
157 if tree is None:
115158 raise Exception('BIND first!')
116159 t = []
117160 for x in attributes:
130173 controls = t
131174
132175 async for entry, err in self._con.pagedsearch(
133 self._tree,
176 tree,
134177 query,
135178 attributes = attributes,
136179 size_limit = self.ldap_query_page_size,
137180 controls = controls,
138 rate_limit=self.target.ldap_query_ratelimit
181 rate_limit=self.ldap_query_ratelimit
139182 ):
140183
141184 if err is not None:
169212 size_limit = self.ldap_query_page_size,
170213 search_scope=LEVEL,
171214 controls = None,
172 rate_limit=self.target.ldap_query_ratelimit
215 rate_limit=self.ldap_query_ratelimit
173216 ):
174217 if err is not None:
175218 raise err
184227 tree[entry['attributes']['distinguishedName']] = subtree
185228 return {root_dn : tree}
186229
187 async def get_all_users(self):
230 async def get_all_users(self, attrs = MSADUser_ATTRS):
188231 """
189232 Fetches all user objects available in the LDAP tree and yields them as MSADUser object.
190233
194237 """
195238 logger.debug('Polling AD for all user objects')
196239 ldap_filter = r'(sAMAccountType=805306368)'
197 async for entry, err in self.pagedsearch(ldap_filter, MSADUser_ATTRS):
240 async for entry, err in self.pagedsearch(ldap_filter, attrs):
198241 if err is not None:
199242 yield None, err
200243 return
221264 yield MSADMachine.from_ldap(entry, self._ldapinfo), None
222265 logger.debug('Finished polling for entries!')
223266
224 async def get_all_gpos(self):
267 async def get_all_gpos(self, attrs = MSADGPO_ATTRS):
225268 """
226269 Fetches all GPOs available in the LDAP tree and yields them as MSADGPO object.
227270
231274 """
232275
233276 ldap_filter = r'(objectCategory=groupPolicyContainer)'
234 async for entry, err in self.pagedsearch(ldap_filter, MSADGPO_ATTRS):
277 async for entry, err in self.pagedsearch(ldap_filter, attrs):
235278 if err is not None:
236279 yield None, err
237280 return
292335 size_limit = self.ldap_query_page_size,
293336 search_scope=BASE,
294337 controls = None,
295 rate_limit=self.target.ldap_query_ratelimit
338 rate_limit=self.ldap_query_ratelimit
296339 ):
297340 if err is not None:
298341 yield None, err
525568 }
526569 return await self._con.modify(object_dn, changes, controls = controls)
527570
528 async def get_all_groups(self):
571 async def get_all_groups(self, attrs = MSADGroup_ATTRS):
529572 """
530573 Yields all Groups present in the LDAP tree.
531574
533576 :rtype: Iterator[(:class:`MSADGroup`, :class:`Exception`)]
534577 """
535578 ldap_filter = r'(objectClass=group)'
536 async for entry, err in self.pagedsearch(ldap_filter, MSADGroup_ATTRS):
579 async for entry, err in self.pagedsearch(ldap_filter, attrs):
537580 if err is not None:
538581 yield None, err
539582 return
568611 if err is not None:
569612 return None, err
570613 return MSADGroup.from_ldap(entry), None
614
615 return None, Exception('Search returned no results!')
571616
572617 async def get_user_by_dn(self, user_dn):
573618 """
597642 :rtype: Iterator[(:class:`MSADUser`, :class:`Exception`)]
598643 """
599644
600 group, err = self.get_group_by_dn(dn)
645 group, err = await self.get_group_by_dn(dn)
601646 if err is not None:
602647 yield None, err
603648 return
604649 for member in group.member:
605 async for result in self.get_object_by_dn(member):
650 async for result, err in self.get_object_by_dn(member):
606651 if isinstance(result, MSADGroup) and recursive:
607652 async for user, err in self.get_group_members(result.distinguishedName, recursive = True):
608653 yield user, err
609654 else:
610655 yield result, err
656
611657
612658 async def get_dn_for_objectsid(self, objectsid):
613659 """
626672 return None, err
627673
628674 return entry['attributes']['distinguishedName'], None
675
676 return None, Exception('Search returned no results!')
629677
630678 async def get_objectsid_for_dn(self, dn):
631679 """
644692 return None, err
645693
646694 return entry['attributes']['objectSid'], None
647
695
696 return None, Exception('Search returned no results!')
697
698 async def get_tokengroups_user(self, samaccountname):
699 ldap_filter = r'(sAMAccountName=%s)' % escape_filter_chars(samaccountname)
700 user_dn = None
701 async for entry, err in self.pagedsearch(ldap_filter, ['distinguishedName']):
702 if err is not None:
703 return None, err
704
705 user_dn = entry['attributes']['distinguishedName']
706
707 if user_dn is None:
708 return None, Exception('User not found! %s' % samaccountname)
709
710 tokengroup = []
711 async for sids, err in self.get_tokengroups(user_dn):
712 if err is not None:
713 return None, err
714 tokengroup.append(sids)
715
716 return tokengroup, None
717
648718 async def get_tokengroups(self, dn):
649719 """
650720 Yields SIDs of groups that the given DN is a member of.
662732 attributes = attributes,
663733 size_limit = self.ldap_query_page_size,
664734 search_scope=BASE,
665 rate_limit=self.target.ldap_query_ratelimit
735 rate_limit=self.ldap_query_ratelimit
666736 ):
667737 if err is not None:
668738 yield None, err
699769 attributes = [b'tokenGroups'],
700770 size_limit = self.ldap_query_page_size,
701771 search_scope=BASE,
702 rate_limit=self.target.ldap_query_ratelimit
772 rate_limit=self.ldap_query_ratelimit
703773 ):
704774
705775 #print(entry2)
11471217 return True, None
11481218 except Exception as e:
11491219 return False, e
1220
1221 async def list_root_cas(self):
1222 try:
1223 ldap_filter = "(objectClass=certificationAuthority)"
1224 tree = "CN=Certification Authorities,CN=Public Key Services,CN=Services,CN=Configuration,%s" % self._ldapinfo.distinguishedName
1225 async for entry, err in self.pagedsearch(ldap_filter, attributes = MSADCA_ATTRS, tree = tree):
1226 if err is not None:
1227 yield None, err
1228 return
1229 yield MSADCA.from_ldap(entry, 'ROOTCA'), None
1230
1231 except Exception as e:
1232 yield None, e
1233 return
1234
1235 async def list_ntcas(self):
1236 try:
1237 ldap_filter = "(objectClass=certificationAuthority)"
1238 tree = "CN=NTAuthCertificates,CN=Public Key Services,CN=Services,CN=Configuration,%s" % self._ldapinfo.distinguishedName
1239 async for entry, err in self.pagedsearch(ldap_filter, attributes = MSADCA_ATTRS, tree = tree):
1240 if err is not None:
1241 yield None, err
1242 return
1243 yield MSADCA.from_ldap(entry, 'NTCA'), None
1244
1245 except Exception as e:
1246 yield None, e
1247 return
1248
1249 async def list_aiacas(self):
1250 try:
1251 ldap_filter = "(objectClass=certificationAuthority)"
1252 tree = "CN=AIA,CN=Public Key Services,CN=Services,CN=Configuration,%s" % self._ldapinfo.distinguishedName
1253 async for entry, err in self.pagedsearch(ldap_filter, attributes = MSADCA_ATTRS, tree = tree):
1254 if err is not None:
1255 yield None, err
1256 return
1257 yield MSADCA.from_ldap(entry, 'AIACA'), None
1258
1259 except Exception as e:
1260 yield None, e
1261 return
1262
1263 async def list_enrollment_services(self):
1264 try:
1265 ldap_filter = "(objectCategory=pKIEnrollmentService)"
1266 tree = "CN=Configuration,%s" % self._ldapinfo.distinguishedName
1267
1268 async for entry, err in self.pagedsearch(ldap_filter, attributes = MSADEnrollmentService_ATTRS, tree = tree):
1269 if err is not None:
1270 yield None, err
1271 return
1272 yield MSADEnrollmentService.from_ldap(entry), None
1273
1274 except Exception as e:
1275 yield None, e
1276 return
1277
1278 async def list_certificate_templates(self, name = None):
1279 try:
1280 req_flags = SDFlagsRequestValue({'Flags' : SDFlagsRequest.DACL_SECURITY_INFORMATION|SDFlagsRequest.GROUP_SECURITY_INFORMATION|SDFlagsRequest.OWNER_SECURITY_INFORMATION})
1281 controls = [('1.2.840.113556.1.4.801', True, req_flags.dump())]
1282
1283 ldap_filter = "(objectCategory=pKICertificateTemplate)"
1284 if name is not None:
1285 ldap_filter = "(&(objectCategory=pKICertificateTemplate)(name=%s))" % name
1286 tree = "CN=Configuration,%s" % self._ldapinfo.distinguishedName
1287
1288 async for entry, err in self.pagedsearch(ldap_filter, attributes = MSADCertificateTemplate_ATTRS, controls=controls, tree = tree):
1289 if err is not None:
1290 yield None, err
1291 return
1292 yield MSADCertificateTemplate.from_ldap(entry), None
1293
1294 except Exception as e:
1295 yield None, e
1296 return
1297
1298 async def resolv_sd(self, sd):
1299 "Resolves all SIDs found in security descriptor, returns lookup table"
1300 try:
1301 if isinstance(sd, bytes):
1302 sd = SECURITY_DESCRIPTOR.from_bytes(sd)
1303
1304 lookup_table = {}
1305 sids = {}
1306 sids[str(sd.Owner)] = 1
1307 sids[str(sd.Group)] = 1
1308 if sd.Dacl is not None:
1309 for ace in sd.Dacl.aces:
1310 sids[str(ace.Sid)] = 1
1311 if sd.Sacl is not None:
1312 for ace in sd.Sacl.aces:
1313 sids[str(ace.Sid)] = 1
1314
1315 for sid in sids:
1316 domain, username, err = await self.resolv_sid(sid)
1317 if err is not None:
1318 raise err
1319 lookup_table[sid] = (domain, username)
1320
1321 return lookup_table, None
1322
1323 except Exception as e:
1324 return None, e
1325
1326 async def resolv_sid(self, sid, use_cache = True):
1327 """Performs a SID lookup for object and returns the domain name and the samaccountname"""
1328 try:
1329 sid = str(sid).upper()
1330 if sid in KNOWN_SIDS:
1331 return "BUILTIN", KNOWN_SIDS[sid], None
1332 domain = None
1333 username = None
1334 domainsid = sid.rsplit('-',1)[0]
1335 if domainsid not in self._domainsid_cache:
1336 logger.debug('Domain SID "%s" was not found! ' % domainsid)
1337 return '???', '???', None
1338 domain = self._domainsid_cache[domainsid]
1339 if sid in self._sid_cache:
1340 username = self._sid_cache[sid]
1341
1342 else:
1343 ldap_filter = r'(objectSid=%s)' % sid
1344 async for entry, err in self.pagedsearch(ldap_filter, attributes = ['sAMAccountName']):
1345 if err is not None:
1346 return None, None, err
1347 username = entry['attributes'].get('sAMAccountName')
1348
1349 if username is None:
1350 return domain, '???', None
1351 #raise Exception('User not found! %s' % sid)
1352
1353 if use_cache is True:
1354 self._sid_cache[sid] = username
1355 return domain, username, None
1356 except Exception as e:
1357 return None, None, e
1358
1359 async def whoami(self):
1360 return await self._con.whoami()
1361
1362 async def whoamifull(self):
1363 """Full whoami"""
1364 #TODO: it can be the case that the server returns the SID of the user
1365 # implement that path!
1366 result = {}
1367 try:
1368 res, err = await self.whoami()
1369 if err is not None:
1370 raise err
1371 result['raw'] = res
1372 if res.startswith('u:') is True:
1373 domain, samaccountname = res[2:].split('\\', 1)
1374 result['domain'] = domain
1375 result['samaccountname'] = samaccountname
1376 user, err = await self.get_user(samaccountname)
1377 if err is not None:
1378 raise err
1379 result['sid'] = str(user.objectSid)
1380 result['groups'] = {}
1381 async for group_sid, err in self.get_tokengroups(user.distinguishedName):
1382 if err is not None:
1383 raise err
1384 result['groups'][group_sid] = ('NA','NA')
1385 domain, username, err = await self.resolv_sid(group_sid)
1386 if err is not None:
1387 raise err
1388 result['groups'][group_sid] = (domain, username)
1389
1390 return result, None
1391 except:
1392 return result, None
11501393
11511394 #async def get_permissions_for_dn(self, dn):
11521395 # """
0 import enum
1 import platform
0 from asyauth.common.credentials.spnego import SPNEGOCredential
1 from asyauth.common.constants import asyauthProtocol
2 from asyauth.common.credentials import UniCredential
23
3 import copy
4 from msldap.commons.credential import MSLDAPCredential, LDAPAuthProtocol
5 from msldap.authentication.spnego.native import SPNEGO
6 from msldap.authentication.ntlm.native import NTLMAUTHHandler, NTLMHandlerSettings
7 from msldap.authentication.kerberos.native import MSLDAPKerberos
8 from msldap.commons.proxy import MSLDAPProxyType
9 from minikerberos.common.target import KerberosTarget
10 from minikerberos.common.proxy import KerberosProxy
11 from minikerberos.common.creds import KerberosCredential
12 from minikerberos.common.spn import KerberosSPN
4 def get_auth_context(credential:UniCredential):
5 if credential.protocol in [asyauthProtocol.NTLM, asyauthProtocol.KERBEROS]:
6 spnego = SPNEGOCredential([credential])
7 return spnego.build_context()
8
9 elif credential.protocol == asyauthProtocol.SICILY:
10 return credential.build_context()
1311
14 from minikerberos.network.selector import KerberosClientSocketSelector
12 elif credential.protocol in [asyauthProtocol.SIMPLE, asyauthProtocol.PLAIN]:
13 return credential
1514
16
17 if platform.system().upper() == 'WINDOWS':
18 from msldap.authentication.kerberos.sspi import MSLDAPKerberosSSPI
19 from msldap.authentication.ntlm.sspi import MSLDAPNTLMSSPI
20
21 class MSLDAPNTLMCredential:
22 def __init__(self):
23 self.username = None
24 self.domain = ''
25 self.password = None
26 self.workstation = None
27 self.is_guest = False
28 self.nt_hash = None
29 self.lm_hash = None
30 self.encrypt = False
31
32 class MSLDAPSIMPLECredential:
33 def __init__(self):
34 self.username = None
35 self.domain = None
36 self.password = None
37
38 class MSLDAPPLAINCredential:
39 def __init__(self):
40 self.username = None
41 self.domain = None
42 self.password = None
43
44 class MSLDAPKerberosCredential:
45 def __init__(self):
46 self.connection = None #KerberosCredential
47 self.target = None #KerberosTarget
48 self.ksoc = None #KerberosSocketAIO
49 self.ccred = None
50 self.encrypt = False
51 self.enctypes = None #[23,17,18]
52
53 class MSLDAPKerberosSSPICredential:
54 def __init__(self):
55 self.domain = None
56 self.password = None
57 self.username = None
58 self.encrypt = False
59
60 class MSLDAPNTLMSSPICredential:
61 def __init__(self):
62 self.username = None
63 self.password = None
64 self.domain = None
65 self.encrypt = False
66
67 class MSLDAPWSNETCredential:
68 def __init__(self):
69 self.type = 'NTLM'
70 self.username = '<CURRENT>'
71 self.domain = '<CURRENT>'
72 self.password = '<CURRENT>'
73 self.target = None
74 self.is_guest = False
75 self.agent_id = None
76 self.encrypt = False
77
78 class MSLDAPSSPIProxyCredential:
79 def __init__(self):
80 self.type = 'NTLM'
81 self.username = '<CURRENT>'
82 self.domain = '<CURRENT>'
83 self.password = '<CURRENT>'
84 self.target = None
85 self.is_guest = False
86 self.agent_id = None
87 self.encrypt = False
88 self.host = '127.0.0.1'
89 self.port = 9999
90 self.proto = 'ws'
91
92
93
94 class MSLDAPMultiplexorCredential:
95 def __init__(self):
96 self.type = 'NTLM'
97 self.username = '<CURRENT>'
98 self.domain = '<CURRENT>'
99 self.password = '<CURRENT>'
100 self.target = None
101 self.is_guest = False
102 self.is_ssl = False
103 self.mp_host = '127.0.0.1'
104 self.mp_port = 9999
105 self.mp_username = None
106 self.mp_domain = None
107 self.mp_password = None
108 self.agent_id = None
109 self.encrypt = False
110
111 def get_url(self):
112 url_temp = 'ws://%s:%s'
113 if self.is_ssl is True:
114 url_temp = 'wss://%s:%s'
115 url = url_temp % (self.mp_host, self.mp_port)
116 return url
117
118 def parse_settings(self, settings):
119 req = ['agentid']
120 for r in req:
121 if r not in settings:
122 raise Exception('%s parameter missing' % r)
123 self.mp_host = settings.get('host', ['127.0.0.1'])[0]
124 self.mp_port = settings.get('port', ['9999'])[0]
125 if self.mp_port is None:
126 self.mp_port = '9999'
127 if 'user' in settings:
128 self.mp_username = settings.get('user')[0]
129 if 'domain' in settings:
130 self.mp_domain = settings.get('domain')[0]
131 if 'password' in settings:
132 self.mp_password = settings.get('password')[0]
133 self.agent_id = settings['agentid'][0]
134
135
136
137 """
138 class LDAPAuthProtocol(enum.Enum):
139 PLAIN = 'PLAIN' #actually SASL-PLAIN
140
141 MULTIPLEXOR = 'MULTIPLEXOR'
142 MULTIPLEXOR_SSL = 'MULTIPLEXOR_SSL'
143 SSPI_NTLM = 'SSPI_NTLM' #actually SASL-GSSAPI-SPNEGO-NTLM but with integrated SSPI
144 SSPI_KERBEROS = 'SSPI_KERBEROS' #actually SASL-GSSAPI-SPNEGO-KERBEROS but with integrated SSPI
145 """
146
147 class AuthenticatorBuilder:
148 def __init__(self, creds, target = None):
149 self.creds = creds
150 self.target = target
151
152 def build(self):
153 if self.creds.auth_method == LDAPAuthProtocol.SICILY:
154 ntlmcred = MSLDAPNTLMCredential()
155 ntlmcred.username = self.creds.username
156 ntlmcred.domain = self.creds.domain if self.creds.domain is not None else ''
157 ntlmcred.workstation = None
158 ntlmcred.is_guest = False
159 ntlmcred.encrypt = self.creds.encrypt
160
161
162 if self.creds.password is None:
163 raise Exception('NTLM authentication requres password/NT hash!')
164
165
166 if len(self.creds.password) == 32:
167 try:
168 bytes.fromhex(self.creds.password)
169 except:
170 ntlmcred.password = self.creds.password
171 else:
172 ntlmcred.nt_hash = self.creds.password
173
174 else:
175 ntlmcred.password = self.creds.password
176
177 settings = NTLMHandlerSettings(ntlmcred)
178 return NTLMAUTHHandler(settings)
179
180 elif self.creds.auth_method == LDAPAuthProtocol.SIMPLE:
181 cred = MSLDAPPLAINCredential()
182 cred.username = self.creds.username
183 cred.domain = self.creds.domain
184 cred.password = self.creds.password
185 return cred
186
187 elif self.creds.auth_method == LDAPAuthProtocol.PLAIN:
188 cred = MSLDAPSIMPLECredential()
189 cred.username = self.creds.username
190 cred.domain = self.creds.domain
191 cred.password = self.creds.password
192 return cred
193
194 elif self.creds.auth_method in [LDAPAuthProtocol.NTLM_PASSWORD, LDAPAuthProtocol.NTLM_NT]:
195 ntlmcred = MSLDAPNTLMCredential()
196 ntlmcred.username = self.creds.username
197 ntlmcred.domain = self.creds.domain if self.creds.domain is not None else ''
198 ntlmcred.workstation = None
199 ntlmcred.is_guest = False
200 ntlmcred.encrypt = self.creds.encrypt
201
202 if self.creds.password is None:
203 raise Exception('NTLM authentication requres password!')
204
205 if self.creds.auth_method == LDAPAuthProtocol.NTLM_PASSWORD:
206 ntlmcred.password = self.creds.password
207 elif self.creds.auth_method == LDAPAuthProtocol.NTLM_NT:
208 ntlmcred.nt_hash = self.creds.password
209 else:
210 raise Exception('Unknown NTLM auth method!')
211
212 settings = NTLMHandlerSettings(ntlmcred)
213 handler = NTLMAUTHHandler(settings)
214
215 ##setting up SPNEGO
216 spneg = SPNEGO()
217 spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
218
219 return spneg
220
221 elif self.creds.auth_method in [
222 LDAPAuthProtocol.KERBEROS_RC4,
223 LDAPAuthProtocol.KERBEROS_NT,
224 LDAPAuthProtocol.KERBEROS_AES,
225 LDAPAuthProtocol.KERBEROS_PASSWORD,
226 LDAPAuthProtocol.KERBEROS_CCACHE,
227 LDAPAuthProtocol.KERBEROS_KEYTAB,
228 LDAPAuthProtocol.KERBEROS_KIRBI]:
229
230 if self.target is None:
231 raise Exception('Target must be specified with Kerberos!')
232
233 if self.target.host is None:
234 raise Exception('target must have a domain name or hostname for kerberos!')
235
236 if self.target.dc_ip is None:
237 raise Exception('target must have a dc_ip for kerberos!')
238
239 kcred = MSLDAPKerberosCredential()
240 if self.creds.auth_method == LDAPAuthProtocol.KERBEROS_KIRBI:
241 kc = KerberosCredential.from_kirbi(self.creds.password, self.creds.username, self.creds.domain)
242 elif self.creds.auth_method == LDAPAuthProtocol.KERBEROS_CCACHE:
243 kc = KerberosCredential.from_ccache_file(self.creds.password, self.creds.username, self.creds.domain)
244 elif self.creds.auth_method == LDAPAuthProtocol.KERBEROS_KEYTAB:
245 kc = KerberosCredential.from_kirbi(self.creds.password, self.creds.username, self.creds.domain)
246 else:
247 kc = KerberosCredential()
248 kc.username = self.creds.username
249 kc.domain = self.creds.domain
250 kcred.enctypes = []
251 if self.creds.auth_method == LDAPAuthProtocol.KERBEROS_PASSWORD:
252 kc.password = self.creds.password
253 kcred.enctypes = [23,17,18]
254 elif self.creds.auth_method == LDAPAuthProtocol.KERBEROS_NT:
255 kc.nt_hash = self.creds.password
256 kcred.enctypes = [23]
257
258 elif self.creds.auth_method == LDAPAuthProtocol.KERBEROS_AES:
259 if len(self.creds.password) == 32:
260 kc.kerberos_key_aes_128 = self.creds.password
261 kcred.enctypes = [17]
262 elif len(self.creds.password) == 64:
263 kc.kerberos_key_aes_256 = self.creds.password
264 kcred.enctypes = [18]
265
266 elif self.creds.auth_method == LDAPAuthProtocol.KERBEROS_RC4:
267 kc.kerberos_key_rc4 = self.creds.password
268 kcred.enctypes = [23]
269
270 elif self.creds.auth_method == LDAPAuthProtocol.KERBEROS_CCACHE:
271 kc.ccache = self.creds.password
272 kcred.enctypes = [23,17,18] # TODO: fix this
273 elif self.creds.auth_method == LDAPAuthProtocol.KERBEROS_KEYTAB:
274 kc.keytab = self.creds.password
275 kcred.enctypes = [23,17,18] # TODO: fix this
276 elif self.creds.auth_method == LDAPAuthProtocol.KERBEROS_KIRBI:
277 kcred.enctypes = [23,17,18] # TODO: fix this
278 else:
279 raise Exception('No suitable secret type found to set up kerberos!')
280
281 if self.creds.etypes is not None:
282 kcred.enctypes = list(set(self.creds.etypes).intersection(set(kcred.enctypes)))
283
284 kcred.ccred = kc
285 kcred.spn = KerberosSPN.from_target_string(self.target.to_target_string())
286 kcred.target = KerberosTarget(self.target.dc_ip)
287 kcred.encrypt = self.creds.encrypt
288
289 if self.target.proxy is not None:
290 kcred.target.proxy = KerberosProxy()
291 kcred.target.proxy.type = self.target.proxy.type
292 kcred.target.proxy.target = copy.deepcopy(self.target.proxy.target)
293 kcred.target.proxy.target[-1].endpoint_ip = self.target.dc_ip
294 kcred.target.proxy.target[-1].endpoint_port = 88
295
296 handler = MSLDAPKerberos(kcred)
297
298 #setting up SPNEGO
299 spneg = SPNEGO()
300 spneg.add_auth_context('MS KRB5 - Microsoft Kerberos 5', handler)
301 return spneg
302
303 elif self.creds.auth_method == LDAPAuthProtocol.SSPI_KERBEROS:
304 if self.target is None:
305 raise Exception('Target must be specified with Kerberos SSPI!')
306
307 kerbcred = MSLDAPKerberosSSPICredential()
308 kerbcred.username = self.creds.domain if self.creds.domain is not None else '<CURRENT>'
309 kerbcred.username = self.creds.username if self.creds.username is not None else '<CURRENT>'
310 kerbcred.password = self.creds.password if self.creds.password is not None else '<CURRENT>'
311 kerbcred.spn = self.target.to_target_string()
312 kerbcred.encrypt = self.creds.encrypt
313
314 handler = MSLDAPKerberosSSPI(kerbcred)
315 #setting up SPNEGO
316 spneg = SPNEGO()
317 spneg.add_auth_context('MS KRB5 - Microsoft Kerberos 5', handler)
318 return spneg
319
320 elif self.creds.auth_method == LDAPAuthProtocol.SSPI_NTLM:
321 ntlmcred = MSLDAPNTLMSSPICredential()
322 ntlmcred.username = self.creds.domain if self.creds.domain is not None else '<CURRENT>'
323 ntlmcred.username = self.creds.username if self.creds.username is not None else '<CURRENT>'
324 ntlmcred.password = self.creds.password if self.creds.password is not None else '<CURRENT>'
325 ntlmcred.encrypt = self.creds.encrypt
326
327 handler = MSLDAPNTLMSSPI(ntlmcred)
328 #setting up SPNEGO
329 spneg = SPNEGO()
330 spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
331 return spneg
332
333 elif self.creds.auth_method.value.startswith('MULTIPLEXOR'):
334 if self.creds.auth_method in [LDAPAuthProtocol.MULTIPLEXOR_SSL_NTLM, LDAPAuthProtocol.MULTIPLEXOR_NTLM]:
335 from msldap.authentication.ntlm.multiplexor import MSLDAPNTLMMultiplexor
336 ntlmcred = MSLDAPMultiplexorCredential()
337 ntlmcred.type = 'NTLM'
338 if self.creds.username is not None:
339 ntlmcred.username = '<CURRENT>'
340 if self.creds.domain is not None:
341 ntlmcred.domain = '<CURRENT>'
342 if self.creds.password is not None:
343 ntlmcred.password = '<CURRENT>'
344 ntlmcred.is_guest = False
345 ntlmcred.is_ssl = True if self.creds.auth_method == LDAPAuthProtocol.MULTIPLEXOR_SSL_NTLM else False
346 ntlmcred.parse_settings(self.creds.settings)
347 ntlmcred.encrypt = self.creds.encrypt
348
349 handler = MSLDAPNTLMMultiplexor(ntlmcred)
350 #setting up SPNEGO
351 spneg = SPNEGO()
352 spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
353 return spneg
354
355 elif self.creds.auth_method in [LDAPAuthProtocol.MULTIPLEXOR_SSL_KERBEROS, LDAPAuthProtocol.MULTIPLEXOR_KERBEROS]:
356 from msldap.authentication.kerberos.multiplexor import MSLDAPKerberosMultiplexor
357
358 ntlmcred = MSLDAPMultiplexorCredential()
359 ntlmcred.type = 'KERBEROS'
360 ntlmcred.target = self.target
361 if self.creds.username is not None:
362 ntlmcred.username = '<CURRENT>'
363 if self.creds.domain is not None:
364 ntlmcred.domain = '<CURRENT>'
365 if self.creds.password is not None:
366 ntlmcred.password = '<CURRENT>'
367 ntlmcred.is_guest = False
368 ntlmcred.is_ssl = True if self.creds.auth_method == LDAPAuthProtocol.MULTIPLEXOR_SSL_NTLM else False
369 ntlmcred.parse_settings(self.creds.settings)
370 ntlmcred.encrypt = self.creds.encrypt
371
372 handler = MSLDAPKerberosMultiplexor(ntlmcred)
373 #setting up SPNEGO
374 spneg = SPNEGO()
375 spneg.add_auth_context('MS KRB5 - Microsoft Kerberos 5', handler)
376 return spneg
377
378 elif self.creds.auth_method.value.startswith('SSPIPROXY'):
379 if self.creds.auth_method == LDAPAuthProtocol.SSPIPROXY_NTLM:
380 from msldap.authentication.ntlm.sspiproxy import MSLDAPSSPIProxyNTLMAuth
381 ntlmcred = MSLDAPSSPIProxyCredential()
382 ntlmcred.type = 'NTLM'
383 if self.creds.username is not None:
384 ntlmcred.username = '<CURRENT>'
385 if self.creds.domain is not None:
386 ntlmcred.domain = '<CURRENT>'
387 if self.creds.password is not None:
388 ntlmcred.password = '<CURRENT>'
389 ntlmcred.is_guest = False
390 ntlmcred.encrypt = self.creds.encrypt
391 ntlmcred.host = self.creds.settings['host'][0]
392 ntlmcred.port = int(self.creds.settings['port'][0])
393 ntlmcred.proto = 'ws'
394 if 'proto' in self.creds.settings:
395 ntlmcred.proto = self.creds.settings['proto'][0]
396 if 'agentid' in self.creds.settings:
397 ntlmcred.agent_id = bytes.fromhex(self.creds.settings['agentid'][0])
398
399 handler = MSLDAPSSPIProxyNTLMAuth(ntlmcred)
400 #setting up SPNEGO
401 spneg = SPNEGO()
402 spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
403 return spneg
404
405 elif self.creds.auth_method == LDAPAuthProtocol.SSPIPROXY_KERBEROS:
406 from msldap.authentication.kerberos.sspiproxyws import MSLDAPSSPIProxyKerberosAuth
407
408 ntlmcred = MSLDAPSSPIProxyCredential()
409 ntlmcred.type = 'KERBEROS'
410 ntlmcred.target = self.target
411 if self.creds.username is not None:
412 ntlmcred.username = '<CURRENT>'
413 if self.creds.domain is not None:
414 ntlmcred.domain = '<CURRENT>'
415 if self.creds.password is not None:
416 ntlmcred.password = '<CURRENT>'
417 ntlmcred.is_guest = False
418 ntlmcred.encrypt = self.creds.encrypt
419 ntlmcred.host = self.creds.settings['host'][0]
420 ntlmcred.port = self.creds.settings['port'][0]
421 ntlmcred.proto = 'ws'
422 if 'proto' in self.creds.settings:
423 ntlmcred.proto = self.creds.settings['proto'][0]
424 if 'agentid' in self.creds.settings:
425 ntlmcred.agent_id = bytes.fromhex(self.creds.settings['agentid'][0])
426
427 handler = MSLDAPSSPIProxyKerberosAuth(ntlmcred)
428 #setting up SPNEGO
429 spneg = SPNEGO()
430 spneg.add_auth_context('MS KRB5 - Microsoft Kerberos 5', handler)
431 return spneg
432
433 elif self.creds.auth_method.value.startswith('WSNET'):
434 if self.creds.auth_method in [LDAPAuthProtocol.WSNET_NTLM]:
435 from msldap.authentication.ntlm.wsnet import MSLDAPWSNetNTLMAuth
436
437 ntlmcred = MSLDAPWSNETCredential()
438 ntlmcred.type = 'NTLM'
439 if self.creds.username is not None:
440 ntlmcred.username = '<CURRENT>'
441 if self.creds.domain is not None:
442 ntlmcred.domain = '<CURRENT>'
443 if self.creds.password is not None:
444 ntlmcred.password = '<CURRENT>'
445 ntlmcred.is_guest = False
446
447 handler = MSLDAPWSNetNTLMAuth(ntlmcred)
448 spneg = SPNEGO()
449 spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
450 return spneg
451
452
453 elif self.creds.auth_method in [LDAPAuthProtocol.WSNET_KERBEROS]:
454 from msldap.authentication.kerberos.wsnet import MSLDAPWSNetKerberosAuth
455
456 ntlmcred = MSLDAPWSNETCredential()
457 ntlmcred.type = 'KERBEROS'
458 ntlmcred.target = self.target
459 if self.creds.username is not None:
460 ntlmcred.username = '<CURRENT>'
461 if self.creds.domain is not None:
462 ntlmcred.domain = '<CURRENT>'
463 if self.creds.password is not None:
464 ntlmcred.password = '<CURRENT>'
465 ntlmcred.is_guest = False
466
467 handler = MSLDAPWSNetKerberosAuth(ntlmcred)
468 #setting up SPNEGO
469 spneg = SPNEGO()
470 spneg.add_auth_context('MS KRB5 - Microsoft Kerberos 5', handler)
471 return spneg
15 else:
16 raise Exception('Unsupported authentication protocol "%s"' % credential.protocol)
17
+0
-124
msldap/commons/credential.py less more
0 #!/usr/bin/env python3
1 #
2 # Author:
3 # Tamas Jos (@skelsec)
4 #
5
6 import enum
7
8 # https://tools.ietf.org/html/rfc4513
9 # simple auth:
10 # - anonymous
11 # - user without password
12 # - username + password
13 #
14 # SASL:
15 # - plain
16 # - gssapi
17 # -SSPI
18 # - NTLM
19 # - KERBEROS
20 # Sicily:
21 # - NTLM
22 # Multiplexor
23 #
24
25 class LDAPAuthProtocol(enum.Enum):
26 SIMPLE = 'SIMPLE' #SIMPLE can be with no creds - anonymous bind
27 PLAIN = 'PLAIN' #actually SASL-PLAIN
28 SICILY = 'SICILY' #NTLM (old proprietary from MS)
29 NTLM_PASSWORD = 'NTLM_PASSWORD' #actually SASL-GSSAPI-SPNEGO-NTLM
30 NTLM_NT = 'NTLM_NT' #actually SASL-GSSAPI-SPNEGO-NTLM
31 KERBEROS_RC4 = 'KERBEROS_RC4'
32 KERBEROS_NT = 'KERBEROS_NT'
33 KERBEROS_AES = 'KERBEROS_AES'
34 KERBEROS_PASSWORD = 'KERBEROS_PASSWORD'
35 KERBEROS_CCACHE = 'KERBEROS_CCACHE'
36 KERBEROS_KEYTAB = 'KERBEROS_KEYTAB'
37 KERBEROS_KIRBI = 'KERBEROS_KIRBI'
38 MULTIPLEXOR_KERBEROS = 'MULTIPLEXOR_KERBEROS'
39 MULTIPLEXOR_NTLM = 'MULTIPLEXOR_NTLM'
40 MULTIPLEXOR_SSL_KERBEROS = 'MULTIPLEXOR_SSL_KERBEROS'
41 MULTIPLEXOR_SSL_NTLM = 'MULTIPLEXOR_SSL_NTLM'
42 SSPI_NTLM = 'SSPI_NTLM' #actually SASL-GSSAPI-SPNEGO-NTLM but with integrated SSPI
43 SSPI_KERBEROS = 'SSPI_KERBEROS' #actually SASL-GSSAPI-SPNEGO-KERBEROS but with integrated SSPI
44 WSNET_NTLM = 'WSNET_NTLM'
45 WSNET_KERBEROS = 'WSNET_KERBEROS'
46 SSPIPROXY_NTLM = 'SSPIPROXY_NTLM'
47 SSPIPROXY_KERBEROS = 'SSPIPROXY_KERBEROS'
48
49 MSLDAP_GSS_METHODS = [
50 LDAPAuthProtocol.NTLM_PASSWORD ,
51 LDAPAuthProtocol.NTLM_NT ,
52 LDAPAuthProtocol.KERBEROS_RC4 ,
53 LDAPAuthProtocol.KERBEROS_NT ,
54 LDAPAuthProtocol.KERBEROS_AES ,
55 LDAPAuthProtocol.KERBEROS_PASSWORD ,
56 LDAPAuthProtocol.KERBEROS_CCACHE ,
57 LDAPAuthProtocol.KERBEROS_KEYTAB ,
58 LDAPAuthProtocol.KERBEROS_KIRBI ,
59 LDAPAuthProtocol.SSPI_NTLM ,
60 LDAPAuthProtocol.SSPI_KERBEROS,
61 LDAPAuthProtocol.MULTIPLEXOR_KERBEROS,
62 LDAPAuthProtocol.MULTIPLEXOR_NTLM,
63 LDAPAuthProtocol.MULTIPLEXOR_SSL_KERBEROS,
64 LDAPAuthProtocol.MULTIPLEXOR_SSL_NTLM,
65 LDAPAuthProtocol.WSNET_NTLM,
66 LDAPAuthProtocol.WSNET_KERBEROS,
67 LDAPAuthProtocol.SSPIPROXY_NTLM,
68 LDAPAuthProtocol.SSPIPROXY_KERBEROS,
69 ]
70
71 MSLDAP_KERBEROS_PROTOCOLS = [
72 LDAPAuthProtocol.KERBEROS_RC4 ,
73 LDAPAuthProtocol.KERBEROS_NT ,
74 LDAPAuthProtocol.KERBEROS_AES ,
75 LDAPAuthProtocol.KERBEROS_PASSWORD ,
76 LDAPAuthProtocol.KERBEROS_CCACHE ,
77 LDAPAuthProtocol.KERBEROS_KEYTAB ,
78 LDAPAuthProtocol.KERBEROS_KIRBI ,
79 ]
80
81 class MSLDAPCredential:
82 """
83 Describes the user's credentials to be used for authentication during the bind operation.
84
85 :param domain: Domain of the user
86 :type domain: str
87 :param username: Username of the user
88 :type username: str
89 :param password: The authentication secret. The actual contents depend on the `auth_method`
90 :type password: str
91 :param auth_method: The ahtentication method to be performed during bind operation
92 :type auth_method: :class:`LDAPAuthProtocol`
93 :param settings: Additional settings
94 :type settings: dict
95 :param etypes: Supported encryption types for Kerberos authentication.
96 :type etypes: List[:class:`int`]
97 :param encrypt: Use protocol-level encryption. Doesnt work on LDAPS
98 :type encrypt: bool
99 """
100 def __init__(self, domain=None, username= None, password = None, auth_method = None, settings = None, etypes = None, encrypt = False):
101 self.auth_method = auth_method
102 self.domain = domain
103 self.username = username
104 self.password = password
105 self.signing_preferred = False
106 self.encryption_preferred = False
107 self.settings = settings
108 self.etypes = etypes
109 self.encrypt = encrypt
110
111 def get_msuser(self):
112 if not self.domain:
113 return self.username
114
115 return '%s\\%s' % (self.domain,self.username)
116
117 def __str__(self):
118 t = '==== MSLDAPCredential ====\r\n'
119 for k in self.__dict__:
120 t += '%s: %s\r\n' % (k, self.__dict__[k])
121
122 return t
123
0
1 #!/usr/bin/env python3
2 #
3 # Author:
4 # Tamas Jos (@skelsec)
5 #
6 import enum
7 import copy
8
9 from msldap.commons.target import MSLDAPTarget
10 from msldap.client import MSLDAPClient
11 from msldap.connection import MSLDAPClientConnection
12 from asyauth.common.credentials import UniCredential
13
14 class LDAPConnectionFactory:
15 """
16 The URL describes both the connection target and the credentials. This class creates all necessary objects to set up the client.
17
18 :param url:
19 :type url: str
20 """
21
22 help_epilog = """
23 MSLDAP URL Format: <protocol>+<auth>://<username>:<password>@<ip_or_host>:<port>/<tree>/?<param>=<value>
24 <protocol> sets the ldap protocol following values supported:
25 - ldap
26 - ldaps
27 <auth> can be omitted if plaintext authentication is to be performed (in that case it default to ntlm-password), otherwise:
28 - ntlm-password
29 - ntlm-nt
30 - kerberos-password (dc option param must be used)
31 - kerberos-rc4 / kerberos-nt (dc option param must be used)
32 - kerberos-aes (dc option param must be used)
33 - kerberos-keytab (dc option param must be used)
34 - kerberos-ccache (dc option param must be used)
35 - sspi-ntlm (windows only!)
36 - sspi-kerberos (windows only!)
37 - anonymous
38 - plain
39 - simple
40 - sicily (same format as ntlm-nt but using the SICILY authentication)
41 <tree>:
42 OPTIONAL. Specifies the root tree of all queries
43 <param> can be:
44 - timeout : connction timeout in seconds
45 - proxytype: currently only socks5 proxy is supported
46 - proxyhost: Ip or hostname of the proxy server
47 - proxyport: port of the proxy server
48 - proxytimeout: timeout in secodns for the proxy connection
49 - dc: the IP address of the domain controller, MUST be used for kerberos authentication
50 - encrypt: enable encryption. Only for NTLM. DOESNT WORK WITH LDAPS
51 - etype: Supported encryption types for Kerberos authentication. Multiple can be specified.
52 - rate: LDAP paged search query rate limit. Will sleep for seconds between each new page. Default: 0 (no limit)
53 - pagesize: LDAP paged search query size per page. Max: 1000. Default: 1000
54
55 Examples:
56 ldap://10.10.10.2 (anonymous bind)
57 ldaps://test.corp (anonymous bind)
58 ldap+sspi-ntlm://test.corp
59 ldap+sspi-kerberos://test.corp
60 ldap://TEST\\victim:<password>@10.10.10.2 (defaults to SASL GSSAPI NTLM)
61 ldap+simple://TEST\\victim:<password>@10.10.10.2 (SASL SIMPLE auth)
62 ldap+plain://TEST\\victim:<password>@10.10.10.2 (SASL SIMPLE auth)
63 ldap+ntlm-password://TEST\\victim:<password>@10.10.10.2
64 ldap+ntlm-nt://TEST\\victim:<nthash>@10.10.10.2
65 ldap+kerberos-password://TEST\\victim:<password>@10.10.10.2
66 ldap+kerberos-rc4://TEST\\victim:<rc4key>@10.10.10.2
67 ldap+kerberos-aes://TEST\\victim:<aes>@10.10.10.2
68 ldap://TEST\\victim:[email protected]/DC=test,DC=corp/
69 ldap://TEST\\victim:[email protected]/DC=test,DC=corp/?timeout=99&proxytype=socks5&proxyhost=127.0.0.1&proxyport=1080&proxytimeout=44
70 """
71
72 def __init__(self, credential:UniCredential = None, target:MSLDAPTarget = None ):
73 self.credential = credential
74 self.target = target
75
76 @staticmethod
77 def from_url(connection_url):
78 target = MSLDAPTarget.from_url(connection_url)
79 credential = UniCredential.from_url(connection_url)
80 return LDAPConnectionFactory(credential, target)
81
82 def get_credential(self) -> UniCredential:
83 """
84 Creates a credential object
85
86 :return: Credential object
87 :rtype: :class:`UniCredential`
88 """
89 return copy.deepcopy(self.credential)
90
91 def get_target(self) -> MSLDAPTarget:
92 """
93 Creates a target object
94
95 :return: Target object
96 :rtype: :class:`MSLDAPTarget`
97 """
98 return copy.deepcopy(self.target)
99
100 def get_client(self) -> MSLDAPClient:
101 """
102 Creates a client that can be used to interface with the server
103
104 :return: LDAP client
105 :rtype: :class:`MSLDAPClient`
106 """
107 cred = self.get_credential()
108 target = self.get_target()
109 return MSLDAPClient(target, cred)
110
111
112 def get_connection(self) -> MSLDAPClientConnection:
113 """
114 Creates a connection that can be used to interface with the server
115
116 :return: LDAP connection
117 :rtype: :class:`MSLDAPClientConnection`
118 """
119 cred = self.get_credential()
120 target = self.get_target()
121 return MSLDAPClientConnection(target, cred)
122
123 def __str__(self):
124 t = '==== LDAPConnectionFactory ====\r\n'
125 for k in self.__dict__:
126 val = self.__dict__[k]
127 if isinstance(val, enum.IntFlag):
128 val = val
129 elif isinstance(val, enum.Enum):
130 val = val.name
131
132 t += '%s: %s\r\n' % (k, str(val))
133
134 return t
135
136 if __name__ == '__main__':
137 url_tests = [
138 'ldap://10.10.10.2',
139 'ldap://10.10.10.2:9999',
140 'ldap://test:[email protected]',
141 'ldap://domain\\[email protected]',
142 'ldap://domain\\test:[email protected]:9999',
143 'ldap://domain\\test:[email protected]:9999',
144 'ldaps+sspi-ntlm://10.10.10.2',
145 'ldaps+sspi-kerberos://10.10.10.2',
146 'ldaps+ntlm-password://domain\\test:[email protected]:9999',
147 'ldaps+ntlm-nt://domain\\test:[email protected]:9999',
148 'ldaps+kerberos-password://domain\\test:[email protected]:9999',
149 'ldaps://10.10.10.2:9999',
150 'ldaps://test:[email protected]',
151 'ldaps://domain\\[email protected]',
152 'ldaps://domain\\test:[email protected]:9999',
153 'ldaps://DOMAIN\\test:[email protected]:9999/?proxytype=socks5&proxyserver=127.0.0.1',
154 'ldaps://DOMAIN\\test:[email protected]:9999/?proxytype=socks5&proxyserver=127.0.0.1&proxyuser=admin&proxypass=alma',
155 'ldaps://DOMAIN\\test:[email protected]:9999/?proxytype=multiplexor&proxyserver=127.0.0.1&proxyport=9999&proxyuser=admin&proxypass=alma',
156 'ldaps://10.10.10.2',
157 'ldaps://10.10.10.2:6666',
158 ]
159 for url in url_tests:
160 print('===========================================================================')
161 print(url)
162 try:
163 dec = LDAPConnectionFactory.from_url(url)
164 creds = dec.get_credential()
165 target = dec.get_target()
166 except Exception as e:
167 import traceback
168 traceback.print_exc()
169 print('ERROR! Reason: %s' % e)
170 input()
171 else:
172 print(str(creds))
173 print(str(target))
174 input()
+0
-145
msldap/commons/proxy.py less more
0
1 #!/usr/bin/env python3
2 #
3 # Author:
4 # Tamas Jos (@skelsec)
5 #
6
7 import enum
8 from urllib.parse import urlparse, parse_qs
9
10 from asysocks.common.clienturl import SocksClientURL
11
12 class MSLDAPProxyType(enum.Enum):
13 SOCKS4 = 'SOCKS4'
14 SOCKS4_SSL = 'SOCKS4_SSL'
15 SOCKS5 = 'SOCKS5'
16 SOCKS5_SSL = 'SOCKS5_SSL'
17 MULTIPLEXOR = 'MULTIPLEXOR'
18 MULTIPLEXOR_SSL = 'MULTIPLEXOR_SSL'
19 WSNET = 'WSNET'
20 WSNETWS = 'WSNETWS'
21 WSNETWSS = 'WSNETWSS'
22
23 class MSLDAPProxy:
24 """
25 Describes the proxy to be used when connecting to the server. Used as a parameter to the `MSLDAPTarget` object
26
27 :param type: Specifies the proxy type
28 :type type: :class:`MSLDAPProxyType`
29 :param target:
30 :type target:
31 :param auth: Specifies the proxy authentication if any
32 :type auth:
33 """
34 def __init__(self, type = None, target = None, auth = None):
35 self.type = type
36 self.target = target
37 self.auth = auth
38
39
40 @staticmethod
41 def from_params(url_str):
42 """
43 Creates a proxy object from the parameters found in an LDAP URL string
44
45 :param type: url_str
46 :type type: str
47 :return: The proxy object
48 :rtype: :class:`MSLDAPProxy`
49 """
50 proxy = MSLDAPProxy()
51 url = urlparse(url_str)
52 if url.query is None:
53 return None
54
55 query = parse_qs(url.query)
56 if 'proxytype' not in query and 'sametype' not in query:
57 return None
58
59 proxy.type = MSLDAPProxyType(query['proxytype'][0].upper())
60 if proxy.type in [MSLDAPProxyType.WSNET, MSLDAPProxyType.WSNETWS, MSLDAPProxyType.WSNETWSS,MSLDAPProxyType.SOCKS4, MSLDAPProxyType.SOCKS4_SSL, MSLDAPProxyType.SOCKS5, MSLDAPProxyType.SOCKS5_SSL]:
61 proxy.target = SocksClientURL.from_params(url_str)
62 else:
63 proxy.target = MSLDAPMultiplexorProxy.from_params(url_str)
64
65 return proxy
66
67 def __str__(self):
68 t = '==== MSLDAPProxy ====\r\n'
69 for k in self.__dict__:
70 t += '%s: %s\r\n' % (k, self.__dict__[k])
71
72 return t
73
74
75 class MSLDAPMultiplexorProxy:
76 def __init__(self):
77 self.ip = None
78 self.port = None
79 self.timeout = 10
80 self.type = MSLDAPProxyType.MULTIPLEXOR
81 self.username = None
82 self.password = None
83 self.domain = None
84 self.agent_id = None
85 self.virtual_socks_port = None
86 self.virtual_socks_ip = None
87
88 def sanity_check(self):
89 if self.ip is None:
90 raise Exception('MULTIPLEXOR server IP is missing!')
91 if self.port is None:
92 raise Exception('MULTIPLEXOR server port is missing!')
93 if self.agent_id is None:
94 raise Exception('MULTIPLEXOR proxy requires agentid to be set!')
95
96 def get_server_url(self):
97 con_str = 'ws://%s:%s' % (self.ip, self.port)
98 if self.type == MSLDAPProxyType.MULTIPLEXOR_SSL:
99 con_str = 'wss://%s:%s' % (self.ip, self.port)
100 return con_str
101
102 @staticmethod
103 def from_params(url_str):
104 res = MSLDAPMultiplexorProxy()
105 url = urlparse(url_str)
106 res.endpoint_ip = url.hostname
107 if url.port:
108 res.endpoint_port = int(url.port)
109 if url.query is not None:
110 query = parse_qs(url.query)
111
112 for k in query:
113 if k.startswith('proxy'):
114 if k[5:] in multiplexorproxyurl_param2var:
115
116 data = query[k][0]
117 for c in multiplexorproxyurl_param2var[k[5:]][1]:
118 data = c(data)
119
120 setattr(
121 res,
122 multiplexorproxyurl_param2var[k[5:]][0],
123 data
124 )
125 res.sanity_check()
126
127 return res
128
129 def stru(x):
130 return str(x).upper()
131
132 multiplexorproxyurl_param2var = {
133 'type' : ('version', [stru, MSLDAPProxyType]),
134 'host' : ('ip', [str]),
135 'port' : ('port', [int]),
136 'timeout': ('timeout', [int]),
137 'user' : ('username', [str]),
138 'pass' : ('password', [str]),
139 #'authtype' : ('authtype', [SOCKS5Method]),
140 'agentid' : ('agent_id', [str]),
141 'domain' : ('domain', [str])
142
143 }
144
44 # Tamas Jos (@skelsec)
55 #
66
7 import enum
7 from os import stat
8 from sqlite3 import connect
9 from asysocks.unicomm.common.target import UniTarget, UniProto
10 from urllib.parse import urlparse, parse_qs
11 from asysocks.unicomm.utils.paramprocessor import str_one, int_one, bool_one
812
9 import platform
10 try:
11 import ssl
12 except:
13 if platform.system() == 'Emscripten':
14 pass
15
16 class LDAPProtocol(enum.Enum):
17 TCP = 'TCP'
18 UDP = 'UDP'
19 SSL = 'SSL'
13 msldaptarget_url_params = {
14 'pagesize' : int_one,
15 'rate' : int_one,
16 }
2017
2118
22 class MSLDAPTarget:
19 class MSLDAPTarget(UniTarget):
2320 """
2421 Describes the connection to the server.
2522
2825 :param port: port of the LDAP service running on the server
2926 :type port: int
3027 :param proto: Connection protocol to be used
31 :type proto: :class:`LDAPProtocol`
28 :type proto: :class:`UniProto`
3229 :param tree: The tree to connect to
3330 :type tree: str
34 :param proxy: specifies what kind of proxy to be used
35 :type proxy: :class:`MSLDAPProxy`
31 :param proxies: specifies what kind of proxy to be used
32 :type proxies: :class:`List[UniProxyTarget]`
3633 :param timeout: connection timeout in seconds
3734 :type timeout: int
3835 :param ldap_query_page_size: Maximum number of elements to fetch in each paged_query call.
3936 :type ldap_query_page_size: int
4037 :param ldap_query_ratelimit: rate limit of paged queries. This will cause a sleep (in seconds) between fetching of each page of the query
4138 :type ldap_query_ratelimit: float
39 :param dc_ip: Ip address of the kerberos server (if kerberos is used)
40 :type dc_ip: str
4241 """
43 def __init__(self, host, port = 389, proto = LDAPProtocol.TCP, tree = None, proxy = None, timeout = 10, ldap_query_page_size = 1000, ldap_query_ratelimit = 0):
44 self.proto = proto
45 self.host = host
42 def __init__(self, ip, port = 389, protocol = UniProto.CLIENT_TCP, tree = None, proxies = None, timeout = 10, ldap_query_page_size = 1000, ldap_query_ratelimit = 0, dns:str=None, dc_ip:str = None, domain:str = None, hostname:str = None):
43 UniTarget.__init__(self, ip, port, protocol, timeout, hostname = hostname, proxies = proxies, domain = domain, dc_ip = dc_ip, dns=dns)
4644 self.tree = tree
47 self.port = port
48 self.proxy = proxy
49 self.timeout = timeout
50 self.dc_ip = None
51 self.serverip = None
52 self.domain = None
53 self.sslctx = None
5445 self.ldap_query_page_size = ldap_query_page_size
5546 self.ldap_query_ratelimit = ldap_query_ratelimit
56
57 def get_ssl_context(self):
58 if self.proto == LDAPProtocol.SSL:
59 if self.sslctx is None:
60 # TODO ssl verification :)
61 self.sslctx = ssl._create_unverified_context()
62 #self.sslctx.verify = False
63 return self.sslctx
64 return None
65
47
6648 def to_target_string(self):
67 return 'ldap/%s@%s' % (self.host,self.domain) #ldap/WIN2019AD.test.corp @ TEST.CORP
49 return 'ldap/%s@%s' % (self.get_hostname_or_ip(), self.domain) #ldap/WIN2019AD.test.corp @ TEST.CORP
6850
6951 def get_host(self):
70 return '%s://%s:%s' % (self.proto, self.host, self.port)
52 if self.protocol == UniProto.CLIENT_SSL_TCP:
53 proto = 'ldaps'
54 elif self.protocol == UniProto.CLIENT_TCP:
55 proto = 'ldap'
56 return '%s://%s:%s' % (proto, self.get_hostname_or_ip(), self.port)
7157
7258 def is_ssl(self):
73 return self.proto == LDAPProtocol.SSL
59 return self.protocol == UniProto.CLIENT_SSL_TCP
60
61 @staticmethod
62 def from_url(connection_url):
63 url_e = urlparse(connection_url)
64 schemes = []
65 for item in url_e.scheme.upper().split('+'):
66 schemes.append(item.replace('-','_'))
67 if schemes[0] == 'LDAP':
68 protocol = UniProto.CLIENT_TCP
69 port = 389
70 elif schemes[0] == 'LDAPS':
71 protocol = UniProto.CLIENT_SSL_TCP
72 port = 636
73 elif schemes[0] == 'LDAP_SSL':
74 protocol = UniProto.CLIENT_SSL_TCP
75 port = 636
76 elif schemes[0] == 'LDAP_TCP':
77 protocol = UniProto.CLIENT_TCP
78 port= 389
79 elif schemes[0] == 'LDAP_UDP':
80 raise NotImplementedError()
81 protocol = UniProto.CLIENT_UDP
82 port = 389
83 else:
84 raise Exception('Unknown protocol! %s' % schemes[0])
85
86 if url_e.port:
87 port = url_e.port
88 if port is None:
89 raise Exception('Port must be provided!')
90
91 path = None
92 if url_e.path not in ['/', '', None]:
93 path = url_e.path
94
95 unitarget, extraparams = UniTarget.from_url(connection_url, protocol, port, msldaptarget_url_params)
96 pagesize = extraparams['pagesize'] if extraparams['pagesize'] is not None else 1000
97 rate = extraparams['rate'] if extraparams['rate'] is not None else 0
98
99 target = MSLDAPTarget(
100 unitarget.ip,
101 port = unitarget.port,
102 protocol = unitarget.protocol,
103 tree = path,
104 proxies = unitarget.proxies,
105 timeout = unitarget.timeout,
106 ldap_query_page_size = pagesize,
107 ldap_query_ratelimit = rate,
108 dns = unitarget.dns,
109 dc_ip = unitarget.dc_ip,
110 domain = unitarget.domain,
111 hostname = unitarget.hostname
112 )
113 return target
114
74115
75116 def __str__(self):
76117 t = '==== MSLDAPTarget ====\r\n'
+0
-416
msldap/commons/url.py less more
0
1 #!/usr/bin/env python3
2 #
3 # Author:
4 # Tamas Jos (@skelsec)
5 #
6
7 import platform
8 import hashlib
9 import getpass
10 import base64
11 import enum
12 from urllib.parse import urlparse, parse_qs
13
14 from msldap.commons.credential import MSLDAPCredential, LDAPAuthProtocol, MSLDAP_KERBEROS_PROTOCOLS
15 from msldap.commons.target import MSLDAPTarget, LDAPProtocol
16 from msldap.commons.proxy import MSLDAPProxy, MSLDAPProxyType
17 from msldap.client import MSLDAPClient
18 from msldap.connection import MSLDAPClientConnection
19
20 class PLAINTEXTSCHEME(enum.Enum):
21 """
22 Additional conveinence functions.
23 """
24 SIMPLE_PROMPT = 'SIMPLE_PROMPT'
25 SIMPLE_HEX = 'SIMPLE_HEX'
26 SIMPLE_B64 = 'SIMPLE_B64'
27 PLAIN_PROMPT = 'PLAIN_PROMPT'
28 PLAIN_HEX = 'PLAIN_HEX'
29 PLAIN_B64 = 'PLAIN_B64'
30 SICILY_PROMPT = 'SICILY_PROMPT'
31 SICILY_HEX = 'SICILY_HEX'
32 SICILY_B64 = 'SICILY_B64'
33 NTLM_PROMPT = 'NTLM_PROMPT'
34 NTLM_HEX = 'NTLM_HEX'
35 NTLM_B64 = 'NTLM_B64'
36
37 class MSLDAPURLDecoder:
38 """
39 The URL describes both the connection target and the credentials. This class creates all necessary objects to set up the client.
40
41 :param url:
42 :type url: str
43 """
44
45 help_epilog = """
46 MSLDAP URL Format: <protocol>+<auth>://<username>:<password>@<ip_or_host>:<port>/<tree>/?<param>=<value>
47 <protocol> sets the ldap protocol following values supported:
48 - ldap
49 - ldaps
50 <auth> can be omitted if plaintext authentication is to be performed (in that case it default to ntlm-password), otherwise:
51 - ntlm-password
52 - ntlm-nt
53 - kerberos-password (dc option param must be used)
54 - kerberos-rc4 / kerberos-nt (dc option param must be used)
55 - kerberos-aes (dc option param must be used)
56 - kerberos-keytab (dc option param must be used)
57 - kerberos-ccache (dc option param must be used)
58 - sspi-ntlm (windows only!)
59 - sspi-kerberos (windows only!)
60 - anonymous
61 - plain
62 - simple
63 - sicily (same format as ntlm-nt but using the SICILY authentication)
64 <tree>:
65 OPTIONAL. Specifies the root tree of all queries
66 <param> can be:
67 - timeout : connction timeout in seconds
68 - proxytype: currently only socks5 proxy is supported
69 - proxyhost: Ip or hostname of the proxy server
70 - proxyport: port of the proxy server
71 - proxytimeout: timeout in secodns for the proxy connection
72 - dc: the IP address of the domain controller, MUST be used for kerberos authentication
73 - encrypt: enable encryption. Only for NTLM. DOESNT WORK WITH LDAPS
74 - etype: Supported encryption types for Kerberos authentication. Multiple can be specified.
75 - rate: LDAP paged search query rate limit. Will sleep for seconds between each new page. Default: 0 (no limit)
76 - pagesize: LDAP paged search query size per page. Max: 1000. Default: 1000
77
78 Examples:
79 ldap://10.10.10.2 (anonymous bind)
80 ldaps://test.corp (anonymous bind)
81 ldap+sspi-ntlm://test.corp
82 ldap+sspi-kerberos://test.corp
83 ldap://TEST\\victim:<password>@10.10.10.2 (defaults to SASL GSSAPI NTLM)
84 ldap+simple://TEST\\victim:<password>@10.10.10.2 (SASL SIMPLE auth)
85 ldap+plain://TEST\\victim:<password>@10.10.10.2 (SASL SIMPLE auth)
86 ldap+ntlm-password://TEST\\victim:<password>@10.10.10.2
87 ldap+ntlm-nt://TEST\\victim:<nthash>@10.10.10.2
88 ldap+kerberos-password://TEST\\victim:<password>@10.10.10.2
89 ldap+kerberos-rc4://TEST\\victim:<rc4key>@10.10.10.2
90 ldap+kerberos-aes://TEST\\victim:<aes>@10.10.10.2
91 ldap://TEST\\victim:[email protected]/DC=test,DC=corp/
92 ldap://TEST\\victim:[email protected]/DC=test,DC=corp/?timeout=99&proxytype=socks5&proxyhost=127.0.0.1&proxyport=1080&proxytimeout=44
93 """
94
95 def __init__(self, url):
96 self.url = url
97 self.ldap_scheme = None
98 self.auth_scheme = None
99
100 self.domain = None
101 self.username = None
102 self.password = None
103 self.encrypt = False
104 self.auth_settings = {}
105 self.etypes = None
106
107 self.ldap_proto = None
108 self.ldap_host = None
109 self.ldap_port = 389
110 self.ldap_tree = None
111 self.target_timeout = 5
112 self.target_pagesize = 1000
113 self.target_ratelimit = 0
114 self.dc_ip = None
115 self.serverip = None
116 self.proxy = None
117
118 self.__pwpreprocess = None
119
120 self.parse()
121
122
123 def get_credential(self):
124 """
125 Creates a credential object
126
127 :return: Credential object
128 :rtype: :class:`MSLDAPCredential`
129 """
130 t = MSLDAPCredential(
131 domain=self.domain,
132 username=self.username,
133 password = self.password,
134 auth_method=self.auth_scheme,
135 settings = self.auth_settings
136 )
137 t.encrypt = self.encrypt
138 t.etypes = self.etypes
139
140 return t
141
142 def get_target(self):
143 """
144 Creates a target object
145
146 :return: Target object
147 :rtype: :class:`MSLDAPTarget`
148 """
149 target = MSLDAPTarget(
150 self.ldap_host,
151 port = self.ldap_port,
152 proto = self.ldap_scheme,
153 tree=self.ldap_tree,
154 timeout = self.target_timeout,
155 ldap_query_page_size = self.target_pagesize,
156 ldap_query_ratelimit = self.target_ratelimit
157 )
158 target.domain = self.domain
159 target.dc_ip = self.dc_ip
160 target.proxy = self.proxy
161 target.serverip = self.serverip
162 return target
163
164 def get_client(self):
165 """
166 Creates a client that can be used to interface with the server
167
168 :return: LDAP client
169 :rtype: :class:`MSLDAPClient`
170 """
171 cred = self.get_credential()
172 target = self.get_target()
173 return MSLDAPClient(target, cred)
174
175 def get_connection(self):
176 """
177 Creates a connection that can be used to interface with the server
178
179 :return: LDAP connection
180 :rtype: :class:`MSLDAPClientConnection`
181 """
182 cred = self.get_credential()
183 target = self.get_target()
184 return MSLDAPClientConnection(target, cred)
185
186 def scheme_decoder(self, scheme):
187 schemes = []
188 for item in scheme.upper().split('+'):
189 schemes.append(item.replace('-','_'))
190
191 if schemes[0] == 'LDAP':
192 self.ldap_scheme = LDAPProtocol.TCP
193 self.ldap_port = 389
194 elif schemes[0] == 'LDAPS':
195 self.ldap_scheme = LDAPProtocol.SSL
196 self.ldap_port = 636
197 elif schemes[0] == 'LDAP_SSL':
198 self.ldap_scheme = LDAPProtocol.SSL
199 self.ldap_port = 636
200 elif schemes[0] == 'LDAP_TCP':
201 self.ldap_scheme = LDAPProtocol.TCP
202 self.ldap_port = 389
203 elif schemes[0] == 'LDAP_UDP':
204 self.ldap_scheme = LDAPProtocol.UDP
205 self.ldap_port = 389
206 else:
207 raise Exception('Unknown protocol! %s' % schemes[0])
208
209 if len(schemes) == 1:
210 return
211
212 try:
213 x = PLAINTEXTSCHEME(schemes[1])
214 if x == PLAINTEXTSCHEME.SIMPLE_PROMPT:
215 self.auth_scheme = LDAPAuthProtocol.SIMPLE
216 self.__pwpreprocess = 'PROMPT'
217
218 if x == PLAINTEXTSCHEME.SIMPLE_HEX:
219 self.auth_scheme = LDAPAuthProtocol.SIMPLE
220 self.__pwpreprocess = 'HEX'
221
222 if x == PLAINTEXTSCHEME.SIMPLE_B64:
223 self.auth_scheme = LDAPAuthProtocol.SIMPLE
224 self.__pwpreprocess = 'B64'
225
226 if x == PLAINTEXTSCHEME.PLAIN_PROMPT:
227 self.auth_scheme = LDAPAuthProtocol.PLAIN
228 self.__pwpreprocess = 'PROMPT'
229
230 if x == PLAINTEXTSCHEME.PLAIN_HEX:
231 self.auth_scheme = LDAPAuthProtocol.PLAIN
232 self.__pwpreprocess = 'HEX'
233
234 if x == PLAINTEXTSCHEME.PLAIN_B64:
235 self.auth_scheme = LDAPAuthProtocol.PLAIN
236 self.__pwpreprocess = 'B64'
237
238 if x == PLAINTEXTSCHEME.SICILY_PROMPT:
239 self.auth_scheme = LDAPAuthProtocol.SICILY
240 self.__pwpreprocess = 'PROMPT'
241
242 if x == PLAINTEXTSCHEME.SICILY_HEX:
243 self.auth_scheme = LDAPAuthProtocol.SICILY
244 self.__pwpreprocess = 'HEX'
245
246 if x == PLAINTEXTSCHEME.SICILY_B64:
247 self.auth_scheme = LDAPAuthProtocol.SICILY
248 self.__pwpreprocess = 'B64'
249
250 if x == PLAINTEXTSCHEME.NTLM_PROMPT:
251 self.auth_scheme = LDAPAuthProtocol.NTLM_PASSWORD
252 self.__pwpreprocess = 'PROMPT'
253
254 if x == PLAINTEXTSCHEME.NTLM_HEX:
255 self.auth_scheme = LDAPAuthProtocol.NTLM_PASSWORD
256 self.__pwpreprocess = 'HEX'
257
258 if x == PLAINTEXTSCHEME.NTLM_B64:
259 self.auth_scheme = LDAPAuthProtocol.NTLM_PASSWORD
260 self.__pwpreprocess = 'B64'
261 except:
262 try:
263 self.auth_scheme = LDAPAuthProtocol(schemes[1])
264 except:
265 raise Exception('Uknown scheme!')
266
267 return
268
269 def parse(self):
270 url_e = urlparse(self.url)
271 self.scheme_decoder(url_e.scheme)
272
273 self.password = url_e.password
274 if self.__pwpreprocess is not None:
275 if self.__pwpreprocess == 'PROMPT':
276 self.password = getpass.getpass()
277
278 elif self.__pwpreprocess == 'HEX':
279 self.password = bytes.fromhex(self.password).decode()
280
281 elif self.__pwpreprocess == 'B64':
282 self.password = base64.b64decode(self.password).decode()
283
284 else:
285 raise Exception('Unknown password preprocess directive %s' % self.__pwpreprocess)
286
287
288 if url_e.username is not None:
289 if url_e.username.find('\\') != -1:
290 self.domain , self.username = url_e.username.split('\\')
291 else:
292 self.domain = None
293 self.username = url_e.username
294
295 #defaulting schemes...
296 if self.auth_scheme is None:
297 if self.username is not None and self.domain is not None and self.password is not None:
298 #tricky parsing to make user feel confortable...
299 if len(self.password) == 32:
300 try:
301 bytes.fromhex(self.password)
302 self.auth_scheme = LDAPAuthProtocol.NTLM_NT
303 except:
304 self.auth_scheme = LDAPAuthProtocol.NTLM_PASSWORD
305 else:
306 self.auth_scheme = LDAPAuthProtocol.NTLM_PASSWORD
307 else:
308 self.auth_scheme = LDAPAuthProtocol.SIMPLE
309
310 self.ldap_host = url_e.hostname
311 if url_e.port is not None:
312 self.ldap_port = int(url_e.port)
313
314 if url_e.path is not None:
315 tree = url_e.path.replace('/','')
316 if tree != '':
317 self.ldap_tree = tree
318
319 proxy_present = False
320 if url_e.query is not None:
321 query = parse_qs(url_e.query)
322 if 'etype' in query:
323 self.etypes = []
324 for k in query:
325 if k.startswith('proxy') is True:
326 proxy_present = True
327 if k == 'dc':
328 self.dc_ip = query[k][0]
329 elif k == 'timeout':
330 self.timeout = int(query[k][0])
331 elif k == 'serverip':
332 self.serverip = query[k][0]
333 elif k == 'dns':
334 self.dns = query[k] #multiple dns can be set, so not trimming here
335 elif k == 'encrypt':
336 self.encrypt = bool(int(query[k][0]))
337 elif k == 'etype':
338 self.etypes = [int(x) for x in query[k]]
339 elif k.startswith('auth'):
340 self.auth_settings[k[len('auth'):]] = query[k]
341 elif k == 'rate':
342 self.target_ratelimit = float(query[k][0])
343 elif k == 'pagesize':
344 self.target_pagesize = int(query[k][0])
345 #elif k.startswith('same'):
346 # self.auth_settings[k[len('same'):]] = query[k]
347
348 if proxy_present is True:
349 self.proxy = MSLDAPProxy.from_params(self.url)
350
351 if self.auth_scheme in [LDAPAuthProtocol.SSPI_NTLM, LDAPAuthProtocol.SSPI_KERBEROS]:
352 if platform.system().upper() != 'WINDOWS':
353 raise Exception('SSPI auth only works on Windows!')
354 if self.username is None:
355 self.username = '<CURRENT>'
356 if self.password is None:
357 self.password = '<CURRENT>'
358 if self.domain is None:
359 self.domain = '<CURRENT>'
360
361 if self.auth_scheme in MSLDAP_KERBEROS_PROTOCOLS and self.dc_ip is None:
362 raise Exception('The "dc" parameter MUST be used for kerberos authentication types!')
363
364
365 # if self.proxy_scheme in [LDAPProxyType.MULTIPLEXOR, LDAPProxyType.MULTIPLEXOR_SSL]:
366 # if self.proxy_port is None:
367 # self.proxy_port = 9999
368 #
369 # if self.proxy_scheme in [LDAPProxyType.MULTIPLEXOR, LDAPProxyType.MULTIPLEXOR_SSL]:
370 # if 'agentid' not in self.proxy_settings:
371 # raise Exception('multiplexor proxy reuires agentid to be set! Set it via proxyagentid parameter!')
372 #
373
374
375
376
377 if __name__ == '__main__':
378 url_tests = [
379 'ldap://10.10.10.2',
380 'ldap://10.10.10.2:9999',
381 'ldap://test:[email protected]',
382 'ldap://domain\\[email protected]',
383 'ldap://domain\\test:[email protected]:9999',
384 'ldap://domain\\test:[email protected]:9999',
385 'ldaps+sspi-ntlm://10.10.10.2',
386 'ldaps+sspi-kerberos://10.10.10.2',
387 'ldaps+ntlm-password://domain\\test:[email protected]:9999',
388 'ldaps+ntlm-nt://domain\\test:[email protected]:9999',
389 'ldaps+kerberos-password://domain\\test:[email protected]:9999',
390 'ldaps://10.10.10.2:9999',
391 'ldaps://test:[email protected]',
392 'ldaps://domain\\[email protected]',
393 'ldaps://domain\\test:[email protected]:9999',
394 'ldaps://DOMAIN\\test:[email protected]:9999/?proxytype=socks5&proxyserver=127.0.0.1',
395 'ldaps://DOMAIN\\test:[email protected]:9999/?proxytype=socks5&proxyserver=127.0.0.1&proxyuser=admin&proxypass=alma',
396 'ldaps://DOMAIN\\test:[email protected]:9999/?proxytype=multiplexor&proxyserver=127.0.0.1&proxyport=9999&proxyuser=admin&proxypass=alma',
397 'ldaps://10.10.10.2',
398 'ldaps://10.10.10.2:6666',
399 ]
400 for url in url_tests:
401 print('===========================================================================')
402 print(url)
403 try:
404 dec = MSLDAPURLDecoder(url)
405 creds = dec.get_credential()
406 target = dec.get_target()
407 except Exception as e:
408 import traceback
409 traceback.print_exc()
410 print('ERROR! Reason: %s' % e)
411 input()
412 else:
413 print(str(creds))
414 print(str(target))
415 input()
00
11 import datetime
22
3 def timestamp2datetime(dt):
3 def timestamp2datetime(dt) -> datetime.datetime:
44 """
55 Converting Windows timestamps to datetime.datetime format
66 :param dt: Windows timestamp as array of bytes
1111 return datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=us)
1212
1313
14 def datetime2timestamp(dt):
14 def datetime2timestamp(dt) -> int:
1515 delta = dt - datetime.datetime(1601, 1, 1)
1616 ns = int((delta / datetime.timedelta(microseconds=1)) * 10)
17 return ns.to_bytes(8, 'little', signed = False)
17 return ns.to_bytes(8, 'little', signed = False)
18
19 def wrap(s, w) -> str:
20 return [s[i:i + w] for i in range(0, len(s), w)]
21
22 def print_cert(cert, offset=0) -> str:
23 cert = cert['tbs_certificate']
24 blanks = " " * offset
25 msg = [
26 "Cert Subject: %s" % cert['subject']['common_name'],
27 "Cert Serial: %s" % cert['serial_number'],
28 "Cert Start: %s" % cert['validity']['not_before'],
29 "Cert End: %s" % cert['validity']['not_after'],
30 "Cert Issuer: %s" % cert['issuer']['common_name'],
31 ]
32 return "{}{}".format(blanks, "\n{}".format(blanks).join(msg))
33
34 KNOWN_SIDS = {
35 "S-1-0": "Null Authority",
36 "S-1-0-0": "Nobody",
37 "S-1-1": "World Authority",
38 "S-1-1-0": "Everyone",
39 "S-1-2": "Local Authority",
40 "S-1-2-0": "Local",
41 "S-1-3": "Creator Authority",
42 "S-1-3-0": "Creator Owner",
43 "S-1-3-1": "Creator Group",
44 "S-1-3-4": "Owner Rights",
45 "S-1-4": "Non-unique Authority",
46 "S-1-5": "NT Authority",
47 "S-1-5-1": "Dialup",
48 "S-1-5-2": "Network",
49 "S-1-5-3": "Batch",
50 "S-1-5-4": "Interactive",
51 "S-1-5-5-X-Y": "Logon Session",
52 "S-1-5-6": "Service",
53 "S-1-5-7": "Anonymous",
54 "S-1-5-9": "Enterprise Domain Controllers",
55 "S-1-5-10": "Principal Self",
56 "S-1-5-11": "Authenticated Users",
57 "S-1-5-12": "Restricted Code",
58 "S-1-5-13": "Terminal Server Users",
59 "S-1-5-14": "Remote Interactive Logon",
60 "S-1-5-17": "IUSR",
61 "S-1-5-18": "Local System",
62 "S-1-5-19": "NT Authority Local Service",
63 "S-1-5-20": "NT Authority Network Service",
64 "S-1-5-32-544": "Administrators",
65 "S-1-5-32-545": "Users",
66 "S-1-5-32-546": "Guests",
67 "S-1-5-32-547": "Power Users",
68 "S-1-5-32-548": "Account Operators",
69 "S-1-5-32-549": "Server Operators",
70 "S-1-5-32-550": "Print Operators",
71 "S-1-5-32-551": "Backup Operators",
72 "S-1-5-32-552": "Replicators",
73 "S-1-5-32-582": "Storage Replica Administrators",
74 "S-1-5-64-10": "NTLM Authentication",
75 "S-1-5-64-14": "SChannel Authentication",
76 "S-1-5-64-21": "Digest Authentication",
77 "S-1-5-80": "NT Service",
78 }
22
33 from msldap import logger
44 from msldap.commons.common import MSLDAPClientStatus
5 from .commons.target import MSLDAPTarget
56 from msldap.protocol.messages import LDAPMessage, BindRequest, \
67 protocolOp, AuthenticationChoice, SaslCredentials, \
78 SearchRequest, AttributeDescription, Filter, Filters, \
89 Controls, Control, SearchControlValue, AddRequest, \
9 ModifyRequest, DelRequest
10 ModifyRequest, DelRequest, ExtendedRequest, ExtendedResponse
1011
1112 from msldap.protocol.utils import calcualte_length
1213 from msldap.protocol.typeconversion import convert_result, convert_attributes, encode_attributes, encode_changes
1314 from msldap.protocol.query import escape_filter_chars, query_syntax_converter
14 from msldap.commons.authbuilder import AuthenticatorBuilder
15 from msldap.commons.credential import MSLDAP_GSS_METHODS
16 from msldap.network.selector import MSLDAPNetworkSelector
17 from msldap.commons.credential import LDAPAuthProtocol
18 from msldap.commons.target import LDAPProtocol
19 from msldap.commons.exceptions import LDAPServerException, LDAPBindException, LDAPAddException, LDAPModifyException, LDAPDeleteException
20 from asn1crypto.x509 import Certificate
15 from msldap.commons.authbuilder import get_auth_context
16 from msldap.network.packetizer import LDAPPacketizer
17 from asysocks.unicomm.common.target import UniProto
18 from msldap.commons.exceptions import LDAPBindException, LDAPAddException, LDAPModifyException, LDAPDeleteException
2119 from hashlib import sha256
2220 from minikerberos.gssapi.channelbindings import ChannelBindingsStruct
21 from asysocks.unicomm.client import UniClient
22 from asyauth.common.constants import asyauthProtocol
23 from asyauth.common.credentials import UniCredential
2324
2425 class MSLDAPClientConnection:
25 def __init__(self, target, creds):
26 if target is None:
27 raise Exception('Target cant be none!')
26 def __init__(self, target:MSLDAPTarget, credential:UniCredential, auth=None):
2827 self.target = target
29 self.creds = creds
30 self.auth = AuthenticatorBuilder(self.creds, self.target).build()
28 self.credential = credential
29 if auth is not None:
30 self.auth = auth
31 else:
32 self.auth = get_auth_context(self.credential)
33
3134 self.connected = False
3235 self.bind_ok = False
3336 self.__sign_messages = False
5255
5356 async def __handle_incoming(self):
5457 try:
55 while True:
56 message_data, err = await self.network.in_queue.get()
57 if err is not None:
58 logger.debug('Client terminating bc __handle_incoming got an error!')
59 raise err
60
58 async for message_data in self.network.read():
6159 #print('Incoming message data: %s' % message_data)
6260 if self.bind_ok is True:
6361 if self.__encrypt_messages is True:
145143 self.encryption_sequence_counter += 1
146144
147145 self.message_table_notify[curr_msg_id] = asyncio.Event()
148 await self.network.out_queue.put(message_data)
146 await self.network.write(message_data)
149147
150148 return curr_msg_id
151149
175173 """
176174 try:
177175 logger.debug('Connecting!')
178 self.network = await MSLDAPNetworkSelector.select(self.target)
179 res, err = await self.network.run()
180 if res is False:
181 return False, err
176 packetizer = LDAPPacketizer()
177 client = UniClient(self.target, packetizer)
178 self.network = await client.connect()
182179
183180 # now processing channel binding options
184 if self.target.proto == LDAPProtocol.SSL:
181 if self.target.protocol == UniProto.CLIENT_SSL_TCP:
185182 certdata = self.network.get_peer_certificate()
186 #cert = Certificate.load(certdata).native
187 #print(cert)
188183 cb_struct = ChannelBindingsStruct()
189184 cb_struct.application_data = b'tls-server-end-point:' + sha256(certdata).digest()
190185
206201
207202 logger.debug('Disconnecting!')
208203 self.bind_ok = False
204 if self.network is not None:
205 await self.network.close()
206 await asyncio.sleep(0)
207
209208 if self.handle_incoming_task is not None:
210209 self.handle_incoming_task.cancel()
211 if self.network is not None:
212 await self.network.terminate()
213210
214211
215212 def __bind_success(self):
219216 """
220217 logger.debug('BIND Success!')
221218 self.bind_ok = True
222 if self.creds.auth_method in MSLDAP_GSS_METHODS or self.creds.auth_method == LDAPAuthProtocol.SICILY:
219 if self.credential.protocol in [asyauthProtocol.NTLM, asyauthProtocol.KERBEROS, asyauthProtocol.SICILY]:
223220 self.__sign_messages = self.auth.signing_needed()
224221 self.__encrypt_messages = self.auth.encryption_needed()
225222 if self.__encrypt_messages or self.__sign_messages:
226 self.network.is_plain_msg = False
223 self.network.packetizer.is_plain_msg = False
227224
228225 async def bind(self):
229226 """
235232 """
236233 logger.debug('BIND in progress...')
237234 try:
238 if self.creds.auth_method == LDAPAuthProtocol.SICILY:
239
240 data, to_continue, err = await self.auth.authenticate(None)
235 if self.credential.protocol == asyauthProtocol.SICILY:
236
237 data, to_continue, err = await self.auth.authenticate(None, spn=self.target.to_target_string())
241238 if err is not None:
242239 return None, err
243240
291288 res['protocolOp']['diagnosticMessage']
292289 )
293290
294 data, to_continue, err = await self.auth.authenticate(res['protocolOp']['matchedDN'])
291 data, to_continue, err = await self.auth.authenticate(res['protocolOp']['matchedDN'], spn=self.target.to_target_string())
295292 if err is not None:
296293 return None, err
297294
324321 self.__bind_success()
325322 return True, None
326323
327 elif self.creds.auth_method == LDAPAuthProtocol.SIMPLE:
324 elif self.credential.protocol == asyauthProtocol.SIMPLE:
328325 pw = b''
329 if self.auth.password != None:
330 pw = self.auth.password.encode()
326 if self.auth.secret != None:
327 pw = self.auth.secret.encode()
331328
332329 user = b''
333330 if self.auth.username != None:
362359 res['protocolOp']['diagnosticMessage']
363360 )
364361
365 elif self.creds.auth_method in MSLDAP_GSS_METHODS:
362 elif self.credential.protocol in [asyauthProtocol.NTLM, asyauthProtocol.KERBEROS]:
366363 challenge = None
367364 while True:
368365 try:
369 data, to_continue, err = await self.auth.authenticate(challenge, cb_data = self.cb_data)
366 data, to_continue, err = await self.auth.authenticate(challenge, cb_data = self.cb_data, spn=self.target.to_target_string())
370367 if err is not None:
371368 raise err
372369 except Exception as e:
397394 res = res.native
398395 if res['protocolOp']['resultCode'] == 'success':
399396 if 'serverSaslCreds' in res['protocolOp']:
400 data, _, err = await self.auth.authenticate(res['protocolOp']['serverSaslCreds'], cb_data = self.cb_data)
397 data, _, err = await self.auth.authenticate(res['protocolOp']['serverSaslCreds'], cb_data = self.cb_data, spn=self.target.to_target_string())
401398 if err is not None:
402399 return False, err
403400
417414 )
418415
419416 else:
420 raise Exception('Not implemented authentication method: %s' % self.creds.auth_method.name)
417 raise Exception('Not implemented authentication method: %s' % self.credential.protocol.name)
421418 except Exception as e:
419 await self.disconnect()
422420 return False, e
423421
424422 async def add(self, entry, attributes):
704702 except Exception as e:
705703 yield (None, e)
706704
705 async def whoami(self):
706 if self.status != MSLDAPClientStatus.RUNNING:
707 return None, Exception('Connection not running! Probably encountered an error')
708
709 ext = {
710 'requestName': b'1.3.6.1.4.1.4203.1.11.3',
711 }
712 br = { 'extendedReq' : ExtendedRequest(ext)}
713 msg = { 'protocolOp' : protocolOp(br)}
714
715 msg_id = await self.send_message(msg)
716 res = await self.recv_message(msg_id)
717 res = res[0]
718 if isinstance(res, Exception):
719 return None, res
720 if res.native['protocolOp']['resultCode'] != 'success':
721 return False, LDAPBindException(
722 res['protocolOp']['resultCode'],
723 res['protocolOp']['diagnosticMessage']
724 )
725 return res.native['protocolOp']['responseValue'].decode(), None
726
727
707728
708729 async def get_serverinfo(self):
709730 if self.status != MSLDAPClientStatus.RUNNING:
753774 return convert_attributes(res.native['protocolOp']['attributes']), None
754775
755776
756 async def amain():
757 import traceback
758 from msldap.commons.url import MSLDAPURLDecoder
759
760 base = 'DC=TEST,DC=CORP'
761
762 #ip = 'WIN2019AD'
763 #domain = 'TEST'
764 #username = 'victim'
765 #password = 'Passw0rd!1'
766 ##auth_method = LDAPAuthProtocol.SICILY
767 #auth_method = LDAPAuthProtocol.SIMPLE
768
769 #cred = MSLDAPCredential(domain, username, password , auth_method)
770 #target = MSLDAPTarget(ip)
771 #target.dc_ip = '10.10.10.2'
772 #target.domain = 'TEST'
773
774 url = 'ldaps+ntlm-password://test\\Administrator:QLFbT8zkiFGlJuf0B3Qq@WIN2019AD/?dc=10.10.10.2'
775
776 dec = MSLDAPURLDecoder(url)
777 cred = dec.get_credential()
778 target = dec.get_target()
779
780 print(cred)
781 print(target)
782
783 input()
784
785 client = MSLDAPClientConnection(target, cred)
786 await client.connect()
787 res, err = await client.bind()
788 if err is not None:
789 raise err
777
790778
791 user = "CN=ldaptest_2,CN=Users,DC=test,DC=corp"
792 #attributes = {'objectClass': ['inetOrgPerson', 'posixGroup', 'top'], 'sn': 'user_sn', 'gidNumber': 0}
793 #res, err = await client.add(user, attributes)
794 #if err is not None:
795 # print(err)
796
797 #changes = {
798 # 'unicodePwd': [('replace', ['"TESTPassw0rd!1"'])],
799 # #'lockoutTime': [('replace', [0])]
800 #}
801
802 #res, err = await client.modify(user, changes)
803 #if err is not None:
804 # print('ERR! %s' % err)
805 #else:
806 # print('OK!')
807
808 res, err = await client.delete(user)
809 if err is not None:
810 print('ERR! %s' % err)
811
812 await client.disconnect()
813
814
815
816 if __name__ == '__main__':
817 from msldap import logger
818 from msldap.commons.credential import MSLDAPCredential, LDAPAuthProtocol
819 from msldap.commons.target import MSLDAPTarget
820 from msldap.protocol.query import query_syntax_converter
821
822 logger.setLevel(2)
823
824
825 asyncio.run(amain())
826
827
828779
829780
830781
+0
-107
msldap/crypto/AES.py less more
0 """
1 The idea here is to offer compatibility with 3rd party libraries by extending wrappers for ech encryption mode
2 This is needed because the pure python implementation for encryption and hashing algorithms are quite slow
3
4 currently it's not the perfect wrapper, needs to be extended
5 """
6
7 from msldap.crypto.BASE import symmetricBASE, cipherMODE
8 from msldap.crypto.pure.AES import AESModeOfOperationECB, AESModeOfOperationCBC, AESModeOfOperationCTR
9 try:
10 from Crypto.Cipher import AES as _pyCryptoAES
11 except:
12 pass
13
14 try:
15 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
16 from cryptography.hazmat.primitives import padding
17 from cryptography.hazmat.backends import default_backend
18 except:
19 pass
20
21 class pureAES(symmetricBASE):
22 def __init__(self, key, mode = cipherMODE.ECB, IV = None, pad = None, padMode = None):
23 self.key = key
24 self.mode = mode
25 self.IV = IV
26 self.pad = pad
27 self.padMode = padMode
28
29 symmetricBASE.__init__(self)
30
31 def setup_cipher(self):
32 if self.mode == cipherMODE.ECB:
33 self._cipher = AESModeOfOperationECB(self.key)
34 elif self.mode == cipherMODE.CBC:
35 self._cipher = AESModeOfOperationCBC(self.key, iv = self.IV)
36 elif self.mode == cipherMODE.CTR:
37 self._cipher = AESModeOfOperationCTR(self.key, iv = self.IV)
38 else:
39 raise Exception('Unknown cipher mode!')
40
41 def encrypt(self, data):
42 return self._cipher.encrypt(data)
43 def decrypt(self, data):
44 return self._cipher.decrypt(data)
45
46 class pyCryptoAES(symmetricBASE):
47 def __init__(self, key, mode = cipherMODE.ECB, IV = None, pad = None, padMode = None):
48 self.key = key
49 self.mode = mode
50 self.IV = IV
51 self.pad = pad
52 self.padMode = padMode
53
54 symmetricBASE.__init__(self)
55
56 def setup_cipher(self):
57 if self.mode == cipherMODE.ECB:
58 self._cipher = _pyCryptoAES.new(self.key, _pyCryptoAES.MODE_ECB)
59
60 elif self.mode == cipherMODE.CBC:
61 self._cipher = _pyCryptoAES.new(self.key, _pyCryptoAES.MODE_CBC, self.IV)
62 elif self.mode == cipherMODE.CTR:
63 self._cipher = _pyCryptoAES.new(self.key, _pyCryptoAES.MODE_CTR, self.IV)
64 else:
65 raise Exception('Unknown cipher mode!')
66
67 def encrypt(self, data):
68 return self._cipher.encrypt(data)
69 def decrypt(self, data):
70 return self._cipher.decrypt(data)
71
72 class cryptographyAES(symmetricBASE):
73 def __init__(self, key, mode = cipherMODE.ECB, IV = None, pad = None, padMode = None):
74 self.IV = IV
75 #the python cryptography module sets the IV in the operational mode!!!
76 if mode == cipherMODE.ECB:
77 self.IV = modes.ECB()
78 elif mode == cipherMODE.CBC:
79 self.IV = modes.CBC(IV)
80 elif mode == cipherMODE.CBC:
81 self.IV = modes.CTR(IV)
82 else:
83 raise Exception('Unknown cipher mode!')
84
85 self.key = key
86
87 """ TODO padding
88 if self.padMode is not None:
89 """
90
91 self.encryptor = None
92 self.decryptor = None
93 symmetricBASE.__init__(self)
94
95 def setup_cipher(self):
96 algorithm = algorithms.AES(self.key)
97 self._cipher = Cipher(algorithm, mode=self.IV, backend=default_backend())
98 self.encryptor = self._cipher.encryptor()
99 self.decryptor = self._cipher.decryptor()
100
101 def encrypt(self, data):
102 return self.encryptor.update(data)
103
104
105 def decrypt(self, data):
106 return self.decryptor.update(data)
+0
-73
msldap/crypto/BASE.py less more
0 from abc import ABC, abstractmethod
1 import enum
2
3 class cipherMODE(enum.Enum):
4 ECB = enum.auto()
5 CBC = enum.auto()
6 CTR = enum.auto()
7
8 class symmetricBASE():
9 def __init__(self):
10 self._cipher = None
11 self.setup_cipher()
12
13 @abstractmethod
14 def setup_cipher(self):
15 #create the hash object here
16 pass
17
18 @abstractmethod
19 def encrypt(self, data):
20 pass
21
22 @abstractmethod
23 def decrypt(self):
24 pass
25
26 class hashBASE():
27 def __init__(self, data):
28 self._hash = None
29 self.setup_hash()
30
31 if data is not None:
32 self._hash.update(data)
33
34 @abstractmethod
35 def setup_hash(self):
36 #create the hash object here
37 pass
38
39 @abstractmethod
40 def update(self, data):
41 pass
42
43 @abstractmethod
44 def digest(self):
45 pass
46
47 @abstractmethod
48 def hexdigest(self):
49 pass
50
51 class hmacBASE():
52 def __init__(self, key):
53 self._key = key
54 self._hash = None
55 self.setup_hash()
56
57 @abstractmethod
58 def setup_hash(self):
59 #create the hash object here
60 pass
61
62 @abstractmethod
63 def update(self, data):
64 pass
65
66 @abstractmethod
67 def digest(self):
68 pass
69
70 @abstractmethod
71 def hexdigest(self):
72 pass
+0
-89
msldap/crypto/DES.py less more
0 """
1 The idea here is to offer compatibility with 3rd party libraries by extending wrappers for ech encryption mode
2 This is needed because the pure python implementation for encryption and hashing algorithms are quite slow
3
4 currently it's not the perfect wrapper, needs to be extended
5 """
6
7 from msldap.crypto.BASE import symmetricBASE, cipherMODE
8 import msldap.crypto.pure.DES.DES as _pyDES
9 try:
10 from Crypto.Cipher import DES as _pyCryptoDES
11 except:
12 pass
13
14 try:
15 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
16 from cryptography.hazmat.backends import default_backend
17 except:
18 pass
19
20
21 # from impacket
22 def expand_DES_key(key):
23 # Expand the key from a 7-byte password key into a 8-byte DES key
24 key = key[:7]
25 key += b'\x00'*(7-len(key))
26 s = (((key[0] >> 1) & 0x7f) << 1).to_bytes(1, byteorder = 'big')
27 s += (((key[0] & 0x01) << 6 | ((key[1] >> 2) & 0x3f)) << 1).to_bytes(1, byteorder = 'big')
28 s += (((key[1] & 0x03) << 5 | ((key[2] >> 3) & 0x1f)) << 1).to_bytes(1, byteorder = 'big')
29 s += (((key[2] & 0x07) << 4 | ((key[3] >> 4) & 0x0f)) << 1).to_bytes(1, byteorder = 'big')
30 s += (((key[3] & 0x0f) << 3 | ((key[4] >> 5) & 0x07)) << 1).to_bytes(1, byteorder = 'big')
31 s += (((key[4] & 0x1f) << 2 | ((key[5] >> 6) & 0x03)) << 1).to_bytes(1, byteorder = 'big')
32 s += (((key[5] & 0x3f) << 1 | ((key[6] >> 7) & 0x01)) << 1).to_bytes(1, byteorder = 'big')
33 s += ( (key[6] & 0x7f) << 1).to_bytes(1, byteorder = 'big')
34 return s
35 #
36
37 class pureDES(symmetricBASE):
38 def __init__(self, key, mode = cipherMODE.ECB, IV = None):
39 self.key = key
40 if len(key) == 7:
41 self.key = expand_DES_key(key)
42
43 self.mode = mode
44 self.IV = IV
45 symmetricBASE.__init__(self)
46
47 def setup_cipher(self):
48 if self.mode == cipherMODE.ECB:
49 mode = _pyDES.ECB
50 elif self.mode == cipherMODE.CBC:
51 mode = _pyDES.CBC
52 else:
53 raise Exception('Unknown cipher mode!')
54
55 self._cipher = _pyDES.des(self.key, mode, self.IV)
56
57 def encrypt(self, data):
58 return self._cipher.encrypt(data)
59 def decrypt(self, data):
60 return self._cipher.decrypt(data)
61
62
63
64 class pyCryptoDES(symmetricBASE):
65 def __init__(self, key, mode = cipherMODE.ECB, IV = None):
66 self.key = key
67 if len(key) == 7:
68 self.key = __expand_DES_key(key)
69
70 self.mode = mode
71 self.IV = IV
72 symmetricBASE.__init__(self)
73
74 def setup_cipher(self):
75 if self.mode == cipherMODE.ECB:
76 self._cipher = _pyCryptoDES.new(self.key)
77 elif self.mode == cipherMODE.CBC:
78 self._cipher = _pyCryptoDES.new(self.key, _pyCryptoDES.MODE_CBC, self.IV)
79 else:
80 raise Exception('Unknown cipher mode!')
81
82
83
84 def encrypt(self, data):
85 return self._cipher.encrypt(data)
86 def decrypt(self, data):
87 return self._cipher.decrypt(data)
88
+0
-181
msldap/crypto/MD4.py less more
0 #!/usr/bin/env python3
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright © 2019 James Seo <[email protected]> (github.com/kangtastic).
4 #
5 # This file is released under the WTFPL, version 2 (wtfpl.net).
6 #
7 # md4.py: An implementation of the MD4 hash algorithm in pure Python 3.
8 #
9 # Description: Zounds! Yet another rendition of pseudocode from RFC1320!
10 # Bonus points for the algorithm literally being from 1992.
11 #
12 # Usage: Why would anybody use this? This is self-rolled crypto, and
13 # self-rolled *obsolete* crypto at that. DO NOT USE if you need
14 # something "performant" or "secure". :P
15 #
16 # Anyway, from the command line:
17 #
18 # $ ./md4.py [messages]
19 #
20 # where [messages] are some strings to be hashed.
21 #
22 # In Python, use similarly to hashlib (not that it even has MD4):
23 #
24 # from .md4 import MD4
25 #
26 # digest = MD4("BEES").hexdigest()
27 #
28 # print(digest) # "501af1ef4b68495b5b7e37b15b4cda68"
29 #
30 #
31 # Sample console output:
32 #
33 # Testing the MD4 class.
34 #
35 # Message: b''
36 # Expected: 31d6cfe0d16ae931b73c59d7e0c089c0
37 # Actual: 31d6cfe0d16ae931b73c59d7e0c089c0
38 #
39 # Message: b'The quick brown fox jumps over the lazy dog'
40 # Expected: 1bee69a46ba811185c194762abaeae90
41 # Actual: 1bee69a46ba811185c194762abaeae90
42 #
43 # Message: b'BEES'
44 # Expected: 501af1ef4b68495b5b7e37b15b4cda68
45 # Actual: 501af1ef4b68495b5b7e37b15b4cda68
46 #
47 import struct
48
49
50 class MD4:
51 """An implementation of the MD4 hash algorithm."""
52
53 width = 32
54 mask = 0xFFFFFFFF
55
56 # Unlike, say, SHA-1, MD4 uses little-endian. Fascinating!
57 h = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476]
58
59 def __init__(self, msg=None):
60 """:param ByteString msg: The message to be hashed."""
61 if msg is None:
62 msg = b""
63
64 self.msg = msg
65
66 # Pre-processing: Total length is a multiple of 512 bits.
67 ml = len(msg) * 8
68 msg += b"\x80"
69 msg += b"\x00" * (-(len(msg) + 8) % 64)
70 msg += struct.pack("<Q", ml)
71
72 # Process the message in successive 512-bit chunks.
73 self._process([msg[i : i + 64] for i in range(0, len(msg), 64)])
74
75 def __repr__(self):
76 if self.msg:
77 return f"{self.__class__.__name__}({self.msg:s})"
78 return f"{self.__class__.__name__}()"
79
80 def __str__(self):
81 return self.hexdigest()
82
83 def __eq__(self, other):
84 return self.h == other.h
85
86 def bytes(self):
87 """:return: The final hash value as a `bytes` object."""
88 return struct.pack("<4L", *self.h)
89
90 def hexbytes(self):
91 """:return: The final hash value as hexbytes."""
92 return self.hexdigest().encode
93
94 def hexdigest(self):
95 """:return: The final hash value as a hexstring."""
96 return "".join(f"{value:02x}" for value in self.bytes())
97
98 def digest(self):
99 return self.bytes()
100
101 def _process(self, chunks):
102 for chunk in chunks:
103 X, h = list(struct.unpack("<16I", chunk)), self.h.copy()
104
105 # Round 1.
106 Xi = [3, 7, 11, 19]
107 for n in range(16):
108 i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
109 K, S = n, Xi[n % 4]
110 hn = h[i] + MD4.F(h[j], h[k], h[l]) + X[K]
111 h[i] = MD4.lrot(hn & MD4.mask, S)
112
113 # Round 2.
114 Xi = [3, 5, 9, 13]
115 for n in range(16):
116 i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
117 K, S = n % 4 * 4 + n // 4, Xi[n % 4]
118 hn = h[i] + MD4.G(h[j], h[k], h[l]) + X[K] + 0x5A827999
119 h[i] = MD4.lrot(hn & MD4.mask, S)
120
121 # Round 3.
122 Xi = [3, 9, 11, 15]
123 Ki = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]
124 for n in range(16):
125 i, j, k, l = map(lambda x: x % 4, range(-n, -n + 4))
126 K, S = Ki[n], Xi[n % 4]
127 hn = h[i] + MD4.H(h[j], h[k], h[l]) + X[K] + 0x6ED9EBA1
128 h[i] = MD4.lrot(hn & MD4.mask, S)
129
130 self.h = [((v + n) & MD4.mask) for v, n in zip(self.h, h)]
131
132 @staticmethod
133 def F(x, y, z):
134 return (x & y) | (~x & z)
135
136 @staticmethod
137 def G(x, y, z):
138 return (x & y) | (x & z) | (y & z)
139
140 @staticmethod
141 def H(x, y, z):
142 return x ^ y ^ z
143
144 @staticmethod
145 def lrot(value, n):
146 lbits, rbits = (value << n) & MD4.mask, value >> (MD4.width - n)
147 return lbits | rbits
148
149
150 def main():
151 # Import is intentionally delayed.
152 import sys
153
154 if len(sys.argv) > 1:
155 messages = [msg.encode() for msg in sys.argv[1:]]
156 for message in messages:
157 print(MD4(message).hexdigest())
158 else:
159 messages = [b"", b"The quick brown fox jumps over the lazy dog", b"BEES"]
160 known_hashes = [
161 "31d6cfe0d16ae931b73c59d7e0c089c0",
162 "1bee69a46ba811185c194762abaeae90",
163 "501af1ef4b68495b5b7e37b15b4cda68",
164 ]
165
166 print("Testing the MD4 class.")
167 print()
168
169 for message, expected in zip(messages, known_hashes):
170 print("Message: ", message)
171 print("Expected:", expected)
172 print("Actual: ", MD4(message).hexdigest())
173 print()
174
175
176 if __name__ == "__main__":
177 try:
178 main()
179 except KeyboardInterrupt:
180 pass
+0
-68
msldap/crypto/RC4.py less more
0 """
1 The idea here is to offer compatibility with 3rd party libraries by extending wrappers for ech encryption mode
2 This is needed because the pure python implementation for encryption and hashing algorithms are quite slow
3
4 currently it's not the perfect wrapper, needs to be extended
5 """
6
7 from msldap.crypto.BASE import symmetricBASE, cipherMODE
8 from msldap.crypto.pure.RC4.RC4 import RC4 as _pureRC4
9 try:
10 from Crypto.Cipher import ARC4 as _pyCryptoRC4
11 except Exception as e:
12 #print(e)
13 pass
14
15 try:
16 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
17 from cryptography.hazmat.backends import default_backend
18 except:
19 pass
20
21 class pureRC4(symmetricBASE):
22 def __init__(self, key):
23 if not isinstance(key, bytes):
24 raise Exception('Key needs to be bytes!')
25 self.key = key
26 symmetricBASE.__init__(self)
27
28 def setup_cipher(self):
29 self._cipher = _pureRC4(self.key)
30
31 def encrypt(self, data):
32 return self._cipher.encrypt(data)
33 def decrypt(self, data):
34 return self._cipher.decrypt(data)
35
36 class pyCryptoRC4(symmetricBASE):
37 def __init__(self, key):
38 self.key = key
39 symmetricBASE.__init__(self)
40
41 def setup_cipher(self):
42 self._cipher = _pyCryptoRC4.new(self.key)
43
44 def encrypt(self, data):
45 return self._cipher.encrypt(data)
46 def decrypt(self, data):
47 return self._cipher.decrypt(data)
48
49 class cryptographyRC4(symmetricBASE):
50 def __init__(self, key):
51 if not isinstance(key, bytes):
52 raise Exception('Key needs to be bytes!')
53 self.key = key
54 self.encryptor = None
55 self.decryptor = None
56 symmetricBASE.__init__(self)
57
58 def setup_cipher(self):
59 algorithm = algorithms.ARC4(self.key)
60 self._cipher = Cipher(algorithm, mode=None, backend=default_backend())
61 self.encryptor = self._cipher.encryptor()
62 self.decryptor = self._cipher.decryptor()
63
64 def encrypt(self, data):
65 return self.encryptor.update(data)
66 def decrypt(self, data):
67 return self.decryptor.update(data)
+0
-108
msldap/crypto/TDES.py less more
0 """
1 The idea here is to offer compatibility with 3rd party libraries by extending wrappers for ech encryption mode
2 This is needed because the pure python implementation for encryption and hashing algorithms are quite slow
3
4 currently it's not the perfect wrapper, needs to be extended
5 """
6
7 from msldap.crypto.BASE import symmetricBASE, cipherMODE, padMode
8 import msldap.crypto.pure.DES.DES as _pyDES
9 try:
10 from Crypto.Cipher import DES3 as _pyCryptoDES3
11 except:
12 pass
13
14 try:
15 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
16 from cryptography.hazmat.primitives import padding
17 from cryptography.hazmat.backends import default_backend
18 except:
19 pass
20
21 class pureTDES(symmetricBASE):
22 def __init__(self, key, mode = cipherMODE.ECB, IV = None, pad = None, padMode = None):
23 symmetricBASE.__init__(self)
24 if not isinstance(key, bytes):
25 raise Exception('Key needs to be bytes!')
26
27 self.mode = mode
28 self.IV = IV
29 self.pad = pad
30 self.padMode = padMode
31
32 def setup_cipher(self):
33 if self.mode == cipherMODE.ECB:
34 mode = _pyDes.ECB
35 self._cipher = _pyDES.triple_des(self.key, mode)
36 elif self.mode == cipherMODE.CBC:
37 mode = _pyDES.CBC
38 if padMode is None:
39 self._cipher = _pyDES.triple_des(self.key, mode, self.IV, self.pad, self.padmode)
40 else:
41 self._cipher = _pyDES.triple_des(self.key, mode, self.IV)
42 else:
43 raise Exception('Unknown cipher mode!')
44
45 def encrypt(self, data):
46 return self._cipher.encrypt(data)
47 def decrypt(self, data):
48 return self._cipher.decrypt(data)
49
50 class pyCryptoTDES(symmetricBASE):
51 def __init__(self, key, mode = cipherMODE.ECB, IV = None, pad = None, padMode = None):
52 self.key = key
53 self.mode = mode
54 self.IV = IV
55 self.pad = pad
56 self.padMode = padMode
57
58 symmetricBASE.__init__(self)
59
60 def setup_cipher(self):
61 if self.mode == cipherMODE.ECB:
62 self._cipher = _pyCryptoDES3.new(self.key, _pyCryptoDES3.MODE_ECB)
63
64 elif self.mode == cipherMODE.CBC:
65 self._cipher = _pyCryptoDES3.new(self.key, _pyCryptoDES3.MODE_CBC, self.IV)
66 else:
67 raise Exception('Unknown cipher mode!')
68
69 def encrypt(self, data):
70 return self._cipher.encrypt(data)
71 def decrypt(self, data):
72 return self._cipher.decrypt(data)
73
74 class cryptographyTDES(symmetricBASE):
75 def __init__(self, key, mode = cipherMODE.ECB, IV = None, pad = None, padMode = None):
76 if not isinstance(key, bytes):
77 raise Exception('Key needs to be bytes!')
78 self.IV = IV
79 if mode == cipherMode.ECB:
80 self.IV = modes.ECB()
81 elif mode == cipherMODE.CBC:
82 self.IV = modes.CBC(IV)
83 else:
84 raise Exception('Unknown cipher mode!')
85 self.key = key
86
87 """ TODO padding
88 if self.padMode is not None:
89 """
90
91 self.encryptor = None
92 self.decryptor = None
93 symmetricBASE.__init__(self)
94
95 def setup_cipher(self):
96 algorithm = algorithms.TripleDES(self.key)
97 self._cipher = Cipher(algorithm, mode=self.IV, backend=default_backend())
98 self.encryptor = self._cipher.encryptor()
99 self.decryptor = self._cipher.decryptor()
100
101 def encrypt(self, data):
102 return self.encryptor.update(data)
103
104
105 def decrypt(self, data):
106 return self.decryptor.update(data)
107
+0
-0
msldap/crypto/__init__.py less more
(Empty file)
+0
-55
msldap/crypto/hashing.py less more
0 import hashlib
1 import hmac
2
3 from msldap.crypto.BASE import hashBASE, hmacBASE
4 from msldap.crypto.MD4 import MD4
5
6 class md5(hashBASE):
7 def __init__(self, data = None):
8 hashBASE.__init__(self, data)
9 def setup_hash(self):
10 self._hash = hashlib.new('md5')
11 def update(self, data):
12 return self._hash.update(data)
13 def digest(self):
14 return self._hash.digest()
15 def hexdigest(self):
16 return self._hash.hexdigest()
17
18 #class md4(hashBASE):
19 # def __init__(self, data = None):
20 # hashBASE.__init__(self, data)
21 # def setup_hash(self):
22 # self._hash = hashlib.new('md4')
23 # def update(self, data):
24 # return self._hash.update(data)
25 # def digest(self):
26 # return self._hash.digest()
27 # def hexdigest(self):
28 # return self._hash.hexdigest()
29
30 md4 = MD4
31
32 class hmac_md5(hmacBASE):
33 def __init__(self, key):
34 hmacBASE.__init__(self, key)
35 def setup_hash(self):
36 self._hmac = hmac.new(self._key, digestmod = hashlib.md5)
37 def update(self, data):
38 return self._hmac.update(data)
39 def digest(self):
40 return self._hmac.digest()
41 def hexdigest(self):
42 return self._hmac.hexdigest()
43
44 class sha256():
45 def __init__(self, data = None):
46 hashBASE.__init__(self, data)
47 def setup_hash(self):
48 self._hash = hashlib.new('sha256')
49 def update(self, data):
50 return self._hash.update(data)
51 def digest(self):
52 return self._hash.digest()
53 def hexdigest(self):
54 return self._hash.hexdigest()
+0
-591
msldap/crypto/pure/AES/AES.py less more
0
1 #https://github.com/ricmoo/pyaes/blob/master/pyaes/aes.py
2 # The MIT License (MIT)
3 #
4 # Copyright (c) 2014 Richard Moore
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining a copy
7 # of this software and associated documentation files (the "Software"), to deal
8 # in the Software without restriction, including without limitation the rights
9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 # copies of the Software, and to permit persons to whom the Software is
11 # furnished to do so, subject to the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be included in
14 # all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 # THE SOFTWARE.
23
24 # This is a pure-Python implementation of the AES algorithm and AES common
25 # modes of operation.
26
27 # See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
28
29 # Honestly, the best description of the modes of operations are the wonderful
30 # diagrams on Wikipedia. They explain in moments what my words could never
31 # achieve. Hence the inline documentation here is sparer than I'd prefer.
32 # See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
33
34 # Also useful, PyCrypto, a crypto library implemented in C with Python bindings:
35 # https://www.dlitz.net/software/pycrypto/
36
37
38 # Supported key sizes:
39 # 128-bit
40 # 192-bit
41 # 256-bit
42
43
44 # Supported modes of operation:
45 # ECB - Electronic Codebook
46 # CBC - Cipher-Block Chaining
47 # CFB - Cipher Feedback
48 # OFB - Output Feedback
49 # CTR - Counter
50
51
52 # See the README.md for API details and general information.
53
54
55 import copy
56 import struct
57
58 __all__ = ["AES", "AESModeOfOperationCTR", "AESModeOfOperationCBC", "AESModeOfOperationCFB",
59 "AESModeOfOperationECB", "AESModeOfOperationOFB", "AESModesOfOperation", "Counter"]
60
61
62 def _compact_word(word):
63 return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3]
64
65 def _string_to_bytes(text):
66 return list(ord(c) for c in text)
67
68 def _bytes_to_string(binary):
69 return "".join(chr(b) for b in binary)
70
71 def _concat_list(a, b):
72 return a + b
73
74
75 # Python 3 compatibility
76 try:
77 xrange
78 except Exception:
79 xrange = range
80
81 # Python 3 supports bytes, which is already an array of integers
82 def _string_to_bytes(text):
83 if isinstance(text, bytes):
84 return text
85 return [ord(c) for c in text]
86
87 # In Python 3, we return bytes
88 def _bytes_to_string(binary):
89 return bytes(binary)
90
91 # Python 3 cannot concatenate a list onto a bytes, so we bytes-ify it first
92 def _concat_list(a, b):
93 return a + bytes(b)
94
95
96 # Based *largely* on the Rijndael implementation
97 # See: http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
98 class AES(object):
99 '''Encapsulates the AES block cipher.
100
101 You generally should not need this. Use the AESModeOfOperation classes
102 below instead.'''
103
104 # Number of rounds by keysize
105 number_of_rounds = {16: 10, 24: 12, 32: 14}
106
107 # Round constant words
108 rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ]
109
110 # S-box and Inverse S-box (S is for Substitution)
111 S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ]
112 Si =[ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ]
113
114 # Transformations for encryption
115 T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ]
116 T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ]
117 T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ]
118 T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ]
119
120 # Transformations for decryption
121 T5 = [ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 ]
122 T6 = [ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 ]
123 T7 = [ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 ]
124 T8 = [ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 ]
125
126 # Transformations for decryption key expansion
127 U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ]
128 U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ]
129 U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ]
130 U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ]
131
132 def __init__(self, key):
133
134 if len(key) not in (16, 24, 32):
135 raise ValueError('Invalid key size')
136
137 rounds = self.number_of_rounds[len(key)]
138
139 # Encryption round keys
140 self._Ke = [[0] * 4 for i in xrange(rounds + 1)]
141
142 # Decryption round keys
143 self._Kd = [[0] * 4 for i in xrange(rounds + 1)]
144
145 round_key_count = (rounds + 1) * 4
146 KC = len(key) // 4
147
148 # Convert the key into ints
149 tk = [ struct.unpack('>i', key[i:i + 4])[0] for i in xrange(0, len(key), 4) ]
150
151 # Copy values into round key arrays
152 for i in xrange(0, KC):
153 self._Ke[i // 4][i % 4] = tk[i]
154 self._Kd[rounds - (i // 4)][i % 4] = tk[i]
155
156 # Key expansion (fips-197 section 5.2)
157 rconpointer = 0
158 t = KC
159 while t < round_key_count:
160
161 tt = tk[KC - 1]
162 tk[0] ^= ((self.S[(tt >> 16) & 0xFF] << 24) ^
163 (self.S[(tt >> 8) & 0xFF] << 16) ^
164 (self.S[ tt & 0xFF] << 8) ^
165 self.S[(tt >> 24) & 0xFF] ^
166 (self.rcon[rconpointer] << 24))
167 rconpointer += 1
168
169 if KC != 8:
170 for i in xrange(1, KC):
171 tk[i] ^= tk[i - 1]
172
173 # Key expansion for 256-bit keys is "slightly different" (fips-197)
174 else:
175 for i in xrange(1, KC // 2):
176 tk[i] ^= tk[i - 1]
177 tt = tk[KC // 2 - 1]
178
179 tk[KC // 2] ^= (self.S[ tt & 0xFF] ^
180 (self.S[(tt >> 8) & 0xFF] << 8) ^
181 (self.S[(tt >> 16) & 0xFF] << 16) ^
182 (self.S[(tt >> 24) & 0xFF] << 24))
183
184 for i in xrange(KC // 2 + 1, KC):
185 tk[i] ^= tk[i - 1]
186
187 # Copy values into round key arrays
188 j = 0
189 while j < KC and t < round_key_count:
190 self._Ke[t // 4][t % 4] = tk[j]
191 self._Kd[rounds - (t // 4)][t % 4] = tk[j]
192 j += 1
193 t += 1
194
195 # Inverse-Cipher-ify the decryption round key (fips-197 section 5.3)
196 for r in xrange(1, rounds):
197 for j in xrange(0, 4):
198 tt = self._Kd[r][j]
199 self._Kd[r][j] = (self.U1[(tt >> 24) & 0xFF] ^
200 self.U2[(tt >> 16) & 0xFF] ^
201 self.U3[(tt >> 8) & 0xFF] ^
202 self.U4[ tt & 0xFF])
203
204 def encrypt(self, plaintext):
205 'Encrypt a block of plain text using the AES block cipher.'
206
207 if len(plaintext) != 16:
208 raise ValueError('wrong block length')
209
210 rounds = len(self._Ke) - 1
211 (s1, s2, s3) = [1, 2, 3]
212 a = [0, 0, 0, 0]
213
214 # Convert plaintext to (ints ^ key)
215 t = [(_compact_word(plaintext[4 * i:4 * i + 4]) ^ self._Ke[0][i]) for i in xrange(0, 4)]
216
217 # Apply round transforms
218 for r in xrange(1, rounds):
219 for i in xrange(0, 4):
220 a[i] = (self.T1[(t[ i ] >> 24) & 0xFF] ^
221 self.T2[(t[(i + s1) % 4] >> 16) & 0xFF] ^
222 self.T3[(t[(i + s2) % 4] >> 8) & 0xFF] ^
223 self.T4[ t[(i + s3) % 4] & 0xFF] ^
224 self._Ke[r][i])
225 t = copy.copy(a)
226
227 # The last round is special
228 result = [ ]
229 for i in xrange(0, 4):
230 tt = self._Ke[rounds][i]
231 result.append((self.S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
232 result.append((self.S[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
233 result.append((self.S[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
234 result.append((self.S[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF)
235
236 return result
237
238 def decrypt(self, ciphertext):
239 'Decrypt a block of cipher text using the AES block cipher.'
240
241 if len(ciphertext) != 16:
242 raise ValueError('wrong block length')
243
244 rounds = len(self._Kd) - 1
245 (s1, s2, s3) = [3, 2, 1]
246 a = [0, 0, 0, 0]
247
248 # Convert ciphertext to (ints ^ key)
249 t = [(_compact_word(ciphertext[4 * i:4 * i + 4]) ^ self._Kd[0][i]) for i in xrange(0, 4)]
250
251 # Apply round transforms
252 for r in xrange(1, rounds):
253 for i in xrange(0, 4):
254 a[i] = (self.T5[(t[ i ] >> 24) & 0xFF] ^
255 self.T6[(t[(i + s1) % 4] >> 16) & 0xFF] ^
256 self.T7[(t[(i + s2) % 4] >> 8) & 0xFF] ^
257 self.T8[ t[(i + s3) % 4] & 0xFF] ^
258 self._Kd[r][i])
259 t = copy.copy(a)
260
261 # The last round is special
262 result = [ ]
263 for i in xrange(0, 4):
264 tt = self._Kd[rounds][i]
265 result.append((self.Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
266 result.append((self.Si[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
267 result.append((self.Si[(t[(i + s2) % 4] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF)
268 result.append((self.Si[ t[(i + s3) % 4] & 0xFF] ^ tt ) & 0xFF)
269
270 return result
271
272
273 class Counter(object):
274 '''A counter object for the Counter (CTR) mode of operation.
275
276 To create a custom counter, you can usually just override the
277 increment method.'''
278
279 def __init__(self, initial_value = 1):
280
281 # Convert the value into an array of bytes long
282 self._counter = [ ((initial_value >> i) % 256) for i in xrange(128 - 8, -1, -8) ]
283
284 value = property(lambda s: s._counter)
285
286 def increment(self):
287 '''Increment the counter (overflow rolls back to 0).'''
288
289 for i in xrange(len(self._counter) - 1, -1, -1):
290 self._counter[i] += 1
291
292 if self._counter[i] < 256: break
293
294 # Carry the one
295 self._counter[i] = 0
296
297 # Overflow
298 else:
299 self._counter = [ 0 ] * len(self._counter)
300
301
302 class AESBlockModeOfOperation(object):
303 '''Super-class for AES modes of operation that require blocks.'''
304 def __init__(self, key):
305 self._aes = AES(key)
306
307 def decrypt(self, ciphertext):
308 raise Exception('not implemented')
309
310 def encrypt(self, plaintext):
311 raise Exception('not implemented')
312
313
314 class AESStreamModeOfOperation(AESBlockModeOfOperation):
315 '''Super-class for AES modes of operation that are stream-ciphers.'''
316
317 class AESSegmentModeOfOperation(AESStreamModeOfOperation):
318 '''Super-class for AES modes of operation that segment data.'''
319
320 segment_bytes = 16
321
322
323
324 class AESModeOfOperationECB(AESBlockModeOfOperation):
325 '''AES Electronic Codebook Mode of Operation.
326
327 o Block-cipher, so data must be padded to 16 byte boundaries
328
329 Security Notes:
330 o This mode is not recommended
331 o Any two identical blocks produce identical encrypted values,
332 exposing data patterns. (See the image of Tux on wikipedia)
333
334 Also see:
335 o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_codebook_.28ECB.29
336 o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.1'''
337
338
339 name = "Electronic Codebook (ECB)"
340
341 def encrypt(self, plaintext):
342 if len(plaintext) != 16:
343 raise ValueError('plaintext block must be 16 bytes')
344
345 plaintext = _string_to_bytes(plaintext)
346 return _bytes_to_string(self._aes.encrypt(plaintext))
347
348 def decrypt(self, ciphertext):
349 if len(ciphertext) != 16:
350 raise ValueError('ciphertext block must be 16 bytes')
351
352 ciphertext = _string_to_bytes(ciphertext)
353 return _bytes_to_string(self._aes.decrypt(ciphertext))
354
355
356
357 class AESModeOfOperationCBC(AESBlockModeOfOperation):
358 '''AES Cipher-Block Chaining Mode of Operation.
359
360 o The Initialization Vector (IV)
361 o Block-cipher, so data must be padded to 16 byte boundaries
362 o An incorrect initialization vector will only cause the first
363 block to be corrupt; all other blocks will be intact
364 o A corrupt bit in the cipher text will cause a block to be
365 corrupted, and the next block to be inverted, but all other
366 blocks will be intact.
367
368 Security Notes:
369 o This method (and CTR) ARE recommended.
370
371 Also see:
372 o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29
373 o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.2'''
374
375
376 name = "Cipher-Block Chaining (CBC)"
377
378 def __init__(self, key, iv = None):
379 if iv is None:
380 self._last_cipherblock = [ 0 ] * 16
381 elif len(iv) != 16:
382 raise ValueError('initialization vector must be 16 bytes')
383 else:
384 self._last_cipherblock = _string_to_bytes(iv)
385
386 AESBlockModeOfOperation.__init__(self, key)
387
388 def encrypt(self, plaintext):
389 if len(plaintext) != 16:
390 raise ValueError('plaintext block must be 16 bytes')
391
392 plaintext = _string_to_bytes(plaintext)
393 precipherblock = [ (p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock) ]
394 self._last_cipherblock = self._aes.encrypt(precipherblock)
395
396 return _bytes_to_string(self._last_cipherblock)
397
398 def decrypt(self, ciphertext):
399 if len(ciphertext) != 16:
400 raise ValueError('ciphertext block must be 16 bytes')
401
402 cipherblock = _string_to_bytes(ciphertext)
403 plaintext = [ (p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock) ]
404 self._last_cipherblock = cipherblock
405
406 return _bytes_to_string(plaintext)
407
408
409
410 class AESModeOfOperationCFB(AESSegmentModeOfOperation):
411 '''AES Cipher Feedback Mode of Operation.
412
413 o A stream-cipher, so input does not need to be padded to blocks,
414 but does need to be padded to segment_size
415
416 Also see:
417 o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_.28CFB.29
418 o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.3'''
419
420
421 name = "Cipher Feedback (CFB)"
422
423 def __init__(self, key, iv, segment_size = 1):
424 if segment_size == 0: segment_size = 1
425
426 if iv is None:
427 self._shift_register = [ 0 ] * 16
428 elif len(iv) != 16:
429 raise ValueError('initialization vector must be 16 bytes')
430 else:
431 self._shift_register = _string_to_bytes(iv)
432
433 self._segment_bytes = segment_size
434
435 AESBlockModeOfOperation.__init__(self, key)
436
437 segment_bytes = property(lambda s: s._segment_bytes)
438
439 def encrypt(self, plaintext):
440 if len(plaintext) % self._segment_bytes != 0:
441 raise ValueError('plaintext block must be a multiple of segment_size')
442
443 plaintext = _string_to_bytes(plaintext)
444
445 # Break block into segments
446 encrypted = [ ]
447 for i in xrange(0, len(plaintext), self._segment_bytes):
448 plaintext_segment = plaintext[i: i + self._segment_bytes]
449 xor_segment = self._aes.encrypt(self._shift_register)[:len(plaintext_segment)]
450 cipher_segment = [ (p ^ x) for (p, x) in zip(plaintext_segment, xor_segment) ]
451
452 # Shift the top bits out and the ciphertext in
453 self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment)
454
455 encrypted.extend(cipher_segment)
456
457 return _bytes_to_string(encrypted)
458
459 def decrypt(self, ciphertext):
460 if len(ciphertext) % self._segment_bytes != 0:
461 raise ValueError('ciphertext block must be a multiple of segment_size')
462
463 ciphertext = _string_to_bytes(ciphertext)
464
465 # Break block into segments
466 decrypted = [ ]
467 for i in xrange(0, len(ciphertext), self._segment_bytes):
468 cipher_segment = ciphertext[i: i + self._segment_bytes]
469 xor_segment = self._aes.encrypt(self._shift_register)[:len(cipher_segment)]
470 plaintext_segment = [ (p ^ x) for (p, x) in zip(cipher_segment, xor_segment) ]
471
472 # Shift the top bits out and the ciphertext in
473 self._shift_register = _concat_list(self._shift_register[len(cipher_segment):], cipher_segment)
474
475 decrypted.extend(plaintext_segment)
476
477 return _bytes_to_string(decrypted)
478
479
480
481 class AESModeOfOperationOFB(AESStreamModeOfOperation):
482 '''AES Output Feedback Mode of Operation.
483
484 o A stream-cipher, so input does not need to be padded to blocks,
485 allowing arbitrary length data.
486 o A bit twiddled in the cipher text, twiddles the same bit in the
487 same bit in the plain text, which can be useful for error
488 correction techniques.
489
490 Also see:
491 o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Output_feedback_.28OFB.29
492 o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.4'''
493
494
495 name = "Output Feedback (OFB)"
496
497 def __init__(self, key, iv = None):
498 if iv is None:
499 self._last_precipherblock = [ 0 ] * 16
500 elif len(iv) != 16:
501 raise ValueError('initialization vector must be 16 bytes')
502 else:
503 self._last_precipherblock = _string_to_bytes(iv)
504
505 self._remaining_block = [ ]
506
507 AESBlockModeOfOperation.__init__(self, key)
508
509 def encrypt(self, plaintext):
510 encrypted = [ ]
511 for p in _string_to_bytes(plaintext):
512 if len(self._remaining_block) == 0:
513 self._remaining_block = self._aes.encrypt(self._last_precipherblock)
514 self._last_precipherblock = [ ]
515 precipherbyte = self._remaining_block.pop(0)
516 self._last_precipherblock.append(precipherbyte)
517 cipherbyte = p ^ precipherbyte
518 encrypted.append(cipherbyte)
519
520 return _bytes_to_string(encrypted)
521
522 def decrypt(self, ciphertext):
523 # AES-OFB is symetric
524 return self.encrypt(ciphertext)
525
526
527
528 class AESModeOfOperationCTR(AESStreamModeOfOperation):
529 '''AES Counter Mode of Operation.
530
531 o A stream-cipher, so input does not need to be padded to blocks,
532 allowing arbitrary length data.
533 o The counter must be the same size as the key size (ie. len(key))
534 o Each block independant of the other, so a corrupt byte will not
535 damage future blocks.
536 o Each block has a uniue counter value associated with it, which
537 contributes to the encrypted value, so no data patterns are
538 leaked.
539 o Also known as: Counter Mode (CM), Integer Counter Mode (ICM) and
540 Segmented Integer Counter (SIC
541
542 Security Notes:
543 o This method (and CBC) ARE recommended.
544 o Each message block is associated with a counter value which must be
545 unique for ALL messages with the same key. Otherwise security may be
546 compromised.
547
548 Also see:
549
550 o https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29
551 o See NIST SP800-38A (http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf); section 6.5
552 and Appendix B for managing the initial counter'''
553
554
555 name = "Counter (CTR)"
556
557 def __init__(self, key, counter = None):
558 AESBlockModeOfOperation.__init__(self, key)
559
560 if counter is None:
561 counter = Counter()
562
563 self._counter = counter
564 self._remaining_counter = [ ]
565
566 def encrypt(self, plaintext):
567 while len(self._remaining_counter) < len(plaintext):
568 self._remaining_counter += self._aes.encrypt(self._counter.value)
569 self._counter.increment()
570
571 plaintext = _string_to_bytes(plaintext)
572
573 encrypted = [ (p ^ c) for (p, c) in zip(plaintext, self._remaining_counter) ]
574 self._remaining_counter = self._remaining_counter[len(encrypted):]
575
576 return _bytes_to_string(encrypted)
577
578 def decrypt(self, crypttext):
579 # AES-CTR is symetric
580 return self.encrypt(crypttext)
581
582
583 # Simple lookup table for each mode
584 AESModesOfOperation = dict(
585 ctr = AESModeOfOperationCTR,
586 cbc = AESModeOfOperationCBC,
587 cfb = AESModeOfOperationCFB,
588 ecb = AESModeOfOperationECB,
589 ofb = AESModeOfOperationOFB,
590 )
+0
-54
msldap/crypto/pure/AES/__init__.py less more
0 #https://raw.githubusercontent.com/ricmoo/pyaes/master/pyaes/__init__.py
1 # The MIT License (MIT)
2 #
3 # Copyright (c) 2014 Richard Moore
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # THE SOFTWARE.
22
23 # This is a pure-Python implementation of the AES algorithm and AES common
24 # modes of operation.
25
26 # See: https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
27 # See: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
28
29
30 # Supported key sizes:
31 # 128-bit
32 # 192-bit
33 # 256-bit
34
35
36 # Supported modes of operation:
37 # ECB - Electronic Codebook
38 # CBC - Cipher-Block Chaining
39 # CFB - Cipher Feedback
40 # OFB - Output Feedback
41 # CTR - Counter
42
43 # See the README.md for API details and general information.
44
45 # Also useful, PyCrypto, a crypto library implemented in C with Python bindings:
46 # https://www.dlitz.net/software/pycrypto/
47
48
49 VERSION = [1, 3, 0]
50
51 from .AES import AES, AESModeOfOperationCTR, AESModeOfOperationCBC, AESModeOfOperationCFB, AESModeOfOperationECB, AESModeOfOperationOFB, AESModesOfOperation, Counter
52 from .blockfeeder import decrypt_stream, Decrypter, encrypt_stream, Encrypter
53 from .blockfeeder import PADDING_NONE, PADDING_DEFAULT
+0
-229
msldap/crypto/pure/AES/blockfeeder.py less more
0
1 #https://github.com/ricmoo/pyaes/blob/master/pyaes/blockfeeder.py
2 # The MIT License (MIT)
3 #
4 # Copyright (c) 2014 Richard Moore
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining a copy
7 # of this software and associated documentation files (the "Software"), to deal
8 # in the Software without restriction, including without limitation the rights
9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 # copies of the Software, and to permit persons to whom the Software is
11 # furnished to do so, subject to the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be included in
14 # all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 # THE SOFTWARE.
23
24
25 from .AES import AESBlockModeOfOperation, AESSegmentModeOfOperation, AESStreamModeOfOperation
26 from .util import append_PKCS7_padding, strip_PKCS7_padding, to_bufferable
27
28
29 # First we inject three functions to each of the modes of operations
30 #
31 # _can_consume(size)
32 # - Given a size, determine how many bytes could be consumed in
33 # a single call to either the decrypt or encrypt method
34 #
35 # _final_encrypt(data, padding = PADDING_DEFAULT)
36 # - call and return encrypt on this (last) chunk of data,
37 # padding as necessary; this will always be at least 16
38 # bytes unless the total incoming input was less than 16
39 # bytes
40 #
41 # _final_decrypt(data, padding = PADDING_DEFAULT)
42 # - same as _final_encrypt except for decrypt, for
43 # stripping off padding
44 #
45
46 PADDING_NONE = 'none'
47 PADDING_DEFAULT = 'default'
48
49 # @TODO: Ciphertext stealing and explicit PKCS#7
50 # PADDING_CIPHERTEXT_STEALING
51 # PADDING_PKCS7
52
53 # ECB and CBC are block-only ciphers
54
55 def _block_can_consume(self, size):
56 if size >= 16: return 16
57 return 0
58
59 # After padding, we may have more than one block
60 def _block_final_encrypt(self, data, padding = PADDING_DEFAULT):
61 if padding == PADDING_DEFAULT:
62 data = append_PKCS7_padding(data)
63
64 elif padding == PADDING_NONE:
65 if len(data) != 16:
66 raise Exception('invalid data length for final block')
67 else:
68 raise Exception('invalid padding option')
69
70 if len(data) == 32:
71 return self.encrypt(data[:16]) + self.encrypt(data[16:])
72
73 return self.encrypt(data)
74
75
76 def _block_final_decrypt(self, data, padding = PADDING_DEFAULT):
77 if padding == PADDING_DEFAULT:
78 return strip_PKCS7_padding(self.decrypt(data))
79
80 if padding == PADDING_NONE:
81 if len(data) != 16:
82 raise Exception('invalid data length for final block')
83 return self.decrypt(data)
84
85 raise Exception('invalid padding option')
86
87 AESBlockModeOfOperation._can_consume = _block_can_consume
88 AESBlockModeOfOperation._final_encrypt = _block_final_encrypt
89 AESBlockModeOfOperation._final_decrypt = _block_final_decrypt
90
91
92
93 # CFB is a segment cipher
94
95 def _segment_can_consume(self, size):
96 return self.segment_bytes * int(size // self.segment_bytes)
97
98 # CFB can handle a non-segment-sized block at the end using the remaining cipherblock
99 def _segment_final_encrypt(self, data, padding = PADDING_DEFAULT):
100 if padding != PADDING_DEFAULT:
101 raise Exception('invalid padding option')
102
103 faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes)))
104 padded = data + to_bufferable(faux_padding)
105 return self.encrypt(padded)[:len(data)]
106
107 # CFB can handle a non-segment-sized block at the end using the remaining cipherblock
108 def _segment_final_decrypt(self, data, padding = PADDING_DEFAULT):
109 if padding != PADDING_DEFAULT:
110 raise Exception('invalid padding option')
111
112 faux_padding = (chr(0) * (self.segment_bytes - (len(data) % self.segment_bytes)))
113 padded = data + to_bufferable(faux_padding)
114 return self.decrypt(padded)[:len(data)]
115
116 AESSegmentModeOfOperation._can_consume = _segment_can_consume
117 AESSegmentModeOfOperation._final_encrypt = _segment_final_encrypt
118 AESSegmentModeOfOperation._final_decrypt = _segment_final_decrypt
119
120
121
122 # OFB and CTR are stream ciphers
123
124 def _stream_can_consume(self, size):
125 return size
126
127 def _stream_final_encrypt(self, data, padding = PADDING_DEFAULT):
128 if padding not in [PADDING_NONE, PADDING_DEFAULT]:
129 raise Exception('invalid padding option')
130
131 return self.encrypt(data)
132
133 def _stream_final_decrypt(self, data, padding = PADDING_DEFAULT):
134 if padding not in [PADDING_NONE, PADDING_DEFAULT]:
135 raise Exception('invalid padding option')
136
137 return self.decrypt(data)
138
139 AESStreamModeOfOperation._can_consume = _stream_can_consume
140 AESStreamModeOfOperation._final_encrypt = _stream_final_encrypt
141 AESStreamModeOfOperation._final_decrypt = _stream_final_decrypt
142
143
144
145 class BlockFeeder(object):
146 '''The super-class for objects to handle chunking a stream of bytes
147 into the appropriate block size for the underlying mode of operation
148 and applying (or stripping) padding, as necessary.'''
149
150 def __init__(self, mode, feed, final, padding = PADDING_DEFAULT):
151 self._mode = mode
152 self._feed = feed
153 self._final = final
154 self._buffer = to_bufferable("")
155 self._padding = padding
156
157 def feed(self, data = None):
158 '''Provide bytes to encrypt (or decrypt), returning any bytes
159 possible from this or any previous calls to feed.
160
161 Call with None or an empty string to flush the mode of
162 operation and return any final bytes; no further calls to
163 feed may be made.'''
164
165 if self._buffer is None:
166 raise ValueError('already finished feeder')
167
168 # Finalize; process the spare bytes we were keeping
169 if data is None:
170 result = self._final(self._buffer, self._padding)
171 self._buffer = None
172 return result
173
174 self._buffer += to_bufferable(data)
175
176 # We keep 16 bytes around so we can determine padding
177 result = to_bufferable('')
178 while len(self._buffer) > 16:
179 can_consume = self._mode._can_consume(len(self._buffer) - 16)
180 if can_consume == 0: break
181 result += self._feed(self._buffer[:can_consume])
182 self._buffer = self._buffer[can_consume:]
183
184 return result
185
186
187 class Encrypter(BlockFeeder):
188 'Accepts bytes of plaintext and returns encrypted ciphertext.'
189
190 def __init__(self, mode, padding = PADDING_DEFAULT):
191 BlockFeeder.__init__(self, mode, mode.encrypt, mode._final_encrypt, padding)
192
193
194 class Decrypter(BlockFeeder):
195 'Accepts bytes of ciphertext and returns decrypted plaintext.'
196
197 def __init__(self, mode, padding = PADDING_DEFAULT):
198 BlockFeeder.__init__(self, mode, mode.decrypt, mode._final_decrypt, padding)
199
200
201 # 8kb blocks
202 BLOCK_SIZE = (1 << 13)
203
204 def _feed_stream(feeder, in_stream, out_stream, block_size = BLOCK_SIZE):
205 'Uses feeder to read and convert from in_stream and write to out_stream.'
206
207 while True:
208 chunk = in_stream.read(block_size)
209 if not chunk:
210 break
211 converted = feeder.feed(chunk)
212 out_stream.write(converted)
213 converted = feeder.feed()
214 out_stream.write(converted)
215
216
217 def encrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT):
218 'Encrypts a stream of bytes from in_stream to out_stream using mode.'
219
220 encrypter = Encrypter(mode, padding = padding)
221 _feed_stream(encrypter, in_stream, out_stream, block_size)
222
223
224 def decrypt_stream(mode, in_stream, out_stream, block_size = BLOCK_SIZE, padding = PADDING_DEFAULT):
225 'Decrypts a stream of bytes from in_stream to out_stream using mode.'
226
227 decrypter = Decrypter(mode, padding = padding)
228 _feed_stream(decrypter, in_stream, out_stream, block_size)
+0
-62
msldap/crypto/pure/AES/util.py less more
0
1 #https://github.com/ricmoo/pyaes/blob/master/pyaes/util.py
2 # The MIT License (MIT)
3 #
4 # Copyright (c) 2014 Richard Moore
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining a copy
7 # of this software and associated documentation files (the "Software"), to deal
8 # in the Software without restriction, including without limitation the rights
9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 # copies of the Software, and to permit persons to whom the Software is
11 # furnished to do so, subject to the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be included in
14 # all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 # THE SOFTWARE.
23
24 # Why to_bufferable?
25 # Python 3 is very different from Python 2.x when it comes to strings of text
26 # and strings of bytes; in Python 3, strings of bytes do not exist, instead to
27 # represent arbitrary binary data, we must use the "bytes" object. This method
28 # ensures the object behaves as we need it to.
29
30 def to_bufferable(binary):
31 return binary
32
33 def _get_byte(c):
34 return ord(c)
35
36 try:
37 xrange
38 except:
39
40 def to_bufferable(binary):
41 if isinstance(binary, bytes):
42 return binary
43 return bytes(ord(b) for b in binary)
44
45 def _get_byte(c):
46 return c
47
48 def append_PKCS7_padding(data):
49 pad = 16 - (len(data) % 16)
50 return data + to_bufferable(chr(pad) * pad)
51
52 def strip_PKCS7_padding(data):
53 if len(data) % 16 != 0:
54 raise ValueError("invalid length")
55
56 pad = _get_byte(data[-1])
57
58 if pad > 16:
59 raise ValueError("invalid padding byte")
60
61 return data[:-pad]
+0
-852
msldap/crypto/pure/DES/DES.py less more
0 #############################################################################
1 # Documentation #
2 #############################################################################
3
4 # Author: Todd Whiteman
5 # Date: 28th April, 2010
6 # Version: 2.0.1
7 # License: MIT
8 # Homepage: http://twhiteman.netfirms.com/des.html
9 #
10 # This is a pure python implementation of the DES encryption algorithm.
11 # It's pure python to avoid portability issues, since most DES
12 # implementations are programmed in C (for performance reasons).
13 #
14 # Triple DES class is also implemented, utilizing the DES base. Triple DES
15 # is either DES-EDE3 with a 24 byte key, or DES-EDE2 with a 16 byte key.
16 #
17 # See the README.txt that should come with this python module for the
18 # implementation methods used.
19 #
20 # Thanks to:
21 # * David Broadwell for ideas, comments and suggestions.
22 # * Mario Wolff for pointing out and debugging some triple des CBC errors.
23 # * Santiago Palladino for providing the PKCS5 padding technique.
24 # * Shaya for correcting the PAD_PKCS5 triple des CBC errors.
25 #
26 """A pure python implementation of the DES and TRIPLE DES encryption algorithms.
27
28 Class initialization
29 --------------------
30 pyDes.des(key, [mode], [IV], [pad], [padmode])
31 pyDes.triple_des(key, [mode], [IV], [pad], [padmode])
32
33 key -> Bytes containing the encryption key. 8 bytes for DES, 16 or 24 bytes
34 for Triple DES
35 mode -> Optional argument for encryption type, can be either
36 pyDes.ECB (Electronic Code Book) or pyDes.CBC (Cypher Block Chaining)
37 IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
38 Length must be 8 bytes.
39 pad -> Optional argument, set the pad character (PAD_NORMAL) to use during
40 all encrypt/decrypt operations done with this instance.
41 padmode -> Optional argument, set the padding mode (PAD_NORMAL or PAD_PKCS5)
42 to use during all encrypt/decrypt operations done with this instance.
43
44 I recommend to use PAD_PKCS5 padding, as then you never need to worry about any
45 padding issues, as the padding can be removed unambiguously upon decrypting
46 data that was encrypted using PAD_PKCS5 padmode.
47
48 Common methods
49 --------------
50 encrypt(data, [pad], [padmode])
51 decrypt(data, [pad], [padmode])
52
53 data -> Bytes to be encrypted/decrypted
54 pad -> Optional argument. Only when using padmode of PAD_NORMAL. For
55 encryption, adds this characters to the end of the data block when
56 data is not a multiple of 8 bytes. For decryption, will remove the
57 trailing characters that match this pad character from the last 8
58 bytes of the unencrypted data block.
59 padmode -> Optional argument, set the padding mode, must be one of PAD_NORMAL
60 or PAD_PKCS5). Defaults to PAD_NORMAL.
61
62
63 Example
64 -------
65 from pyDes import *
66
67 data = "Please encrypt my data"
68 k = des("DESCRYPT", CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
69 # For Python3, you'll need to use bytes, i.e.:
70 # data = b"Please encrypt my data"
71 # k = des(b"DESCRYPT", CBC, b"\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
72 d = k.encrypt(data)
73 print "Encrypted: %r" % d
74 print "Decrypted: %r" % k.decrypt(d)
75 assert k.decrypt(d, padmode=PAD_PKCS5) == data
76
77
78 See the module source (pyDes.py) for more examples of use.
79 You can also run the pyDes.py file without and arguments to see a simple test.
80
81 Note: This code was not written for high-end systems needing a fast
82 implementation, but rather a handy portable solution with small usage.
83
84 """
85
86 import sys
87
88 # _pythonMajorVersion is used to handle Python2 and Python3 differences.
89 _pythonMajorVersion = sys.version_info[0]
90
91 # Modes of crypting / cyphering
92 ECB = 0
93 CBC = 1
94
95 # Modes of padding
96 PAD_NORMAL = 1
97 PAD_PKCS5 = 2
98
99 # PAD_PKCS5: is a method that will unambiguously remove all padding
100 # characters after decryption, when originally encrypted with
101 # this padding mode.
102 # For a good description of the PKCS5 padding technique, see:
103 # http://www.faqs.org/rfcs/rfc1423.html
104
105 # The base class shared by des and triple des.
106 class _baseDes(object):
107 def __init__(self, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
108 if IV:
109 IV = self._guardAgainstUnicode(IV)
110 if pad:
111 pad = self._guardAgainstUnicode(pad)
112 self.block_size = 8
113 # Sanity checking of arguments.
114 if pad and padmode == PAD_PKCS5:
115 raise ValueError("Cannot use a pad character with PAD_PKCS5")
116 if IV and len(IV) != self.block_size:
117 raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
118
119 # Set the passed in variables
120 self._mode = mode
121 self._iv = IV
122 self._padding = pad
123 self._padmode = padmode
124
125 def getKey(self):
126 """getKey() -> bytes"""
127 return self.__key
128
129 def setKey(self, key):
130 """Will set the crypting key for this object."""
131 key = self._guardAgainstUnicode(key)
132 self.__key = key
133
134 def getMode(self):
135 """getMode() -> pyDes.ECB or pyDes.CBC"""
136 return self._mode
137
138 def setMode(self, mode):
139 """Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
140 self._mode = mode
141
142 def getPadding(self):
143 """getPadding() -> bytes of length 1. Padding character."""
144 return self._padding
145
146 def setPadding(self, pad):
147 """setPadding() -> bytes of length 1. Padding character."""
148 if pad is not None:
149 pad = self._guardAgainstUnicode(pad)
150 self._padding = pad
151
152 def getPadMode(self):
153 """getPadMode() -> pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
154 return self._padmode
155
156 def setPadMode(self, mode):
157 """Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
158 self._padmode = mode
159
160 def getIV(self):
161 """getIV() -> bytes"""
162 return self._iv
163
164 def setIV(self, IV):
165 """Will set the Initial Value, used in conjunction with CBC mode"""
166 if not IV or len(IV) != self.block_size:
167 raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
168 IV = self._guardAgainstUnicode(IV)
169 self._iv = IV
170
171 def _padData(self, data, pad, padmode):
172 # Pad data depending on the mode
173 if padmode is None:
174 # Get the default padding mode.
175 padmode = self.getPadMode()
176 if pad and padmode == PAD_PKCS5:
177 raise ValueError("Cannot use a pad character with PAD_PKCS5")
178
179 if padmode == PAD_NORMAL:
180 if len(data) % self.block_size == 0:
181 # No padding required.
182 return data
183
184 if not pad:
185 # Get the default padding.
186 pad = self.getPadding()
187 if not pad:
188 raise ValueError("Data must be a multiple of " + str(self.block_size) + " bytes in length. Use padmode=PAD_PKCS5 or set the pad character.")
189 data += (self.block_size - (len(data) % self.block_size)) * pad
190
191 elif padmode == PAD_PKCS5:
192 pad_len = 8 - (len(data) % self.block_size)
193 if _pythonMajorVersion < 3:
194 data += pad_len * chr(pad_len)
195 else:
196 data += bytes([pad_len] * pad_len)
197
198 return data
199
200 def _unpadData(self, data, pad, padmode):
201 # Unpad data depending on the mode.
202 if not data:
203 return data
204 if pad and padmode == PAD_PKCS5:
205 raise ValueError("Cannot use a pad character with PAD_PKCS5")
206 if padmode is None:
207 # Get the default padding mode.
208 padmode = self.getPadMode()
209
210 if padmode == PAD_NORMAL:
211 if not pad:
212 # Get the default padding.
213 pad = self.getPadding()
214 if pad:
215 data = data[:-self.block_size] + \
216 data[-self.block_size:].rstrip(pad)
217
218 elif padmode == PAD_PKCS5:
219 if _pythonMajorVersion < 3:
220 pad_len = ord(data[-1])
221 else:
222 pad_len = data[-1]
223 data = data[:-pad_len]
224
225 return data
226
227 def _guardAgainstUnicode(self, data):
228 # Only accept byte strings or ascii unicode values, otherwise
229 # there is no way to correctly decode the data into bytes.
230 if _pythonMajorVersion < 3:
231 if isinstance(data, unicode):
232 raise ValueError("pyDes can only work with bytes, not Unicode strings.")
233 else:
234 if isinstance(data, str):
235 # Only accept ascii unicode values.
236 try:
237 return data.encode('ascii')
238 except UnicodeEncodeError:
239 pass
240 raise ValueError("pyDes can only work with encoded strings, not Unicode.")
241 return data
242
243 #############################################################################
244 # DES #
245 #############################################################################
246 class des(_baseDes):
247 """DES encryption/decrytpion class
248
249 Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
250
251 pyDes.des(key,[mode], [IV])
252
253 key -> Bytes containing the encryption key, must be exactly 8 bytes
254 mode -> Optional argument for encryption type, can be either pyDes.ECB
255 (Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
256 IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
257 Must be 8 bytes in length.
258 pad -> Optional argument, set the pad character (PAD_NORMAL) to use
259 during all encrypt/decrypt operations done with this instance.
260 padmode -> Optional argument, set the padding mode (PAD_NORMAL or
261 PAD_PKCS5) to use during all encrypt/decrypt operations done
262 with this instance.
263 """
264
265
266 # Permutation and translation tables for DES
267 __pc1 = [56, 48, 40, 32, 24, 16, 8,
268 0, 57, 49, 41, 33, 25, 17,
269 9, 1, 58, 50, 42, 34, 26,
270 18, 10, 2, 59, 51, 43, 35,
271 62, 54, 46, 38, 30, 22, 14,
272 6, 61, 53, 45, 37, 29, 21,
273 13, 5, 60, 52, 44, 36, 28,
274 20, 12, 4, 27, 19, 11, 3
275 ]
276
277 # number left rotations of pc1
278 __left_rotations = [
279 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
280 ]
281
282 # permuted choice key (table 2)
283 __pc2 = [
284 13, 16, 10, 23, 0, 4,
285 2, 27, 14, 5, 20, 9,
286 22, 18, 11, 3, 25, 7,
287 15, 6, 26, 19, 12, 1,
288 40, 51, 30, 36, 46, 54,
289 29, 39, 50, 44, 32, 47,
290 43, 48, 38, 55, 33, 52,
291 45, 41, 49, 35, 28, 31
292 ]
293
294 # initial permutation IP
295 __ip = [57, 49, 41, 33, 25, 17, 9, 1,
296 59, 51, 43, 35, 27, 19, 11, 3,
297 61, 53, 45, 37, 29, 21, 13, 5,
298 63, 55, 47, 39, 31, 23, 15, 7,
299 56, 48, 40, 32, 24, 16, 8, 0,
300 58, 50, 42, 34, 26, 18, 10, 2,
301 60, 52, 44, 36, 28, 20, 12, 4,
302 62, 54, 46, 38, 30, 22, 14, 6
303 ]
304
305 # Expansion table for turning 32 bit blocks into 48 bits
306 __expansion_table = [
307 31, 0, 1, 2, 3, 4,
308 3, 4, 5, 6, 7, 8,
309 7, 8, 9, 10, 11, 12,
310 11, 12, 13, 14, 15, 16,
311 15, 16, 17, 18, 19, 20,
312 19, 20, 21, 22, 23, 24,
313 23, 24, 25, 26, 27, 28,
314 27, 28, 29, 30, 31, 0
315 ]
316
317 # The (in)famous S-boxes
318 __sbox = [
319 # S1
320 [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
321 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
322 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
323 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
324
325 # S2
326 [15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
327 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
328 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
329 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
330
331 # S3
332 [10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
333 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
334 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
335 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
336
337 # S4
338 [7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
339 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
340 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
341 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
342
343 # S5
344 [2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
345 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
346 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
347 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
348
349 # S6
350 [12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
351 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
352 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
353 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
354
355 # S7
356 [4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
357 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
358 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
359 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
360
361 # S8
362 [13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
363 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
364 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
365 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],
366 ]
367
368
369 # 32-bit permutation function P used on the output of the S-boxes
370 __p = [
371 15, 6, 19, 20, 28, 11,
372 27, 16, 0, 14, 22, 25,
373 4, 17, 30, 9, 1, 7,
374 23,13, 31, 26, 2, 8,
375 18, 12, 29, 5, 21, 10,
376 3, 24
377 ]
378
379 # final permutation IP^-1
380 __fp = [
381 39, 7, 47, 15, 55, 23, 63, 31,
382 38, 6, 46, 14, 54, 22, 62, 30,
383 37, 5, 45, 13, 53, 21, 61, 29,
384 36, 4, 44, 12, 52, 20, 60, 28,
385 35, 3, 43, 11, 51, 19, 59, 27,
386 34, 2, 42, 10, 50, 18, 58, 26,
387 33, 1, 41, 9, 49, 17, 57, 25,
388 32, 0, 40, 8, 48, 16, 56, 24
389 ]
390
391 # Type of crypting being done
392 ENCRYPT = 0x00
393 DECRYPT = 0x01
394
395 # Initialisation
396 def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
397 # Sanity checking of arguments.
398 if len(key) != 8:
399 raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
400 _baseDes.__init__(self, mode, IV, pad, padmode)
401 self.key_size = 8
402
403 self.L = []
404 self.R = []
405 self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
406 self.final = []
407
408 self.setKey(key)
409
410 def setKey(self, key):
411 """Will set the crypting key for this object. Must be 8 bytes."""
412 _baseDes.setKey(self, key)
413 self.__create_sub_keys()
414
415 def __String_to_BitList(self, data):
416 """Turn the string data, into a list of bits (1, 0)'s"""
417 if _pythonMajorVersion < 3:
418 # Turn the strings into integers. Python 3 uses a bytes
419 # class, which already has this behaviour.
420 data = [ord(c) for c in data]
421 l = len(data) * 8
422 result = [0] * l
423 pos = 0
424 for ch in data:
425 i = 7
426 while i >= 0:
427 if ch & (1 << i) != 0:
428 result[pos] = 1
429 else:
430 result[pos] = 0
431 pos += 1
432 i -= 1
433
434 return result
435
436 def __BitList_to_String(self, data):
437 """Turn the list of bits -> data, into a string"""
438 result = []
439 pos = 0
440 c = 0
441 while pos < len(data):
442 c += data[pos] << (7 - (pos % 8))
443 if (pos % 8) == 7:
444 result.append(c)
445 c = 0
446 pos += 1
447
448 if _pythonMajorVersion < 3:
449 return ''.join([ chr(c) for c in result ])
450 else:
451 return bytes(result)
452
453 def __permutate(self, table, block):
454 """Permutate this block with the specified table"""
455 return list(map(lambda x: block[x], table))
456
457 # Transform the secret key, so that it is ready for data processing
458 # Create the 16 subkeys, K[1] - K[16]
459 def __create_sub_keys(self):
460 """Create the 16 subkeys K[1] to K[16] from the given key"""
461 key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey()))
462 i = 0
463 # Split into Left and Right sections
464 self.L = key[:28]
465 self.R = key[28:]
466 while i < 16:
467 j = 0
468 # Perform circular left shifts
469 while j < des.__left_rotations[i]:
470 self.L.append(self.L[0])
471 del self.L[0]
472
473 self.R.append(self.R[0])
474 del self.R[0]
475
476 j += 1
477
478 # Create one of the 16 subkeys through pc2 permutation
479 self.Kn[i] = self.__permutate(des.__pc2, self.L + self.R)
480
481 i += 1
482
483 # Main part of the encryption algorithm, the number cruncher :)
484 def __des_crypt(self, block, crypt_type):
485 """Crypt the block of data through DES bit-manipulation"""
486 block = self.__permutate(des.__ip, block)
487 self.L = block[:32]
488 self.R = block[32:]
489
490 # Encryption starts from Kn[1] through to Kn[16]
491 if crypt_type == des.ENCRYPT:
492 iteration = 0
493 iteration_adjustment = 1
494 # Decryption starts from Kn[16] down to Kn[1]
495 else:
496 iteration = 15
497 iteration_adjustment = -1
498
499 i = 0
500 while i < 16:
501 # Make a copy of R[i-1], this will later become L[i]
502 tempR = self.R[:]
503
504 # Permutate R[i - 1] to start creating R[i]
505 self.R = self.__permutate(des.__expansion_table, self.R)
506
507 # Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here
508 self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))
509 B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
510 # Optimization: Replaced below commented code with above
511 #j = 0
512 #B = []
513 #while j < len(self.R):
514 # self.R[j] = self.R[j] ^ self.Kn[iteration][j]
515 # j += 1
516 # if j % 6 == 0:
517 # B.append(self.R[j-6:j])
518
519 # Permutate B[1] to B[8] using the S-Boxes
520 j = 0
521 Bn = [0] * 32
522 pos = 0
523 while j < 8:
524 # Work out the offsets
525 m = (B[j][0] << 1) + B[j][5]
526 n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
527
528 # Find the permutation value
529 v = des.__sbox[j][(m << 4) + n]
530
531 # Turn value into bits, add it to result: Bn
532 Bn[pos] = (v & 8) >> 3
533 Bn[pos + 1] = (v & 4) >> 2
534 Bn[pos + 2] = (v & 2) >> 1
535 Bn[pos + 3] = v & 1
536
537 pos += 4
538 j += 1
539
540 # Permutate the concatination of B[1] to B[8] (Bn)
541 self.R = self.__permutate(des.__p, Bn)
542
543 # Xor with L[i - 1]
544 self.R = list(map(lambda x, y: x ^ y, self.R, self.L))
545 # Optimization: This now replaces the below commented code
546 #j = 0
547 #while j < len(self.R):
548 # self.R[j] = self.R[j] ^ self.L[j]
549 # j += 1
550
551 # L[i] becomes R[i - 1]
552 self.L = tempR
553
554 i += 1
555 iteration += iteration_adjustment
556
557 # Final permutation of R[16]L[16]
558 self.final = self.__permutate(des.__fp, self.R + self.L)
559 return self.final
560
561
562 # Data to be encrypted/decrypted
563 def crypt(self, data, crypt_type):
564 """Crypt the data in blocks, running it through des_crypt()"""
565
566 # Error check the data
567 if not data:
568 return ''
569 if len(data) % self.block_size != 0:
570 if crypt_type == des.DECRYPT: # Decryption must work on 8 byte blocks
571 raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
572 if not self.getPadding():
573 raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
574 else:
575 data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
576 # print "Len of data: %f" % (len(data) / self.block_size)
577
578 if self.getMode() == CBC:
579 if self.getIV():
580 iv = self.__String_to_BitList(self.getIV())
581 else:
582 raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
583
584 # Split the data into blocks, crypting each one seperately
585 i = 0
586 dict = {}
587 result = []
588 #cached = 0
589 #lines = 0
590 while i < len(data):
591 # Test code for caching encryption results
592 #lines += 1
593 #if dict.has_key(data[i:i+8]):
594 #print "Cached result for: %s" % data[i:i+8]
595 # cached += 1
596 # result.append(dict[data[i:i+8]])
597 # i += 8
598 # continue
599
600 block = self.__String_to_BitList(data[i:i+8])
601
602 # Xor with IV if using CBC mode
603 if self.getMode() == CBC:
604 if crypt_type == des.ENCRYPT:
605 block = list(map(lambda x, y: x ^ y, block, iv))
606 #j = 0
607 #while j < len(block):
608 # block[j] = block[j] ^ iv[j]
609 # j += 1
610
611 processed_block = self.__des_crypt(block, crypt_type)
612
613 if crypt_type == des.DECRYPT:
614 processed_block = list(map(lambda x, y: x ^ y, processed_block, iv))
615 #j = 0
616 #while j < len(processed_block):
617 # processed_block[j] = processed_block[j] ^ iv[j]
618 # j += 1
619 iv = block
620 else:
621 iv = processed_block
622 else:
623 processed_block = self.__des_crypt(block, crypt_type)
624
625
626 # Add the resulting crypted block to our list
627 #d = self.__BitList_to_String(processed_block)
628 #result.append(d)
629 result.append(self.__BitList_to_String(processed_block))
630 #dict[data[i:i+8]] = d
631 i += 8
632
633 # print "Lines: %d, cached: %d" % (lines, cached)
634
635 # Return the full crypted string
636 if _pythonMajorVersion < 3:
637 return ''.join(result)
638 else:
639 return bytes.fromhex('').join(result)
640
641 def encrypt(self, data, pad=None, padmode=None):
642 """encrypt(data, [pad], [padmode]) -> bytes
643
644 data : Bytes to be encrypted
645 pad : Optional argument for encryption padding. Must only be one byte
646 padmode : Optional argument for overriding the padding mode.
647
648 The data must be a multiple of 8 bytes and will be encrypted
649 with the already specified key. Data does not have to be a
650 multiple of 8 bytes if the padding character is supplied, or
651 the padmode is set to PAD_PKCS5, as bytes will then added to
652 ensure the be padded data is a multiple of 8 bytes.
653 """
654 data = self._guardAgainstUnicode(data)
655 if pad is not None:
656 pad = self._guardAgainstUnicode(pad)
657 data = self._padData(data, pad, padmode)
658 return self.crypt(data, des.ENCRYPT)
659
660 def decrypt(self, data, pad=None, padmode=None):
661 """decrypt(data, [pad], [padmode]) -> bytes
662
663 data : Bytes to be decrypted
664 pad : Optional argument for decryption padding. Must only be one byte
665 padmode : Optional argument for overriding the padding mode.
666
667 The data must be a multiple of 8 bytes and will be decrypted
668 with the already specified key. In PAD_NORMAL mode, if the
669 optional padding character is supplied, then the un-encrypted
670 data will have the padding characters removed from the end of
671 the bytes. This pad removal only occurs on the last 8 bytes of
672 the data (last data block). In PAD_PKCS5 mode, the special
673 padding end markers will be removed from the data after decrypting.
674 """
675 data = self._guardAgainstUnicode(data)
676 if pad is not None:
677 pad = self._guardAgainstUnicode(pad)
678 data = self.crypt(data, des.DECRYPT)
679 return self._unpadData(data, pad, padmode)
680
681
682
683 #############################################################################
684 # Triple DES #
685 #############################################################################
686 class triple_des(_baseDes):
687 """Triple DES encryption/decrytpion class
688
689 This algorithm uses the DES-EDE3 (when a 24 byte key is supplied) or
690 the DES-EDE2 (when a 16 byte key is supplied) encryption methods.
691 Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.
692
693 pyDes.des(key, [mode], [IV])
694
695 key -> Bytes containing the encryption key, must be either 16 or
696 24 bytes long
697 mode -> Optional argument for encryption type, can be either pyDes.ECB
698 (Electronic Code Book), pyDes.CBC (Cypher Block Chaining)
699 IV -> Optional Initial Value bytes, must be supplied if using CBC mode.
700 Must be 8 bytes in length.
701 pad -> Optional argument, set the pad character (PAD_NORMAL) to use
702 during all encrypt/decrypt operations done with this instance.
703 padmode -> Optional argument, set the padding mode (PAD_NORMAL or
704 PAD_PKCS5) to use during all encrypt/decrypt operations done
705 with this instance.
706 """
707 def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):
708 _baseDes.__init__(self, mode, IV, pad, padmode)
709 self.setKey(key)
710
711 def setKey(self, key):
712 """Will set the crypting key for this object. Either 16 or 24 bytes long."""
713 self.key_size = 24 # Use DES-EDE3 mode
714 if len(key) != self.key_size:
715 if len(key) == 16: # Use DES-EDE2 mode
716 self.key_size = 16
717 else:
718 raise ValueError("Invalid triple DES key size. Key must be either 16 or 24 bytes long")
719 if self.getMode() == CBC:
720 if not self.getIV():
721 # Use the first 8 bytes of the key
722 self._iv = key[:self.block_size]
723 if len(self.getIV()) != self.block_size:
724 raise ValueError("Invalid IV, must be 8 bytes in length")
725 self.__key1 = des(key[:8], self._mode, self._iv,
726 self._padding, self._padmode)
727 self.__key2 = des(key[8:16], self._mode, self._iv,
728 self._padding, self._padmode)
729 if self.key_size == 16:
730 self.__key3 = self.__key1
731 else:
732 self.__key3 = des(key[16:], self._mode, self._iv,
733 self._padding, self._padmode)
734 _baseDes.setKey(self, key)
735
736 # Override setter methods to work on all 3 keys.
737
738 def setMode(self, mode):
739 """Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""
740 _baseDes.setMode(self, mode)
741 for key in (self.__key1, self.__key2, self.__key3):
742 key.setMode(mode)
743
744 def setPadding(self, pad):
745 """setPadding() -> bytes of length 1. Padding character."""
746 _baseDes.setPadding(self, pad)
747 for key in (self.__key1, self.__key2, self.__key3):
748 key.setPadding(pad)
749
750 def setPadMode(self, mode):
751 """Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""
752 _baseDes.setPadMode(self, mode)
753 for key in (self.__key1, self.__key2, self.__key3):
754 key.setPadMode(mode)
755
756 def setIV(self, IV):
757 """Will set the Initial Value, used in conjunction with CBC mode"""
758 _baseDes.setIV(self, IV)
759 for key in (self.__key1, self.__key2, self.__key3):
760 key.setIV(IV)
761
762 def encrypt(self, data, pad=None, padmode=None):
763 """encrypt(data, [pad], [padmode]) -> bytes
764
765 data : bytes to be encrypted
766 pad : Optional argument for encryption padding. Must only be one byte
767 padmode : Optional argument for overriding the padding mode.
768
769 The data must be a multiple of 8 bytes and will be encrypted
770 with the already specified key. Data does not have to be a
771 multiple of 8 bytes if the padding character is supplied, or
772 the padmode is set to PAD_PKCS5, as bytes will then added to
773 ensure the be padded data is a multiple of 8 bytes.
774 """
775 ENCRYPT = des.ENCRYPT
776 DECRYPT = des.DECRYPT
777 data = self._guardAgainstUnicode(data)
778 if pad is not None:
779 pad = self._guardAgainstUnicode(pad)
780 # Pad the data accordingly.
781 data = self._padData(data, pad, padmode)
782 if self.getMode() == CBC:
783 self.__key1.setIV(self.getIV())
784 self.__key2.setIV(self.getIV())
785 self.__key3.setIV(self.getIV())
786 i = 0
787 result = []
788 while i < len(data):
789 block = self.__key1.crypt(data[i:i+8], ENCRYPT)
790 block = self.__key2.crypt(block, DECRYPT)
791 block = self.__key3.crypt(block, ENCRYPT)
792 self.__key1.setIV(block)
793 self.__key2.setIV(block)
794 self.__key3.setIV(block)
795 result.append(block)
796 i += 8
797 if _pythonMajorVersion < 3:
798 return ''.join(result)
799 else:
800 return bytes.fromhex('').join(result)
801 else:
802 data = self.__key1.crypt(data, ENCRYPT)
803 data = self.__key2.crypt(data, DECRYPT)
804 return self.__key3.crypt(data, ENCRYPT)
805
806 def decrypt(self, data, pad=None, padmode=None):
807 """decrypt(data, [pad], [padmode]) -> bytes
808
809 data : bytes to be encrypted
810 pad : Optional argument for decryption padding. Must only be one byte
811 padmode : Optional argument for overriding the padding mode.
812
813 The data must be a multiple of 8 bytes and will be decrypted
814 with the already specified key. In PAD_NORMAL mode, if the
815 optional padding character is supplied, then the un-encrypted
816 data will have the padding characters removed from the end of
817 the bytes. This pad removal only occurs on the last 8 bytes of
818 the data (last data block). In PAD_PKCS5 mode, the special
819 padding end markers will be removed from the data after
820 decrypting, no pad character is required for PAD_PKCS5.
821 """
822 ENCRYPT = des.ENCRYPT
823 DECRYPT = des.DECRYPT
824 data = self._guardAgainstUnicode(data)
825 if pad is not None:
826 pad = self._guardAgainstUnicode(pad)
827 if self.getMode() == CBC:
828 self.__key1.setIV(self.getIV())
829 self.__key2.setIV(self.getIV())
830 self.__key3.setIV(self.getIV())
831 i = 0
832 result = []
833 while i < len(data):
834 iv = data[i:i+8]
835 block = self.__key3.crypt(iv, DECRYPT)
836 block = self.__key2.crypt(block, ENCRYPT)
837 block = self.__key1.crypt(block, DECRYPT)
838 self.__key1.setIV(iv)
839 self.__key2.setIV(iv)
840 self.__key3.setIV(iv)
841 result.append(block)
842 i += 8
843 if _pythonMajorVersion < 3:
844 data = ''.join(result)
845 else:
846 data = bytes.fromhex('').join(result)
847 else:
848 data = self.__key3.crypt(data, DECRYPT)
849 data = self.__key2.crypt(data, ENCRYPT)
850 data = self.__key1.crypt(data, DECRYPT)
851 return self._unpadData(data, pad, padmode)
+0
-0
msldap/crypto/pure/DES/__init__.py less more
(Empty file)
+0
-64
msldap/crypto/pure/RC4/RC4.py less more
0 #!/usr/bin/env python
1
2
3 """
4 https://raw.githubusercontent.com/bozhu/RC4-Python/master/rc4.py
5
6 Copyright (C) 2012 Bo Zhu http://about.bozhu.me
7
8 Permission is hereby granted, free of charge, to any person obtaining a
9 copy of this software and associated documentation files (the "Software"),
10 to deal in the Software without restriction, including without limitation
11 the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 and/or sell copies of the Software, and to permit persons to whom the
13 Software is furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in
16 all copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 DEALINGS IN THE SOFTWARE.
25 """
26 class RC4():
27 def __init__(self, key):
28 self.key = key
29 self.S = self.KSA()
30 self.keystream = self.PRGA()
31
32 def KSA(self):
33 keylength = len(self.key)
34
35 S = list(range(256))
36
37 j = 0
38 for i in range(256):
39 j = (j + S[i] + self.key[i % keylength]) % 256
40 S[i], S[j] = S[j], S[i] # swap
41
42 return S
43
44 def PRGA(self):
45 i = 0
46 j = 0
47 while True:
48 i = (i + 1) % 256
49 j = (j + self.S[i]) % 256
50 self.S[i], self.S[j] = self.S[j], self.S[i] # swap
51
52 K = self.S[(self.S[i] + self.S[j]) % 256]
53 yield K
54
55
56 def encrypt(self, data):
57 res = b''
58 for b in data:
59 res += (b ^ next(self.keystream)).to_bytes(1, byteorder = 'big', signed = False)
60 return res
61
62 def decrypt(self, data):
63 return self.encrypt(data)
+0
-0
msldap/crypto/pure/RC4/__init__.py less more
(Empty file)
+0
-0
msldap/crypto/pure/__init__.py less more
(Empty file)
+0
-67
msldap/crypto/symmetric.py less more
0 import importlib
1 import importlib.util
2
3 #
4 ##key = name of the cipher, value=list of module names, in order of preference
5 #preftable = {
6 # 'DES' : ['pyCrypto','pure'],
7 # 'TDES': ['pyCrypto','pure'],
8 # 'AES' : ['cryptography','pyCrypto','pure'],
9 # 'RC4' : ['cryptography','pyCrypto','pure'],
10 #
11 #}
12 #
13 #available_modules = ['pure']
14 #
15 #if importlib.util.find_spec("cryptography") is not None:
16 # #print('Found cryptography package!')
17 # available_modules.append("cryptography")
18 #
19 #elif importlib.util.find_spec("pyCrypto") is not None:
20 # #print('Found cryptography package!')
21 # available_modules.append("pyCrypto")
22 #
23 #
24 ##https://stackoverflow.com/questions/8790003/dynamically-import-a-method-in-a-file-from-a-string
25 #def import_from(module, name):
26 # module = __import__(module, fromlist=[name])
27 # return getattr(module, name)
28 #
29 #
30 #def getPreferredCipher(cipherName):
31 # if cipherName not in preftable:
32 # raise Exception('Cipher %s doesnt have any preferences set!' % cipherName)
33 # possible_prefmodule = list(set(preftable[cipherName]).intersection(set(available_modules)))
34 # selected_module = None
35 # for moduleName in preftable[cipherName]:
36 # if moduleName in possible_prefmodule:
37 # selected_module = moduleName
38 #
39 # if selected_module is None:
40 # raise Exception('Could not find any modules to load cipher %s' % cipherName)
41 #
42 #
43 # #print('Preferred module selected for cipher %s is %s' % (cipherName, selected_module))
44 # moduleName = 'aiosmb.crypto.%s' % cipherName
45 # objectName = selected_module + cipherName
46 # return import_from(moduleName , objectName)
47 #
48 #def getSpecificCipher(cipherName, moduleBaseName):
49 # moduleName = 'aiosmb.crypto.%s' % cipherName
50 # objectName = '%s%s' % (moduleBaseName, cipherName)
51 # return import_from(moduleName , objectName)
52
53
54 #import ciphers
55 # TODO: fix the dynamic imports, currently only supporting pure-python ciphers for two reasons:
56 # 1. dynamic import messes up some scripts like pyinstaller/nuitka/py2exe
57 # 2. additional effort needed to support more crypto libs anyhow
58
59 from msldap.crypto.AES import pureAES
60 from msldap.crypto.RC4 import pureRC4
61 from msldap.crypto.DES import pureDES
62
63 DES = pureDES #getPreferredCipher('DES')
64 AES = pureAES #getPreferredCipher('AES')
65 RC4 = pureRC4 #getPreferredCipher('RC4')
66 #TDES = getPreferredCipher('TDES')
44 #
55
66 import asyncio
7 from os import terminal_size
78 import traceback
89 import logging
910 import csv
1011 import shlex
1112 import datetime
1213 import copy
14 import typing
1315
1416 from msldap.external.aiocmd.aiocmd import aiocmd
1517 from msldap.external.asciitree.asciitree import LeftAligned
1820 from msldap import logger
1921 from asysocks import logger as sockslogger
2022 from msldap.client import MSLDAPClient
21 from msldap.commons.url import MSLDAPURLDecoder
23 from msldap.commons.factory import LDAPConnectionFactory
2224 from msldap.ldap_objects import MSADUser, MSADMachine, MSADUser_TSV_ATTRS
2325
2426 from winacl.dtyp.security_descriptor import SECURITY_DESCRIPTOR
25 from winacl.dtyp.ace import ACCESS_ALLOWED_OBJECT_ACE, ADS_ACCESS_MASK
27 from winacl.dtyp.ace import ACCESS_ALLOWED_OBJECT_ACE, ADS_ACCESS_MASK, AceFlags, ACE_OBJECT_PRESENCE
2628 from winacl.dtyp.sid import SID
2729 from winacl.dtyp.guid import GUID
30
31 from msldap.ldap_objects.adcertificatetemplate import MSADCertificateTemplate, EX_RIGHT_CERTIFICATE_ENROLLMENT, CertificateNameFlag
32 from msldap.wintypes.asn1.sdflagsrequest import SDFlagsRequest
2833
2934
3035 class MSLDAPClientConsole(aiocmd.PromptToolkitCmd):
3237 aiocmd.PromptToolkitCmd.__init__(self, ignore_sigint=False) #Setting this to false, since True doesnt work on windows...
3338 self.conn_url = None
3439 if url is not None:
35 self.conn_url = MSLDAPURLDecoder(url)
40 self.conn_url = LDAPConnectionFactory.from_url(url)
3641 self.connection = None
3742 self.adinfo = None
3843 self.ldapinfo = None
4449 if self.conn_url is None and url is None:
4550 print('Not url was set, cant do logon')
4651 if url is not None:
47 self.conn_url = MSLDAPURLDecoder(url)
52 self.conn_url = LDAPConnectionFactory.from_url(url)
4853
4954 logger.debug(self.conn_url.get_credential())
5055 logger.debug(self.conn_url.get_target())
5459 _, err = await self.connection.connect()
5560 if err is not None:
5661 raise err
62 print('BIND OK!')
5763
5864 return True
5965 except:
211217 traceback.print_exc()
212218 return False
213219
214 async def do_user(self, samaccountname):
220 async def do_user(self, samaccountname, to_print=True):
215221 """Feteches a user object based on the sAMAccountName of the user"""
216222 try:
217223 await self.do_ldapinfo(False)
219225 user, err = await self.connection.get_user(samaccountname)
220226 if err is not None:
221227 raise err
222 if user is None:
223 print('User not found!')
224 else:
225 print(user)
226
227 return True
228
229 if to_print is True:
230 if user is None:
231 print('User not found!')
232 else:
233 print(user)
234
235 return user
228236 except:
229237 traceback.print_exc()
230238 return False
604612 except:
605613 traceback.print_exc()
606614 return False
607
615
616 async def do_rootcas(self, to_print = True):
617 """Lists Root CA certificates"""
618 try:
619 cas = []
620 async for ca, err in self.connection.list_root_cas():
621 if err is not None:
622 raise err
623 cas.append(ca)
624 if to_print is True:
625 print(ca)
626 return cas
627 except:
628 traceback.print_exc()
629 return False
630
631 async def do_ntcas(self, to_print = True):
632 """Lists NT CA certificates"""
633 try:
634 cas = []
635 async for ca, err in self.connection.list_ntcas():
636 if err is not None:
637 raise err
638 cas.append(ca)
639 if to_print is True:
640 print(ca)
641 return cas
642 except:
643 traceback.print_exc()
644 return False
645
646 async def do_aiacas(self, to_print = True):
647 """Lists AIA CA certificates"""
648 try:
649 cas = []
650 async for ca, err in self.connection.list_aiacas():
651 if err is not None:
652 raise err
653 cas.append(ca)
654 if to_print is True:
655 print(ca)
656 return cas
657 except:
658 traceback.print_exc()
659 return False
660
661 async def do_enrollmentservices(self, to_print=True):
662 """Lists AIA CA certificates"""
663 try:
664 services = []
665 async for srv, err in self.connection.list_enrollment_services():
666 if err is not None:
667 raise err
668 services.append(srv)
669 if to_print is True:
670 print(srv)
671 return services
672 except:
673 traceback.print_exc()
674 return False
675
676 async def do_addcerttemplatenameflagaltname(self, certtemplatename, flags = None):
677 """Modifyies the msPKI-Certificate-Name-Flag value of the specified certificate template and enables ENROLLEE_SUPPLIES_SUBJECT_ALT_NAME bit. If 'flags' is present then it will assign that value."""
678 try:
679 template = None
680 async for template, err in self.connection.list_certificate_templates(certtemplatename):
681 if err is not None:
682 raise err
683 break
684
685 if template is None:
686 raise Exception("Template could not be found!")
687
688 template = typing.cast(MSADCertificateTemplate, template)
689 if flags is not None:
690 flags = int(flags)
691 else:
692 flags = int(CertificateNameFlag(template.Certificate_Name_Flag) | CertificateNameFlag.ENROLLEE_SUPPLIES_SUBJECT_ALT_NAME)
693
694 changes = {
695 'msPKI-Certificate-Name-Flag' : [('replace', [flags])]
696 }
697
698 _, err = await self.connection.modify(template.distinguishedName, changes)
699 if err is not None:
700 raise err
701
702 print('Modify OK!')
703 return True
704
705
706 except:
707 traceback.print_exc()
708 return False
709
710 async def do_addenrollmentright(self, certtemplatename, user_dn):
711 """Grants enrollment rights to a user (by DN) for the specified certificate template."""
712 try:
713 user_sid, err = await self.connection.get_objectsid_for_dn(user_dn)
714 if err is not None:
715 raise err
716
717 template = None
718 async for template, err in self.connection.list_certificate_templates(certtemplatename):
719 if err is not None:
720 raise err
721 break
722
723 if template is None:
724 raise Exception("Template could not be found!")
725 template = typing.cast(MSADCertificateTemplate, template)
726 new_sd = copy.deepcopy(template.nTSecurityDescriptor)
727 ace = ACCESS_ALLOWED_OBJECT_ACE()
728 ace.Sid = SID.from_string(user_sid)
729 ace.ObjectType = GUID.from_string(EX_RIGHT_CERTIFICATE_ENROLLMENT)
730 ace.AceFlags = AceFlags(0)
731 ace.Mask = ADS_ACCESS_MASK.READ_PROP | ADS_ACCESS_MASK.WRITE_PROP | ADS_ACCESS_MASK.CONTROL_ACCESS
732 ace.Flags = ACE_OBJECT_PRESENCE.ACE_OBJECT_TYPE_PRESENT
733 new_sd.Dacl.aces.append(ace)
734 _, err = await self.connection.set_objectacl_by_dn(template.distinguishedName, new_sd.to_bytes(), flags=SDFlagsRequest.DACL_SECURITY_INFORMATION)
735 if err is not None:
736 raise err
737 print('SD set sucessfully')
738 return True
739 except:
740 traceback.print_exc()
741 return False
742
743 async def do_certtemplates(self, name = None, to_print = True):
744 """Lists certificate templates"""
745 try:
746 services = await self.do_enrollmentservices(to_print=False)
747 templates = []
748 async for template, err in self.connection.list_certificate_templates(name):
749 if err is not None:
750 raise err
751
752 lt = None
753 if template.nTSecurityDescriptor is not None:
754 lt, err = await self.connection.resolv_sd(template.nTSecurityDescriptor)
755 if err is not None:
756 raise err
757 template.sid_lookup_table = lt
758 for srv in services:
759 if template.name in srv.certificateTemplates:
760 template.enroll_services.append('%s\\%s' % (srv.dNSHostName, srv.name))
761
762 templates.append(template)
763 if to_print is True:
764 print(template.prettyprint())
765
766 return templates
767 except:
768 traceback.print_exc()
769 return False
770
771 async def do_sidresolv(self, sid, to_print = True):
772 """Returns the domain and username for SID"""
773 try:
774 domain, username, err = await self.connection.resolv_sid(sid)
775 if err is not None:
776 raise err
777 res = '%s\\%s' % (domain, username)
778 if to_print is True:
779 print(res)
780 return res
781 except:
782 traceback.print_exc()
783 return False
784
785 async def do_certify(self, cmd = None, username = None):
786 """ADCA security test"""
787 try:
788 es = await self.do_enrollmentservices(to_print=False)
789 if es is False:
790 raise Exception('Listing enrollment Services error! %s' % es)
791 if es is None:
792 raise Exception('No Enrollment Services present, stopping!')
793
794 templates = await self.do_certtemplates(to_print=False)
795 if templates is False:
796 raise Exception('Listing templates error! %s' % es)
797
798 if templates is None:
799 raise Exception('No templates exists!')
800
801 for enrollment in es:
802 print(enrollment)
803
804 if cmd is not None:
805 if cmd.lower().startswith('vuln') is True:
806 tokengroups = None
807 if username is not None:
808 tokengroups, err = await self.connection.get_tokengroups_user(username)
809 if err is not None:
810 raise err
811
812 for template in templates:
813 isvuln, reason = template.is_vulnerable(tokengroups)
814 if isvuln is True:
815 print(reason)
816 print(template)
817 else:
818 for template in templates:
819 print(template)
820
821 return True
822 except:
823 traceback.print_exc()
824 return False
825
826 async def do_whoamiraw(self):
827 """Simple whoami"""
828 try:
829 res, err = await self.connection.whoami()
830 if err is not None:
831 raise err
832 print(res)
833 except:
834 traceback.print_exc()
835 return False
836
837 async def do_whoami(self):
838 """Full whoami"""
839 try:
840 res, err = await self.connection.whoamifull()
841 if err is not None:
842 raise err
843
844 for x in res:
845 if isinstance(res[x], str) is True:
846 print('%s: %s' % (x, res[x]))
847 elif isinstance(res[x], dict) is True:
848 for k in res[x]:
849 print('Group: %s (%s)' % (k,'\\'.join(res[x][k])))
850 return True
851 except:
852 traceback.print_exc()
853 return False, None
854
608855 async def do_test(self):
609856 """testing, dontuse"""
610857 try:
66 import asyncio
77 import traceback
88 import logging
9 import csv
10 import shlex
11 import datetime
12
13 from msldap.external.aiocmd.aiocmd import aiocmd
14 from msldap.external.asciitree.asciitree import LeftAligned
15 from tqdm import tqdm
169
1710 from msldap import logger
1811 from asysocks import logger as sockslogger
19 from msldap.client import MSLDAPClient
20 from msldap.commons.url import MSLDAPURLDecoder
21 from msldap.ldap_objects import MSADUser, MSADMachine, MSADUser_TSV_ATTRS
22
23 from winacl.dtyp.security_descriptor import SECURITY_DESCRIPTOR
24
12 from msldap.commons.factory import LDAPConnectionFactory
2513
2614 class MSLDAPCompDomainList:
2715 def __init__(self, ldap_url):
6856 _, err = await self.do_adinfo(False)
6957 if err is not None:
7058 raise err
71 #machine_filename = '%s_computers_%s.txt' % (self.domain_name, datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
7259
7360 async for machine, err in self.connection.get_all_machines(attrs=['sAMAccountName', 'dNSHostName']):
7461 if err is not None:
10188 logger.setLevel(logging.DEBUG)
10289 logging.basicConfig(level=logging.DEBUG)
10390
104 ldap_url = MSLDAPURLDecoder(args.url)
91 ldap_url = LDAPConnectionFactory.from_url(args.url)
10592 compdomlist = MSLDAPCompDomainList(ldap_url)
10693
10794
1313 from msldap.ldap_objects.adgpo import MSADGPO, MSADGPO_ATTRS
1414 from msldap.ldap_objects.adtrust import MSADDomainTrust, MSADDomainTrust_ATTRS
1515 from msldap.ldap_objects.adschemaentry import MSADSCHEMAENTRY_ATTRS, MSADSchemaEntry
16 from msldap.ldap_objects.adca import MSADCA, MSADCA_ATTRS
17 from msldap.ldap_objects.adenrollmentservice import MSADEnrollmentService_ATTRS, MSADEnrollmentService
18 from msldap.ldap_objects.adcertificatetemplate import MSADCertificateTemplate, MSADCertificateTemplate_ATTRS
1619
1720 __all__ = [
1821 'MSADUser',
3639 'MSADOU_ATTRS',
3740 'MSADSCHEMAENTRY_ATTRS',
3841 'MSADSchemaEntry',
42 'MSADCA',
43 'MSADCA_ATTRS',
44 'MSADEnrollmentService_ATTRS',
45 'MSADEnrollmentService',
46 'MSADCertificateTemplate',
47 'MSADCertificateTemplate_ATTRS',
48
3949 ]
0
1 from asn1crypto.x509 import Certificate
2
3 MSADCA_ATTRS = ['cACertificate', 'cn', 'sn', 'distinguishedName', 'whenChanged', 'whenCreated', 'name']
4
5 class MSADCA:
6 def __init__(self):
7 self.location = None
8 self.sn = None #str
9 self.cn = None #str
10 self.distinguishedName = None #dn
11 self.whenChanged = None
12 self.whenCreated = None
13 self.cACertificate = None
14 self.name = None
15
16 @staticmethod
17 def from_ldap(entry, location):
18 adi = MSADCA()
19 adi.location = location
20 adi.sn = entry['attributes'].get('sn')
21 adi.cn = entry['attributes'].get('cn')
22 adi.distinguishedName = entry['attributes'].get('distinguishedName')
23 adi.whenChanged = entry['attributes'].get('whenChanged')
24 adi.whenCreated = entry['attributes'].get('whenCreated')
25 adi.cACertificate = entry['attributes'].get('cACertificate')
26 if adi.cACertificate is not None:
27 adi.cACertificate = Certificate.load(adi.cACertificate)
28 adi.name = entry['attributes'].get('name')
29
30 return adi
31
32 def __str__(self):
33 t = '== MSADCA ==\r\n'
34 for k in self.__dict__:
35 t += '%s: %s\r\n' % (k, self.__dict__[k])
36
37 return t
0 import enum
1 from winacl.dtyp.security_descriptor import SECURITY_DESCRIPTOR
2 from winacl.dtyp.ace import ACEType, ACE_OBJECT_PRESENCE, ACCESS_MASK, ADS_ACCESS_MASK
3
4
5 EX_RIGHT_CERTIFICATE_ENROLLMENT = "0e10c968-78fb-11d2-90d4-00c04f79dc55"
6 EX_RIGHT_CERTIFICATE_AUTOENROLLMENT = "a05b8cc2-17bc-4802-a710-e7c15ab866a2"
7
8
9 class CertificateNameFlag(enum.IntFlag):
10 ENROLLEE_SUPPLIES_SUBJECT = 0x00000001
11 ENROLLEE_SUPPLIES_SUBJECT_ALT_NAME = 0x00010000
12 SUBJECT_ALT_REQUIRE_DOMAIN_DNS = 0x00400000
13 SUBJECT_ALT_REQUIRE_SPN = 0x00800000
14 SUBJECT_ALT_REQUIRE_DIRECTORY_GUID = 0x01000000
15 SUBJECT_ALT_REQUIRE_UPN = 0x02000000
16 SUBJECT_ALT_REQUIRE_EMAIL = 0x04000000
17 SUBJECT_ALT_REQUIRE_DNS = 0x08000000
18 SUBJECT_REQUIRE_DNS_AS_CN = 0x10000000
19 SUBJECT_REQUIRE_EMAIL = 0x20000000
20 SUBJECT_REQUIRE_COMMON_NAME = 0x40000000
21 SUBJECT_REQUIRE_DIRECTORY_PATH = 0x80000000
22 OLD_CERT_SUPPLIES_SUBJECT_AND_ALT_NAME = 0x00000008
23
24 class EnrollmentFlag(enum.IntFlag):
25 INCLUDE_SYMMETRIC_ALGORITHMS = 0x00000001
26 PEND_ALL_REQUESTS = 0x00000002
27 PUBLISH_TO_KRA_CONTAINER = 0x00000004
28 PUBLISH_TO_DS = 0x00000008
29 AUTO_ENROLLMENT_CHECK_USER_DS_CERTIFICATE = 0x00000010
30 AUTO_ENROLLMENT = 0x00000020
31 PREVIOUS_APPROVAL_VALIDATE_REENROLLMENT = 0x00000040
32 USER_INTERACTION_REQUIRED = 0x00000100
33 REMOVE_INVALID_CERTIFICATE_FROM_PERSONAL_STORE = 0x00000400
34 ALLOW_ENROLL_ON_BEHALF_OF = 0x00000800
35 ADD_OCSP_NOCHECK = 0x00001000
36 ENABLE_KEY_REUSE_ON_NT_TOKEN_KEYSET_STORAGE_FULL = 0x00002000
37 NOREVOCATIONINFOINISSUEDCERTS = 0x00004000
38 INCLUDE_BASIC_CONSTRAINTS_FOR_EE_CERTS = 0x00008000
39 ALLOW_PREVIOUS_APPROVAL_KEYBASEDRENEWAL_VALIDATE_REENROLLMENT = 0x00010000
40 ISSUANCE_POLICIES_FROM_REQUEST = 0x00020000
41 SKIP_AUTO_RENEWAL = 0x00040000
42
43 class PrivateKeyFlag(enum.IntFlag):
44 REQUIRE_PRIVATE_KEY_ARCHIVAL = 0x00000001
45 EXPORTABLE_KEY = 0x00000010
46 STRONG_KEY_PROTECTION_REQUIRED = 0x00000020
47 REQUIRE_ALTERNATE_SIGNATURE_ALGORITHM = 0x00000040
48 REQUIRE_SAME_KEY_RENEWAL = 0x00000080
49 USE_LEGACY_PROVIDER = 0x00000100
50 ATTEST_REQUIRED = 0x000002000
51 ATTEST_PREFERRED = 0x000001000
52 HELLO_LOGON_KEY = 0x00200000
53
54 EKU_CLIENT_AUTHENTICATION_OID = "1.3.6.1.5.5.7.3.2"
55 EKU_PKINIT_CLIENT_AUTHENTICATION_OID = "1.3.6.1.5.2.3.4"
56 EKU_SMART_CARD_LOGON_OID = "1.3.6.1.4.1.311.20.2.2"
57 EKU_ANY_PURPOSE_OID = "2.5.29.37.0"
58 EKU_CERTIFICATE_REQUEST_AGENT_OID = "1.3.6.1.4.1.311.20.2.1"
59
60 EKUS_NAMES = {
61 "1.3.6.1.4.1.311.2.6.1": "SpcRelaxedPEMarkerCheck",
62 "1.3.6.1.4.1.311.2.6.2": "SpcEncryptedDigestRetryCount",
63 "1.3.6.1.4.1.311.10.3.6": "Windows System Component Verification",
64 "1.3.6.1.4.1.311.10.3.22": "Protected Process Light Verification",
65 "1.3.6.1.4.1.311.10.3.27": "Preview Build Signing",
66 "1.3.6.1.4.1.311.10.3.1": "Microsoft Trust List Signing",
67 "1.3.6.1.4.1.311.10.3.2": "Microsoft Time Stamping",
68 "1.3.6.1.4.1.311.10.3.7": "OEM Windows System Component Verification",
69 "1.3.6.1.4.1.311.10.3.13": "Lifetime Signing",
70 "1.3.6.1.4.1.311.10.3.11": "Key Recovery",
71 "1.3.6.1.4.1.311.10.3.23": "Windows TCB Component",
72 "1.3.6.1.4.1.311.10.3.25": "Windows Third Party Application Component",
73 "1.3.6.1.4.1.311.10.3.26": "Windows Software Extension Verification",
74 "1.3.6.1.4.1.311.10.3.8": "Embedded Windows System Component Verification",
75 "1.3.6.1.4.1.311.10.3.20": "Windows Kits Component",
76 "1.3.6.1.4.1.311.10.3.5": "Windows Hardware Driver Verification",
77 "1.3.6.1.4.1.311.10.3.39": "Windows Hardware Driver Extended Verification",
78 "1.3.6.1.4.1.311.10.3.5.1": "Windows Hardware Driver Attested Verification",
79 "1.3.6.1.4.1.311.10.3.4.1": "File Recovery",
80 "1.3.6.1.4.1.311.10.3.30": "Disallowed List",
81 "1.3.6.1.4.1.311.10.3.19": "Revoked List Signer",
82 "1.3.6.1.4.1.311.10.3.21": "Windows RT Verification",
83 "1.3.6.1.4.1.311.10.3.10": "Qualified Subordination",
84 "1.3.6.1.4.1.311.10.3.12": "Document Signing",
85 "1.3.6.1.4.1.311.10.3.24": "Protected Process Verification",
86 "1.3.6.1.4.1.311.10.3.4": "Encrypting File System",
87 "1.3.6.1.4.1.311.10.3.9": "Root List Signer",
88 "1.3.6.1.4.1.311.10.5.1": "Digital Rights",
89 "1.3.6.1.4.1.311.10.6.2": "License Server Verification",
90 "1.3.6.1.4.1.311.10.6.1": "Key Pack Licenses",
91 EKU_SMART_CARD_LOGON_OID: "Smart Card Logon",
92 EKU_CERTIFICATE_REQUEST_AGENT_OID: "Certificate Request Agent",
93 "1.3.6.1.4.1.311.20.1": "CTL Usage",
94 "1.3.6.1.4.1.311.21.6": "Key Recovery Agent",
95 "1.3.6.1.4.1.311.21.19": "Directory Service Email Replication",
96 "1.3.6.1.4.1.311.21.5": "Private Key Archival",
97 "1.3.6.1.4.1.311.61.1.1": "Kernel Mode Code Signing",
98 "1.3.6.1.4.1.311.61.4.1": "Early Launch Antimalware Driver",
99 "1.3.6.1.4.1.311.61.5.1": "HAL Extension",
100 "1.3.6.1.4.1.311.64.1.1": "Domain Name System (DNS) Server Trust",
101 "1.3.6.1.4.1.311.76.6.1": "Windows Update",
102 "1.3.6.1.4.1.311.76.3.1": "Windows Store",
103 "1.3.6.1.4.1.311.76.5.1": "Dynamic Code Generator",
104 "1.3.6.1.4.1.311.76.8.1": "Microsoft Publisher",
105 "1.3.6.1.4.1.311.80.1": "Document Encryption",
106 EKU_PKINIT_CLIENT_AUTHENTICATION_OID: "PKINIT Client Authentication",
107 "1.3.6.1.5.2.3.5": "KDC Authentication",
108 "1.3.6.1.5.5.7.3.7": "IP security user",
109 EKU_CLIENT_AUTHENTICATION_OID: "Client Authentication",
110 "1.3.6.1.5.5.7.3.9": "OCSP Signing",
111 "1.3.6.1.5.5.7.3.3": "Code Signing",
112 "1.3.6.1.5.5.7.3.4": "Secure Email",
113 "1.3.6.1.5.5.7.3.5": "IP security end system",
114 "1.3.6.1.5.5.7.3.6": "IP security tunnel termination",
115 "1.3.6.1.5.5.8.2.2": "IP security IKE intermediate",
116 "1.3.6.1.5.5.7.3.8": "Time Stamping",
117 "1.3.6.1.5.5.7.3.1": "Server Authentication",
118 EKU_ANY_PURPOSE_OID: "Any Purpose",
119 "2.23.133.8.1": "Endorsement Key Certificate",
120 "2.23.133.8.2": "Platform Certificate",
121 "2.23.133.8.3": "Attestation Identity Key Certificate",
122 }
123
124 ENROLLMENT_FLAGS_NAMES = {
125 EnrollmentFlag.INCLUDE_SYMMETRIC_ALGORITHMS: "INCLUDE_SYMMETRIC_ALGORITHMS",
126 EnrollmentFlag.PEND_ALL_REQUESTS: "PEND_ALL_REQUESTS",
127 EnrollmentFlag.PUBLISH_TO_KRA_CONTAINER: "PUBLISH_TO_KRA_CONTAINER",
128 EnrollmentFlag.PUBLISH_TO_DS: "PUBLISH_TO_DS",
129 EnrollmentFlag.AUTO_ENROLLMENT_CHECK_USER_DS_CERTIFICATE: "AUTO_ENROLLMENT_CHECK_USER_DS_CERTIFICATE",
130 EnrollmentFlag.AUTO_ENROLLMENT: "AUTO_ENROLLMENT",
131 EnrollmentFlag.PREVIOUS_APPROVAL_VALIDATE_REENROLLMENT: "PREVIOUS_APPROVAL_VALIDATE_REENROLLMENT",
132 EnrollmentFlag.USER_INTERACTION_REQUIRED: "USER_INTERACTION_REQUIRED",
133 EnrollmentFlag.REMOVE_INVALID_CERTIFICATE_FROM_PERSONAL_STORE: "REMOVE_INVALID_CERTIFICATE_FROM_PERSONAL_STORE",
134 EnrollmentFlag.ALLOW_ENROLL_ON_BEHALF_OF: "ALLOW_ENROLL_ON_BEHALF_OF",
135 EnrollmentFlag.ADD_OCSP_NOCHECK: "ADD_OCSP_NOCHECK",
136 EnrollmentFlag.ENABLE_KEY_REUSE_ON_NT_TOKEN_KEYSET_STORAGE_FULL: "ENABLE_KEY_REUSE_ON_NT_TOKEN_KEYSET_STORAGE_FULL",
137 EnrollmentFlag.NOREVOCATIONINFOINISSUEDCERTS: "NOREVOCATIONINFOINISSUEDCERTS",
138 EnrollmentFlag.INCLUDE_BASIC_CONSTRAINTS_FOR_EE_CERTS: "INCLUDE_BASIC_CONSTRAINTS_FOR_EE_CERTS",
139 EnrollmentFlag.ALLOW_PREVIOUS_APPROVAL_KEYBASEDRENEWAL_VALIDATE_REENROLLMENT: "ALLOW_PREVIOUS_APPROVAL_KEYBASEDRENEWAL_VALIDATE_REENROLLMENT",
140 EnrollmentFlag.ISSUANCE_POLICIES_FROM_REQUEST: "ISSUANCE_POLICIES_FROM_REQUEST",
141 EnrollmentFlag.SKIP_AUTO_RENEWAL: "SKIP_AUTO_RENEWAL",
142 }
143
144 MSADCertificateTemplate_ATTRS = [
145 'cn', 'sn', 'distinguishedName', 'name', 'msPKI-RA-Application-Policies', 'msPKI-Certificate-Application-Policy',
146 'msPKI-Template-Schema-Version', 'msPKI-Certificate-Name-Flag', 'msPKI-Enrollment-Flag', 'msPKI-RA-Signature',
147 'msPKI-Private-Key-Flag', 'pKIExtendedKeyUsage', 'nTSecurityDescriptor'
148 ]
149
150 class MSADCertificateTemplate:
151 def __init__(self):
152 self.sn = None #str
153 self.cn = None #str
154 self.distinguishedName = None #dn
155 self.name = None
156 self.RA_Application_Policies = None
157 self.Certificate_Application_Policy = None
158 self.Template_Schema_Version = None
159 self.Certificate_Name_Flag = None
160 self.Enrollment_Flag = None
161 self.RA_Signature = None
162 self.Private_Key_Flag = None
163 self.pKIExtendedKeyUsage = None
164 self.nTSecurityDescriptor:SECURITY_DESCRIPTOR = None
165
166 self.vulns = []
167 self.enroll_sids = set()
168 self.autoenroll_sids = set()
169 self.write_owner_sids = set()
170 self.write_dacl_sids = set()
171 self.write_property_sids = set()
172 self.fullcontrol_sids = set()
173 self.allextendedrights_sids = set()
174 self.sid_lookup_table = None
175 self.enroll_services = []
176
177 @staticmethod
178 def from_ldap(entry):
179 adi = MSADCertificateTemplate()
180 adi.sn = entry['attributes'].get('sn')
181 adi.cn = entry['attributes'].get('cn')
182 adi.distinguishedName = entry['attributes'].get('distinguishedName')
183 adi.name = entry['attributes'].get('name')
184 adi.RA_Application_Policies = entry['attributes'].get('msPKI-RA-Application-Policies')
185 adi.Certificate_Application_Policy = entry['attributes'].get('msPKI-Certificate-Application-Policy')
186 adi.Template_Schema_Version = entry['attributes'].get('msPKI-Template-Schema-Version')
187 adi.Certificate_Name_Flag = entry['attributes'].get('msPKI-Certificate-Name-Flag')
188 adi.Enrollment_Flag = entry['attributes'].get('msPKI-Enrollment-Flag')
189 adi.RA_Signature = entry['attributes'].get('msPKI-RA-Signature')
190 adi.Private_Key_Flag = entry['attributes'].get('msPKI-Private-Key-Flag')
191 adi.pKIExtendedKeyUsage = entry['attributes'].get('pKIExtendedKeyUsage', [])
192 adi.nTSecurityDescriptor = entry['attributes'].get('nTSecurityDescriptor')
193 if adi.nTSecurityDescriptor is not None:
194 adi.nTSecurityDescriptor = SECURITY_DESCRIPTOR.from_bytes(adi.nTSecurityDescriptor)
195
196 adi.calc_aces()
197 return adi
198
199 def allows_authentication(self):
200 return self.can_be_used_for_any_purpose() or len(set([EKU_CLIENT_AUTHENTICATION_OID, EKU_SMART_CARD_LOGON_OID, EKU_PKINIT_CLIENT_AUTHENTICATION_OID]).intersection(set(self.pKIExtendedKeyUsage))) > 0
201
202 def can_be_used_for_any_purpose(self):
203 return len(self.pKIExtendedKeyUsage) == 0 or EKU_ANY_PURPOSE_OID in self.pKIExtendedKeyUsage
204
205 def requires_manager_approval(self):
206 return EnrollmentFlag.PEND_ALL_REQUESTS in EnrollmentFlag(self.Enrollment_Flag)
207
208 def requires_authorized_signatures(self):
209 return self.RA_Signature != None and self.RA_Signature > 0
210
211 def allows_to_specify_san(self):
212 return CertificateNameFlag(self.Certificate_Name_Flag) & CertificateNameFlag.ENROLLEE_SUPPLIES_SUBJECT > 0
213
214 def allows_to_request_agent_certificate(self):
215 return EKU_CERTIFICATE_REQUEST_AGENT_OID in self.pKIExtendedKeyUsage
216
217 def allows_to_use_agent_certificate(self):
218 return self.Template_Schema_Version == 1 \
219 or (
220 self.Template_Schema_Version > 1 \
221 and self.RA_Signature == 1 \
222 and EKU_CERTIFICATE_REQUEST_AGENT_OID in self.RA_Application_Policies
223 )
224
225 def is_vulnerable(self, tokengroups = None):
226 def isLowPrivSid(sid):
227 sid = str(sid)
228 if sid in ['S-1-1-0', 'S-1-5-11']:
229 return True
230 if sid.startswith('S-1-5-21-') is True and sid.rsplit('-',1)[1] in ['513','515','545']:
231 return True
232 return False
233
234 if tokengroups is None:
235 if isLowPrivSid(str(self.nTSecurityDescriptor.Owner)) is True:
236 return True, 'Owner is low priv user'
237
238 else:
239 if str(self.nTSecurityDescriptor.Owner) in tokengroups:
240 return True, 'Owner can be controlled by current user'
241
242 lowprivcanenroll = False
243 if tokengroups is None:
244 if any(isLowPrivSid(str(sid)) for sid in self.fullcontrol_sids) is True:
245 return True, 'Lowpriv SID has full control'
246
247 if any(isLowPrivSid(str(sid)) for sid in self.write_dacl_sids) is True:
248 return True, 'Lowpriv SID can write DACLs'
249
250 if any(isLowPrivSid(str(sid)) for sid in self.write_owner_sids) is True:
251 return True, 'Lowpriv SID can change Owner'
252
253 if any(isLowPrivSid(str(sid)) for sid in self.write_property_sids) is True:
254 return True, 'Lowpriv SID can write property'
255
256 if any(isLowPrivSid(str(sid)) for sid in self.enroll_sids) is True:
257 lowprivcanenroll = True
258
259 if any(isLowPrivSid(str(sid)) for sid in self.allextendedrights_sids) is True:
260 lowprivcanenroll = True
261
262 else:
263 if len(self.enroll_sids.intersection(set(tokengroups))) > 0 or len(self.allextendedrights_sids.intersection(set(tokengroups))) > 0:
264 lowprivcanenroll = True
265
266 if len(self.write_dacl_sids.intersection(set(tokengroups))) > 0:
267 return True, 'Current user can write DACLs'
268
269 if len(self.write_owner_sids.intersection(set(tokengroups))) > 0:
270 return True, 'Current user can change Owner'
271
272 if len(self.write_property_sids.intersection(set(tokengroups))) > 0:
273 return True, 'Current user can write property'
274
275 if len(self.fullcontrol_sids.intersection(set(tokengroups))) > 0:
276 return True, 'Current user has full control'
277
278 if self.requires_manager_approval() is True:
279 return False, 'Needs manager approval'
280
281 if self.requires_authorized_signatures() is True:
282 return False, 'Needs authorized signature'
283
284 if self.allows_authentication() and lowprivcanenroll and self.allows_to_specify_san():
285 return True, 'Enrollee supplies subject'
286
287 if lowprivcanenroll and self.allows_to_use_agent_certificate() and self.allows_to_request_agent_certificate():
288 return True, 'Certificate request agent'
289
290 return False, 'No match found'
291
292 def calc_aces(self):
293 if self.nTSecurityDescriptor is None:
294 return
295 for ace in self.nTSecurityDescriptor.Dacl.aces:
296 if ace.AceType != ACEType.ACCESS_ALLOWED_OBJECT_ACE_TYPE and ace.AceType != ACEType.ACCESS_ALLOWED_ACE_TYPE:
297 continue
298
299 if ace.AceType == ACEType.ACCESS_ALLOWED_OBJECT_ACE_TYPE:
300 if str(ace.ObjectType) == EX_RIGHT_CERTIFICATE_ENROLLMENT:
301 self.enroll_sids.add(str(ace.Sid))
302 elif str(ace.ObjectType) == EX_RIGHT_CERTIFICATE_AUTOENROLLMENT:
303 self.autoenroll_sids.add(str(ace.Sid))
304 elif ADS_ACCESS_MASK.CONTROL_ACCESS in ADS_ACCESS_MASK(ace.Mask) and ace.ObjectType is None:
305 self.allextendedrights_sids.add(str(ace.Sid))
306 continue
307
308 if ADS_ACCESS_MASK.GENERIC_ALL in ADS_ACCESS_MASK(ace.Mask):
309 self.fullcontrol_sids.add(str(ace.Sid))
310
311 if ADS_ACCESS_MASK.WRITE_DACL in ADS_ACCESS_MASK(ace.Mask):
312 self.write_dacl_sids.add(str(ace.Sid))
313
314 if ADS_ACCESS_MASK.WRITE_OWNER in ADS_ACCESS_MASK(ace.Mask):
315 self.write_owner_sids.add(str(ace.Sid))
316
317 if ADS_ACCESS_MASK.WRITE_PROP in ADS_ACCESS_MASK(ace.Mask):
318 self.write_property_sids.add(str(ace.Sid))
319
320 if ADS_ACCESS_MASK.CONTROL_ACCESS in ADS_ACCESS_MASK(ace.Mask):
321 self.allextendedrights_sids.add(str(ace.Sid))
322
323 @property
324 def is_enabled(self):
325 return len(self.enroll_services) > 0
326
327 def __str__(self):
328 t = '== MSADCertificateTemplate ==\r\n'
329 for k in self.__dict__:
330 t += '%s: %s\r\n' % (k, self.__dict__[k])
331
332 return t
333
334 def prettyprint(self):
335 def print_sids(buffer, sidlist, offset = 6):
336 for sid in sidlist:
337 if self.sid_lookup_table is not None and sid in self.sid_lookup_table:
338 buffer += '%s%s\\%s [%s]\r\n' % (offset*' ', self.sid_lookup_table[sid][0], self.sid_lookup_table[sid][1], sid)
339 else:
340 buffer += '%s%s\r\n' % (offset*' ', sid)
341 return buffer
342
343 t = '== MSADCertificateTemplate ==\r\n'
344 t += "Name: %s\r\n" % self.name
345 t += 'distinguishedName: %s\r\n' % self.distinguishedName
346 t += "Schema Version: %s\r\n" % self.Template_Schema_Version
347
348 if self.enroll_services:
349 t += "Enroll Services: %s\r\n" % ", ".join(self.enroll_services)
350
351 if len(self.vulns) > 0:
352 t += "Vulnerabilities: %s\r\n" % ", ".join(self.vulns)
353
354 if self.Certificate_Name_Flag is not None:
355 t += "msPKI-Certificate-Name-Flag: %s\r\n" % str(CertificateNameFlag(self.Certificate_Name_Flag)).split('.',1)[1].replace('|',', ')
356 if self.Enrollment_Flag is not None:
357 t += "msPKI-Enrollment-Flag: %s\r\n" % str(EnrollmentFlag(self.Enrollment_Flag)).split('.',1)[1].replace('|',', ')
358 if self.RA_Signature is not None:
359 t += "msPKI-RA-Signature: %s\r\n" % self.RA_Signature
360 if self.pKIExtendedKeyUsage is not None:
361 t += "pKIExtendedKeyUsage: %s\r\n" % ", ".join([EKUS_NAMES.get(oid, oid) for oid in self.pKIExtendedKeyUsage])
362 if self.Certificate_Application_Policy is not None:
363 t += "msPKI-Certificate-Application-Policy: %s\r\n" % ", ".join([EKUS_NAMES.get(oid, oid) for oid in self.Certificate_Application_Policy])
364 if self.RA_Application_Policies is not None:
365 t += "msPKI-RA-Application-Policy: %s\r\n" % ", ".join([EKUS_NAMES.get(oid, oid) for oid in self.RA_Application_Policies])
366
367 t += "Permissions\r\n"
368 t += " Enrollment Permissions\r\n"
369 if len(self.enroll_sids) > 0:
370 t += " Enrollment Rights\r\n"
371 t = print_sids(t, self.enroll_sids)
372
373 if len(self.autoenroll_sids) > 0:
374 t += " AutoEnrollment Rights\r\n"
375 t = print_sids(t, self.autoenroll_sids)
376
377 if len(self.allextendedrights_sids) > 0:
378 t += " All Extended Rights\r\n"
379 t = print_sids(t, self.allextendedrights_sids)
380
381 t+= " Object Control Permissions\r\n"
382 t+= ' Owner\r\n'
383 sid = str(self.nTSecurityDescriptor.Owner)
384 if self.sid_lookup_table is not None and sid in self.sid_lookup_table:
385 t += ' %s\\%s [%s]\r\n' % (self.sid_lookup_table[sid][0], self.sid_lookup_table[sid][1], sid)
386 else:
387 t += " %s\r\n" % sid
388
389 if len(self.fullcontrol_sids) > 0:
390 t+= " Full Control\r\n"
391 t = print_sids(t, self.fullcontrol_sids)
392
393 t += " Write Owner\r\n"
394 t = print_sids(t, self.write_owner_sids)
395
396 t += " Write DACL\r\n"
397 t = print_sids(t, self.write_dacl_sids)
398
399 t += " Write Property\r\n"
400 t = print_sids(t, self.write_property_sids)
401
402 t += " SDDL\r\n"
403 t += self.nTSecurityDescriptor.to_sddl()
404 t += '\r\n'
405 return t
406
407 def __str__(self):
408 return self.prettyprint()
0
1
2 from msldap.commons.utils import print_cert
3 from asn1crypto.x509 import Certificate
4
5 MSADEnrollmentService_ATTRS = ['cACertificate', 'msPKI-Enrollment-Servers', 'dNSHostName', 'cn', 'sn', 'distinguishedName', 'whenChanged', 'whenCreated', 'name', 'displayName', 'cACertificateDN', 'certificateTemplates']
6
7 class MSADEnrollmentService:
8 def __init__(self):
9 self.sn = None #str
10 self.cn = None #str
11 self.distinguishedName = None #dn
12 self.name = None
13 self.displayName = None
14 self.cACertificate = None
15 self.cACertificateDN = None
16 self.dNSHostName = None
17 self.certificateTemplates = []
18 self.enrollmentServers = []
19
20 @staticmethod
21 def from_ldap(entry):
22 adi = MSADEnrollmentService()
23 adi.sn = entry['attributes'].get('sn')
24 adi.cn = entry['attributes'].get('cn')
25 adi.distinguishedName = entry['attributes'].get('distinguishedName')
26 adi.cACertificate = entry['attributes'].get('cACertificate')
27 if adi.cACertificate is not None:
28 adi.cACertificate = Certificate.load(adi.cACertificate)
29 adi.name = entry['attributes'].get('name')
30 adi.displayName = entry['attributes'].get('displayName')
31 adi.dNSHostName = entry['attributes'].get('dNSHostName')
32 adi.cACertificateDN = entry['attributes'].get('cACertificateDN')
33 adi.certificateTemplates = entry['attributes'].get('certificateTemplates')
34 for serverdef in entry['attributes'].get('msPKI-Enrollment-Servers', []):
35 adi.enrollmentServers.append(serverdef.split('\n')[3])
36 return adi
37
38 def __str__(self):
39 t = '== MSADEnrollmentService ==\r\n'
40 t += "Name: %s\r\n" % self.name
41 t += "DNS name: %s\r\n" % self.dNSHostName
42 t += "Templates: %s\r\n" % ', '.join(self.certificateTemplates)
43 if len(self.enrollmentServers) > 0:
44 t += "Web services: %s\r\n" % ", ".join(self.enrollmentServers)
45 t += "Certificate: \r\n%s\r\n" % print_cert(self.cACertificate.native, 2)
46
47 return t
+0
-81
msldap/network/multiplexor.py less more
0 import enum
1 import asyncio
2 import ipaddress
3 import copy
4
5 from asysocks.common.clienturl import SocksClientURL
6 from asysocks.common.constants import SocksServerVersion, SocksProtocol, SOCKS5Method
7 from asysocks.common.target import SocksTarget
8
9 from msldap import logger
10 from msldap.network.socks import SocksProxyConnection
11 from msldap.commons.proxy import MSLDAPProxy, MSLDAPProxyType
12 from minikerberos.common.target import KerberosTarget
13 from minikerberos.common.proxy import KerberosProxy
14
15
16
17 class MultiplexorProxyConnection:
18 """
19 """
20 def __init__(self, target):
21 self.target = target
22
23 async def connect(self, is_kerberos = False):
24 """
25
26 """
27 #hiding the import, so you'll only need to install multiplexor only when actually using it
28 from multiplexor.operator import MultiplexorOperator
29
30 con_str = self.target.proxy.target.get_server_url()
31 #creating operator and connecting to multiplexor server
32 self.operator = MultiplexorOperator(con_str, logging_sink = logger)
33 await self.operator.connect()
34 #creating socks5 proxy
35 server_info = await self.operator.start_socks5(self.target.proxy.target.agent_id)
36 await self.operator.terminate()
37 #print(server_info)
38 if is_kerberos is False:
39
40 #copying the original target, then feeding it to socks5proxy object. it will hold the actual socks5 proxy server address we created before
41 tp = MSLDAPProxy()
42 tp.target = SocksTarget()
43 tp.target.version = SocksServerVersion.SOCKS5
44 tp.target.server_ip = server_info['listen_ip']
45 tp.target.server_port = server_info['listen_port']
46 tp.target.is_bind = False
47 tp.target.proto = SocksProtocol.TCP
48 tp.target.timeout = self.target.timeout
49 tp.target.buffer_size = 4096
50
51 tp.target.endpoint_ip = self.target.host
52 tp.target.endpoint_port = self.target.port
53 tp.target.endpoint_timeout = None # TODO: maybe implement endpoint timeout in the msldap target?
54 tp.type = MSLDAPProxyType.SOCKS5
55
56 newtarget = copy.deepcopy(self.target)
57 newtarget.proxy = tp
58
59
60
61 return SocksProxyConnection(target = newtarget)
62
63 else:
64 kt = copy.deepcopy(self.target)
65 kt.proxy = KerberosProxy()
66 kt.proxy.target = SocksTarget()
67 kt.proxy.target.version = SocksServerVersion.SOCKS5
68 kt.proxy.target.server_ip = server_info['listen_ip']
69 kt.proxy.target.server_port = server_info['listen_port']
70 kt.proxy.target.is_bind = False
71 kt.proxy.target.proto = SocksProtocol.TCP
72 kt.proxy.target.timeout = 10
73 kt.proxy.target.buffer_size = 4096
74
75 kt.proxy.target.endpoint_ip = self.target.ip
76 kt.proxy.target.endpoint_port = self.target.port
77 #kt.proxy.creds = copy.deepcopy(self.target.proxy.auth)
78
79 return kt
80
0 import asyncio
1
2 from msldap import logger
3 from msldap.protocol.utils import calcualte_length
4 from asysocks.unicomm.common.packetizers import Packetizer
5
6 class LDAPPacketizer(Packetizer):
7 def __init__(self):
8 Packetizer.__init__(self, 65535)
9 self.in_buffer = b''
10 self.is_plain_msg = True
11
12 def process_buffer(self):
13 preread = 6
14 remaining_length = -1
15 while True:
16 if len(self.in_buffer) < preread:
17 break
18 lb = self.in_buffer[:preread]
19 if self.is_plain_msg is True:
20 remaining_length = calcualte_length(lb) - preread
21 else:
22 remaining_length = int.from_bytes(lb[:4], byteorder = 'big', signed = False)
23 remaining_length = (remaining_length + 4) - preread
24 if len(self.in_buffer) >= remaining_length+preread:
25 data = self.in_buffer[:remaining_length+preread]
26 self.in_buffer = self.in_buffer[remaining_length+preread:]
27 yield data
28 continue
29 break
30
31
32 async def data_out(self, data):
33 yield data
34
35 async def data_in(self, data):
36 if data is None:
37 yield data
38 self.in_buffer += data
39 for packet in self.process_buffer():
40 yield packet
+0
-0
msldap/network/proxy/__init__.py less more
(Empty file)
+0
-103
msldap/network/proxy/handler.py less more
0 import copy
1 import asyncio
2
3 from msldap.commons.proxy import MSLDAPProxyType
4
5
6 class Proxyhandler:
7 def __init__(self, target):
8 self.target = target
9
10 def select(self):
11 if self.target.proxy is None:
12 return self.target
13
14 if self.target.proxy.proxy_type in [MSLDAPProxyType.SOCKS5, MSLDAPProxyType.SOCKS5_SSL]:
15 import socket
16 try:
17 from socks5line.socks5line import Socks5LineProxyServer,SOCKS5Line
18 except ImportError:
19 raise Exception('Failed to import socks5line proxy emulator! Install it then retry!')
20 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
21 s.bind(('127.0.0.1', 0))
22 new_port = s.getsockname()[1]
23 proxy = Socks5LineProxyServer()
24 proxy.ip = self.target.proxy.ip
25 proxy.port = self.target.proxy.port
26 proxy.timeout = self.target.proxy.timeout
27 proxy.username = self.target.proxy.username
28 proxy.password = self.target.proxy.secret
29
30 sl = SOCKS5Line(proxy, self.target.host, self.target.port)
31 sl.run_newthread(s)
32
33 newtarget = copy.deepcopy(self.target)
34 newtarget.proxy = None
35 newtarget.host = '127.0.0.1'
36 newtarget.port = new_port
37
38 return newtarget
39
40 elif self.target.proxy.proxy_type in [LDAPProxyType.MULTIPLEXOR, LDAPProxyType.MULTIPLEXOR_SSL]:
41 import socket
42 try:
43 from socks5line.socks5line import Socks5LineProxyServer,SOCKS5Line
44 except ImportError:
45 raise Exception('Failed to import socks5line proxy emulator! Install it then retry!')
46 try:
47 from multiplexor.operator import MultiplexorOperator
48 except ImportError:
49 raise Exception('Failed to import multiplexor! Install it then retry!')
50
51 async def create_proxy(connection_string, agent_id):
52 try:
53 #creating operator and connecting to multiplexor server
54 self.operator = MultiplexorOperator(con_str, reconnect_tries = 1)
55 await self.operator.connect()
56 #creating socks5 proxy
57 server_info = await self.operator.start_socks5(agent_id)
58 asyncio.create_task(self.operator.terminate())
59 return server_info
60 except Exception as e:
61 asyncio.create_task(self.operator.terminate())
62 return e
63
64 #creating connection string
65 if self.target.proxy.proxy_type == LDAPProxyType.MULTIPLEXOR:
66 con_str = 'ws://%s:%s' % (self.target.proxy.ip, self.target.proxy.port)
67 else:
68 con_str = 'wss://%s:%s' % (self.target.proxy.ip, self.target.proxy.port)
69
70
71 #because of URL stuff, this logic needs to be in place
72 #if self.target.proxy.domain is None:
73 # agent_id = self.target.proxy.username
74 #else:
75 # agent_id = self.target.proxy.domain
76 print('proxy_connecting')
77 server_info = asyncio.run(create_proxy(con_str, self.target.proxy.settings['agentid'][0]))
78 print('socks5 server info %s' % server_info)
79 if isinstance(server_info, Exception):
80 raise Exception('Failed to create socks proxy Reason: %s '% server_info)
81 #copying the original target, then feeding it to socks5proxy object. it will hold the actual socks5 proxy server address we created before
82
83 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
84 s.bind(('127.0.0.1', 0))
85 new_port = s.getsockname()[1]
86 proxy = Socks5LineProxyServer()
87 proxy.ip = server_info['listen_ip']
88 proxy.port = server_info['listen_port']
89 proxy.timeout = self.target.proxy.timeout
90 proxy.username = self.target.proxy.username
91 proxy.password = self.target.proxy.secret
92
93 sl = SOCKS5Line(proxy, self.target.host, self.target.port)
94 sl.run_newthread(s)
95
96 print('socks5 socks5line ready')
97 newtarget = copy.deepcopy(self.target)
98 newtarget.proxy = None
99 newtarget.host = '127.0.0.1'
100 newtarget.port = new_port
101
102 return newtarget
+0
-32
msldap/network/selector.py less more
0
1 from msldap import logger
2 from msldap.network.tcp import MSLDAPTCPNetwork
3 from msldap.network.socks import SocksProxyConnection
4 from msldap.network.multiplexor import MultiplexorProxyConnection
5 from msldap.commons.proxy import MSLDAPProxyType
6
7 MSLDAP_SOCKS_PROXY_TYPES = [
8 MSLDAPProxyType.SOCKS4,
9 MSLDAPProxyType.SOCKS4_SSL,
10 MSLDAPProxyType.SOCKS5,
11 MSLDAPProxyType.SOCKS5_SSL,
12 MSLDAPProxyType.WSNET,
13 MSLDAPProxyType.WSNETWS,
14 MSLDAPProxyType.WSNETWSS,
15 ]
16
17 class MSLDAPNetworkSelector:
18 def __init__(self):
19 pass
20
21 @staticmethod
22 async def select(target):
23 if target.proxy is not None:
24 if target.proxy.type in MSLDAP_SOCKS_PROXY_TYPES:
25 return SocksProxyConnection(target)
26 else:
27 mpc = MultiplexorProxyConnection(target)
28 socks_proxy = await mpc.connect()
29 return socks_proxy
30
31 return MSLDAPTCPNetwork(target)
+0
-121
msldap/network/socks.py less more
0
1 import enum
2 import asyncio
3 import ipaddress
4
5 from msldap import logger
6
7 from asysocks.client import SOCKSClient
8 from asysocks.common.comms import SocksQueueComms
9 from msldap.protocol.utils import calcualte_length
10
11
12 class SocksProxyConnection:
13 """
14 Generic asynchronous TCP socket class, nothing SMB related.
15 Creates the connection and channels incoming/outgoing bytes via asynchonous queues.
16 """
17 def __init__(self, target):
18 self.target = target
19
20 self.client = None
21 self.proxy_task = None
22 self.handle_in_task = None
23
24 self.out_queue = None#asyncio.Queue()
25 self.in_queue = None#asyncio.Queue()
26
27 self.proxy_in_queue = None#asyncio.Queue()
28 self.is_plain_msg = True
29
30 async def disconnect(self):
31 """
32 Disconnects from the socket.
33 Stops the reader and writer streams.
34 """
35 if self.client is not None:
36 await self.client.terminate()
37 if self.proxy_task is not None:
38 self.proxy_task.cancel()
39 if self.handle_in_q is not None:
40 self.handle_in_task.cancel()
41
42 async def terminate(self):
43 await self.disconnect()
44
45 def get_peer_certificate(self):
46 raise Exception('Not yet implemented! SSL implementation on socks is missing!')
47 return self.writer.get_extra_info('socket').getpeercert(True)
48
49 def get_one_message(self,data):
50 if len(data) < 6:
51 return None
52
53 if self.is_plain_msg is True:
54 dl = calcualte_length(data[:6])
55 else:
56 dl = int.from_bytes(data[:4], byteorder = 'big', signed = False)
57 dl = dl + 4
58
59
60 #print(dl)
61 if len(data) >= dl:
62 return data[:dl]
63
64 async def handle_in_q(self):
65 try:
66 data = b''
67 while True:
68 while True:
69 msg_data = self.get_one_message(data)
70 if msg_data is None:
71 break
72
73 await self.in_queue.put((msg_data, None))
74 data = data[len(msg_data):]
75
76 temp, err = await self.proxy_in_queue.get()
77 #print(temp)
78 if err is not None:
79 raise err
80
81 if temp == b'' or temp is None:
82 logger.debug('Server finished!')
83 return
84
85 data += temp
86 continue
87
88 except asyncio.CancelledError:
89 return
90 except Exception as e:
91 logger.exception('handle_in_q')
92 await self.in_queue.put((None, e))
93
94 finally:
95 self.proxy_task.cancel()
96
97
98
99 async def run(self):
100 """
101
102 """
103 try:
104 self.out_queue = asyncio.Queue()
105 self.in_queue = asyncio.Queue()
106
107 self.proxy_in_queue = asyncio.Queue()
108 comms = SocksQueueComms(self.out_queue, self.proxy_in_queue)
109
110 self.target.proxy.target[-1].endpoint_ip = self.target.host if self.target.serverip is None else self.target.serverip
111 self.target.proxy.target[-1].endpoint_port = int(self.target.port)
112 self.target.proxy.target[-1].endpoint_timeout = None #TODO: maybe implement endpoint timeout?
113 self.target.proxy.target[-1].timeout = self.target.timeout
114 self.client = SOCKSClient(comms, self.target.proxy.target)
115 self.proxy_task = asyncio.create_task(self.client.run())
116 self.handle_in_task = asyncio.create_task(self.handle_in_q())
117 return True, None
118 except Exception as e:
119 return False, e
120
+0
-99
msldap/network/tcp.py less more
0
1 import asyncio
2
3 from msldap import logger
4 from msldap.protocol.utils import calcualte_length
5
6 class MSLDAPTCPNetwork:
7 def __init__(self, target):
8 self.target = target
9 self.timeout = None
10 self.in_queue = None
11 self.out_queue = None
12 self.reader = None
13 self.writer = None
14
15 self.handle_in_task = None
16 self.handle_out_task = None
17
18 self.is_plain_msg = True
19
20 async def terminate(self):
21 self.handle_in_task.cancel()
22 self.handle_out_task.cancel()
23
24 def get_peer_certificate(self):
25 return self.writer.get_extra_info('ssl_object').getpeercert(True)
26
27 async def handle_in_q(self):
28 try:
29 while True:
30
31 preread = 6
32 lb = await asyncio.wait_for(self.reader.readexactly(preread), self.timeout)
33 if lb is None:
34 logger.debug('Server timed out!')
35 return
36 if lb == b'':
37 logger.debug('Server finished!')
38 return
39
40 if self.is_plain_msg is True:
41 remaining_length = calcualte_length(lb) - preread
42 else:
43 remaining_length = int.from_bytes(lb[:4], byteorder = 'big', signed = False)
44 remaining_length = (remaining_length + 4) - preread
45 #print('Reading %s' % remaining_length)
46
47 remaining_data = await asyncio.wait_for(self.reader.readexactly(remaining_length), self.timeout)
48
49 await self.in_queue.put((lb+remaining_data, None))
50
51
52 #except asyncio.CancelledError:
53 # return
54 except Exception as e:
55 #logger.exception('handle_in_q')
56 await self.in_queue.put((None, e))
57
58 finally:
59 self.handle_out_task.cancel()
60
61 async def handle_out_q(self):
62 try:
63 while True:
64 data = await self.out_queue.get()
65 if data is None:
66 logger.debug('Client finished!')
67 return
68
69 self.writer.write(data)
70 await self.writer.drain()
71 except asyncio.CancelledError:
72 return
73 except:
74 logger.exception('handle_out_q')
75
76 finally:
77 self.writer.close()
78 self.handle_in_task.cancel()
79
80
81 async def run(self):
82 try:
83 self.in_queue = asyncio.Queue()
84 self.out_queue = asyncio.Queue()
85 self.reader, self.writer = await asyncio.wait_for(
86 asyncio.open_connection(
87 self.target.serverip if self.target.serverip is not None else self.target.host,
88 self.target.port,
89 ssl=self.target.get_ssl_context()
90 ),
91 timeout = self.target.timeout
92 )
93
94 self.handle_in_task = asyncio.create_task(self.handle_in_q())
95 self.handle_out_task = asyncio.create_task(self.handle_out_q())
96 return True, None
97 except Exception as e:
98 return False, e
+0
-125
msldap/network/wsnet.py less more
0
1 #
2 #
3 #
4 #
5 #
6 #
7
8
9 import enum
10 import asyncio
11 import ipaddress
12
13 from msldap import logger
14 from msldap.protocol.utils import calcualte_length
15
16 from pyodidewsnet.client import WSNetworkTCP
17
18
19
20 class WSNetProxyConnection:
21 """
22 Generic asynchronous TCP socket class, nothing SMB related.
23 Creates the connection and channels incoming/outgoing bytes via asynchonous queues.
24 """
25 def __init__(self, target):
26 self.target = target
27
28 self.client = None
29 self.handle_in_task = None
30
31 self.out_queue = None#asyncio.Queue()
32 self.in_queue = None#asyncio.Queue()
33
34 self.proxy_in_queue = None#asyncio.Queue()
35 self.is_plain_msg = True
36
37 async def disconnect(self):
38 """
39 Disconnects from the socket.
40 Stops the reader and writer streams.
41 """
42 if self.client is not None:
43 await self.client.terminate()
44 if self.handle_in_q is not None:
45 self.handle_in_task.cancel()
46
47 async def terminate(self):
48 await self.disconnect()
49
50 def get_peer_certificate(self):
51 raise Exception('Not yet implemented! SSL implementation on socks is missing!')
52 return self.writer.get_extra_info('socket').getpeercert(True)
53
54 def get_one_message(self,data):
55 if len(data) < 6:
56 return None
57
58 if self.is_plain_msg is True:
59 dl = calcualte_length(data[:6])
60 else:
61 dl = int.from_bytes(data[:4], byteorder = 'big', signed = False)
62 dl = dl + 4
63
64
65 #print(dl)
66 if len(data) >= dl:
67 return data[:dl]
68
69 async def handle_in_q(self):
70 try:
71 data = b''
72 while True:
73 while True:
74 msg_data = self.get_one_message(data)
75 if msg_data is None:
76 break
77
78 await self.in_queue.put((msg_data, None))
79 data = data[len(msg_data):]
80
81 temp, err = await self.proxy_in_queue.get()
82 #print(temp)
83 if err is not None:
84 raise err
85
86 if temp == b'' or temp is None:
87 logger.debug('Server finished!')
88 return
89
90 data += temp
91 continue
92
93 except asyncio.CancelledError:
94 return
95 except Exception as e:
96 logger.exception('handle_in_q')
97 await self.in_queue.put((None, e))
98
99 finally:
100 await self.client.terminate()
101
102
103
104 async def run(self):
105 """
106
107 """
108 try:
109 self.out_queue = asyncio.Queue()
110 self.in_queue = asyncio.Queue()
111 self.proxy_in_queue = asyncio.Queue()
112
113 self.client = WSNetworkTCP(self.target.host, int(self.target.port), self.proxy_in_queue, self.out_queue)
114 _, err = await self.client.run()
115 if err is not None:
116 raise err
117
118 self.handle_in_task = asyncio.create_task(self.handle_in_q())
119
120 return True, None
121
122 except Exception as e:
123 return False, e
124
2020 elif value == '*':
2121 return Filter({
2222 'present' : AttributeDescription(attr.encode())
23 })
24
25 elif value.startswith('*') and value.endswith('*'):
26 return Filter({
27 'substrings' : SubstringFilter({
28 'type' : attr.encode(),
29 'substrings' : Substrings([
30 Substring({
31 'any' : value[1:-1].encode()
32 })
33 ])
34 })
2335 })
2436
2537 elif value.startswith('*') is True:
9292 def list_bytes_one_enc(x):
9393 return x
9494
95 def bytes2timedelta(x):
96 return int2timedelta([int.from_bytes(x[0], byteorder='little', signed=True)])
97
9598 def int2timedelta(x):
9699 x = int(x[0])
97100 if x == '-9223372036854775808':
165168 t.append(ts2dt((a, None)))
166169 return t
167170
171 def list_ts2dt_one(x):
172 return ts2dt(x[0])
168173
169174 LDAP_ATTRIBUTE_TYPES = {
170175 'supportedCapabilities' : list_str,
274279 'unicodePwd' : list_str_one,
275280 'ms-Mcs-AdmPwd' : list_str_one,
276281 'msDS-AllowedToActOnBehalfOfOtherIdentity' : list_bytes_one,
282 'cACertificate': list_bytes_one,
283 'certificateTemplates': list_str,
284 'cACertificateDN': list_str_one,
285 'msPKI-Enrollment-Servers': list_str, # it's actually a 5-part multi string split by '\n'
286 'revision' : list_int_one,
287 'pKIKeyUsage' : list_bytes_one,
288 'pKIDefaultKeySpec' : list_str_one,
289 'pKIMaxIssuingDepth' : list_int_one,
290 'pKICriticalExtensions' : list_str_one,
291 'pKIExpirationPeriod' : bytes2timedelta,
292 'pKIOverlapPeriod' : bytes2timedelta,
293 'pKIExtendedKeyUsage' : list_str,
294 'msPKI-RA-Signature' : list_int_one,
295 'msPKI-Enrollment-Flag' : list_int_one,
296 'msPKI-Private-Key-Flag' : list_int_one,
297 'msPKI-Certificate-Name-Flag' : list_int_one,
298 'msPKI-Minimal-Key-Size' : list_int_one,
299 'msPKI-Template-Schema-Version': list_int_one,
300 'msPKI-Template-Minor-Revision': list_int_one,
301 'msPKI-Cert-Template-OID' : list_str_one,
302 'msPKI-Certificate-Application-Policy' : list_str,
303 'msPKI-RA-Application-Policies' : list_str, #I'm guessing here
277304 }
278305
279306 LDAP_ATTRIBUTE_TYPES_ENC = {
294321 'member' : list_str_enc,
295322 'msDS-AllowedToActOnBehalfOfOtherIdentity' : list_bytes_one_enc,
296323 'nTSecurityDescriptor' : list_bytes_one_enc,
324 'msPKI-Certificate-Name-Flag' : list_int_one_enc
297325 }
298326
299327 def encode_attributes(x):
0 Metadata-Version: 1.2
0 Metadata-Version: 2.1
11 Name: msldap
2 Version: 0.3.30
2 Version: 0.4.1
33 Summary: Python library to play with MS LDAP
44 Home-page: https://github.com/skelsec/msldap
55 Author: Tamas Jos
66 Author-email: [email protected]
77 License: UNKNOWN
8 Description: Python library to play with MS LDAP
98 Platform: UNKNOWN
109 Classifier: Programming Language :: Python :: 3.7
1110 Classifier: Programming Language :: Python :: 3.8
1211 Classifier: License :: OSI Approved :: MIT License
1312 Classifier: Operating System :: OS Independent
1413 Requires-Python: >=3.7
14 License-File: LICENSE
15
16 Python library to play with MS LDAP
17
1313 msldap.egg-info/not-zip-safe
1414 msldap.egg-info/requires.txt
1515 msldap.egg-info/top_level.txt
16 msldap/authentication/__init__.py
17 msldap/authentication/kerberos/__init__.py
18 msldap/authentication/kerberos/gssapi.py
19 msldap/authentication/kerberos/multiplexor.py
20 msldap/authentication/kerberos/native.py
21 msldap/authentication/kerberos/sspi.py
22 msldap/authentication/kerberos/sspiproxyws.py
23 msldap/authentication/kerberos/wsnet.py
24 msldap/authentication/ntlm/__init__.py
25 msldap/authentication/ntlm/creds_calc.py
26 msldap/authentication/ntlm/multiplexor.py
27 msldap/authentication/ntlm/native.py
28 msldap/authentication/ntlm/sspi.py
29 msldap/authentication/ntlm/sspiproxy.py
30 msldap/authentication/ntlm/wsnet.py
31 msldap/authentication/ntlm/messages/__init__.py
32 msldap/authentication/ntlm/messages/authenticate.py
33 msldap/authentication/ntlm/messages/challenge.py
34 msldap/authentication/ntlm/messages/negotiate.py
35 msldap/authentication/ntlm/structures/__init__.py
36 msldap/authentication/ntlm/structures/avpair.py
37 msldap/authentication/ntlm/structures/challenge_response.py
38 msldap/authentication/ntlm/structures/fields.py
39 msldap/authentication/ntlm/structures/negotiate_flags.py
40 msldap/authentication/ntlm/structures/ntlmssp_message_signature.py
41 msldap/authentication/ntlm/structures/ntlmssp_message_signature_noext.py
42 msldap/authentication/ntlm/structures/version.py
43 msldap/authentication/ntlm/templates/__init__.py
44 msldap/authentication/ntlm/templates/client.py
45 msldap/authentication/ntlm/templates/server.py
46 msldap/authentication/spnego/__init__.py
47 msldap/authentication/spnego/asn1_structs.py
48 msldap/authentication/spnego/native.py
49 msldap/authentication/spnego/sspi.py
5016 msldap/commons/__init__.py
5117 msldap/commons/authbuilder.py
5218 msldap/commons/common.py
53 msldap/commons/credential.py
5419 msldap/commons/exceptions.py
55 msldap/commons/proxy.py
20 msldap/commons/factory.py
5621 msldap/commons/target.py
57 msldap/commons/url.py
5822 msldap/commons/utils.py
59 msldap/crypto/AES.py
60 msldap/crypto/BASE.py
61 msldap/crypto/DES.py
62 msldap/crypto/MD4.py
63 msldap/crypto/RC4.py
64 msldap/crypto/TDES.py
65 msldap/crypto/__init__.py
66 msldap/crypto/hashing.py
67 msldap/crypto/symmetric.py
68 msldap/crypto/pure/__init__.py
69 msldap/crypto/pure/AES/AES.py
70 msldap/crypto/pure/AES/__init__.py
71 msldap/crypto/pure/AES/blockfeeder.py
72 msldap/crypto/pure/AES/util.py
73 msldap/crypto/pure/DES/DES.py
74 msldap/crypto/pure/DES/__init__.py
75 msldap/crypto/pure/RC4/RC4.py
76 msldap/crypto/pure/RC4/__init__.py
7723 msldap/examples/__init__.py
7824 msldap/examples/msldapclient.py
7925 msldap/examples/msldapcompdnslist.py
9036 msldap/external/asciitree/asciitree/traversal.py
9137 msldap/external/asciitree/asciitree/util.py
9238 msldap/ldap_objects/__init__.py
39 msldap/ldap_objects/adca.py
40 msldap/ldap_objects/adcertificatetemplate.py
9341 msldap/ldap_objects/adcomp.py
42 msldap/ldap_objects/adenrollmentservice.py
9443 msldap/ldap_objects/adgpo.py
9544 msldap/ldap_objects/adgroup.py
9645 msldap/ldap_objects/adinfo.py
10150 msldap/ldap_objects/aduser.py
10251 msldap/ldap_objects/common.py
10352 msldap/network/__init__.py
104 msldap/network/multiplexor.py
105 msldap/network/selector.py
106 msldap/network/socks.py
107 msldap/network/tcp.py
108 msldap/network/wsnet.py
109 msldap/network/proxy/__init__.py
110 msldap/network/proxy/handler.py
53 msldap/network/packetizer.py
11154 msldap/protocol/__init__.py
11255 msldap/protocol/constants.py
11356 msldap/protocol/messages.py
0 asn1crypto
1 asysocks>=0.1.1
2 minikerberos>=0.2.14
0 asn1crypto>=1.3.0
1 asyauth>=0.0.2
2 asysocks>=0.2.1
3 minikerberos>=0.3.1
34 prompt-toolkit>=3.0.2
45 tqdm
5 winacl>=0.1.1
6
7 [:platform_system == "Windows"]
8 winsspi>=0.0.9
6 unicrypto>=0.0.9
7 wcwidth
8 winacl>=0.1.4
4646 "Operating System :: OS Independent",
4747 ),
4848 install_requires=[
49 'asn1crypto',
50 'winsspi>=0.0.9;platform_system=="Windows"',
51 'minikerberos>=0.2.14',
52 'asysocks>=0.1.1',
53 'winacl>=0.1.1',
49 'unicrypto>=0.0.9',
50 'asyauth>=0.0.2',
51 'asysocks>=0.2.1',
52 'asn1crypto>=1.3.0',
53 'minikerberos>=0.3.1',
54 'winacl>=0.1.4',
5455 'prompt-toolkit>=3.0.2',
5556 'tqdm',
57 'wcwidth',
5658 ],
5759 entry_points={
5860 'console_scripts': [
6062 'msldapcompdns = msldap.examples.msldapcompdnslist:main',
6163 ],
6264 }
63 )
65 )