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
37 USER_NAME = "CryptSHATestUser"
38 # Create a random 32 character password, containing only letters and
39 # digits to avoid issues when used on the command line.
40 USER_PASS = ''.join(random.choice(string.ascii_uppercase +
41 string.ascii_lowercase +
42 string.digits) for _ in range(32))
43 HASH_OPTION = "password hash userPassword schemes"
45 # Get the value of an attribute from the output string
46 # Note: Does not correctly handle values spanning multiple lines,
47 # which is acceptable for it's usage in these tests.
48 def _get_attribute(out, name):
49 p = re.compile("^"+name+":\s+(\S+)")
50 for line in out.split("\n"):
56 class UserCmdCryptShaTestCase(SambaToolCmdTest):
58 Tests for samba-tool user subcommands generation of the virtualCryptSHA256
59 and virtualCryptSHA512 attributes
65 super(UserCmdCryptShaTestCase, self).setUp()
67 def add_user(self, hashes = ""):
68 self.lp = samba.tests.env_loadparm()
70 # set the extra hashes to be calculated
71 self.lp.set(HASH_OPTION, hashes)
73 self.creds = Credentials()
74 self.session = system_session()
76 session_info=self.session,
77 credentials=self.creds,
80 self.runsubcmd("user",
86 super(UserCmdCryptShaTestCase, self).tearDown()
87 self.runsubcmd("user", "delete", USER_NAME)
89 def _get_password(self, attributes, decrypt = False):
96 command.append("--decrypt-samba-gpg")
98 (result, out, err) = self.runsubcmd(*command)
99 self.assertCmdSuccess(result,
102 "Ensure getpassword runs")
103 self.assertEqual(err, "", "getpassword")
104 self.assertMatch(out,
106 "getpassword out[%s]" % out)
109 # Change the just the NT password hash, as would happen if the password
110 # was updated by Windows, the userPassword values are now obsolete.
112 def _change_nt_hash(self):
113 res = self.ldb.search(expression = "cn=%s" % USER_NAME,
114 scope = ldb.SCOPE_SUBTREE)
117 msg["unicodePwd"] = ldb.MessageElement(b"ABCDEF1234567890",
118 ldb.FLAG_MOD_REPLACE,
122 controls=["local_oid:%s:0" %
123 dsdb.DSDB_CONTROL_BYPASS_PASSWORD_HASH_OID])
125 # gpg decryption not enabled.
126 # both virtual attributes specified, no rounds option
127 # no hashes stored in supplementalCredentials
128 # Should not get values
129 def test_no_gpg_both_hashes_no_rounds(self):
131 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
133 self.assertTrue("virtualCryptSHA256:" not in out)
134 self.assertTrue("virtualCryptSHA512:" not in out)
135 self.assertTrue("rounds=" not in out)
137 # gpg decryption not enabled.
139 # no hashes stored in supplementalCredentials
142 # Should not get values
143 def test_no_gpg_sha256_no_rounds(self):
145 out = self._get_password("virtualCryptSHA256")
147 self.assertTrue("virtualCryptSHA256:" not in out)
148 self.assertTrue("virtualCryptSHA512:" not in out)
149 self.assertTrue("rounds=" not in out)
151 # gpg decryption not enabled.
153 # no hashes stored in supplementalCredentials
156 # Should not get values
157 def test_no_gpg_sha512_no_rounds(self):
159 out = self._get_password("virtualCryptSHA512")
161 self.assertTrue("virtualCryptSHA256:" not in out)
162 self.assertTrue("virtualCryptSHA512:" not in out)
163 self.assertTrue("rounds=" not in out)
165 # gpg decryption not enabled.
166 # SHA128 specified, i.e. invalid/unknown algorithm
167 # no hashes stored in supplementalCredentials
170 # Should not get values
171 def test_no_gpg_invalid_alg_no_rounds(self):
173 out = self._get_password("virtualCryptSHA128")
175 self.assertTrue("virtualCryptSHA256:" not in out)
176 self.assertTrue("virtualCryptSHA512:" not in out)
177 self.assertTrue("rounds=" not in out)
179 # gpg decryption enabled.
180 # both virtual attributes specified, no rounds option
181 # no hashes stored in supplementalCredentials
183 def test_gpg_both_hashes_no_rounds(self):
185 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
187 self.assertTrue("virtualCryptSHA256:" in out)
188 self.assertTrue("virtualCryptSHA512:" in out)
189 self.assertTrue("rounds=" not in out)
191 # gpg decryption enabled.
193 # no hashes stored in supplementalCredentials
197 def test_gpg_sha256_no_rounds(self):
199 out = self._get_password("virtualCryptSHA256", True)
201 self.assertTrue("virtualCryptSHA256:" in out)
202 self.assertTrue("virtualCryptSHA512:" not in out)
203 self.assertTrue("rounds=" not in out)
205 # gpg decryption enabled.
207 # no hashes stored in supplementalCredentials
211 def test_gpg_sha512_no_rounds(self):
213 out = self._get_password("virtualCryptSHA512", True)
215 self.assertTrue("virtualCryptSHA256:" not in out)
216 self.assertTrue("virtualCryptSHA512:" in out)
217 self.assertTrue("rounds=" not in out)
219 # gpg decryption enabled.
220 # SHA128 specified, i.e. invalid/unknown algorithm
221 # no hashes stored in supplementalCredentials
224 # Should not get values
225 def test_gpg_invalid_alg_no_rounds(self):
227 out = self._get_password("virtualCryptSHA128", True)
229 self.assertTrue("virtualCryptSHA256:" not in out)
230 self.assertTrue("virtualCryptSHA512:" not in out)
231 self.assertTrue("rounds=" not in out)
233 # gpg decryption enabled.
234 # both virtual attributes specified, no rounds option
235 # no hashes stored in supplementalCredentials
236 # underlying windows password changed, so plain text password is
238 # Should not get values
239 def test_gpg_both_hashes_no_rounds_pwd_changed(self):
241 self._change_nt_hash()
242 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
244 self.assertTrue("virtualCryptSHA256:" not in out)
245 self.assertTrue("virtualCryptSHA512:" not in out)
246 self.assertTrue("rounds=" not in out)
248 # gpg decryption enabled.
249 # SHA256 specified, no rounds option
250 # no hashes stored in supplementalCredentials
251 # underlying windows password changed, so plain text password is
253 # Should not get values
254 def test_gpg_sha256_no_rounds_pwd_changed(self):
256 self._change_nt_hash()
257 out = self._get_password("virtualCryptSHA256", True)
259 self.assertTrue("virtualCryptSHA256:" not in out)
260 self.assertTrue("virtualCryptSHA512:" not in out)
261 self.assertTrue("rounds=" not in out)
263 # gpg decryption enabled.
264 # SHA512 specified, no rounds option
265 # no hashes stored in supplementalCredentials
266 # underlying windows password changed, so plain text password is
268 # Should not get values
269 def test_gpg_sha512_no_rounds_pwd_changed(self):
271 self._change_nt_hash()
272 out = self._get_password("virtualCryptSHA256", True)
274 self.assertTrue("virtualCryptSHA256:" not in out)
275 self.assertTrue("virtualCryptSHA512:" not in out)
276 self.assertTrue("rounds=" not in out)
278 # gpg decryption enabled.
279 # both virtual attributes specified, rounds specified
280 # no hashes stored in supplementalCredentials
281 # Should get values reflecting the requested rounds
282 def test_gpg_both_hashes_both_rounds(self):
284 out = self._get_password(
285 "virtualCryptSHA256;rounds=10123,virtualCryptSHA512;rounds=10456",
288 self.assertTrue("virtualCryptSHA256:" in out)
289 self.assertTrue("virtualCryptSHA512:" in out)
291 sha256 = _get_attribute(out, "virtualCryptSHA256")
292 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=10123$"))
294 sha512 = _get_attribute(out, "virtualCryptSHA512")
295 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=10456$"))
297 # gpg decryption enabled.
298 # both virtual attributes specified, rounds specified
299 # invalid rounds for sha256
300 # no hashes stored in supplementalCredentials
301 # Should get values, no rounds for sha256, rounds for sha 512
302 def test_gpg_both_hashes_sha256_rounds_invalid(self):
304 out = self._get_password(
305 "virtualCryptSHA256;rounds=invalid,virtualCryptSHA512;rounds=3125",
308 self.assertTrue("virtualCryptSHA256:" in out)
309 self.assertTrue("virtualCryptSHA512:" in out)
311 sha256 = _get_attribute(out, "virtualCryptSHA256")
312 self.assertTrue(sha256.startswith("{CRYPT}$5$"))
313 self.assertTrue("rounds" not in sha256)
315 sha512 = _get_attribute(out, "virtualCryptSHA512")
316 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=3125$"))
318 # gpg decryption not enabled.
319 # both virtual attributes specified, no rounds option
320 # both hashes stored in supplementalCredentials
322 def test_no_gpg_both_hashes_no_rounds_stored_hashes(self):
323 self.add_user("CryptSHA512 CryptSHA256")
325 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
327 self.assertTrue("virtualCryptSHA256:" in out)
328 self.assertTrue("virtualCryptSHA512:" in out)
329 self.assertTrue("rounds=" not in out)
331 # Should be using the pre computed hash in supplementalCredentials
332 # so it should not change between calls.
333 sha256 = _get_attribute(out, "virtualCryptSHA256")
334 sha512 = _get_attribute(out, "virtualCryptSHA512")
336 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
337 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
338 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
340 # gpg decryption not enabled.
341 # both virtual attributes specified, rounds specified
342 # both hashes stored in supplementalCredentials, with not rounds
343 # Should get hashes for the first matching scheme entry
344 def test_no_gpg_both_hashes_rounds_stored_hashes(self):
345 self.add_user("CryptSHA512 CryptSHA256")
347 out = self._get_password("virtualCryptSHA256;rounds=2561," +
348 "virtualCryptSHA512;rounds=5129")
350 self.assertTrue("virtualCryptSHA256:" in out)
351 self.assertTrue("virtualCryptSHA512:" in out)
352 self.assertTrue("rounds=" not in out)
354 # Should be using the pre computed hash in supplementalCredentials
355 # so it should not change between calls.
356 sha256 = _get_attribute(out, "virtualCryptSHA256")
357 sha512 = _get_attribute(out, "virtualCryptSHA512")
359 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
360 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
361 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
363 # gpg decryption not enabled.
364 # both virtual attributes specified, rounds specified
365 # both hashes stored in supplementalCredentials, with rounds
367 def test_no_gpg_both_hashes_rounds_stored_hashes_with_rounds(self):
368 self.add_user("CryptSHA512 " +
370 "CryptSHA512:rounds=5129 " +
371 "CryptSHA256:rounds=2561")
373 out = self._get_password("virtualCryptSHA256;rounds=2561," +
374 "virtualCryptSHA512;rounds=5129")
376 self.assertTrue("virtualCryptSHA256:" in out)
377 self.assertTrue("virtualCryptSHA512:" in out)
378 self.assertTrue("rounds=" in out)
380 # Should be using the pre computed hash in supplementalCredentials
381 # so it should not change between calls.
382 sha256 = _get_attribute(out, "virtualCryptSHA256")
383 sha512 = _get_attribute(out, "virtualCryptSHA512")
385 out = self._get_password("virtualCryptSHA256;rounds=2561," +
386 "virtualCryptSHA512;rounds=5129")
387 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
388 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
390 # Number of rounds should match that specified
391 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=2561"))
392 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5129"))
394 # gpg decryption not enabled.
395 # both virtual attributes specified, rounds specified
396 # both hashes stored in supplementalCredentials, with rounds
397 # number of rounds stored/requested do not match
398 # Should get the precomputed hashes for CryptSHA512 and CryptSHA256
399 def test_no_gpg_both_hashes_rounds_stored_hashes_with_rounds_no_match(self):
400 self.add_user("CryptSHA512 " +
402 "CryptSHA512:rounds=5129 " +
403 "CryptSHA256:rounds=2561")
405 out = self._get_password("virtualCryptSHA256;rounds=4000," +
406 "virtualCryptSHA512;rounds=5000")
408 self.assertTrue("virtualCryptSHA256:" in out)
409 self.assertTrue("virtualCryptSHA512:" in out)
410 self.assertTrue("rounds=" not in out)
412 # Should be using the pre computed hash in supplementalCredentials
413 # so it should not change between calls.
414 sha256 = _get_attribute(out, "virtualCryptSHA256")
415 sha512 = _get_attribute(out, "virtualCryptSHA512")
417 out = self._get_password("virtualCryptSHA256;rounds=4000," +
418 "virtualCryptSHA512;rounds=5000")
419 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
420 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
422 # As the number of rounds did not match, should have returned the
423 # first hash of the coresponding scheme
424 out = self._get_password("virtualCryptSHA256," +
425 "virtualCryptSHA512")
426 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
427 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
429 # gpg decryption enabled.
430 # both virtual attributes specified, no rounds option
431 # both hashes stored in supplementalCredentials
433 def test_gpg_both_hashes_no_rounds_stored_hashes(self):
434 self.add_user("CryptSHA512 CryptSHA256")
436 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
438 self.assertTrue("virtualCryptSHA256:" in out)
439 self.assertTrue("virtualCryptSHA512:" in out)
440 self.assertTrue("rounds=" not in out)
442 # Should be using the pre computed hash in supplementalCredentials
443 # so it should not change between calls.
444 sha256 = _get_attribute(out, "virtualCryptSHA256")
445 sha512 = _get_attribute(out, "virtualCryptSHA512")
447 out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
448 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
449 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
451 # gpg decryption enabled.
452 # both virtual attributes specified, rounds specified
453 # both hashes stored in supplementalCredentials, with no rounds
454 # Should get calculated hashed with the correct number of rounds
455 def test_gpg_both_hashes_rounds_stored_hashes(self):
456 self.add_user("CryptSHA512 CryptSHA256")
458 out = self._get_password("virtualCryptSHA256;rounds=2561," +
459 "virtualCryptSHA512;rounds=5129",
462 self.assertTrue("virtualCryptSHA256:" in out)
463 self.assertTrue("virtualCryptSHA512:" in out)
464 self.assertTrue("rounds=" in out)
466 # Should be calculating the hashes
467 # so they should change between calls.
468 sha256 = _get_attribute(out, "virtualCryptSHA256")
469 sha512 = _get_attribute(out, "virtualCryptSHA512")
471 out = self._get_password("virtualCryptSHA256;rounds=2561," +
472 "virtualCryptSHA512;rounds=5129",
474 self.assertFalse(sha256 == _get_attribute(out, "virtualCryptSHA256"))
475 self.assertFalse(sha512 ==_get_attribute(out, "virtualCryptSHA512"))
477 # The returned hashes should specify the correct number of rounds
478 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=2561"))
479 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5129"))
481 # gpg decryption enabled.
482 # both virtual attributes specified, rounds specified
483 # both hashes stored in supplementalCredentials, with rounds
485 def test_gpg_both_hashes_rounds_stored_hashes_with_rounds(self):
486 self.add_user("CryptSHA512 " +
488 "CryptSHA512:rounds=5129 " +
489 "CryptSHA256:rounds=2561")
491 out = self._get_password("virtualCryptSHA256;rounds=2561," +
492 "virtualCryptSHA512;rounds=5129",
495 self.assertTrue("virtualCryptSHA256:" in out)
496 self.assertTrue("virtualCryptSHA512:" in out)
497 self.assertTrue("rounds=" in out)
499 # Should be using the pre computed hash in supplementalCredentials
500 # so it should not change between calls.
501 sha256 = _get_attribute(out, "virtualCryptSHA256")
502 sha512 = _get_attribute(out, "virtualCryptSHA512")
504 out = self._get_password("virtualCryptSHA256;rounds=2561," +
505 "virtualCryptSHA512;rounds=5129",
507 self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
508 self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
510 # The returned hashes should specify the correct number of rounds
511 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=2561"))
512 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5129"))
514 # gpg decryption enabled.
515 # both virtual attributes specified, rounds specified
516 # both hashes stored in supplementalCredentials, with rounds
517 # number of rounds stored/requested do not match
518 # Should get calculated hashes with the correct number of rounds
519 def test_gpg_both_hashes_rounds_stored_hashes_with_rounds_no_match(self):
520 self.add_user("CryptSHA512 " +
522 "CryptSHA512:rounds=5129 " +
523 "CryptSHA256:rounds=2561")
525 out = self._get_password("virtualCryptSHA256;rounds=4000," +
526 "virtualCryptSHA512;rounds=5000",
529 self.assertTrue("virtualCryptSHA256:" in out)
530 self.assertTrue("virtualCryptSHA512:" in out)
531 self.assertTrue("rounds=" in out)
533 # Should be calculating the hashes
534 # so they should change between calls.
535 sha256 = _get_attribute(out, "virtualCryptSHA256")
536 sha512 = _get_attribute(out, "virtualCryptSHA512")
538 out = self._get_password("virtualCryptSHA256;rounds=4000," +
539 "virtualCryptSHA512;rounds=5000",
541 self.assertFalse(sha256 == _get_attribute(out, "virtualCryptSHA256"))
542 self.assertFalse(sha512 == _get_attribute(out, "virtualCryptSHA512"))
544 # The calculated hashes should specify the correct number of rounds
545 self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=4000"))
546 self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=5000"))