1 # Tests for the samba-tool user sub command reading Primary:WDigest
3 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
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.
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.
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/>.
25 from samba.tests.samba_tool.base import SambaToolCmdTest
31 from samba.ndr import ndr_unpack
32 from samba.dcerpc import drsblobs
33 from hashlib import md5
36 from samba.compat import text_type
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))
45 # Calculate the MD5 password digest from the supplied user, realm and password
49 def calc_digest(user, realm, password):
50 data = "%s:%s:%s" % (user, realm, password)
51 if isinstance(data, text_type):
52 data = data.encode('utf8')
54 return "%s:%s:%s" % (user, realm, md5(data).hexdigest())
57 class UserCmdWdigestTestCase(SambaToolCmdTest):
58 """Tests for samba-tool user subcommands extraction of the wdigest values
59 Test results validated against Windows Server 2012 R2.
60 NOTE: That as at 22-05-2017 the values Documented at
61 3.1.1.8.11.3.1 WDIGEST_CREDENTIALS Construction
68 super(UserCmdWdigestTestCase, self).setUp()
69 self.lp = samba.tests.env_loadparm()
70 self.samdb = self.getSamDB(
71 "-H", "ldap://%s" % os.environ["DC_SERVER"],
72 "-U%s%%%s" % (os.environ["DC_USERNAME"],
73 os.environ["DC_PASSWORD"]))
74 self.dns_domain = self.samdb.domain_dns_name()
75 res = self.samdb.search(
76 base=self.samdb.get_config_basedn(),
77 expression="ncName=%s" % self.samdb.get_default_basedn(),
78 attrs=["nETBIOSName"])
79 self.netbios_domain = res[0]["nETBIOSName"][0]
80 self.runsubcmd("user",
85 "ldap://%s" % os.environ["DC_SERVER"],
87 os.environ["DC_USERNAME"],
88 os.environ["DC_PASSWORD"]))
91 super(UserCmdWdigestTestCase, self).tearDown()
92 self.runsubcmd("user", "delete", USER_NAME)
94 def _testWDigest(self, attribute, expected, missing=False):
96 (result, out, err) = self.runsubcmd("user",
101 self.assertCmdSuccess(result,
104 "Ensure getpassword runs")
105 self.assertEqual(err, "", "getpassword")
106 self.assertMatch(out,
108 "getpassword out[%s]" % out)
111 self.assertTrue(attribute not in out)
113 self.assertMatch(out.replace('\n ', ''),
114 "%s: %s" % (attribute, expected))
116 def test_Wdigest_no_suffix(self):
117 attribute = "virtualWDigest"
118 self._testWDigest(attribute, None, True)
120 def test_Wdigest_non_numeric_suffix(self):
121 attribute = "virtualWDigestss"
122 self._testWDigest(attribute, None, True)
124 def test_Wdigest00(self):
125 attribute = "virtualWDigest00"
126 self._testWDigest(attribute, None, True)
128 # Hash01 MD5(sAMAccountName,
132 def test_Wdigest01(self):
133 attribute = "virtualWDigest01"
134 expected = calc_digest(USER_NAME,
137 self._testWDigest(attribute, expected)
139 # Hash02 MD5(LOWER(sAMAccountName),
140 # LOWER(NETBIOSDomainName),
143 def test_Wdigest02(self):
144 attribute = "virtualWDigest02"
145 expected = calc_digest(USER_NAME.lower(),
146 self.netbios_domain.lower(),
148 self._testWDigest(attribute, expected)
150 # Hash03 MD5(UPPER(sAMAccountName),
151 # UPPER(NETBIOSDomainName),
154 def test_Wdigest03(self):
155 attribute = "virtualWDigest03"
156 expected = calc_digest(USER_NAME.upper(),
157 self.netbios_domain.upper(),
159 self._testWDigest(attribute, expected)
161 # Hash04 MD5(sAMAccountName,
162 # UPPER(NETBIOSDomainName),
165 def test_Wdigest04(self):
166 attribute = "virtualWDigest04"
167 expected = calc_digest(USER_NAME,
168 self.netbios_domain.upper(),
170 self._testWDigest(attribute, expected)
172 # Hash05 MD5(sAMAccountName,
173 # LOWER(NETBIOSDomainName),
176 def test_Wdigest05(self):
177 attribute = "virtualWDigest05"
178 expected = calc_digest(USER_NAME,
179 self.netbios_domain.lower(),
181 self._testWDigest(attribute, expected)
183 # Hash06 MD5(UPPER(sAMAccountName),
184 # LOWER(NETBIOSDomainName),
187 def test_Wdigest06(self):
188 attribute = "virtualWDigest06"
189 expected = calc_digest(USER_NAME.upper(),
190 self.netbios_domain.lower(),
192 self._testWDigest(attribute, expected)
194 # Hash07 MD5(LOWER(sAMAccountName),
195 # UPPER(NETBIOSDomainName),
198 def test_Wdigest07(self):
199 attribute = "virtualWDigest07"
200 expected = calc_digest(USER_NAME.lower(),
201 self.netbios_domain.upper(),
203 self._testWDigest(attribute, expected)
205 # Hash08 MD5(sAMAccountName,
209 # Note: Samba lowercases the DNSDomainName at provision time,
210 # Windows preserves the case. This means that the WDigest08 values
211 # calculated byt Samba and Windows differ.
213 def test_Wdigest08(self):
214 attribute = "virtualWDigest08"
215 expected = calc_digest(USER_NAME,
218 self._testWDigest(attribute, expected)
220 # Hash09 MD5(LOWER(sAMAccountName),
221 # LOWER(DNSDomainName),
224 def test_Wdigest09(self):
225 attribute = "virtualWDigest09"
226 expected = calc_digest(USER_NAME.lower(),
227 self.dns_domain.lower(),
229 self._testWDigest(attribute, expected)
231 # Hash10 MD5(UPPER(sAMAccountName),
232 # UPPER(DNSDomainName),
235 def test_Wdigest10(self):
236 attribute = "virtualWDigest10"
237 expected = calc_digest(USER_NAME.upper(),
238 self.dns_domain.upper(),
240 self._testWDigest(attribute, expected)
242 # Hash11 MD5(sAMAccountName,
243 # UPPER(DNSDomainName),
246 def test_Wdigest11(self):
247 attribute = "virtualWDigest11"
248 expected = calc_digest(USER_NAME,
249 self.dns_domain.upper(),
251 self._testWDigest(attribute, expected)
253 # Hash12 MD5(sAMAccountName,
254 # LOWER(DNSDomainName),
257 def test_Wdigest12(self):
258 attribute = "virtualWDigest12"
259 expected = calc_digest(USER_NAME,
260 self.dns_domain.lower(),
262 self._testWDigest(attribute, expected)
264 # Hash13 MD5(UPPER(sAMAccountName),
265 # LOWER(DNSDomainName),
268 def test_Wdigest13(self):
269 attribute = "virtualWDigest13"
270 expected = calc_digest(USER_NAME.upper(),
271 self.dns_domain.lower(),
273 self._testWDigest(attribute, expected)
276 # Hash14 MD5(LOWER(sAMAccountName),
277 # UPPER(DNSDomainName),
282 def test_Wdigest14(self):
283 attribute = "virtualWDigest14"
284 expected = calc_digest(USER_NAME.lower(),
285 self.dns_domain.upper(),
287 self._testWDigest(attribute, expected)
289 # Hash15 MD5(userPrincipalName,
292 def test_Wdigest15(self):
293 attribute = "virtualWDigest15"
294 name = "%s@%s" % (USER_NAME, self.dns_domain)
295 expected = calc_digest(name,
298 self._testWDigest(attribute, expected)
300 # Hash16 MD5(LOWER(userPrincipalName),
303 def test_Wdigest16(self):
304 attribute = "virtualWDigest16"
305 name = "%s@%s" % (USER_NAME.lower(), self.dns_domain.lower())
306 expected = calc_digest(name,
309 self._testWDigest(attribute, expected)
311 # Hash17 MD5(UPPER(userPrincipalName),
314 def test_Wdigest17(self):
315 attribute = "virtualWDigest17"
316 name = "%s@%s" % (USER_NAME.upper(), self.dns_domain.upper())
317 expected = calc_digest(name,
320 self._testWDigest(attribute, expected)
322 # Hash18 MD5(NETBIOSDomainName\sAMAccountName,
325 def test_Wdigest18(self):
326 attribute = "virtualWDigest18"
327 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
328 expected = calc_digest(name,
331 self._testWDigest(attribute, expected)
333 # Hash19 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
336 def test_Wdigest19(self):
337 attribute = "virtualWDigest19"
338 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
339 expected = calc_digest(name.lower(),
342 self._testWDigest(attribute, expected)
344 # Hash20 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
347 def test_Wdigest20(self):
348 attribute = "virtualWDigest20"
349 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
350 expected = calc_digest(name.upper(),
353 self._testWDigest(attribute, expected)
355 # Hash21 MD5(sAMAccountName,
359 def test_Wdigest21(self):
360 attribute = "virtualWDigest21"
361 expected = calc_digest(USER_NAME,
364 self._testWDigest(attribute, expected)
366 # Hash22 MD5(LOWER(sAMAccountName),
370 def test_Wdigest22(self):
371 attribute = "virtualWDigest22"
372 expected = calc_digest(USER_NAME.lower(),
375 self._testWDigest(attribute, expected)
377 # Hash23 MD5(UPPER(sAMAccountName),
381 def test_Wdigest23(self):
382 attribute = "virtualWDigest23"
383 expected = calc_digest(USER_NAME.upper(),
386 self._testWDigest(attribute, expected)
388 # Hash24 MD5(userPrincipalName),
392 def test_Wdigest24(self):
393 attribute = "virtualWDigest24"
394 name = "%s@%s" % (USER_NAME, self.dns_domain)
395 expected = calc_digest(name,
398 self._testWDigest(attribute, expected)
400 # Hash25 MD5(LOWER(userPrincipalName),
404 def test_Wdigest25(self):
405 attribute = "virtualWDigest25"
406 name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
407 expected = calc_digest(name.lower(),
410 self._testWDigest(attribute, expected)
412 # Hash26 MD5(UPPER(userPrincipalName),
416 def test_Wdigest26(self):
417 attribute = "virtualWDigest26"
418 name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
419 expected = calc_digest(name.upper(),
422 self._testWDigest(attribute, expected)
423 # Hash27 MD5(NETBIOSDomainName\sAMAccountName,
428 def test_Wdigest27(self):
429 attribute = "virtualWDigest27"
430 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
431 expected = calc_digest(name,
434 self._testWDigest(attribute, expected)
436 # Hash28 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
440 def test_Wdigest28(self):
441 attribute = "virtualWDigest28"
442 name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower())
443 expected = calc_digest(name,
446 self._testWDigest(attribute, expected)
448 # Hash29 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
452 def test_Wdigest29(self):
453 attribute = "virtualWDigest29"
454 name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper())
455 expected = calc_digest(name,
458 self._testWDigest(attribute, expected)
460 def test_Wdigest30(self):
461 attribute = "virtualWDigest30"
462 self._testWDigest(attribute, None, True)
464 # Check digest calculation against an known htdigest value
465 def test_calc_digest(self):
466 htdigest = "gary:fred:2204fcc247cb47ded249ef2fe0013255"
467 digest = calc_digest("gary", "fred", "password")
468 self.assertEqual(htdigest, digest)