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