PEP8: fix E301: expected 1 blank line, found 0
[metze/samba/wip.git] / python / samba / tests / samba_tool / user_wdigest.py
1 # Tests for the samba-tool user sub command reading Primary:WDigest
2 #
3 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
4 #
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import os
21 import time
22 import base64
23 import ldb
24 import samba
25 from samba.tests.samba_tool.base import SambaToolCmdTest
26 from samba import (
27         credentials,
28         nttime2unix,
29         dsdb
30         )
31 from samba.ndr import ndr_unpack
32 from samba.dcerpc import drsblobs
33 from hashlib import md5
34 import random
35 import string
36 from samba.compat import text_type
37
38 USER_NAME = "WdigestTestUser"
39 # Create a random 32 character password, containing only letters and
40 # digits to avoid issues when used on the command line.
41 USER_PASS = ''.join(random.choice(string.ascii_uppercase +
42                                   string.ascii_lowercase +
43                                   string.digits) for _ in range(32))
44
45 # Calculate the MD5 password digest from the supplied user, realm and password
46 #
47 def calc_digest(user, realm, password):
48     data = "%s:%s:%s" % (user, realm, password)
49     if isinstance(data, text_type):
50         data = data.encode('utf8')
51
52     return "%s:%s:%s" % (user, realm, md5(data).hexdigest())
53
54
55 class UserCmdWdigestTestCase(SambaToolCmdTest):
56     """Tests for samba-tool user subcommands extraction of the wdigest values
57        Test results validated against Windows Server 2012 R2.
58        NOTE: That as at 22-05-2017 the values Documented at
59              3.1.1.8.11.3.1 WDIGEST_CREDENTIALS Construction
60              are incorrect.
61     """
62     users = []
63     samdb = None
64
65     def setUp(self):
66         super(UserCmdWdigestTestCase, self).setUp()
67         self.lp = samba.tests.env_loadparm()
68         self.samdb = self.getSamDB(
69             "-H", "ldap://%s" % os.environ["DC_SERVER"],
70             "-U%s%%%s" % (os.environ["DC_USERNAME"],
71                           os.environ["DC_PASSWORD"]))
72         self.dns_domain = self.samdb.domain_dns_name()
73         res = self.samdb.search(
74             base=self.samdb.get_config_basedn(),
75             expression="ncName=%s" % self.samdb.get_default_basedn(),
76             attrs=["nETBIOSName"])
77         self.netbios_domain = res[0]["nETBIOSName"][0]
78         self.runsubcmd("user",
79                        "create",
80                        USER_NAME,
81                        USER_PASS,
82                        "-H",
83                        "ldap://%s" % os.environ["DC_SERVER"],
84                        "-U%s%%%s" % (
85                             os.environ["DC_USERNAME"],
86                             os.environ["DC_PASSWORD"]))
87
88     def tearDown(self):
89         super(UserCmdWdigestTestCase, self).tearDown()
90         self.runsubcmd("user", "delete", USER_NAME)
91
92     def _testWDigest(self, attribute, expected, missing=False):
93
94         (result, out, err) = self.runsubcmd("user",
95                                             "getpassword",
96                                             USER_NAME,
97                                             "--attributes",
98                                             attribute)
99         self.assertCmdSuccess(result,
100                               out,
101                               err,
102                               "Ensure getpassword runs")
103         self.assertEqual(err, "", "getpassword")
104         self.assertMatch(out,
105                          "Got password OK",
106                          "getpassword out[%s]" % out)
107
108         if missing:
109             self.assertTrue(attribute not in out)
110         else:
111             self.assertMatch(out.replace('\n ', ''),
112                              "%s: %s" % (attribute, expected))
113
114     def test_Wdigest_no_suffix(self):
115         attribute = "virtualWDigest"
116         self._testWDigest(attribute, None, True)
117
118     def test_Wdigest_non_numeric_suffix(self):
119         attribute = "virtualWDigestss"
120         self._testWDigest(attribute, None, True)
121
122     def test_Wdigest00(self):
123         attribute = "virtualWDigest00"
124         self._testWDigest(attribute, None, True)
125
126     # Hash01  MD5(sAMAccountName,
127     #            NETBIOSDomainName,
128     #            password)
129     #
130     def test_Wdigest01(self):
131         attribute = "virtualWDigest01"
132         expected = calc_digest(USER_NAME,
133                                self.netbios_domain,
134                                USER_PASS)
135         self._testWDigest(attribute, expected)
136
137     # Hash02 MD5(LOWER(sAMAccountName),
138     #            LOWER(NETBIOSDomainName),
139     #            password)
140     #
141     def test_Wdigest02(self):
142         attribute = "virtualWDigest02"
143         expected = calc_digest(USER_NAME.lower(),
144                                self.netbios_domain.lower(),
145                                USER_PASS)
146         self._testWDigest(attribute, expected)
147
148     # Hash03 MD5(UPPER(sAMAccountName),
149     #            UPPER(NETBIOSDomainName),
150     #            password)
151     #
152     def test_Wdigest03(self):
153         attribute = "virtualWDigest03"
154         expected = calc_digest(USER_NAME.upper(),
155                                self.netbios_domain.upper(),
156                                USER_PASS)
157         self._testWDigest(attribute, expected)
158
159     # Hash04 MD5(sAMAccountName,
160     #            UPPER(NETBIOSDomainName),
161     #            password)
162     #
163     def test_Wdigest04(self):
164         attribute = "virtualWDigest04"
165         expected = calc_digest(USER_NAME,
166                                self.netbios_domain.upper(),
167                                USER_PASS)
168         self._testWDigest(attribute, expected)
169
170     # Hash05 MD5(sAMAccountName,
171     #            LOWER(NETBIOSDomainName),
172     #            password)
173     #
174     def test_Wdigest05(self):
175         attribute = "virtualWDigest05"
176         expected = calc_digest(USER_NAME,
177                                self.netbios_domain.lower(),
178                                USER_PASS)
179         self._testWDigest(attribute, expected)
180
181     # Hash06 MD5(UPPER(sAMAccountName),
182     #            LOWER(NETBIOSDomainName),
183     #            password)
184     #
185     def test_Wdigest06(self):
186         attribute = "virtualWDigest06"
187         expected = calc_digest(USER_NAME.upper(),
188                                self.netbios_domain.lower(),
189                                USER_PASS)
190         self._testWDigest(attribute, expected)
191
192     # Hash07 MD5(LOWER(sAMAccountName),
193     #            UPPER(NETBIOSDomainName),
194     #            password)
195     #
196     def test_Wdigest07(self):
197         attribute = "virtualWDigest07"
198         expected = calc_digest(USER_NAME.lower(),
199                                self.netbios_domain.upper(),
200                                USER_PASS)
201         self._testWDigest(attribute, expected)
202
203     # Hash08 MD5(sAMAccountName,
204     #            DNSDomainName,
205     #            password)
206     #
207     # Note: Samba lowercases the DNSDomainName at provision time,
208     #       Windows preserves the case. This means that the WDigest08 values
209     #       calculated byt Samba and Windows differ.
210     #
211     def test_Wdigest08(self):
212         attribute = "virtualWDigest08"
213         expected = calc_digest(USER_NAME,
214                                self.dns_domain,
215                                USER_PASS)
216         self._testWDigest(attribute, expected)
217
218     # Hash09 MD5(LOWER(sAMAccountName),
219     #            LOWER(DNSDomainName),
220     #            password)
221     #
222     def test_Wdigest09(self):
223         attribute = "virtualWDigest09"
224         expected = calc_digest(USER_NAME.lower(),
225                                self.dns_domain.lower(),
226                                USER_PASS)
227         self._testWDigest(attribute, expected)
228
229     # Hash10 MD5(UPPER(sAMAccountName),
230     #            UPPER(DNSDomainName),
231     #            password)
232     #
233     def test_Wdigest10(self):
234         attribute = "virtualWDigest10"
235         expected = calc_digest(USER_NAME.upper(),
236                                self.dns_domain.upper(),
237                                USER_PASS)
238         self._testWDigest(attribute, expected)
239
240     # Hash11 MD5(sAMAccountName,
241     #            UPPER(DNSDomainName),
242     #            password)
243     #
244     def test_Wdigest11(self):
245         attribute = "virtualWDigest11"
246         expected = calc_digest(USER_NAME,
247                                self.dns_domain.upper(),
248                                USER_PASS)
249         self._testWDigest(attribute, expected)
250
251     # Hash12 MD5(sAMAccountName,
252     #            LOWER(DNSDomainName),
253     #            password)
254     #
255     def test_Wdigest12(self):
256         attribute = "virtualWDigest12"
257         expected = calc_digest(USER_NAME,
258                                self.dns_domain.lower(),
259                                USER_PASS)
260         self._testWDigest(attribute, expected)
261
262     # Hash13 MD5(UPPER(sAMAccountName),
263     #            LOWER(DNSDomainName),
264     #            password)
265     #
266     def test_Wdigest13(self):
267         attribute = "virtualWDigest13"
268         expected = calc_digest(USER_NAME.upper(),
269                                self.dns_domain.lower(),
270                                USER_PASS)
271         self._testWDigest(attribute, expected)
272
273
274     # Hash14 MD5(LOWER(sAMAccountName),
275     #            UPPER(DNSDomainName),
276     #            password)
277     #
278
279
280     def test_Wdigest14(self):
281         attribute = "virtualWDigest14"
282         expected = calc_digest(USER_NAME.lower(),
283                                self.dns_domain.upper(),
284                                USER_PASS)
285         self._testWDigest(attribute, expected)
286
287     # Hash15 MD5(userPrincipalName,
288     #            password)
289     #
290     def test_Wdigest15(self):
291         attribute = "virtualWDigest15"
292         name = "%s@%s" % (USER_NAME, self.dns_domain)
293         expected = calc_digest(name,
294                                "",
295                                USER_PASS)
296         self._testWDigest(attribute, expected)
297
298     # Hash16 MD5(LOWER(userPrincipalName),
299     #            password)
300     #
301     def test_Wdigest16(self):
302         attribute = "virtualWDigest16"
303         name = "%s@%s" % (USER_NAME.lower(), self.dns_domain.lower())
304         expected = calc_digest(name,
305                                "",
306                                USER_PASS)
307         self._testWDigest(attribute, expected)
308
309     # Hash17 MD5(UPPER(userPrincipalName),
310     #            password)
311     #
312     def test_Wdigest17(self):
313         attribute = "virtualWDigest17"
314         name = "%s@%s" % (USER_NAME.upper(), self.dns_domain.upper())
315         expected = calc_digest(name,
316                                "",
317                                USER_PASS)
318         self._testWDigest(attribute, expected)
319
320     # Hash18 MD5(NETBIOSDomainName\sAMAccountName,
321     #            password)
322     #
323     def test_Wdigest18(self):
324         attribute = "virtualWDigest18"
325         name = "%s\\%s" % (self.netbios_domain, USER_NAME)
326         expected = calc_digest(name,
327                                "",
328                                USER_PASS)
329         self._testWDigest(attribute, expected)
330
331     # Hash19 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
332     #            password)
333     #
334     def test_Wdigest19(self):
335         attribute = "virtualWDigest19"
336         name = "%s\\%s" % (self.netbios_domain, USER_NAME)
337         expected = calc_digest(name.lower(),
338                                "",
339                                USER_PASS)
340         self._testWDigest(attribute, expected)
341
342     # Hash20 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
343     #            password)
344     #
345     def test_Wdigest20(self):
346         attribute = "virtualWDigest20"
347         name = "%s\\%s" % (self.netbios_domain, USER_NAME)
348         expected = calc_digest(name.upper(),
349                                "",
350                                USER_PASS)
351         self._testWDigest(attribute, expected)
352
353     # Hash21 MD5(sAMAccountName,
354     #            "Digest",
355     #            password)
356     #
357     def test_Wdigest21(self):
358         attribute = "virtualWDigest21"
359         expected = calc_digest(USER_NAME,
360                                "Digest",
361                                USER_PASS)
362         self._testWDigest(attribute, expected)
363
364     # Hash22 MD5(LOWER(sAMAccountName),
365     #            "Digest",
366     #            password)
367     #
368     def test_Wdigest22(self):
369         attribute = "virtualWDigest22"
370         expected = calc_digest(USER_NAME.lower(),
371                                "Digest",
372                                USER_PASS)
373         self._testWDigest(attribute, expected)
374
375     # Hash23 MD5(UPPER(sAMAccountName),
376     #            "Digest",
377     #            password)
378     #
379     def test_Wdigest23(self):
380         attribute = "virtualWDigest23"
381         expected = calc_digest(USER_NAME.upper(),
382                                "Digest",
383                                USER_PASS)
384         self._testWDigest(attribute, expected)
385
386     # Hash24  MD5(userPrincipalName),
387     #             "Digest",
388     #              password)
389     #
390     def test_Wdigest24(self):
391         attribute = "virtualWDigest24"
392         name = "%s@%s" % (USER_NAME, self.dns_domain)
393         expected = calc_digest(name,
394                                "Digest",
395                                USER_PASS)
396         self._testWDigest(attribute, expected)
397
398     # Hash25 MD5(LOWER(userPrincipalName),
399     #            "Digest",
400     #            password)
401     #
402     def test_Wdigest25(self):
403         attribute = "virtualWDigest25"
404         name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
405         expected = calc_digest(name.lower(),
406                                "Digest",
407                                USER_PASS)
408         self._testWDigest(attribute, expected)
409
410     # Hash26 MD5(UPPER(userPrincipalName),
411     #            "Digest",
412     #             password)
413     #
414     def test_Wdigest26(self):
415         attribute = "virtualWDigest26"
416         name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
417         expected = calc_digest(name.upper(),
418                                "Digest",
419                                USER_PASS)
420         self._testWDigest(attribute, expected)
421     # Hash27 MD5(NETBIOSDomainName\sAMAccountName,
422     #            "Digest",
423     #            password)
424     #
425
426     def test_Wdigest27(self):
427         attribute = "virtualWDigest27"
428         name = "%s\\%s" % (self.netbios_domain, USER_NAME)
429         expected = calc_digest(name,
430                                "Digest",
431                                USER_PASS)
432         self._testWDigest(attribute, expected)
433
434     # Hash28 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
435     #            "Digest",
436     #            password)
437     #
438     def test_Wdigest28(self):
439         attribute = "virtualWDigest28"
440         name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower())
441         expected = calc_digest(name,
442                                "Digest",
443                                USER_PASS)
444         self._testWDigest(attribute, expected)
445
446     # Hash29 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
447     #            "Digest",
448     #             password)
449     #
450     def test_Wdigest29(self):
451         attribute = "virtualWDigest29"
452         name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper())
453         expected = calc_digest(name,
454                                "Digest",
455                                USER_PASS)
456         self._testWDigest(attribute, expected)
457
458     def test_Wdigest30(self):
459         attribute = "virtualWDigest30"
460         self._testWDigest(attribute, None, True)
461
462     # Check digest calculation against an known htdigest value
463     def test_calc_digest(self):
464         htdigest = "gary:fred:2204fcc247cb47ded249ef2fe0013255"
465         digest = calc_digest("gary", "fred", "password")
466         self.assertEqual(htdigest, digest)