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
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))
46 # Calculate the MD5 password digest from the supplied user, realm and password
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()))
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
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",
82 "ldap://%s" % os.environ["DC_SERVER"],
84 os.environ["DC_USERNAME"],
85 os.environ["DC_PASSWORD"]))
88 super(UserCmdWdigestTestCase, self).tearDown()
89 self.runsubcmd("user", "delete", USER_NAME)
91 def _testWDigest(self, attribute, expected, missing=False):
93 (result, out, err) = self.runsubcmd("user",
98 self.assertCmdSuccess(result,
101 "Ensure getpassword runs")
102 self.assertEqual(err, "", "getpassword")
103 self.assertMatch(out,
105 "getpassword out[%s]" % out)
108 self.assertTrue(attribute not in out)
110 result = re.sub(r"\n\s*", '', out)
111 self.assertMatch(result, "%s: %s" % (attribute, expected))
113 def test_Wdigest_no_suffix(self):
114 attribute = "virtualWDigest"
115 self._testWDigest(attribute, None, True)
117 def test_Wdigest_non_numeric_suffix(self):
118 attribute = "virtualWDigestss"
119 self._testWDigest(attribute, None, True)
121 def test_Wdigest00(self):
122 attribute = "virtualWDigest00"
123 self._testWDigest(attribute, None, True)
125 # Hash01 MD5(sAMAccountName,
129 def test_Wdigest01(self):
130 attribute = "virtualWDigest01"
131 expected = calc_digest(USER_NAME,
134 self._testWDigest(attribute, expected)
136 # Hash02 MD5(LOWER(sAMAccountName),
137 # LOWER(NETBIOSDomainName),
140 def test_Wdigest02(self):
141 attribute = "virtualWDigest02"
142 expected = calc_digest(USER_NAME.lower(),
143 self.netbios_domain.lower(),
145 self._testWDigest(attribute, expected)
147 # Hash03 MD5(UPPER(sAMAccountName),
148 # UPPER(NETBIOSDomainName),
151 def test_Wdigest03(self):
152 attribute = "virtualWDigest03"
153 expected = calc_digest(USER_NAME.upper(),
154 self.netbios_domain.upper(),
156 self._testWDigest(attribute, expected)
158 # Hash04 MD5(sAMAccountName,
159 # UPPER(NETBIOSDomainName),
162 def test_Wdigest04(self):
163 attribute = "virtualWDigest04"
164 expected = calc_digest(USER_NAME,
165 self.netbios_domain.upper(),
167 self._testWDigest(attribute, expected)
169 # Hash05 MD5(sAMAccountName,
170 # LOWER(NETBIOSDomainName),
173 def test_Wdigest05(self):
174 attribute = "virtualWDigest05"
175 expected = calc_digest(USER_NAME,
176 self.netbios_domain.lower(),
178 self._testWDigest(attribute, expected)
180 # Hash06 MD5(UPPER(sAMAccountName),
181 # LOWER(NETBIOSDomainName),
184 def test_Wdigest06(self):
185 attribute = "virtualWDigest06"
186 expected = calc_digest(USER_NAME.upper(),
187 self.netbios_domain.lower(),
189 self._testWDigest(attribute, expected)
191 # Hash07 MD5(LOWER(sAMAccountName),
192 # UPPER(NETBIOSDomainName),
195 def test_Wdigest07(self):
196 attribute = "virtualWDigest07"
197 expected = calc_digest(USER_NAME.lower(),
198 self.netbios_domain.upper(),
200 self._testWDigest(attribute, expected)
202 # Hash08 MD5(sAMAccountName,
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.
210 def test_Wdigest08(self):
211 attribute = "virtualWDigest08"
212 expected = calc_digest(USER_NAME,
215 self._testWDigest(attribute, expected)
217 # Hash09 MD5(LOWER(sAMAccountName),
218 # LOWER(DNSDomainName),
221 def test_Wdigest09(self):
222 attribute = "virtualWDigest09"
223 expected = calc_digest(USER_NAME.lower(),
224 self.dns_domain.lower(),
226 self._testWDigest(attribute, expected)
228 # Hash10 MD5(UPPER(sAMAccountName),
229 # UPPER(DNSDomainName),
232 def test_Wdigest10(self):
233 attribute = "virtualWDigest10"
234 expected = calc_digest(USER_NAME.upper(),
235 self.dns_domain.upper(),
237 self._testWDigest(attribute, expected)
239 # Hash11 MD5(sAMAccountName,
240 # UPPER(DNSDomainName),
243 def test_Wdigest11(self):
244 attribute = "virtualWDigest11"
245 expected = calc_digest(USER_NAME,
246 self.dns_domain.upper(),
248 self._testWDigest(attribute, expected)
250 # Hash12 MD5(sAMAccountName,
251 # LOWER(DNSDomainName),
254 def test_Wdigest12(self):
255 attribute = "virtualWDigest12"
256 expected = calc_digest(USER_NAME,
257 self.dns_domain.lower(),
259 self._testWDigest(attribute, expected)
261 # Hash13 MD5(UPPER(sAMAccountName),
262 # LOWER(DNSDomainName),
265 def test_Wdigest13(self):
266 attribute = "virtualWDigest13"
267 expected = calc_digest(USER_NAME.upper(),
268 self.dns_domain.lower(),
270 self._testWDigest(attribute, expected)
273 # Hash14 MD5(LOWER(sAMAccountName),
274 # UPPER(DNSDomainName),
277 def test_Wdigest14(self):
278 attribute = "virtualWDigest14"
279 expected = calc_digest(USER_NAME.lower(),
280 self.dns_domain.upper(),
282 self._testWDigest(attribute, expected)
284 # Hash15 MD5(userPrincipalName,
287 def test_Wdigest15(self):
288 attribute = "virtualWDigest15"
289 name = "%s@%s" % (USER_NAME, self.dns_domain)
290 expected = calc_digest(name,
293 self._testWDigest(attribute, expected)
295 # Hash16 MD5(LOWER(userPrincipalName),
298 def test_Wdigest16(self):
299 attribute = "virtualWDigest16"
300 name = "%s@%s" % (USER_NAME.lower(), self.dns_domain.lower())
301 expected = calc_digest(name,
304 self._testWDigest(attribute, expected)
306 # Hash17 MD5(UPPER(userPrincipalName),
309 def test_Wdigest17(self):
310 attribute = "virtualWDigest17"
311 name = "%s@%s" % (USER_NAME.upper(), self.dns_domain.upper())
312 expected = calc_digest(name,
315 self._testWDigest(attribute, expected)
317 # Hash18 MD5(NETBIOSDomainName\sAMAccountName,
320 def test_Wdigest18(self):
321 attribute = "virtualWDigest18"
322 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
323 expected = calc_digest(name,
326 self._testWDigest(attribute, expected)
328 # Hash19 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
331 def test_Wdigest19(self):
332 attribute = "virtualWDigest19"
333 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
334 expected = calc_digest(name.lower(),
337 self._testWDigest(attribute, expected)
339 # Hash20 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
342 def test_Wdigest20(self):
343 attribute = "virtualWDigest20"
344 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
345 expected = calc_digest(name.upper(),
348 self._testWDigest(attribute, expected)
350 # Hash21 MD5(sAMAccountName,
354 def test_Wdigest21(self):
355 attribute = "virtualWDigest21"
356 expected = calc_digest(USER_NAME,
359 self._testWDigest(attribute, expected)
361 # Hash22 MD5(LOWER(sAMAccountName),
365 def test_Wdigest22(self):
366 attribute = "virtualWDigest22"
367 expected = calc_digest(USER_NAME.lower(),
370 self._testWDigest(attribute, expected)
372 # Hash23 MD5(UPPER(sAMAccountName),
376 def test_Wdigest23(self):
377 attribute = "virtualWDigest23"
378 expected = calc_digest(USER_NAME.upper(),
381 self._testWDigest(attribute, expected)
383 # Hash24 MD5(userPrincipalName),
387 def test_Wdigest24(self):
388 attribute = "virtualWDigest24"
389 name = "%s@%s" % (USER_NAME, self.dns_domain)
390 expected = calc_digest(name,
393 self._testWDigest(attribute, expected)
395 # Hash25 MD5(LOWER(userPrincipalName),
399 def test_Wdigest25(self):
400 attribute = "virtualWDigest25"
401 name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
402 expected = calc_digest(name.lower(),
405 self._testWDigest(attribute, expected)
407 # Hash26 MD5(UPPER(userPrincipalName),
411 def test_Wdigest26(self):
412 attribute = "virtualWDigest26"
413 name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
414 expected = calc_digest(name.upper(),
417 self._testWDigest(attribute, expected)
418 # Hash27 MD5(NETBIOSDomainName\sAMAccountName,
422 def test_Wdigest27(self):
423 attribute = "virtualWDigest27"
424 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
425 expected = calc_digest(name,
428 self._testWDigest(attribute, expected)
430 # Hash28 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
434 def test_Wdigest28(self):
435 attribute = "virtualWDigest28"
436 name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower())
437 expected = calc_digest(name,
440 self._testWDigest(attribute, expected)
442 # Hash29 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
446 def test_Wdigest29(self):
447 attribute = "virtualWDigest29"
448 name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper())
449 expected = calc_digest(name,
452 self._testWDigest(attribute, expected)
454 def test_Wdigest30(self):
455 attribute = "virtualWDigest30"
456 self._testWDigest(attribute, None, True)
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)