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
)