tests/samba-tool user_wdigest: avoid py3-incompatible md5 module
[samba.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 re
35 import random
36 import string
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     return "%s:%s:%s" % (user, realm, md5(data).hexdigest())
50
51
52 class UserCmdWdigestTestCase(SambaToolCmdTest):
53     """Tests for samba-tool user subcommands extraction of the wdigest values
54        Test results validated against Windows Server 2012 R2.
55        NOTE: That as at 22-05-2017 the values Documented at
56              3.1.1.8.11.3.1 WDIGEST_CREDENTIALS Construction
57              are incorrect.
58     """
59     users = []
60     samdb = None
61
62     def setUp(self):
63         super(UserCmdWdigestTestCase, self).setUp()
64         self.lp = samba.tests.env_loadparm()
65         self.samdb = self.getSamDB(
66             "-H", "ldap://%s" % os.environ["DC_SERVER"],
67             "-U%s%%%s" % (os.environ["DC_USERNAME"],
68             os.environ["DC_PASSWORD"]))
69         self.dns_domain = self.samdb.domain_dns_name()
70         res = self.samdb.search(
71             base=self.samdb.get_config_basedn(),
72             expression="ncName=%s" % self.samdb.get_default_basedn(),
73             attrs=["nETBIOSName"])
74         self.netbios_domain = res[0]["nETBIOSName"][0]
75         self.runsubcmd("user",
76                        "create",
77                        USER_NAME,
78                        USER_PASS,
79                        "-H",
80                        "ldap://%s" % os.environ["DC_SERVER"],
81                        "-U%s%%%s" % (
82                             os.environ["DC_USERNAME"],
83                             os.environ["DC_PASSWORD"]))
84
85     def tearDown(self):
86         super(UserCmdWdigestTestCase, self).tearDown()
87         self.runsubcmd("user", "delete", USER_NAME)
88
89     def _testWDigest(self,  attribute, expected, missing=False):
90
91         (result, out, err) = self.runsubcmd("user",
92                                             "getpassword",
93                                             USER_NAME,
94                                             "--attributes",
95                                             attribute)
96         self.assertCmdSuccess(result,
97                               out,
98                               err,
99                               "Ensure getpassword runs")
100         self.assertEqual(err, "", "getpassword")
101         self.assertMatch(out,
102                          "Got password OK",
103                          "getpassword out[%s]" % out)
104
105         if missing:
106             self.assertTrue(attribute not in out)
107         else:
108             result = re.sub(r"\n\s*", '', out)
109             self.assertMatch(result, "%s: %s" % (attribute, expected))
110
111     def test_Wdigest_no_suffix(self):
112         attribute = "virtualWDigest"
113         self._testWDigest(attribute, None, True)
114
115     def test_Wdigest_non_numeric_suffix(self):
116         attribute = "virtualWDigestss"
117         self._testWDigest(attribute, None, True)
118
119     def test_Wdigest00(self):
120         attribute = "virtualWDigest00"
121         self._testWDigest(attribute, None, True)
122
123     # Hash01  MD5(sAMAccountName,
124     #            NETBIOSDomainName,
125     #            password)
126     #
127     def test_Wdigest01(self):
128         attribute = "virtualWDigest01"
129         expected = calc_digest(USER_NAME,
130                                self.netbios_domain,
131                                USER_PASS)
132         self._testWDigest(attribute, expected)
133
134     # Hash02 MD5(LOWER(sAMAccountName),
135     #            LOWER(NETBIOSDomainName),
136     #            password)
137     #
138     def test_Wdigest02(self):
139         attribute = "virtualWDigest02"
140         expected = calc_digest(USER_NAME.lower(),
141                                self.netbios_domain.lower(),
142                                USER_PASS)
143         self._testWDigest(attribute, expected)
144
145     # Hash03 MD5(UPPER(sAMAccountName),
146     #            UPPER(NETBIOSDomainName),
147     #            password)
148     #
149     def test_Wdigest03(self):
150         attribute = "virtualWDigest03"
151         expected = calc_digest(USER_NAME.upper(),
152                                self.netbios_domain.upper(),
153                                USER_PASS)
154         self._testWDigest(attribute, expected)
155
156     # Hash04 MD5(sAMAccountName,
157     #            UPPER(NETBIOSDomainName),
158     #            password)
159     #
160     def test_Wdigest04(self):
161         attribute = "virtualWDigest04"
162         expected = calc_digest(USER_NAME,
163                                self.netbios_domain.upper(),
164                                USER_PASS)
165         self._testWDigest(attribute, expected)
166
167     # Hash05 MD5(sAMAccountName,
168     #            LOWER(NETBIOSDomainName),
169     #            password)
170     #
171     def test_Wdigest05(self):
172         attribute = "virtualWDigest05"
173         expected = calc_digest(USER_NAME,
174                                self.netbios_domain.lower(),
175                                USER_PASS)
176         self._testWDigest(attribute, expected)
177
178     # Hash06 MD5(UPPER(sAMAccountName),
179     #            LOWER(NETBIOSDomainName),
180     #            password)
181     #
182     def test_Wdigest06(self):
183         attribute = "virtualWDigest06"
184         expected = calc_digest(USER_NAME.upper(),
185                                self.netbios_domain.lower(),
186                                USER_PASS)
187         self._testWDigest(attribute, expected)
188
189     # Hash07 MD5(LOWER(sAMAccountName),
190     #            UPPER(NETBIOSDomainName),
191     #            password)
192     #
193     def test_Wdigest07(self):
194         attribute = "virtualWDigest07"
195         expected = calc_digest(USER_NAME.lower(),
196                                self.netbios_domain.upper(),
197                                USER_PASS)
198         self._testWDigest(attribute, expected)
199
200     # Hash08 MD5(sAMAccountName,
201     #            DNSDomainName,
202     #            password)
203     #
204     # Note: Samba lowercases the DNSDomainName at provision time,
205     #       Windows preserves the case. This means that the WDigest08 values
206     #       calculated byt Samba and Windows differ.
207     #
208     def test_Wdigest08(self):
209         attribute = "virtualWDigest08"
210         expected = calc_digest(USER_NAME,
211                                self.dns_domain,
212                                USER_PASS)
213         self._testWDigest(attribute, expected)
214
215     # Hash09 MD5(LOWER(sAMAccountName),
216     #            LOWER(DNSDomainName),
217     #            password)
218     #
219     def test_Wdigest09(self):
220         attribute = "virtualWDigest09"
221         expected = calc_digest(USER_NAME.lower(),
222                                self.dns_domain.lower(),
223                                USER_PASS)
224         self._testWDigest(attribute, expected)
225
226     # Hash10 MD5(UPPER(sAMAccountName),
227     #            UPPER(DNSDomainName),
228     #            password)
229     #
230     def test_Wdigest10(self):
231         attribute = "virtualWDigest10"
232         expected = calc_digest(USER_NAME.upper(),
233                                self.dns_domain.upper(),
234                                USER_PASS)
235         self._testWDigest(attribute, expected)
236
237     # Hash11 MD5(sAMAccountName,
238     #            UPPER(DNSDomainName),
239     #            password)
240     #
241     def test_Wdigest11(self):
242         attribute = "virtualWDigest11"
243         expected = calc_digest(USER_NAME,
244                                self.dns_domain.upper(),
245                                USER_PASS)
246         self._testWDigest(attribute, expected)
247
248     # Hash12 MD5(sAMAccountName,
249     #            LOWER(DNSDomainName),
250     #            password)
251     #
252     def test_Wdigest12(self):
253         attribute = "virtualWDigest12"
254         expected = calc_digest(USER_NAME,
255                                self.dns_domain.lower(),
256                                USER_PASS)
257         self._testWDigest(attribute, expected)
258
259     # Hash13 MD5(UPPER(sAMAccountName),
260     #            LOWER(DNSDomainName),
261     #            password)
262     #
263     def test_Wdigest13(self):
264         attribute = "virtualWDigest13"
265         expected = calc_digest(USER_NAME.upper(),
266                                self.dns_domain.lower(),
267                                USER_PASS)
268         self._testWDigest(attribute, expected)
269
270
271     # Hash14 MD5(LOWER(sAMAccountName),
272     #            UPPER(DNSDomainName),
273     #            password)
274     #
275     def test_Wdigest14(self):
276         attribute = "virtualWDigest14"
277         expected = calc_digest(USER_NAME.lower(),
278                                self.dns_domain.upper(),
279                                USER_PASS)
280         self._testWDigest(attribute, expected)
281
282     # Hash15 MD5(userPrincipalName,
283     #            password)
284     #
285     def test_Wdigest15(self):
286         attribute = "virtualWDigest15"
287         name = "%s@%s" % (USER_NAME, self.dns_domain)
288         expected = calc_digest(name,
289                                "",
290                                USER_PASS)
291         self._testWDigest(attribute, expected)
292
293     # Hash16 MD5(LOWER(userPrincipalName),
294     #            password)
295     #
296     def test_Wdigest16(self):
297         attribute = "virtualWDigest16"
298         name = "%s@%s" % (USER_NAME.lower(), self.dns_domain.lower())
299         expected = calc_digest(name,
300                                "",
301                                USER_PASS)
302         self._testWDigest(attribute, expected)
303
304     # Hash17 MD5(UPPER(userPrincipalName),
305     #            password)
306     #
307     def test_Wdigest17(self):
308         attribute = "virtualWDigest17"
309         name = "%s@%s" % (USER_NAME.upper(), self.dns_domain.upper())
310         expected = calc_digest(name,
311                                "",
312                                USER_PASS)
313         self._testWDigest(attribute, expected)
314
315     # Hash18 MD5(NETBIOSDomainName\sAMAccountName,
316     #            password)
317     #
318     def test_Wdigest18(self):
319         attribute = "virtualWDigest18"
320         name = "%s\\%s" % (self.netbios_domain, USER_NAME)
321         expected = calc_digest(name,
322                                "",
323                                USER_PASS)
324         self._testWDigest(attribute, expected)
325
326     # Hash19 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
327     #            password)
328     #
329     def test_Wdigest19(self):
330         attribute = "virtualWDigest19"
331         name = "%s\\%s" % (self.netbios_domain, USER_NAME)
332         expected = calc_digest(name.lower(),
333                                "",
334                                USER_PASS)
335         self._testWDigest(attribute, expected)
336
337     # Hash20 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
338     #            password)
339     #
340     def test_Wdigest20(self):
341         attribute = "virtualWDigest20"
342         name = "%s\\%s" % (self.netbios_domain, USER_NAME)
343         expected = calc_digest(name.upper(),
344                                "",
345                                USER_PASS)
346         self._testWDigest(attribute, expected)
347
348     # Hash21 MD5(sAMAccountName,
349     #            "Digest",
350     #            password)
351     #
352     def test_Wdigest21(self):
353         attribute = "virtualWDigest21"
354         expected = calc_digest(USER_NAME,
355                                "Digest",
356                                USER_PASS)
357         self._testWDigest(attribute, expected)
358
359     # Hash22 MD5(LOWER(sAMAccountName),
360     #            "Digest",
361     #            password)
362     #
363     def test_Wdigest22(self):
364         attribute = "virtualWDigest22"
365         expected = calc_digest(USER_NAME.lower(),
366                                "Digest",
367                                USER_PASS)
368         self._testWDigest(attribute, expected)
369
370     # Hash23 MD5(UPPER(sAMAccountName),
371     #            "Digest",
372     #            password)
373     #
374     def test_Wdigest23(self):
375         attribute = "virtualWDigest23"
376         expected = calc_digest(USER_NAME.upper(),
377                                "Digest",
378                                USER_PASS)
379         self._testWDigest(attribute, expected)
380
381     # Hash24  MD5(userPrincipalName),
382     #             "Digest",
383     #              password)
384     #
385     def test_Wdigest24(self):
386         attribute = "virtualWDigest24"
387         name = "%s@%s" % (USER_NAME, self.dns_domain)
388         expected = calc_digest(name,
389                                "Digest",
390                                USER_PASS)
391         self._testWDigest(attribute, expected)
392
393     # Hash25 MD5(LOWER(userPrincipalName),
394     #            "Digest",
395     #            password)
396     #
397     def test_Wdigest25(self):
398         attribute = "virtualWDigest25"
399         name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
400         expected = calc_digest(name.lower(),
401                                "Digest",
402                                USER_PASS)
403         self._testWDigest(attribute, expected)
404
405     # Hash26 MD5(UPPER(userPrincipalName),
406     #            "Digest",
407     #             password)
408     #
409     def test_Wdigest26(self):
410         attribute = "virtualWDigest26"
411         name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
412         expected = calc_digest(name.upper(),
413                                "Digest",
414                                USER_PASS)
415         self._testWDigest(attribute, expected)
416     # Hash27 MD5(NETBIOSDomainName\sAMAccountName,
417     #            "Digest",
418     #            password)
419     #
420     def test_Wdigest27(self):
421         attribute = "virtualWDigest27"
422         name = "%s\\%s" % (self.netbios_domain, USER_NAME)
423         expected = calc_digest(name,
424                                "Digest",
425                                USER_PASS)
426         self._testWDigest(attribute, expected)
427
428     # Hash28 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
429     #            "Digest",
430     #            password)
431     #
432     def test_Wdigest28(self):
433         attribute = "virtualWDigest28"
434         name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower())
435         expected = calc_digest(name,
436                                "Digest",
437                                USER_PASS)
438         self._testWDigest(attribute, expected)
439
440     # Hash29 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
441     #            "Digest",
442     #             password)
443     #
444     def test_Wdigest29(self):
445         attribute = "virtualWDigest29"
446         name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper())
447         expected = calc_digest(name,
448                                "Digest",
449                                USER_PASS)
450         self._testWDigest(attribute, expected)
451
452     def test_Wdigest30(self):
453         attribute = "virtualWDigest30"
454         self._testWDigest(attribute, None, True)
455
456     # Check digest calculation against an known htdigest value
457     def test_calc_digest(self):
458         htdigest = "gary:fred:2204fcc247cb47ded249ef2fe0013255"
459         digest = calc_digest("gary", "fred", "password")
460         self.assertEqual(htdigest, digest)