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),
280 def test_Wdigest14(self):
281 attribute = "virtualWDigest14"
282 expected = calc_digest(USER_NAME.lower(),
283 self.dns_domain.upper(),
285 self._testWDigest(attribute, expected)
287 # Hash15 MD5(userPrincipalName,
290 def test_Wdigest15(self):
291 attribute = "virtualWDigest15"
292 name = "%s@%s" % (USER_NAME, self.dns_domain)
293 expected = calc_digest(name,
296 self._testWDigest(attribute, expected)
298 # Hash16 MD5(LOWER(userPrincipalName),
301 def test_Wdigest16(self):
302 attribute = "virtualWDigest16"
303 name = "%s@%s" % (USER_NAME.lower(), self.dns_domain.lower())
304 expected = calc_digest(name,
307 self._testWDigest(attribute, expected)
309 # Hash17 MD5(UPPER(userPrincipalName),
312 def test_Wdigest17(self):
313 attribute = "virtualWDigest17"
314 name = "%s@%s" % (USER_NAME.upper(), self.dns_domain.upper())
315 expected = calc_digest(name,
318 self._testWDigest(attribute, expected)
320 # Hash18 MD5(NETBIOSDomainName\sAMAccountName,
323 def test_Wdigest18(self):
324 attribute = "virtualWDigest18"
325 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
326 expected = calc_digest(name,
329 self._testWDigest(attribute, expected)
331 # Hash19 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
334 def test_Wdigest19(self):
335 attribute = "virtualWDigest19"
336 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
337 expected = calc_digest(name.lower(),
340 self._testWDigest(attribute, expected)
342 # Hash20 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
345 def test_Wdigest20(self):
346 attribute = "virtualWDigest20"
347 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
348 expected = calc_digest(name.upper(),
351 self._testWDigest(attribute, expected)
353 # Hash21 MD5(sAMAccountName,
357 def test_Wdigest21(self):
358 attribute = "virtualWDigest21"
359 expected = calc_digest(USER_NAME,
362 self._testWDigest(attribute, expected)
364 # Hash22 MD5(LOWER(sAMAccountName),
368 def test_Wdigest22(self):
369 attribute = "virtualWDigest22"
370 expected = calc_digest(USER_NAME.lower(),
373 self._testWDigest(attribute, expected)
375 # Hash23 MD5(UPPER(sAMAccountName),
379 def test_Wdigest23(self):
380 attribute = "virtualWDigest23"
381 expected = calc_digest(USER_NAME.upper(),
384 self._testWDigest(attribute, expected)
386 # Hash24 MD5(userPrincipalName),
390 def test_Wdigest24(self):
391 attribute = "virtualWDigest24"
392 name = "%s@%s" % (USER_NAME, self.dns_domain)
393 expected = calc_digest(name,
396 self._testWDigest(attribute, expected)
398 # Hash25 MD5(LOWER(userPrincipalName),
402 def test_Wdigest25(self):
403 attribute = "virtualWDigest25"
404 name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
405 expected = calc_digest(name.lower(),
408 self._testWDigest(attribute, expected)
410 # Hash26 MD5(UPPER(userPrincipalName),
414 def test_Wdigest26(self):
415 attribute = "virtualWDigest26"
416 name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
417 expected = calc_digest(name.upper(),
420 self._testWDigest(attribute, expected)
421 # Hash27 MD5(NETBIOSDomainName\sAMAccountName,
426 def test_Wdigest27(self):
427 attribute = "virtualWDigest27"
428 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
429 expected = calc_digest(name,
432 self._testWDigest(attribute, expected)
434 # Hash28 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
438 def test_Wdigest28(self):
439 attribute = "virtualWDigest28"
440 name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower())
441 expected = calc_digest(name,
444 self._testWDigest(attribute, expected)
446 # Hash29 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
450 def test_Wdigest29(self):
451 attribute = "virtualWDigest29"
452 name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper())
453 expected = calc_digest(name,
456 self._testWDigest(attribute, expected)
458 def test_Wdigest30(self):
459 attribute = "virtualWDigest30"
460 self._testWDigest(attribute, None, True)
462 # Check digest calculation against an known htdigest value
463 def test_calc_digest(self):
464 htdigest = "gary:fred:2204fcc247cb47ded249ef2fe0013255"
465 digest = calc_digest("gary", "fred", "password")
466 self.assertEqual(htdigest, digest)