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
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')
52 return "%s:%s:%s" % (user, realm, md5(data).hexdigest())
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
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",
83 "ldap://%s" % os.environ["DC_SERVER"],
85 os.environ["DC_USERNAME"],
86 os.environ["DC_PASSWORD"]))
89 super(UserCmdWdigestTestCase, self).tearDown()
90 self.runsubcmd("user", "delete", USER_NAME)
92 def _testWDigest(self, attribute, expected, missing=False):
94 (result, out, err) = self.runsubcmd("user",
99 self.assertCmdSuccess(result,
102 "Ensure getpassword runs")
103 self.assertEqual(err, "", "getpassword")
104 self.assertMatch(out,
106 "getpassword out[%s]" % out)
109 self.assertTrue(attribute not in out)
111 self.assertMatch(out.replace('\n ', ''),
112 "%s: %s" % (attribute, expected))
114 def test_Wdigest_no_suffix(self):
115 attribute = "virtualWDigest"
116 self._testWDigest(attribute, None, True)
118 def test_Wdigest_non_numeric_suffix(self):
119 attribute = "virtualWDigestss"
120 self._testWDigest(attribute, None, True)
122 def test_Wdigest00(self):
123 attribute = "virtualWDigest00"
124 self._testWDigest(attribute, None, True)
126 # Hash01 MD5(sAMAccountName,
130 def test_Wdigest01(self):
131 attribute = "virtualWDigest01"
132 expected = calc_digest(USER_NAME,
135 self._testWDigest(attribute, expected)
137 # Hash02 MD5(LOWER(sAMAccountName),
138 # LOWER(NETBIOSDomainName),
141 def test_Wdigest02(self):
142 attribute = "virtualWDigest02"
143 expected = calc_digest(USER_NAME.lower(),
144 self.netbios_domain.lower(),
146 self._testWDigest(attribute, expected)
148 # Hash03 MD5(UPPER(sAMAccountName),
149 # UPPER(NETBIOSDomainName),
152 def test_Wdigest03(self):
153 attribute = "virtualWDigest03"
154 expected = calc_digest(USER_NAME.upper(),
155 self.netbios_domain.upper(),
157 self._testWDigest(attribute, expected)
159 # Hash04 MD5(sAMAccountName,
160 # UPPER(NETBIOSDomainName),
163 def test_Wdigest04(self):
164 attribute = "virtualWDigest04"
165 expected = calc_digest(USER_NAME,
166 self.netbios_domain.upper(),
168 self._testWDigest(attribute, expected)
170 # Hash05 MD5(sAMAccountName,
171 # LOWER(NETBIOSDomainName),
174 def test_Wdigest05(self):
175 attribute = "virtualWDigest05"
176 expected = calc_digest(USER_NAME,
177 self.netbios_domain.lower(),
179 self._testWDigest(attribute, expected)
181 # Hash06 MD5(UPPER(sAMAccountName),
182 # LOWER(NETBIOSDomainName),
185 def test_Wdigest06(self):
186 attribute = "virtualWDigest06"
187 expected = calc_digest(USER_NAME.upper(),
188 self.netbios_domain.lower(),
190 self._testWDigest(attribute, expected)
192 # Hash07 MD5(LOWER(sAMAccountName),
193 # UPPER(NETBIOSDomainName),
196 def test_Wdigest07(self):
197 attribute = "virtualWDigest07"
198 expected = calc_digest(USER_NAME.lower(),
199 self.netbios_domain.upper(),
201 self._testWDigest(attribute, expected)
203 # Hash08 MD5(sAMAccountName,
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.
211 def test_Wdigest08(self):
212 attribute = "virtualWDigest08"
213 expected = calc_digest(USER_NAME,
216 self._testWDigest(attribute, expected)
218 # Hash09 MD5(LOWER(sAMAccountName),
219 # LOWER(DNSDomainName),
222 def test_Wdigest09(self):
223 attribute = "virtualWDigest09"
224 expected = calc_digest(USER_NAME.lower(),
225 self.dns_domain.lower(),
227 self._testWDigest(attribute, expected)
229 # Hash10 MD5(UPPER(sAMAccountName),
230 # UPPER(DNSDomainName),
233 def test_Wdigest10(self):
234 attribute = "virtualWDigest10"
235 expected = calc_digest(USER_NAME.upper(),
236 self.dns_domain.upper(),
238 self._testWDigest(attribute, expected)
240 # Hash11 MD5(sAMAccountName,
241 # UPPER(DNSDomainName),
244 def test_Wdigest11(self):
245 attribute = "virtualWDigest11"
246 expected = calc_digest(USER_NAME,
247 self.dns_domain.upper(),
249 self._testWDigest(attribute, expected)
251 # Hash12 MD5(sAMAccountName,
252 # LOWER(DNSDomainName),
255 def test_Wdigest12(self):
256 attribute = "virtualWDigest12"
257 expected = calc_digest(USER_NAME,
258 self.dns_domain.lower(),
260 self._testWDigest(attribute, expected)
262 # Hash13 MD5(UPPER(sAMAccountName),
263 # LOWER(DNSDomainName),
266 def test_Wdigest13(self):
267 attribute = "virtualWDigest13"
268 expected = calc_digest(USER_NAME.upper(),
269 self.dns_domain.lower(),
271 self._testWDigest(attribute, expected)
274 # Hash14 MD5(LOWER(sAMAccountName),
275 # UPPER(DNSDomainName),
278 def test_Wdigest14(self):
279 attribute = "virtualWDigest14"
280 expected = calc_digest(USER_NAME.lower(),
281 self.dns_domain.upper(),
283 self._testWDigest(attribute, expected)
285 # Hash15 MD5(userPrincipalName,
288 def test_Wdigest15(self):
289 attribute = "virtualWDigest15"
290 name = "%s@%s" % (USER_NAME, self.dns_domain)
291 expected = calc_digest(name,
294 self._testWDigest(attribute, expected)
296 # Hash16 MD5(LOWER(userPrincipalName),
299 def test_Wdigest16(self):
300 attribute = "virtualWDigest16"
301 name = "%s@%s" % (USER_NAME.lower(), self.dns_domain.lower())
302 expected = calc_digest(name,
305 self._testWDigest(attribute, expected)
307 # Hash17 MD5(UPPER(userPrincipalName),
310 def test_Wdigest17(self):
311 attribute = "virtualWDigest17"
312 name = "%s@%s" % (USER_NAME.upper(), self.dns_domain.upper())
313 expected = calc_digest(name,
316 self._testWDigest(attribute, expected)
318 # Hash18 MD5(NETBIOSDomainName\sAMAccountName,
321 def test_Wdigest18(self):
322 attribute = "virtualWDigest18"
323 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
324 expected = calc_digest(name,
327 self._testWDigest(attribute, expected)
329 # Hash19 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
332 def test_Wdigest19(self):
333 attribute = "virtualWDigest19"
334 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
335 expected = calc_digest(name.lower(),
338 self._testWDigest(attribute, expected)
340 # Hash20 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
343 def test_Wdigest20(self):
344 attribute = "virtualWDigest20"
345 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
346 expected = calc_digest(name.upper(),
349 self._testWDigest(attribute, expected)
351 # Hash21 MD5(sAMAccountName,
355 def test_Wdigest21(self):
356 attribute = "virtualWDigest21"
357 expected = calc_digest(USER_NAME,
360 self._testWDigest(attribute, expected)
362 # Hash22 MD5(LOWER(sAMAccountName),
366 def test_Wdigest22(self):
367 attribute = "virtualWDigest22"
368 expected = calc_digest(USER_NAME.lower(),
371 self._testWDigest(attribute, expected)
373 # Hash23 MD5(UPPER(sAMAccountName),
377 def test_Wdigest23(self):
378 attribute = "virtualWDigest23"
379 expected = calc_digest(USER_NAME.upper(),
382 self._testWDigest(attribute, expected)
384 # Hash24 MD5(userPrincipalName),
388 def test_Wdigest24(self):
389 attribute = "virtualWDigest24"
390 name = "%s@%s" % (USER_NAME, self.dns_domain)
391 expected = calc_digest(name,
394 self._testWDigest(attribute, expected)
396 # Hash25 MD5(LOWER(userPrincipalName),
400 def test_Wdigest25(self):
401 attribute = "virtualWDigest25"
402 name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
403 expected = calc_digest(name.lower(),
406 self._testWDigest(attribute, expected)
408 # Hash26 MD5(UPPER(userPrincipalName),
412 def test_Wdigest26(self):
413 attribute = "virtualWDigest26"
414 name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
415 expected = calc_digest(name.upper(),
418 self._testWDigest(attribute, expected)
419 # Hash27 MD5(NETBIOSDomainName\sAMAccountName,
423 def test_Wdigest27(self):
424 attribute = "virtualWDigest27"
425 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
426 expected = calc_digest(name,
429 self._testWDigest(attribute, expected)
431 # Hash28 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
435 def test_Wdigest28(self):
436 attribute = "virtualWDigest28"
437 name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower())
438 expected = calc_digest(name,
441 self._testWDigest(attribute, expected)
443 # Hash29 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
447 def test_Wdigest29(self):
448 attribute = "virtualWDigest29"
449 name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper())
450 expected = calc_digest(name,
453 self._testWDigest(attribute, expected)
455 def test_Wdigest30(self):
456 attribute = "virtualWDigest30"
457 self._testWDigest(attribute, None, True)
459 # Check digest calculation against an known htdigest value
460 def test_calc_digest(self):
461 htdigest = "gary:fred:2204fcc247cb47ded249ef2fe0013255"
462 digest = calc_digest("gary", "fred", "password")
463 self.assertEqual(htdigest, digest)