New upstream release.
Kali Janitor
1 year, 8 months ago
0 | Metadata-Version: 1.2 | |
0 | Metadata-Version: 2.1 | |
1 | 1 | Name: msldap |
2 | Version: 0.3.30 | |
2 | Version: 0.4.1 | |
3 | 3 | Summary: Python library to play with MS LDAP |
4 | 4 | Home-page: https://github.com/skelsec/msldap |
5 | 5 | Author: Tamas Jos |
6 | 6 | Author-email: [email protected] |
7 | License: UNKNOWN | |
8 | Description: Python library to play with MS LDAP | |
9 | Platform: UNKNOWN | |
10 | 7 | Classifier: Programming Language :: Python :: 3.7 |
11 | 8 | Classifier: Programming Language :: Python :: 3.8 |
12 | 9 | Classifier: License :: OSI Approved :: MIT License |
13 | 10 | Classifier: Operating System :: OS Independent |
14 | 11 | Requires-Python: >=3.7 |
12 | License-File: LICENSE | |
13 | ||
14 | Python library to play with MS LDAP |
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) | |
1 | 1 | |
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/ | |
4 | 3 | |
5 | 4 | # msldap |
6 | 5 | 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 | ||
7 | 18 | |
8 | 19 | # Documentation |
9 | 20 | [Awesome documentation here!](https://msldap.readthedocs.io/en/latest/) |
28 | 39 | `pip install msldap` |
29 | 40 | |
30 | 41 | # Prerequisites |
31 | - `winsspi` module. For windows only. This supports SSPI based authentication. | |
32 | 42 | - `asn1crypto` module. Some LDAP queries incorporate ASN1 strucutres to be sent on top of the ASN1 transport XD |
33 | 43 | - `asysocks` module. To support socks proxying. |
34 | 44 | - `aiocmd` For the interactive client |
59 | 69 | - kerberos-aes (dc option param must be used) |
60 | 70 | - kerberos-keytab (dc option param must be used) |
61 | 71 | - 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) | |
62 | 75 | - sspi-ntlm (windows only!) |
63 | 76 | - sspi-kerberos (windows only!) |
64 | 77 | - anonymous |
95 | 108 | ``` |
96 | 109 | |
97 | 110 | # Kudos |
98 | ||
111 | Certificate services functionality was based on [certi](https://github.com/zer1t0/certi) created by @zer1t0 |
0 | msldap (0.4.1-0kali1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream release. | |
3 | ||
4 | -- Kali Janitor <[email protected]> Thu, 15 Sep 2022 03:15:11 -0000 | |
5 | ||
0 | 6 | msldap (0.3.30-0kali1) kali-dev; urgency=medium |
1 | 7 | |
2 | 8 | [ Kali Janitor ] |
5 | 5 | |
6 | 6 | import logging |
7 | 7 | 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 | |
10 | 10 | from msldap.ldap_objects import MSADUser, MSADMachine |
11 | 11 | |
12 | 12 | |
38 | 38 | else: |
39 | 39 | logging.basicConfig(level=logging.DEBUG) |
40 | 40 | |
41 | url_dec = MSLDAPURLDecoder(args.connection) | |
41 | url_dec = LDAPConnectionFactory.from_url(args.connection) | |
42 | 42 | creds = url_dec.get_credential() |
43 | 43 | target = url_dec.get_target() |
44 | 44 | print(str(creds)) |
45 | 45 | print(str(target)) |
46 | connection = MSLDAPConnection(creds, target) | |
46 | connection = MSLDAPClientConnection(creds, target) | |
47 | 47 | |
48 | 48 | if args.command == 'dsa': |
49 | 49 | print(connection.get_server_info()) |
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 | ||
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 | # | |
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 | # | |
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 | ||
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 | ||
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 | 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 | 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 | 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 | 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 | # | |
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 | 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 | # | |
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 | 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 | 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 | 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 | ||
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 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | #!/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 | #!/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 | #!/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 | ⏎ |
3 | 3 | # Tamas Jos (@skelsec) |
4 | 4 | # |
5 | 5 | |
6 | from codecs import lookup | |
6 | 7 | import copy |
7 | 8 | import asyncio |
8 | 9 | |
15 | 16 | from msldap.connection import MSLDAPClientConnection |
16 | 17 | from msldap.protocol.messages import Control |
17 | 18 | from msldap.ldap_objects import * |
19 | from msldap.commons.utils import KNOWN_SIDS | |
18 | 20 | |
19 | 21 | from winacl.dtyp.security_descriptor import SECURITY_DESCRIPTOR |
20 | 22 | from winacl.dtyp.ace import ACCESS_ALLOWED_OBJECT_ACE, ADS_ACCESS_MASK |
37 | 39 | :rtype: dict |
38 | 40 | |
39 | 41 | """ |
40 | def __init__(self, target, creds): | |
42 | def __init__(self, target, creds, connection = None, keepalive = False): | |
41 | 43 | self.creds = creds |
42 | 44 | 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 | ||
45 | 54 | self._tree = None |
46 | 55 | 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 | |
48 | 62 | |
49 | 63 | async def __aenter__(self): |
50 | 64 | return self |
52 | 66 | async def __aexit__(self, exc_type, exc, traceback): |
53 | 67 | await asyncio.wait_for(self.disconnect(), timeout = 1) |
54 | 68 | |
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 | ||
55 | 87 | async def disconnect(self): |
56 | 88 | try: |
89 | if self.__keepalive_task is not None: | |
90 | self.__keepalive_task.cancel() | |
57 | 91 | if self._con is not None: |
58 | 92 | await self._con.disconnect() |
93 | ||
94 | self.disconnected_evt.set() | |
59 | 95 | |
60 | 96 | except Exception as e: |
61 | 97 | return False, e |
62 | 98 | |
63 | 99 | async def connect(self): |
64 | 100 | 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 | |
72 | 110 | res, err = await self._con.get_serverinfo() |
73 | 111 | if err is not None: |
74 | 112 | raise err |
75 | 113 | self._serverinfo = res |
76 | 114 | self._tree = res['defaultNamingContext'] |
77 | 115 | 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()) | |
78 | 119 | if err is not None: |
79 | 120 | raise err |
80 | 121 | return True, None |
84 | 125 | def get_server_info(self): |
85 | 126 | return self._serverinfo |
86 | 127 | |
87 | async def pagedsearch(self, query, attributes, controls = None): | |
128 | async def pagedsearch(self, query, attributes, controls = None, tree = None): | |
88 | 129 | """ |
89 | 130 | Performs a paged search on the AD, using the filter and attributes as a normal query does. |
90 | 131 | !The LDAP connection MUST be active before invoking this function! |
111 | 152 | print('Theconnection is in stopped state!') |
112 | 153 | return |
113 | 154 | |
114 | if self._tree is None: | |
155 | if tree is None: | |
156 | tree = self._tree | |
157 | if tree is None: | |
115 | 158 | raise Exception('BIND first!') |
116 | 159 | t = [] |
117 | 160 | for x in attributes: |
130 | 173 | controls = t |
131 | 174 | |
132 | 175 | async for entry, err in self._con.pagedsearch( |
133 | self._tree, | |
176 | tree, | |
134 | 177 | query, |
135 | 178 | attributes = attributes, |
136 | 179 | size_limit = self.ldap_query_page_size, |
137 | 180 | controls = controls, |
138 | rate_limit=self.target.ldap_query_ratelimit | |
181 | rate_limit=self.ldap_query_ratelimit | |
139 | 182 | ): |
140 | 183 | |
141 | 184 | if err is not None: |
169 | 212 | size_limit = self.ldap_query_page_size, |
170 | 213 | search_scope=LEVEL, |
171 | 214 | controls = None, |
172 | rate_limit=self.target.ldap_query_ratelimit | |
215 | rate_limit=self.ldap_query_ratelimit | |
173 | 216 | ): |
174 | 217 | if err is not None: |
175 | 218 | raise err |
184 | 227 | tree[entry['attributes']['distinguishedName']] = subtree |
185 | 228 | return {root_dn : tree} |
186 | 229 | |
187 | async def get_all_users(self): | |
230 | async def get_all_users(self, attrs = MSADUser_ATTRS): | |
188 | 231 | """ |
189 | 232 | Fetches all user objects available in the LDAP tree and yields them as MSADUser object. |
190 | 233 | |
194 | 237 | """ |
195 | 238 | logger.debug('Polling AD for all user objects') |
196 | 239 | 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): | |
198 | 241 | if err is not None: |
199 | 242 | yield None, err |
200 | 243 | return |
221 | 264 | yield MSADMachine.from_ldap(entry, self._ldapinfo), None |
222 | 265 | logger.debug('Finished polling for entries!') |
223 | 266 | |
224 | async def get_all_gpos(self): | |
267 | async def get_all_gpos(self, attrs = MSADGPO_ATTRS): | |
225 | 268 | """ |
226 | 269 | Fetches all GPOs available in the LDAP tree and yields them as MSADGPO object. |
227 | 270 | |
231 | 274 | """ |
232 | 275 | |
233 | 276 | 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): | |
235 | 278 | if err is not None: |
236 | 279 | yield None, err |
237 | 280 | return |
292 | 335 | size_limit = self.ldap_query_page_size, |
293 | 336 | search_scope=BASE, |
294 | 337 | controls = None, |
295 | rate_limit=self.target.ldap_query_ratelimit | |
338 | rate_limit=self.ldap_query_ratelimit | |
296 | 339 | ): |
297 | 340 | if err is not None: |
298 | 341 | yield None, err |
525 | 568 | } |
526 | 569 | return await self._con.modify(object_dn, changes, controls = controls) |
527 | 570 | |
528 | async def get_all_groups(self): | |
571 | async def get_all_groups(self, attrs = MSADGroup_ATTRS): | |
529 | 572 | """ |
530 | 573 | Yields all Groups present in the LDAP tree. |
531 | 574 | |
533 | 576 | :rtype: Iterator[(:class:`MSADGroup`, :class:`Exception`)] |
534 | 577 | """ |
535 | 578 | 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): | |
537 | 580 | if err is not None: |
538 | 581 | yield None, err |
539 | 582 | return |
568 | 611 | if err is not None: |
569 | 612 | return None, err |
570 | 613 | return MSADGroup.from_ldap(entry), None |
614 | ||
615 | return None, Exception('Search returned no results!') | |
571 | 616 | |
572 | 617 | async def get_user_by_dn(self, user_dn): |
573 | 618 | """ |
597 | 642 | :rtype: Iterator[(:class:`MSADUser`, :class:`Exception`)] |
598 | 643 | """ |
599 | 644 | |
600 | group, err = self.get_group_by_dn(dn) | |
645 | group, err = await self.get_group_by_dn(dn) | |
601 | 646 | if err is not None: |
602 | 647 | yield None, err |
603 | 648 | return |
604 | 649 | 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): | |
606 | 651 | if isinstance(result, MSADGroup) and recursive: |
607 | 652 | async for user, err in self.get_group_members(result.distinguishedName, recursive = True): |
608 | 653 | yield user, err |
609 | 654 | else: |
610 | 655 | yield result, err |
656 | ||
611 | 657 | |
612 | 658 | async def get_dn_for_objectsid(self, objectsid): |
613 | 659 | """ |
626 | 672 | return None, err |
627 | 673 | |
628 | 674 | return entry['attributes']['distinguishedName'], None |
675 | ||
676 | return None, Exception('Search returned no results!') | |
629 | 677 | |
630 | 678 | async def get_objectsid_for_dn(self, dn): |
631 | 679 | """ |
644 | 692 | return None, err |
645 | 693 | |
646 | 694 | 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 | ||
648 | 718 | async def get_tokengroups(self, dn): |
649 | 719 | """ |
650 | 720 | Yields SIDs of groups that the given DN is a member of. |
662 | 732 | attributes = attributes, |
663 | 733 | size_limit = self.ldap_query_page_size, |
664 | 734 | search_scope=BASE, |
665 | rate_limit=self.target.ldap_query_ratelimit | |
735 | rate_limit=self.ldap_query_ratelimit | |
666 | 736 | ): |
667 | 737 | if err is not None: |
668 | 738 | yield None, err |
699 | 769 | attributes = [b'tokenGroups'], |
700 | 770 | size_limit = self.ldap_query_page_size, |
701 | 771 | search_scope=BASE, |
702 | rate_limit=self.target.ldap_query_ratelimit | |
772 | rate_limit=self.ldap_query_ratelimit | |
703 | 773 | ): |
704 | 774 | |
705 | 775 | #print(entry2) |
1147 | 1217 | return True, None |
1148 | 1218 | except Exception as e: |
1149 | 1219 | 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 | |
1150 | 1393 | |
1151 | 1394 | #async def get_permissions_for_dn(self, dn): |
1152 | 1395 | # """ |
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 | |
2 | 3 | |
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() | |
13 | 11 | |
14 | from minikerberos.network.selector import KerberosClientSocketSelector | |
12 | elif credential.protocol in [asyauthProtocol.SIMPLE, asyauthProtocol.PLAIN]: | |
13 | return credential | |
15 | 14 | |
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 | #!/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 | ||
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 |
4 | 4 | # Tamas Jos (@skelsec) |
5 | 5 | # |
6 | 6 | |
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 | |
8 | 12 | |
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 | } | |
20 | 17 | |
21 | 18 | |
22 | class MSLDAPTarget: | |
19 | class MSLDAPTarget(UniTarget): | |
23 | 20 | """ |
24 | 21 | Describes the connection to the server. |
25 | 22 | |
28 | 25 | :param port: port of the LDAP service running on the server |
29 | 26 | :type port: int |
30 | 27 | :param proto: Connection protocol to be used |
31 | :type proto: :class:`LDAPProtocol` | |
28 | :type proto: :class:`UniProto` | |
32 | 29 | :param tree: The tree to connect to |
33 | 30 | :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]` | |
36 | 33 | :param timeout: connection timeout in seconds |
37 | 34 | :type timeout: int |
38 | 35 | :param ldap_query_page_size: Maximum number of elements to fetch in each paged_query call. |
39 | 36 | :type ldap_query_page_size: int |
40 | 37 | :param ldap_query_ratelimit: rate limit of paged queries. This will cause a sleep (in seconds) between fetching of each page of the query |
41 | 38 | :type ldap_query_ratelimit: float |
39 | :param dc_ip: Ip address of the kerberos server (if kerberos is used) | |
40 | :type dc_ip: str | |
42 | 41 | """ |
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) | |
46 | 44 | 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 | |
54 | 45 | self.ldap_query_page_size = ldap_query_page_size |
55 | 46 | 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 | ||
66 | 48 | 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 | |
68 | 50 | |
69 | 51 | 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) | |
71 | 57 | |
72 | 58 | 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 | ||
74 | 115 | |
75 | 116 | def __str__(self): |
76 | 117 | t = '==== MSLDAPTarget ====\r\n' |
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() |
0 | 0 | |
1 | 1 | import datetime |
2 | 2 | |
3 | def timestamp2datetime(dt): | |
3 | def timestamp2datetime(dt) -> datetime.datetime: | |
4 | 4 | """ |
5 | 5 | Converting Windows timestamps to datetime.datetime format |
6 | 6 | :param dt: Windows timestamp as array of bytes |
11 | 11 | return datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=us) |
12 | 12 | |
13 | 13 | |
14 | def datetime2timestamp(dt): | |
14 | def datetime2timestamp(dt) -> int: | |
15 | 15 | delta = dt - datetime.datetime(1601, 1, 1) |
16 | 16 | 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 | } |
2 | 2 | |
3 | 3 | from msldap import logger |
4 | 4 | from msldap.commons.common import MSLDAPClientStatus |
5 | from .commons.target import MSLDAPTarget | |
5 | 6 | from msldap.protocol.messages import LDAPMessage, BindRequest, \ |
6 | 7 | protocolOp, AuthenticationChoice, SaslCredentials, \ |
7 | 8 | SearchRequest, AttributeDescription, Filter, Filters, \ |
8 | 9 | Controls, Control, SearchControlValue, AddRequest, \ |
9 | ModifyRequest, DelRequest | |
10 | ModifyRequest, DelRequest, ExtendedRequest, ExtendedResponse | |
10 | 11 | |
11 | 12 | from msldap.protocol.utils import calcualte_length |
12 | 13 | from msldap.protocol.typeconversion import convert_result, convert_attributes, encode_attributes, encode_changes |
13 | 14 | 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 | |
21 | 19 | from hashlib import sha256 |
22 | 20 | 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 | |
23 | 24 | |
24 | 25 | 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): | |
28 | 27 | 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 | ||
31 | 34 | self.connected = False |
32 | 35 | self.bind_ok = False |
33 | 36 | self.__sign_messages = False |
52 | 55 | |
53 | 56 | async def __handle_incoming(self): |
54 | 57 | 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(): | |
61 | 59 | #print('Incoming message data: %s' % message_data) |
62 | 60 | if self.bind_ok is True: |
63 | 61 | if self.__encrypt_messages is True: |
145 | 143 | self.encryption_sequence_counter += 1 |
146 | 144 | |
147 | 145 | 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) | |
149 | 147 | |
150 | 148 | return curr_msg_id |
151 | 149 | |
175 | 173 | """ |
176 | 174 | try: |
177 | 175 | 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() | |
182 | 179 | |
183 | 180 | # now processing channel binding options |
184 | if self.target.proto == LDAPProtocol.SSL: | |
181 | if self.target.protocol == UniProto.CLIENT_SSL_TCP: | |
185 | 182 | certdata = self.network.get_peer_certificate() |
186 | #cert = Certificate.load(certdata).native | |
187 | #print(cert) | |
188 | 183 | cb_struct = ChannelBindingsStruct() |
189 | 184 | cb_struct.application_data = b'tls-server-end-point:' + sha256(certdata).digest() |
190 | 185 | |
206 | 201 | |
207 | 202 | logger.debug('Disconnecting!') |
208 | 203 | self.bind_ok = False |
204 | if self.network is not None: | |
205 | await self.network.close() | |
206 | await asyncio.sleep(0) | |
207 | ||
209 | 208 | if self.handle_incoming_task is not None: |
210 | 209 | self.handle_incoming_task.cancel() |
211 | if self.network is not None: | |
212 | await self.network.terminate() | |
213 | 210 | |
214 | 211 | |
215 | 212 | def __bind_success(self): |
219 | 216 | """ |
220 | 217 | logger.debug('BIND Success!') |
221 | 218 | 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]: | |
223 | 220 | self.__sign_messages = self.auth.signing_needed() |
224 | 221 | self.__encrypt_messages = self.auth.encryption_needed() |
225 | 222 | if self.__encrypt_messages or self.__sign_messages: |
226 | self.network.is_plain_msg = False | |
223 | self.network.packetizer.is_plain_msg = False | |
227 | 224 | |
228 | 225 | async def bind(self): |
229 | 226 | """ |
235 | 232 | """ |
236 | 233 | logger.debug('BIND in progress...') |
237 | 234 | 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()) | |
241 | 238 | if err is not None: |
242 | 239 | return None, err |
243 | 240 | |
291 | 288 | res['protocolOp']['diagnosticMessage'] |
292 | 289 | ) |
293 | 290 | |
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()) | |
295 | 292 | if err is not None: |
296 | 293 | return None, err |
297 | 294 | |
324 | 321 | self.__bind_success() |
325 | 322 | return True, None |
326 | 323 | |
327 | elif self.creds.auth_method == LDAPAuthProtocol.SIMPLE: | |
324 | elif self.credential.protocol == asyauthProtocol.SIMPLE: | |
328 | 325 | 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() | |
331 | 328 | |
332 | 329 | user = b'' |
333 | 330 | if self.auth.username != None: |
362 | 359 | res['protocolOp']['diagnosticMessage'] |
363 | 360 | ) |
364 | 361 | |
365 | elif self.creds.auth_method in MSLDAP_GSS_METHODS: | |
362 | elif self.credential.protocol in [asyauthProtocol.NTLM, asyauthProtocol.KERBEROS]: | |
366 | 363 | challenge = None |
367 | 364 | while True: |
368 | 365 | 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()) | |
370 | 367 | if err is not None: |
371 | 368 | raise err |
372 | 369 | except Exception as e: |
397 | 394 | res = res.native |
398 | 395 | if res['protocolOp']['resultCode'] == 'success': |
399 | 396 | 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()) | |
401 | 398 | if err is not None: |
402 | 399 | return False, err |
403 | 400 | |
417 | 414 | ) |
418 | 415 | |
419 | 416 | 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) | |
421 | 418 | except Exception as e: |
419 | await self.disconnect() | |
422 | 420 | return False, e |
423 | 421 | |
424 | 422 | async def add(self, entry, attributes): |
704 | 702 | except Exception as e: |
705 | 703 | yield (None, e) |
706 | 704 | |
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 | ||
707 | 728 | |
708 | 729 | async def get_serverinfo(self): |
709 | 730 | if self.status != MSLDAPClientStatus.RUNNING: |
753 | 774 | return convert_attributes(res.native['protocolOp']['attributes']), None |
754 | 775 | |
755 | 776 | |
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 | ||
790 | 778 | |
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 | ||
828 | 779 | |
829 | 780 | |
830 | 781 |
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 | 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 | """ | |
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 | #!/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 | """ | |
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 | """ | |
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 | 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 | ||
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 | #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 | ||
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 | ||
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 | ############################################################################# | |
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 | #!/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 | 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') |
4 | 4 | # |
5 | 5 | |
6 | 6 | import asyncio |
7 | from os import terminal_size | |
7 | 8 | import traceback |
8 | 9 | import logging |
9 | 10 | import csv |
10 | 11 | import shlex |
11 | 12 | import datetime |
12 | 13 | import copy |
14 | import typing | |
13 | 15 | |
14 | 16 | from msldap.external.aiocmd.aiocmd import aiocmd |
15 | 17 | from msldap.external.asciitree.asciitree import LeftAligned |
18 | 20 | from msldap import logger |
19 | 21 | from asysocks import logger as sockslogger |
20 | 22 | from msldap.client import MSLDAPClient |
21 | from msldap.commons.url import MSLDAPURLDecoder | |
23 | from msldap.commons.factory import LDAPConnectionFactory | |
22 | 24 | from msldap.ldap_objects import MSADUser, MSADMachine, MSADUser_TSV_ATTRS |
23 | 25 | |
24 | 26 | 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 | |
26 | 28 | from winacl.dtyp.sid import SID |
27 | 29 | 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 | |
28 | 33 | |
29 | 34 | |
30 | 35 | class MSLDAPClientConsole(aiocmd.PromptToolkitCmd): |
32 | 37 | aiocmd.PromptToolkitCmd.__init__(self, ignore_sigint=False) #Setting this to false, since True doesnt work on windows... |
33 | 38 | self.conn_url = None |
34 | 39 | if url is not None: |
35 | self.conn_url = MSLDAPURLDecoder(url) | |
40 | self.conn_url = LDAPConnectionFactory.from_url(url) | |
36 | 41 | self.connection = None |
37 | 42 | self.adinfo = None |
38 | 43 | self.ldapinfo = None |
44 | 49 | if self.conn_url is None and url is None: |
45 | 50 | print('Not url was set, cant do logon') |
46 | 51 | if url is not None: |
47 | self.conn_url = MSLDAPURLDecoder(url) | |
52 | self.conn_url = LDAPConnectionFactory.from_url(url) | |
48 | 53 | |
49 | 54 | logger.debug(self.conn_url.get_credential()) |
50 | 55 | logger.debug(self.conn_url.get_target()) |
54 | 59 | _, err = await self.connection.connect() |
55 | 60 | if err is not None: |
56 | 61 | raise err |
62 | print('BIND OK!') | |
57 | 63 | |
58 | 64 | return True |
59 | 65 | except: |
211 | 217 | traceback.print_exc() |
212 | 218 | return False |
213 | 219 | |
214 | async def do_user(self, samaccountname): | |
220 | async def do_user(self, samaccountname, to_print=True): | |
215 | 221 | """Feteches a user object based on the sAMAccountName of the user""" |
216 | 222 | try: |
217 | 223 | await self.do_ldapinfo(False) |
219 | 225 | user, err = await self.connection.get_user(samaccountname) |
220 | 226 | if err is not None: |
221 | 227 | 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 | |
228 | 236 | except: |
229 | 237 | traceback.print_exc() |
230 | 238 | return False |
604 | 612 | except: |
605 | 613 | traceback.print_exc() |
606 | 614 | 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 | ||
608 | 855 | async def do_test(self): |
609 | 856 | """testing, dontuse""" |
610 | 857 | try: |
6 | 6 | import asyncio |
7 | 7 | import traceback |
8 | 8 | 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 | |
16 | 9 | |
17 | 10 | from msldap import logger |
18 | 11 | 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 | |
25 | 13 | |
26 | 14 | class MSLDAPCompDomainList: |
27 | 15 | def __init__(self, ldap_url): |
68 | 56 | _, err = await self.do_adinfo(False) |
69 | 57 | if err is not None: |
70 | 58 | raise err |
71 | #machine_filename = '%s_computers_%s.txt' % (self.domain_name, datetime.datetime.now().strftime("%Y%m%d-%H%M%S")) | |
72 | 59 | |
73 | 60 | async for machine, err in self.connection.get_all_machines(attrs=['sAMAccountName', 'dNSHostName']): |
74 | 61 | if err is not None: |
101 | 88 | logger.setLevel(logging.DEBUG) |
102 | 89 | logging.basicConfig(level=logging.DEBUG) |
103 | 90 | |
104 | ldap_url = MSLDAPURLDecoder(args.url) | |
91 | ldap_url = LDAPConnectionFactory.from_url(args.url) | |
105 | 92 | compdomlist = MSLDAPCompDomainList(ldap_url) |
106 | 93 | |
107 | 94 |
13 | 13 | from msldap.ldap_objects.adgpo import MSADGPO, MSADGPO_ATTRS |
14 | 14 | from msldap.ldap_objects.adtrust import MSADDomainTrust, MSADDomainTrust_ATTRS |
15 | 15 | 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 | |
16 | 19 | |
17 | 20 | __all__ = [ |
18 | 21 | 'MSADUser', |
36 | 39 | 'MSADOU_ATTRS', |
37 | 40 | 'MSADSCHEMAENTRY_ATTRS', |
38 | 41 | 'MSADSchemaEntry', |
42 | 'MSADCA', | |
43 | 'MSADCA_ATTRS', | |
44 | 'MSADEnrollmentService_ATTRS', | |
45 | 'MSADEnrollmentService', | |
46 | 'MSADCertificateTemplate', | |
47 | 'MSADCertificateTemplate_ATTRS', | |
48 | ||
39 | 49 | ]⏎ |
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 | 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 | 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 | ||
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 | ||
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 | ||
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 | ||
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 |
20 | 20 | elif value == '*': |
21 | 21 | return Filter({ |
22 | 22 | '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 | }) | |
23 | 35 | }) |
24 | 36 | |
25 | 37 | elif value.startswith('*') is True: |
92 | 92 | def list_bytes_one_enc(x): |
93 | 93 | return x |
94 | 94 | |
95 | def bytes2timedelta(x): | |
96 | return int2timedelta([int.from_bytes(x[0], byteorder='little', signed=True)]) | |
97 | ||
95 | 98 | def int2timedelta(x): |
96 | 99 | x = int(x[0]) |
97 | 100 | if x == '-9223372036854775808': |
165 | 168 | t.append(ts2dt((a, None))) |
166 | 169 | return t |
167 | 170 | |
171 | def list_ts2dt_one(x): | |
172 | return ts2dt(x[0]) | |
168 | 173 | |
169 | 174 | LDAP_ATTRIBUTE_TYPES = { |
170 | 175 | 'supportedCapabilities' : list_str, |
274 | 279 | 'unicodePwd' : list_str_one, |
275 | 280 | 'ms-Mcs-AdmPwd' : list_str_one, |
276 | 281 | '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 | |
277 | 304 | } |
278 | 305 | |
279 | 306 | LDAP_ATTRIBUTE_TYPES_ENC = { |
294 | 321 | 'member' : list_str_enc, |
295 | 322 | 'msDS-AllowedToActOnBehalfOfOtherIdentity' : list_bytes_one_enc, |
296 | 323 | 'nTSecurityDescriptor' : list_bytes_one_enc, |
324 | 'msPKI-Certificate-Name-Flag' : list_int_one_enc | |
297 | 325 | } |
298 | 326 | |
299 | 327 | def encode_attributes(x): |
0 | Metadata-Version: 1.2 | |
0 | Metadata-Version: 2.1 | |
1 | 1 | Name: msldap |
2 | Version: 0.3.30 | |
2 | Version: 0.4.1 | |
3 | 3 | Summary: Python library to play with MS LDAP |
4 | 4 | Home-page: https://github.com/skelsec/msldap |
5 | 5 | Author: Tamas Jos |
6 | 6 | Author-email: [email protected] |
7 | License: UNKNOWN | |
8 | Description: Python library to play with MS LDAP | |
9 | Platform: UNKNOWN | |
10 | 7 | Classifier: Programming Language :: Python :: 3.7 |
11 | 8 | Classifier: Programming Language :: Python :: 3.8 |
12 | 9 | Classifier: License :: OSI Approved :: MIT License |
13 | 10 | Classifier: Operating System :: OS Independent |
14 | 11 | Requires-Python: >=3.7 |
12 | License-File: LICENSE | |
13 | ||
14 | Python library to play with MS LDAP |
13 | 13 | msldap.egg-info/not-zip-safe |
14 | 14 | msldap.egg-info/requires.txt |
15 | 15 | 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 | |
50 | 16 | msldap/commons/__init__.py |
51 | 17 | msldap/commons/authbuilder.py |
52 | 18 | msldap/commons/common.py |
53 | msldap/commons/credential.py | |
54 | 19 | msldap/commons/exceptions.py |
55 | msldap/commons/proxy.py | |
20 | msldap/commons/factory.py | |
56 | 21 | msldap/commons/target.py |
57 | msldap/commons/url.py | |
58 | 22 | 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 | |
77 | 23 | msldap/examples/__init__.py |
78 | 24 | msldap/examples/msldapclient.py |
79 | 25 | msldap/examples/msldapcompdnslist.py |
90 | 36 | msldap/external/asciitree/asciitree/traversal.py |
91 | 37 | msldap/external/asciitree/asciitree/util.py |
92 | 38 | msldap/ldap_objects/__init__.py |
39 | msldap/ldap_objects/adca.py | |
40 | msldap/ldap_objects/adcertificatetemplate.py | |
93 | 41 | msldap/ldap_objects/adcomp.py |
42 | msldap/ldap_objects/adenrollmentservice.py | |
94 | 43 | msldap/ldap_objects/adgpo.py |
95 | 44 | msldap/ldap_objects/adgroup.py |
96 | 45 | msldap/ldap_objects/adinfo.py |
101 | 50 | msldap/ldap_objects/aduser.py |
102 | 51 | msldap/ldap_objects/common.py |
103 | 52 | 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 | |
111 | 54 | msldap/protocol/__init__.py |
112 | 55 | msldap/protocol/constants.py |
113 | 56 | msldap/protocol/messages.py |
0 | 0 | [console_scripts] |
1 | 1 | msldap = msldap.examples.msldapclient:main |
2 | 2 | msldapcompdns = msldap.examples.msldapcompdnslist:main |
3 |
0 | asn1crypto | |
1 | asysocks>=0.1.1 | |
2 | minikerberos>=0.2.14 | |
0 | unicrypto>=0.0.9 | |
1 | asyauth>=0.0.2 | |
2 | asysocks>=0.2.1 | |
3 | asn1crypto>=1.3.0 | |
4 | minikerberos>=0.3.1 | |
5 | winacl>=0.1.4 | |
3 | 6 | prompt-toolkit>=3.0.2 |
4 | 7 | tqdm |
5 | winacl>=0.1.1 | |
6 | ||
7 | [:platform_system == "Windows"] | |
8 | winsspi>=0.0.9 | |
8 | wcwidth |
46 | 46 | "Operating System :: OS Independent", |
47 | 47 | ), |
48 | 48 | 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', | |
54 | 55 | 'prompt-toolkit>=3.0.2', |
55 | 56 | 'tqdm', |
57 | 'wcwidth', | |
56 | 58 | ], |
57 | 59 | entry_points={ |
58 | 60 | 'console_scripts': [ |
60 | 62 | 'msldapcompdns = msldap.examples.msldapcompdnslist:main', |
61 | 63 | ], |
62 | 64 | } |
63 | )⏎ | |
65 | ) |