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
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 return "%s:%s:%s" % (user, realm, md5(data).hexdigest())
52 class UserCmdWdigestTestCase(SambaToolCmdTest):
53 """Tests for samba-tool user subcommands extraction of the wdigest values
54 Test results validated against Windows Server 2012 R2.
55 NOTE: That as at 22-05-2017 the values Documented at
56 3.1.1.8.11.3.1 WDIGEST_CREDENTIALS Construction
63 super(UserCmdWdigestTestCase, self).setUp()
64 self.lp = samba.tests.env_loadparm()
65 self.samdb = self.getSamDB(
66 "-H", "ldap://%s" % os.environ["DC_SERVER"],
67 "-U%s%%%s" % (os.environ["DC_USERNAME"],
68 os.environ["DC_PASSWORD"]))
69 self.dns_domain = self.samdb.domain_dns_name()
70 res = self.samdb.search(
71 base=self.samdb.get_config_basedn(),
72 expression="ncName=%s" % self.samdb.get_default_basedn(),
73 attrs=["nETBIOSName"])
74 self.netbios_domain = res[0]["nETBIOSName"][0]
75 self.runsubcmd("user",
80 "ldap://%s" % os.environ["DC_SERVER"],
82 os.environ["DC_USERNAME"],
83 os.environ["DC_PASSWORD"]))
86 super(UserCmdWdigestTestCase, self).tearDown()
87 self.runsubcmd("user", "delete", USER_NAME)
89 def _testWDigest(self, attribute, expected, missing=False):
91 (result, out, err) = self.runsubcmd("user",
96 self.assertCmdSuccess(result,
99 "Ensure getpassword runs")
100 self.assertEqual(err, "", "getpassword")
101 self.assertMatch(out,
103 "getpassword out[%s]" % out)
106 self.assertTrue(attribute not in out)
108 result = re.sub(r"\n\s*", '', out)
109 self.assertMatch(result, "%s: %s" % (attribute, expected))
111 def test_Wdigest_no_suffix(self):
112 attribute = "virtualWDigest"
113 self._testWDigest(attribute, None, True)
115 def test_Wdigest_non_numeric_suffix(self):
116 attribute = "virtualWDigestss"
117 self._testWDigest(attribute, None, True)
119 def test_Wdigest00(self):
120 attribute = "virtualWDigest00"
121 self._testWDigest(attribute, None, True)
123 # Hash01 MD5(sAMAccountName,
127 def test_Wdigest01(self):
128 attribute = "virtualWDigest01"
129 expected = calc_digest(USER_NAME,
132 self._testWDigest(attribute, expected)
134 # Hash02 MD5(LOWER(sAMAccountName),
135 # LOWER(NETBIOSDomainName),
138 def test_Wdigest02(self):
139 attribute = "virtualWDigest02"
140 expected = calc_digest(USER_NAME.lower(),
141 self.netbios_domain.lower(),
143 self._testWDigest(attribute, expected)
145 # Hash03 MD5(UPPER(sAMAccountName),
146 # UPPER(NETBIOSDomainName),
149 def test_Wdigest03(self):
150 attribute = "virtualWDigest03"
151 expected = calc_digest(USER_NAME.upper(),
152 self.netbios_domain.upper(),
154 self._testWDigest(attribute, expected)
156 # Hash04 MD5(sAMAccountName,
157 # UPPER(NETBIOSDomainName),
160 def test_Wdigest04(self):
161 attribute = "virtualWDigest04"
162 expected = calc_digest(USER_NAME,
163 self.netbios_domain.upper(),
165 self._testWDigest(attribute, expected)
167 # Hash05 MD5(sAMAccountName,
168 # LOWER(NETBIOSDomainName),
171 def test_Wdigest05(self):
172 attribute = "virtualWDigest05"
173 expected = calc_digest(USER_NAME,
174 self.netbios_domain.lower(),
176 self._testWDigest(attribute, expected)
178 # Hash06 MD5(UPPER(sAMAccountName),
179 # LOWER(NETBIOSDomainName),
182 def test_Wdigest06(self):
183 attribute = "virtualWDigest06"
184 expected = calc_digest(USER_NAME.upper(),
185 self.netbios_domain.lower(),
187 self._testWDigest(attribute, expected)
189 # Hash07 MD5(LOWER(sAMAccountName),
190 # UPPER(NETBIOSDomainName),
193 def test_Wdigest07(self):
194 attribute = "virtualWDigest07"
195 expected = calc_digest(USER_NAME.lower(),
196 self.netbios_domain.upper(),
198 self._testWDigest(attribute, expected)
200 # Hash08 MD5(sAMAccountName,
204 # Note: Samba lowercases the DNSDomainName at provision time,
205 # Windows preserves the case. This means that the WDigest08 values
206 # calculated byt Samba and Windows differ.
208 def test_Wdigest08(self):
209 attribute = "virtualWDigest08"
210 expected = calc_digest(USER_NAME,
213 self._testWDigest(attribute, expected)
215 # Hash09 MD5(LOWER(sAMAccountName),
216 # LOWER(DNSDomainName),
219 def test_Wdigest09(self):
220 attribute = "virtualWDigest09"
221 expected = calc_digest(USER_NAME.lower(),
222 self.dns_domain.lower(),
224 self._testWDigest(attribute, expected)
226 # Hash10 MD5(UPPER(sAMAccountName),
227 # UPPER(DNSDomainName),
230 def test_Wdigest10(self):
231 attribute = "virtualWDigest10"
232 expected = calc_digest(USER_NAME.upper(),
233 self.dns_domain.upper(),
235 self._testWDigest(attribute, expected)
237 # Hash11 MD5(sAMAccountName,
238 # UPPER(DNSDomainName),
241 def test_Wdigest11(self):
242 attribute = "virtualWDigest11"
243 expected = calc_digest(USER_NAME,
244 self.dns_domain.upper(),
246 self._testWDigest(attribute, expected)
248 # Hash12 MD5(sAMAccountName,
249 # LOWER(DNSDomainName),
252 def test_Wdigest12(self):
253 attribute = "virtualWDigest12"
254 expected = calc_digest(USER_NAME,
255 self.dns_domain.lower(),
257 self._testWDigest(attribute, expected)
259 # Hash13 MD5(UPPER(sAMAccountName),
260 # LOWER(DNSDomainName),
263 def test_Wdigest13(self):
264 attribute = "virtualWDigest13"
265 expected = calc_digest(USER_NAME.upper(),
266 self.dns_domain.lower(),
268 self._testWDigest(attribute, expected)
271 # Hash14 MD5(LOWER(sAMAccountName),
272 # UPPER(DNSDomainName),
275 def test_Wdigest14(self):
276 attribute = "virtualWDigest14"
277 expected = calc_digest(USER_NAME.lower(),
278 self.dns_domain.upper(),
280 self._testWDigest(attribute, expected)
282 # Hash15 MD5(userPrincipalName,
285 def test_Wdigest15(self):
286 attribute = "virtualWDigest15"
287 name = "%s@%s" % (USER_NAME, self.dns_domain)
288 expected = calc_digest(name,
291 self._testWDigest(attribute, expected)
293 # Hash16 MD5(LOWER(userPrincipalName),
296 def test_Wdigest16(self):
297 attribute = "virtualWDigest16"
298 name = "%s@%s" % (USER_NAME.lower(), self.dns_domain.lower())
299 expected = calc_digest(name,
302 self._testWDigest(attribute, expected)
304 # Hash17 MD5(UPPER(userPrincipalName),
307 def test_Wdigest17(self):
308 attribute = "virtualWDigest17"
309 name = "%s@%s" % (USER_NAME.upper(), self.dns_domain.upper())
310 expected = calc_digest(name,
313 self._testWDigest(attribute, expected)
315 # Hash18 MD5(NETBIOSDomainName\sAMAccountName,
318 def test_Wdigest18(self):
319 attribute = "virtualWDigest18"
320 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
321 expected = calc_digest(name,
324 self._testWDigest(attribute, expected)
326 # Hash19 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
329 def test_Wdigest19(self):
330 attribute = "virtualWDigest19"
331 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
332 expected = calc_digest(name.lower(),
335 self._testWDigest(attribute, expected)
337 # Hash20 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
340 def test_Wdigest20(self):
341 attribute = "virtualWDigest20"
342 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
343 expected = calc_digest(name.upper(),
346 self._testWDigest(attribute, expected)
348 # Hash21 MD5(sAMAccountName,
352 def test_Wdigest21(self):
353 attribute = "virtualWDigest21"
354 expected = calc_digest(USER_NAME,
357 self._testWDigest(attribute, expected)
359 # Hash22 MD5(LOWER(sAMAccountName),
363 def test_Wdigest22(self):
364 attribute = "virtualWDigest22"
365 expected = calc_digest(USER_NAME.lower(),
368 self._testWDigest(attribute, expected)
370 # Hash23 MD5(UPPER(sAMAccountName),
374 def test_Wdigest23(self):
375 attribute = "virtualWDigest23"
376 expected = calc_digest(USER_NAME.upper(),
379 self._testWDigest(attribute, expected)
381 # Hash24 MD5(userPrincipalName),
385 def test_Wdigest24(self):
386 attribute = "virtualWDigest24"
387 name = "%s@%s" % (USER_NAME, self.dns_domain)
388 expected = calc_digest(name,
391 self._testWDigest(attribute, expected)
393 # Hash25 MD5(LOWER(userPrincipalName),
397 def test_Wdigest25(self):
398 attribute = "virtualWDigest25"
399 name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
400 expected = calc_digest(name.lower(),
403 self._testWDigest(attribute, expected)
405 # Hash26 MD5(UPPER(userPrincipalName),
409 def test_Wdigest26(self):
410 attribute = "virtualWDigest26"
411 name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
412 expected = calc_digest(name.upper(),
415 self._testWDigest(attribute, expected)
416 # Hash27 MD5(NETBIOSDomainName\sAMAccountName,
420 def test_Wdigest27(self):
421 attribute = "virtualWDigest27"
422 name = "%s\\%s" % (self.netbios_domain, USER_NAME)
423 expected = calc_digest(name,
426 self._testWDigest(attribute, expected)
428 # Hash28 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
432 def test_Wdigest28(self):
433 attribute = "virtualWDigest28"
434 name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower())
435 expected = calc_digest(name,
438 self._testWDigest(attribute, expected)
440 # Hash29 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
444 def test_Wdigest29(self):
445 attribute = "virtualWDigest29"
446 name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper())
447 expected = calc_digest(name,
450 self._testWDigest(attribute, expected)
452 def test_Wdigest30(self):
453 attribute = "virtualWDigest30"
454 self._testWDigest(attribute, None, True)
456 # Check digest calculation against an known htdigest value
457 def test_calc_digest(self):
458 htdigest = "gary:fred:2204fcc247cb47ded249ef2fe0013255"
459 digest = calc_digest("gary", "fred", "password")
460 self.assertEqual(htdigest, digest)