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.
56 def _get_attribute(out, name):
57 p = re.compile("^" + name + ":\s+(\S+)")
58 for line in out.split("\n"):
65 class UserCmdCryptShaTestCase(SambaToolCmdTest):
67 Tests for samba-tool user subcommands generation of the virtualCryptSHA256
68 and virtualCryptSHA512 attributes
74 super(UserCmdCryptShaTestCase, self).setUp()
76 def add_user(self, hashes=""):
77 self.lp = samba.tests.env_loadparm()
79 # set the extra hashes to be calculated
80 self.lp.set(HASH_OPTION, hashes)
82 self.creds = Credentials()
83 self.session = system_session()
85 session_info=self.session,
86 credentials=self.creds,
89 self.runsubcmd("user",
95 super(UserCmdCryptShaTestCase, self).tearDown()
96 self.runsubcmd("user", "delete", USER_NAME)
98 def _get_password(self, attributes, decrypt=False):
105 command.append("--decrypt-samba-gpg")
107 (result, out, err) = self.runsubcmd(*command)
108 self.assertCmdSuccess(result,
111 "Ensure getpassword runs")
112 self.assertEqual(err, "", "getpassword")
113 self.assertMatch(out,
115 "getpassword out[%s]" % out)
118 # Change the just the NT password hash, as would happen if the password
119 # was updated by Windows, the userPassword values are now obsolete.
121 def _change_nt_hash(self):
122 res = self.ldb.search(expression = "cn=%s" % USER_NAME,
123 scope = ldb.SCOPE_SUBTREE)
126 msg["unicodePwd"] = ldb.MessageElement(b"ABCDEF1234567890",
127 ldb.FLAG_MOD_REPLACE,
131 controls=["local_oid:%s:0" %
132 dsdb.DSDB_CONTROL_BYPASS_PASSWORD_HASH_OID])
134 # gpg decryption not enabled.
135 # both virtual attributes specified, no rounds option
136 # no hashes stored in supplementalCredentials
137 # Should not get values
138 def test_no_gpg_both_hashes_no_rounds(self):
140 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
142 self.assertTrue("virtualCryptSHA256:" not in out)
143 self.assertTrue("virtualCryptSHA512:" not in out)
144 self.assertTrue("rounds=" not in out)
146 # gpg decryption not enabled.
148 # no hashes stored in supplementalCredentials
151 # Should not get values
152 def test_no_gpg_sha256_no_rounds(self):
154 out = self._get_password("virtualCryptSHA256")
156 self.assertTrue("virtualCryptSHA256:" not in out)
157 self.assertTrue("virtualCryptSHA512:" not in out)
158 self.assertTrue("rounds=" not in out)
160 # gpg decryption not enabled.
162 # no hashes stored in supplementalCredentials
165 # Should not get values
166 def test_no_gpg_sha512_no_rounds(self):
168 out = self._get_password("virtualCryptSHA512")
170 self.assertTrue("virtualCryptSHA256:" not in out)
171 self.assertTrue("virtualCryptSHA512:" not in out)
172 self.assertTrue("rounds=" not in out)
174 # gpg decryption not enabled.
175 # SHA128 specified, i.e. invalid/unknown algorithm
176 # no hashes stored in supplementalCredentials
179 # Should not get values
180 def test_no_gpg_invalid_alg_no_rounds(self):
182 out = self._get_password("virtualCryptSHA128")
184 self.assertTrue("virtualCryptSHA256:" not in out)
185 self.assertTrue("virtualCryptSHA512:" not in out)
186 self.assertTrue("rounds=" not in out)
188 # gpg decryption enabled.
189 # both virtual attributes specified, no rounds option
190 # no hashes stored in supplementalCredentials
192 def test_gpg_both_hashes_no_rounds(self):
194 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
196 self.assertTrue("virtualCryptSHA256:" in out)
197 self.assertTrue("virtualCryptSHA512:" in out)
198 self.assertTrue("rounds=" not in out)
200 # gpg decryption enabled.
202 # no hashes stored in supplementalCredentials
206 def test_gpg_sha256_no_rounds(self):
208 out = self._get_password("virtualCryptSHA256", True)
210 self.assertTrue("virtualCryptSHA256:" in out)
211 self.assertTrue("virtualCryptSHA512:" not in out)
212 self.assertTrue("rounds=" not in out)
214 # gpg decryption enabled.
216 # no hashes stored in supplementalCredentials
220 def test_gpg_sha512_no_rounds(self):
222 out = self._get_password("virtualCryptSHA512", True)
224 self.assertTrue("virtualCryptSHA256:" not in out)
225 self.assertTrue("virtualCryptSHA512:" in out)
226 self.assertTrue("rounds=" not in out)
228 # gpg decryption enabled.
229 # SHA128 specified, i.e. invalid/unknown algorithm
230 # no hashes stored in supplementalCredentials
233 # Should not get values
234 def test_gpg_invalid_alg_no_rounds(self):
236 out = self._get_password("virtualCryptSHA128", True)
238 self.assertTrue("virtualCryptSHA256:" not in out)
239 self.assertTrue("virtualCryptSHA512:" not in out)
240 self.assertTrue("rounds=" not in out)
242 # gpg decryption enabled.
243 # both virtual attributes specified, no rounds option
244 # no hashes stored in supplementalCredentials
245 # underlying windows password changed, so plain text password is
247 # Should not get values
248 def test_gpg_both_hashes_no_rounds_pwd_changed(self):
250 self._change_nt_hash()
251 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
253 self.assertTrue("virtualCryptSHA256:" not in out)
254 self.assertTrue("virtualCryptSHA512:" not in out)
255 self.assertTrue("rounds=" not in out)
257 # gpg decryption enabled.
258 # SHA256 specified, no rounds option
259 # no hashes stored in supplementalCredentials
260 # underlying windows password changed, so plain text password is
262 # Should not get values
263 def test_gpg_sha256_no_rounds_pwd_changed(self):
265 self._change_nt_hash()
266 out = self._get_password("virtualCryptSHA256", True)
268 self.assertTrue("virtualCryptSHA256:" not in out)
269 self.assertTrue("virtualCryptSHA512:" not in out)
270 self.assertTrue("rounds=" not in out)
272 # gpg decryption enabled.
273 # SHA512 specified, no rounds option
274 # no hashes stored in supplementalCredentials
275 # underlying windows password changed, so plain text password is
277 # Should not get values
278 def test_gpg_sha512_no_rounds_pwd_changed(self):
280 self._change_nt_hash()
281 out = self._get_password("virtualCryptSHA256", True)
283 self.assertTrue("virtualCryptSHA256:" not in out)
284 self.assertTrue("virtualCryptSHA512:" not in out)
285 self.assertTrue("rounds=" not in out)
287 # gpg decryption enabled.
288 # both virtual attributes specified, rounds specified
289 # no hashes stored in supplementalCredentials
290 # Should get values reflecting the requested rounds
291 def test_gpg_both_hashes_both_rounds(self):
293 out = self._get_password(
294 "virtualCryptSHA256;rounds=10123,virtualCryptSHA512;rounds=10456",
297 self.assertTrue("virtualCryptSHA256:" in out)
298 self.assertTrue("virtualCryptSHA512:" in out)
300 sha256 = _get_attribute(out, "virtualCryptSHA256")
301 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=10123$"))
303 sha512 = _get_attribute(out, "virtualCryptSHA512")
304 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=10456$"))
306 # gpg decryption enabled.
307 # both virtual attributes specified, rounds specified
308 # invalid rounds for sha256
309 # no hashes stored in supplementalCredentials
310 # Should get values, no rounds for sha256, rounds for sha 512
311 def test_gpg_both_hashes_sha256_rounds_invalid(self):
313 out = self._get_password(
314 "virtualCryptSHA256;rounds=invalid,virtualCryptSHA512;rounds=3125",
317 self.assertTrue("virtualCryptSHA256:" in out)
318 self.assertTrue("virtualCryptSHA512:" in out)
320 sha256 = _get_attribute(out, "virtualCryptSHA256")
321 self.assertTrue(sha256.startswith("{CRYPT}$5$"))
322 self.assertTrue("rounds" not in sha256)
324 sha512 = _get_attribute(out, "virtualCryptSHA512")
325 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=3125$"))
327 # gpg decryption not enabled.
328 # both virtual attributes specified, no rounds option
329 # both hashes stored in supplementalCredentials
331 def test_no_gpg_both_hashes_no_rounds_stored_hashes(self):
332 self.add_user("CryptSHA512 CryptSHA256")
334 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
336 self.assertTrue("virtualCryptSHA256:" in out)
337 self.assertTrue("virtualCryptSHA512:" in out)
338 self.assertTrue("rounds=" not in out)
340 # Should be using the pre computed hash in supplementalCredentials
341 # so it should not change between calls.
342 sha256 = _get_attribute(out, "virtualCryptSHA256")
343 sha512 = _get_attribute(out, "virtualCryptSHA512")
345 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
346 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
347 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
349 # gpg decryption not enabled.
350 # both virtual attributes specified, rounds specified
351 # both hashes stored in supplementalCredentials, with not rounds
352 # Should get hashes for the first matching scheme entry
353 def test_no_gpg_both_hashes_rounds_stored_hashes(self):
354 self.add_user("CryptSHA512 CryptSHA256")
356 out = self._get_password("virtualCryptSHA256;rounds=2561," +
357 "virtualCryptSHA512;rounds=5129")
359 self.assertTrue("virtualCryptSHA256:" in out)
360 self.assertTrue("virtualCryptSHA512:" in out)
361 self.assertTrue("rounds=" not in out)
363 # Should be using the pre computed hash in supplementalCredentials
364 # so it should not change between calls.
365 sha256 = _get_attribute(out, "virtualCryptSHA256")
366 sha512 = _get_attribute(out, "virtualCryptSHA512")
368 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
369 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
370 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
372 # gpg decryption not enabled.
373 # both virtual attributes specified, rounds specified
374 # both hashes stored in supplementalCredentials, with rounds
376 def test_no_gpg_both_hashes_rounds_stored_hashes_with_rounds(self):
377 self.add_user("CryptSHA512 " +
379 "CryptSHA512:rounds=5129 " +
380 "CryptSHA256:rounds=2561")
382 out = self._get_password("virtualCryptSHA256;rounds=2561," +
383 "virtualCryptSHA512;rounds=5129")
385 self.assertTrue("virtualCryptSHA256:" in out)
386 self.assertTrue("virtualCryptSHA512:" in out)
387 self.assertTrue("rounds=" in out)
389 # Should be using the pre computed hash in supplementalCredentials
390 # so it should not change between calls.
391 sha256 = _get_attribute(out, "virtualCryptSHA256")
392 sha512 = _get_attribute(out, "virtualCryptSHA512")
394 out = self._get_password("virtualCryptSHA256;rounds=2561," +
395 "virtualCryptSHA512;rounds=5129")
396 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
397 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
399 # Number of rounds should match that specified
400 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=2561"))
401 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5129"))
403 # gpg decryption not enabled.
404 # both virtual attributes specified, rounds specified
405 # both hashes stored in supplementalCredentials, with rounds
406 # number of rounds stored/requested do not match
407 # Should get the precomputed hashes for CryptSHA512 and CryptSHA256
408 def test_no_gpg_both_hashes_rounds_stored_hashes_with_rounds_no_match(self):
409 self.add_user("CryptSHA512 " +
411 "CryptSHA512:rounds=5129 " +
412 "CryptSHA256:rounds=2561")
414 out = self._get_password("virtualCryptSHA256;rounds=4000," +
415 "virtualCryptSHA512;rounds=5000")
417 self.assertTrue("virtualCryptSHA256:" in out)
418 self.assertTrue("virtualCryptSHA512:" in out)
419 self.assertTrue("rounds=" not in out)
421 # Should be using the pre computed hash in supplementalCredentials
422 # so it should not change between calls.
423 sha256 = _get_attribute(out, "virtualCryptSHA256")
424 sha512 = _get_attribute(out, "virtualCryptSHA512")
426 out = self._get_password("virtualCryptSHA256;rounds=4000," +
427 "virtualCryptSHA512;rounds=5000")
428 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
429 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
431 # As the number of rounds did not match, should have returned the
432 # first hash of the coresponding scheme
433 out = self._get_password("virtualCryptSHA256," +
434 "virtualCryptSHA512")
435 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
436 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
438 # gpg decryption enabled.
439 # both virtual attributes specified, no rounds option
440 # both hashes stored in supplementalCredentials
442 def test_gpg_both_hashes_no_rounds_stored_hashes(self):
443 self.add_user("CryptSHA512 CryptSHA256")
445 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
447 self.assertTrue("virtualCryptSHA256:" in out)
448 self.assertTrue("virtualCryptSHA512:" in out)
449 self.assertTrue("rounds=" not in out)
451 # Should be using the pre computed hash in supplementalCredentials
452 # so it should not change between calls.
453 sha256 = _get_attribute(out, "virtualCryptSHA256")
454 sha512 = _get_attribute(out, "virtualCryptSHA512")
456 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
457 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
458 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
460 # gpg decryption enabled.
461 # both virtual attributes specified, rounds specified
462 # both hashes stored in supplementalCredentials, with no rounds
463 # Should get calculated hashed with the correct number of rounds
464 def test_gpg_both_hashes_rounds_stored_hashes(self):
465 self.add_user("CryptSHA512 CryptSHA256")
467 out = self._get_password("virtualCryptSHA256;rounds=2561," +
468 "virtualCryptSHA512;rounds=5129",
471 self.assertTrue("virtualCryptSHA256:" in out)
472 self.assertTrue("virtualCryptSHA512:" in out)
473 self.assertTrue("rounds=" in out)
475 # Should be calculating the hashes
476 # so they should change between calls.
477 sha256 = _get_attribute(out, "virtualCryptSHA256")
478 sha512 = _get_attribute(out, "virtualCryptSHA512")
480 out = self._get_password("virtualCryptSHA256;rounds=2561," +
481 "virtualCryptSHA512;rounds=5129",
483 self.assertFalse(sha256 == _get_attribute(out, "virtualCryptSHA256"))
484 self.assertFalse(sha512 == _get_attribute(out, "virtualCryptSHA512"))
486 # The returned hashes should specify the correct number of rounds
487 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=2561"))
488 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5129"))
490 # gpg decryption enabled.
491 # both virtual attributes specified, rounds specified
492 # both hashes stored in supplementalCredentials, with rounds
494 def test_gpg_both_hashes_rounds_stored_hashes_with_rounds(self):
495 self.add_user("CryptSHA512 " +
497 "CryptSHA512:rounds=5129 " +
498 "CryptSHA256:rounds=2561")
500 out = self._get_password("virtualCryptSHA256;rounds=2561," +
501 "virtualCryptSHA512;rounds=5129",
504 self.assertTrue("virtualCryptSHA256:" in out)
505 self.assertTrue("virtualCryptSHA512:" in out)
506 self.assertTrue("rounds=" in out)
508 # Should be using the pre computed hash in supplementalCredentials
509 # so it should not change between calls.
510 sha256 = _get_attribute(out, "virtualCryptSHA256")
511 sha512 = _get_attribute(out, "virtualCryptSHA512")
513 out = self._get_password("virtualCryptSHA256;rounds=2561," +
514 "virtualCryptSHA512;rounds=5129",
516 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
517 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
519 # The returned hashes should specify the correct number of rounds
520 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=2561"))
521 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5129"))
523 # gpg decryption enabled.
524 # both virtual attributes specified, rounds specified
525 # both hashes stored in supplementalCredentials, with rounds
526 # number of rounds stored/requested do not match
527 # Should get calculated hashes with the correct number of rounds
528 def test_gpg_both_hashes_rounds_stored_hashes_with_rounds_no_match(self):
529 self.add_user("CryptSHA512 " +
531 "CryptSHA512:rounds=5129 " +
532 "CryptSHA256:rounds=2561")
534 out = self._get_password("virtualCryptSHA256;rounds=4000," +
535 "virtualCryptSHA512;rounds=5000",
538 self.assertTrue("virtualCryptSHA256:" in out)
539 self.assertTrue("virtualCryptSHA512:" in out)
540 self.assertTrue("rounds=" in out)
542 # Should be calculating the hashes
543 # so they should change between calls.
544 sha256 = _get_attribute(out, "virtualCryptSHA256")
545 sha512 = _get_attribute(out, "virtualCryptSHA512")
547 out = self._get_password("virtualCryptSHA256;rounds=4000," +
548 "virtualCryptSHA512;rounds=5000",
550 self.assertFalse(sha256 == _get_attribute(out, "virtualCryptSHA256"))
551 self.assertFalse(sha512 == _get_attribute(out, "virtualCryptSHA512"))
553 # The calculated hashes should specify the correct number of rounds
554 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=4000"))
555 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5000"))