1 # Tests for the samba-tool user sub command reading Primary:userPassword
3 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 from samba.tests.samba_tool.base import SambaToolCmdTest
25 from samba.credentials import Credentials
26 from samba.samdb import SamDB
27 from samba.auth import system_session
28 from samba.ndr import ndr_unpack
29 from samba.dcerpc import drsblobs
30 from samba import dsdb
35 USER_NAME = "CryptSHATestUser"
36 # Create a random 32 character password, containing only letters and
37 # digits to avoid issues when used on the command line.
38 # Ensuring the password includes at least:
43 USER_PASS = (''.join(random.choice(string.ascii_uppercase +
44 string.ascii_lowercase +
45 string.digits) for _ in range(29)) +
46 random.choice(string.ascii_uppercase) +
47 random.choice(string.ascii_lowercase) +
48 random.choice(string.digits))
49 HASH_OPTION = "password hash userPassword schemes"
51 # Get the value of an attribute from the output string
52 # Note: Does not correctly handle values spanning multiple lines,
53 # which is acceptable for it's usage in these tests.
54 def _get_attribute(out, name):
55 p = re.compile("^" + name + ":\s+(\S+)")
56 for line in out.split("\n"):
62 class UserCmdCryptShaTestCase(SambaToolCmdTest):
64 Tests for samba-tool user subcommands generation of the virtualCryptSHA256
65 and virtualCryptSHA512 attributes
71 super(UserCmdCryptShaTestCase, self).setUp()
73 def add_user(self, hashes=""):
74 self.lp = samba.tests.env_loadparm()
76 # set the extra hashes to be calculated
77 self.lp.set(HASH_OPTION, hashes)
79 self.creds = Credentials()
80 self.session = system_session()
82 session_info=self.session,
83 credentials=self.creds,
86 self.runsubcmd("user",
92 super(UserCmdCryptShaTestCase, self).tearDown()
93 self.runsubcmd("user", "delete", USER_NAME)
95 def _get_password(self, attributes, decrypt=False):
102 command.append("--decrypt-samba-gpg")
104 (result, out, err) = self.runsubcmd(*command)
105 self.assertCmdSuccess(result,
108 "Ensure getpassword runs")
109 self.assertEqual(err, "", "getpassword")
110 self.assertMatch(out,
112 "getpassword out[%s]" % out)
115 # Change the just the NT password hash, as would happen if the password
116 # was updated by Windows, the userPassword values are now obsolete.
118 def _change_nt_hash(self):
119 res = self.ldb.search(expression = "cn=%s" % USER_NAME,
120 scope = ldb.SCOPE_SUBTREE)
123 msg["unicodePwd"] = ldb.MessageElement(b"ABCDEF1234567890",
124 ldb.FLAG_MOD_REPLACE,
128 controls=["local_oid:%s:0" %
129 dsdb.DSDB_CONTROL_BYPASS_PASSWORD_HASH_OID])
131 # gpg decryption not enabled.
132 # both virtual attributes specified, no rounds option
133 # no hashes stored in supplementalCredentials
134 # Should not get values
135 def test_no_gpg_both_hashes_no_rounds(self):
137 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
139 self.assertTrue("virtualCryptSHA256:" not in out)
140 self.assertTrue("virtualCryptSHA512:" not in out)
141 self.assertTrue("rounds=" not in out)
143 # gpg decryption not enabled.
145 # no hashes stored in supplementalCredentials
148 # Should not get values
149 def test_no_gpg_sha256_no_rounds(self):
151 out = self._get_password("virtualCryptSHA256")
153 self.assertTrue("virtualCryptSHA256:" not in out)
154 self.assertTrue("virtualCryptSHA512:" not in out)
155 self.assertTrue("rounds=" not in out)
157 # gpg decryption not enabled.
159 # no hashes stored in supplementalCredentials
162 # Should not get values
163 def test_no_gpg_sha512_no_rounds(self):
165 out = self._get_password("virtualCryptSHA512")
167 self.assertTrue("virtualCryptSHA256:" not in out)
168 self.assertTrue("virtualCryptSHA512:" not in out)
169 self.assertTrue("rounds=" not in out)
171 # gpg decryption not enabled.
172 # SHA128 specified, i.e. invalid/unknown algorithm
173 # no hashes stored in supplementalCredentials
176 # Should not get values
177 def test_no_gpg_invalid_alg_no_rounds(self):
179 out = self._get_password("virtualCryptSHA128")
181 self.assertTrue("virtualCryptSHA256:" not in out)
182 self.assertTrue("virtualCryptSHA512:" not in out)
183 self.assertTrue("rounds=" not in out)
185 # gpg decryption enabled.
186 # both virtual attributes specified, no rounds option
187 # no hashes stored in supplementalCredentials
189 def test_gpg_both_hashes_no_rounds(self):
191 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
193 self.assertTrue("virtualCryptSHA256:" in out)
194 self.assertTrue("virtualCryptSHA512:" in out)
195 self.assertTrue("rounds=" not in out)
197 # gpg decryption enabled.
199 # no hashes stored in supplementalCredentials
203 def test_gpg_sha256_no_rounds(self):
205 out = self._get_password("virtualCryptSHA256", True)
207 self.assertTrue("virtualCryptSHA256:" in out)
208 self.assertTrue("virtualCryptSHA512:" not in out)
209 self.assertTrue("rounds=" not in out)
211 # gpg decryption enabled.
213 # no hashes stored in supplementalCredentials
217 def test_gpg_sha512_no_rounds(self):
219 out = self._get_password("virtualCryptSHA512", True)
221 self.assertTrue("virtualCryptSHA256:" not in out)
222 self.assertTrue("virtualCryptSHA512:" in out)
223 self.assertTrue("rounds=" not in out)
225 # gpg decryption enabled.
226 # SHA128 specified, i.e. invalid/unknown algorithm
227 # no hashes stored in supplementalCredentials
230 # Should not get values
231 def test_gpg_invalid_alg_no_rounds(self):
233 out = self._get_password("virtualCryptSHA128", True)
235 self.assertTrue("virtualCryptSHA256:" not in out)
236 self.assertTrue("virtualCryptSHA512:" not in out)
237 self.assertTrue("rounds=" not in out)
239 # gpg decryption enabled.
240 # both virtual attributes specified, no rounds option
241 # no hashes stored in supplementalCredentials
242 # underlying windows password changed, so plain text password is
244 # Should not get values
245 def test_gpg_both_hashes_no_rounds_pwd_changed(self):
247 self._change_nt_hash()
248 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
250 self.assertTrue("virtualCryptSHA256:" not in out)
251 self.assertTrue("virtualCryptSHA512:" not in out)
252 self.assertTrue("rounds=" not in out)
254 # gpg decryption enabled.
255 # SHA256 specified, no rounds option
256 # no hashes stored in supplementalCredentials
257 # underlying windows password changed, so plain text password is
259 # Should not get values
260 def test_gpg_sha256_no_rounds_pwd_changed(self):
262 self._change_nt_hash()
263 out = self._get_password("virtualCryptSHA256", True)
265 self.assertTrue("virtualCryptSHA256:" not in out)
266 self.assertTrue("virtualCryptSHA512:" not in out)
267 self.assertTrue("rounds=" not in out)
269 # gpg decryption enabled.
270 # SHA512 specified, no rounds option
271 # no hashes stored in supplementalCredentials
272 # underlying windows password changed, so plain text password is
274 # Should not get values
275 def test_gpg_sha512_no_rounds_pwd_changed(self):
277 self._change_nt_hash()
278 out = self._get_password("virtualCryptSHA256", True)
280 self.assertTrue("virtualCryptSHA256:" not in out)
281 self.assertTrue("virtualCryptSHA512:" not in out)
282 self.assertTrue("rounds=" not in out)
284 # gpg decryption enabled.
285 # both virtual attributes specified, rounds specified
286 # no hashes stored in supplementalCredentials
287 # Should get values reflecting the requested rounds
288 def test_gpg_both_hashes_both_rounds(self):
290 out = self._get_password(
291 "virtualCryptSHA256;rounds=10123,virtualCryptSHA512;rounds=10456",
294 self.assertTrue("virtualCryptSHA256:" in out)
295 self.assertTrue("virtualCryptSHA512:" in out)
297 sha256 = _get_attribute(out, "virtualCryptSHA256")
298 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=10123$"))
300 sha512 = _get_attribute(out, "virtualCryptSHA512")
301 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=10456$"))
303 # gpg decryption enabled.
304 # both virtual attributes specified, rounds specified
305 # invalid rounds for sha256
306 # no hashes stored in supplementalCredentials
307 # Should get values, no rounds for sha256, rounds for sha 512
308 def test_gpg_both_hashes_sha256_rounds_invalid(self):
310 out = self._get_password(
311 "virtualCryptSHA256;rounds=invalid,virtualCryptSHA512;rounds=3125",
314 self.assertTrue("virtualCryptSHA256:" in out)
315 self.assertTrue("virtualCryptSHA512:" in out)
317 sha256 = _get_attribute(out, "virtualCryptSHA256")
318 self.assertTrue(sha256.startswith("{CRYPT}$5$"))
319 self.assertTrue("rounds" not in sha256)
321 sha512 = _get_attribute(out, "virtualCryptSHA512")
322 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=3125$"))
324 # gpg decryption not enabled.
325 # both virtual attributes specified, no rounds option
326 # both hashes stored in supplementalCredentials
328 def test_no_gpg_both_hashes_no_rounds_stored_hashes(self):
329 self.add_user("CryptSHA512 CryptSHA256")
331 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
333 self.assertTrue("virtualCryptSHA256:" in out)
334 self.assertTrue("virtualCryptSHA512:" in out)
335 self.assertTrue("rounds=" not in out)
337 # Should be using the pre computed hash in supplementalCredentials
338 # so it should not change between calls.
339 sha256 = _get_attribute(out, "virtualCryptSHA256")
340 sha512 = _get_attribute(out, "virtualCryptSHA512")
342 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
343 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
344 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
346 # gpg decryption not enabled.
347 # both virtual attributes specified, rounds specified
348 # both hashes stored in supplementalCredentials, with not rounds
349 # Should get hashes for the first matching scheme entry
350 def test_no_gpg_both_hashes_rounds_stored_hashes(self):
351 self.add_user("CryptSHA512 CryptSHA256")
353 out = self._get_password("virtualCryptSHA256;rounds=2561," +
354 "virtualCryptSHA512;rounds=5129")
356 self.assertTrue("virtualCryptSHA256:" in out)
357 self.assertTrue("virtualCryptSHA512:" in out)
358 self.assertTrue("rounds=" not in out)
360 # Should be using the pre computed hash in supplementalCredentials
361 # so it should not change between calls.
362 sha256 = _get_attribute(out, "virtualCryptSHA256")
363 sha512 = _get_attribute(out, "virtualCryptSHA512")
365 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
366 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
367 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
369 # gpg decryption not enabled.
370 # both virtual attributes specified, rounds specified
371 # both hashes stored in supplementalCredentials, with rounds
373 def test_no_gpg_both_hashes_rounds_stored_hashes_with_rounds(self):
374 self.add_user("CryptSHA512 " +
376 "CryptSHA512:rounds=5129 " +
377 "CryptSHA256:rounds=2561")
379 out = self._get_password("virtualCryptSHA256;rounds=2561," +
380 "virtualCryptSHA512;rounds=5129")
382 self.assertTrue("virtualCryptSHA256:" in out)
383 self.assertTrue("virtualCryptSHA512:" in out)
384 self.assertTrue("rounds=" in out)
386 # Should be using the pre computed hash in supplementalCredentials
387 # so it should not change between calls.
388 sha256 = _get_attribute(out, "virtualCryptSHA256")
389 sha512 = _get_attribute(out, "virtualCryptSHA512")
391 out = self._get_password("virtualCryptSHA256;rounds=2561," +
392 "virtualCryptSHA512;rounds=5129")
393 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
394 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
396 # Number of rounds should match that specified
397 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=2561"))
398 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5129"))
400 # gpg decryption not enabled.
401 # both virtual attributes specified, rounds specified
402 # both hashes stored in supplementalCredentials, with rounds
403 # number of rounds stored/requested do not match
404 # Should get the precomputed hashes for CryptSHA512 and CryptSHA256
405 def test_no_gpg_both_hashes_rounds_stored_hashes_with_rounds_no_match(self):
406 self.add_user("CryptSHA512 " +
408 "CryptSHA512:rounds=5129 " +
409 "CryptSHA256:rounds=2561")
411 out = self._get_password("virtualCryptSHA256;rounds=4000," +
412 "virtualCryptSHA512;rounds=5000")
414 self.assertTrue("virtualCryptSHA256:" in out)
415 self.assertTrue("virtualCryptSHA512:" in out)
416 self.assertTrue("rounds=" not in out)
418 # Should be using the pre computed hash in supplementalCredentials
419 # so it should not change between calls.
420 sha256 = _get_attribute(out, "virtualCryptSHA256")
421 sha512 = _get_attribute(out, "virtualCryptSHA512")
423 out = self._get_password("virtualCryptSHA256;rounds=4000," +
424 "virtualCryptSHA512;rounds=5000")
425 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
426 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
428 # As the number of rounds did not match, should have returned the
429 # first hash of the coresponding scheme
430 out = self._get_password("virtualCryptSHA256," +
431 "virtualCryptSHA512")
432 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
433 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
435 # gpg decryption enabled.
436 # both virtual attributes specified, no rounds option
437 # both hashes stored in supplementalCredentials
439 def test_gpg_both_hashes_no_rounds_stored_hashes(self):
440 self.add_user("CryptSHA512 CryptSHA256")
442 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
444 self.assertTrue("virtualCryptSHA256:" in out)
445 self.assertTrue("virtualCryptSHA512:" in out)
446 self.assertTrue("rounds=" not in out)
448 # Should be using the pre computed hash in supplementalCredentials
449 # so it should not change between calls.
450 sha256 = _get_attribute(out, "virtualCryptSHA256")
451 sha512 = _get_attribute(out, "virtualCryptSHA512")
453 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
454 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
455 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
457 # gpg decryption enabled.
458 # both virtual attributes specified, rounds specified
459 # both hashes stored in supplementalCredentials, with no rounds
460 # Should get calculated hashed with the correct number of rounds
461 def test_gpg_both_hashes_rounds_stored_hashes(self):
462 self.add_user("CryptSHA512 CryptSHA256")
464 out = self._get_password("virtualCryptSHA256;rounds=2561," +
465 "virtualCryptSHA512;rounds=5129",
468 self.assertTrue("virtualCryptSHA256:" in out)
469 self.assertTrue("virtualCryptSHA512:" in out)
470 self.assertTrue("rounds=" in out)
472 # Should be calculating the hashes
473 # so they should change between calls.
474 sha256 = _get_attribute(out, "virtualCryptSHA256")
475 sha512 = _get_attribute(out, "virtualCryptSHA512")
477 out = self._get_password("virtualCryptSHA256;rounds=2561," +
478 "virtualCryptSHA512;rounds=5129",
480 self.assertFalse(sha256 == _get_attribute(out, "virtualCryptSHA256"))
481 self.assertFalse(sha512 == _get_attribute(out, "virtualCryptSHA512"))
483 # The returned hashes should specify the correct number of rounds
484 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=2561"))
485 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5129"))
487 # gpg decryption enabled.
488 # both virtual attributes specified, rounds specified
489 # both hashes stored in supplementalCredentials, with rounds
491 def test_gpg_both_hashes_rounds_stored_hashes_with_rounds(self):
492 self.add_user("CryptSHA512 " +
494 "CryptSHA512:rounds=5129 " +
495 "CryptSHA256:rounds=2561")
497 out = self._get_password("virtualCryptSHA256;rounds=2561," +
498 "virtualCryptSHA512;rounds=5129",
501 self.assertTrue("virtualCryptSHA256:" in out)
502 self.assertTrue("virtualCryptSHA512:" in out)
503 self.assertTrue("rounds=" in out)
505 # Should be using the pre computed hash in supplementalCredentials
506 # so it should not change between calls.
507 sha256 = _get_attribute(out, "virtualCryptSHA256")
508 sha512 = _get_attribute(out, "virtualCryptSHA512")
510 out = self._get_password("virtualCryptSHA256;rounds=2561," +
511 "virtualCryptSHA512;rounds=5129",
513 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
514 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
516 # The returned hashes should specify the correct number of rounds
517 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=2561"))
518 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5129"))
520 # gpg decryption enabled.
521 # both virtual attributes specified, rounds specified
522 # both hashes stored in supplementalCredentials, with rounds
523 # number of rounds stored/requested do not match
524 # Should get calculated hashes with the correct number of rounds
525 def test_gpg_both_hashes_rounds_stored_hashes_with_rounds_no_match(self):
526 self.add_user("CryptSHA512 " +
528 "CryptSHA512:rounds=5129 " +
529 "CryptSHA256:rounds=2561")
531 out = self._get_password("virtualCryptSHA256;rounds=4000," +
532 "virtualCryptSHA512;rounds=5000",
535 self.assertTrue("virtualCryptSHA256:" in out)
536 self.assertTrue("virtualCryptSHA512:" in out)
537 self.assertTrue("rounds=" in out)
539 # Should be calculating the hashes
540 # so they should change between calls.
541 sha256 = _get_attribute(out, "virtualCryptSHA256")
542 sha512 = _get_attribute(out, "virtualCryptSHA512")
544 out = self._get_password("virtualCryptSHA256;rounds=4000," +
545 "virtualCryptSHA512;rounds=5000",
547 self.assertFalse(sha256 == _get_attribute(out, "virtualCryptSHA256"))
548 self.assertFalse(sha512 == _get_attribute(out, "virtualCryptSHA512"))
550 # The calculated hashes should specify the correct number of rounds
551 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=4000"))
552 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5000"))