f66fc0e9ccf9c90dc42357e4815a9643e17e9621
[nivanova/samba-autobuild/.git] / python / samba / tests / samba_tool / user_virtualCryptSHA.py
1 # Tests for the samba-tool user sub command reading Primary:userPassword
2 #
3 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
4 #
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.
9 #
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.
14 #
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/>.
17 #
18
19 import os
20 import time
21 import base64
22 import ldb
23 import samba
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
31 import re
32 import random
33 import string
34
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:
39 #   1 upper case letter
40 #   1 lower case letter
41 #   1 digit.
42 #
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"
50
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"):
57         m = p.match(line)
58         if m:
59             return m.group(1)
60     return ""
61
62 class UserCmdCryptShaTestCase(SambaToolCmdTest):
63     """
64     Tests for samba-tool user subcommands generation of the virtualCryptSHA256
65     and virtualCryptSHA512 attributes
66     """
67     users = []
68     samdb = None
69
70     def setUp(self):
71         super(UserCmdCryptShaTestCase, self).setUp()
72
73     def add_user(self, hashes=""):
74         self.lp = samba.tests.env_loadparm()
75
76         # set the extra hashes to be calculated
77         self.lp.set(HASH_OPTION, hashes)
78
79         self.creds = Credentials()
80         self.session = system_session()
81         self.ldb = SamDB(
82             session_info=self.session,
83             credentials=self.creds,
84             lp=self.lp)
85
86         self.runsubcmd("user",
87                        "create",
88                        USER_NAME,
89                        USER_PASS)
90
91     def tearDown(self):
92         super(UserCmdCryptShaTestCase, self).tearDown()
93         self.runsubcmd("user", "delete", USER_NAME)
94
95     def _get_password(self, attributes, decrypt=False):
96         command = ["user",
97                    "getpassword",
98                    USER_NAME,
99                    "--attributes",
100                    attributes]
101         if decrypt:
102             command.append("--decrypt-samba-gpg")
103
104         (result, out, err) = self.runsubcmd(*command)
105         self.assertCmdSuccess(result,
106                               out,
107                               err,
108                               "Ensure getpassword runs")
109         self.assertEqual(err, "", "getpassword")
110         self.assertMatch(out,
111                          "Got password OK",
112                          "getpassword out[%s]" % out)
113         return out
114
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.
117     #
118     def _change_nt_hash(self):
119         res = self.ldb.search(expression = "cn=%s" % USER_NAME,
120                               scope      = ldb.SCOPE_SUBTREE)
121         msg = ldb.Message()
122         msg.dn = res[0].dn
123         msg["unicodePwd"] = ldb.MessageElement(b"ABCDEF1234567890",
124                                                ldb.FLAG_MOD_REPLACE,
125                                                "unicodePwd")
126         self.ldb.modify(
127             msg,
128             controls=["local_oid:%s:0" %
129                       dsdb.DSDB_CONTROL_BYPASS_PASSWORD_HASH_OID])
130
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):
136         self.add_user()
137         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
138
139         self.assertTrue("virtualCryptSHA256:" not in out)
140         self.assertTrue("virtualCryptSHA512:" not in out)
141         self.assertTrue("rounds=" not in out)
142
143     # gpg decryption not enabled.
144     # SHA256 specified
145     # no hashes stored in supplementalCredentials
146     # No rounds
147     #
148     # Should not get values
149     def test_no_gpg_sha256_no_rounds(self):
150         self.add_user()
151         out = self._get_password("virtualCryptSHA256")
152
153         self.assertTrue("virtualCryptSHA256:" not in out)
154         self.assertTrue("virtualCryptSHA512:" not in out)
155         self.assertTrue("rounds=" not in out)
156
157     # gpg decryption not enabled.
158     # SHA512 specified
159     # no hashes stored in supplementalCredentials
160     # No rounds
161     #
162     # Should not get values
163     def test_no_gpg_sha512_no_rounds(self):
164         self.add_user()
165         out = self._get_password("virtualCryptSHA512")
166
167         self.assertTrue("virtualCryptSHA256:" not in out)
168         self.assertTrue("virtualCryptSHA512:" not in out)
169         self.assertTrue("rounds=" not in out)
170
171     # gpg decryption not enabled.
172     # SHA128 specified, i.e. invalid/unknown algorithm
173     # no hashes stored in supplementalCredentials
174     # No rounds
175     #
176     # Should not get values
177     def test_no_gpg_invalid_alg_no_rounds(self):
178         self.add_user()
179         out = self._get_password("virtualCryptSHA128")
180
181         self.assertTrue("virtualCryptSHA256:" not in out)
182         self.assertTrue("virtualCryptSHA512:" not in out)
183         self.assertTrue("rounds=" not in out)
184
185     # gpg decryption enabled.
186     # both virtual attributes specified, no rounds option
187     # no hashes stored in supplementalCredentials
188     # Should get values
189     def test_gpg_both_hashes_no_rounds(self):
190         self.add_user()
191         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
192
193         self.assertTrue("virtualCryptSHA256:" in out)
194         self.assertTrue("virtualCryptSHA512:" in out)
195         self.assertTrue("rounds=" not in out)
196
197     # gpg decryption enabled.
198     # SHA256 specified
199     # no hashes stored in supplementalCredentials
200     # No rounds
201     #
202     # Should get values
203     def test_gpg_sha256_no_rounds(self):
204         self.add_user()
205         out = self._get_password("virtualCryptSHA256", True)
206
207         self.assertTrue("virtualCryptSHA256:" in out)
208         self.assertTrue("virtualCryptSHA512:" not in out)
209         self.assertTrue("rounds=" not in out)
210
211     # gpg decryption enabled.
212     # SHA512 specified
213     # no hashes stored in supplementalCredentials
214     # No rounds
215     #
216     # Should get values
217     def test_gpg_sha512_no_rounds(self):
218         self.add_user()
219         out = self._get_password("virtualCryptSHA512", True)
220
221         self.assertTrue("virtualCryptSHA256:" not in out)
222         self.assertTrue("virtualCryptSHA512:" in out)
223         self.assertTrue("rounds=" not in out)
224
225     # gpg decryption enabled.
226     # SHA128 specified, i.e. invalid/unknown algorithm
227     # no hashes stored in supplementalCredentials
228     # No rounds
229     #
230     # Should not get values
231     def test_gpg_invalid_alg_no_rounds(self):
232         self.add_user()
233         out = self._get_password("virtualCryptSHA128", True)
234
235         self.assertTrue("virtualCryptSHA256:" not in out)
236         self.assertTrue("virtualCryptSHA512:" not in out)
237         self.assertTrue("rounds=" not in out)
238
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
243     # invalid.
244     # Should not get values
245     def test_gpg_both_hashes_no_rounds_pwd_changed(self):
246         self.add_user()
247         self._change_nt_hash()
248         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
249
250         self.assertTrue("virtualCryptSHA256:" not in out)
251         self.assertTrue("virtualCryptSHA512:" not in out)
252         self.assertTrue("rounds=" not in out)
253
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
258     # invalid.
259     # Should not get values
260     def test_gpg_sha256_no_rounds_pwd_changed(self):
261         self.add_user()
262         self._change_nt_hash()
263         out = self._get_password("virtualCryptSHA256", True)
264
265         self.assertTrue("virtualCryptSHA256:" not in out)
266         self.assertTrue("virtualCryptSHA512:" not in out)
267         self.assertTrue("rounds=" not in out)
268
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
273     # invalid.
274     # Should not get values
275     def test_gpg_sha512_no_rounds_pwd_changed(self):
276         self.add_user()
277         self._change_nt_hash()
278         out = self._get_password("virtualCryptSHA256", True)
279
280         self.assertTrue("virtualCryptSHA256:" not in out)
281         self.assertTrue("virtualCryptSHA512:" not in out)
282         self.assertTrue("rounds=" not in out)
283
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):
289         self.add_user()
290         out = self._get_password(
291             "virtualCryptSHA256;rounds=10123,virtualCryptSHA512;rounds=10456",
292             True)
293
294         self.assertTrue("virtualCryptSHA256:" in out)
295         self.assertTrue("virtualCryptSHA512:" in out)
296
297         sha256 = _get_attribute(out, "virtualCryptSHA256")
298         self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=10123$"))
299
300         sha512 = _get_attribute(out, "virtualCryptSHA512")
301         self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=10456$"))
302
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):
309         self.add_user()
310         out = self._get_password(
311             "virtualCryptSHA256;rounds=invalid,virtualCryptSHA512;rounds=3125",
312             True)
313
314         self.assertTrue("virtualCryptSHA256:" in out)
315         self.assertTrue("virtualCryptSHA512:" in out)
316
317         sha256 = _get_attribute(out, "virtualCryptSHA256")
318         self.assertTrue(sha256.startswith("{CRYPT}$5$"))
319         self.assertTrue("rounds" not in sha256)
320
321         sha512 = _get_attribute(out, "virtualCryptSHA512")
322         self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=3125$"))
323
324     # gpg decryption not enabled.
325     # both virtual attributes specified, no rounds option
326     # both hashes stored in supplementalCredentials
327     # Should get values
328     def test_no_gpg_both_hashes_no_rounds_stored_hashes(self):
329         self.add_user("CryptSHA512 CryptSHA256")
330
331         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
332
333         self.assertTrue("virtualCryptSHA256:" in out)
334         self.assertTrue("virtualCryptSHA512:" in out)
335         self.assertTrue("rounds=" not in out)
336
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")
341
342         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
343         self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
344         self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
345
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")
352
353         out = self._get_password("virtualCryptSHA256;rounds=2561," +
354                                  "virtualCryptSHA512;rounds=5129")
355
356         self.assertTrue("virtualCryptSHA256:" in out)
357         self.assertTrue("virtualCryptSHA512:" in out)
358         self.assertTrue("rounds=" not in out)
359
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")
364
365         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
366         self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
367         self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
368
369     # gpg decryption not enabled.
370     # both virtual attributes specified, rounds specified
371     # both hashes stored in supplementalCredentials, with rounds
372     # Should get values
373     def test_no_gpg_both_hashes_rounds_stored_hashes_with_rounds(self):
374         self.add_user("CryptSHA512 " +
375                       "CryptSHA256 " +
376                       "CryptSHA512:rounds=5129 " +
377                       "CryptSHA256:rounds=2561")
378
379         out = self._get_password("virtualCryptSHA256;rounds=2561," +
380                                  "virtualCryptSHA512;rounds=5129")
381
382         self.assertTrue("virtualCryptSHA256:" in out)
383         self.assertTrue("virtualCryptSHA512:" in out)
384         self.assertTrue("rounds=" in out)
385
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")
390
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"))
395
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"))
399
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 " +
407                       "CryptSHA256 " +
408                       "CryptSHA512:rounds=5129 " +
409                       "CryptSHA256:rounds=2561")
410
411         out = self._get_password("virtualCryptSHA256;rounds=4000," +
412                                  "virtualCryptSHA512;rounds=5000")
413
414         self.assertTrue("virtualCryptSHA256:" in out)
415         self.assertTrue("virtualCryptSHA512:" in out)
416         self.assertTrue("rounds=" not in out)
417
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")
422
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"))
427
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"))
434
435     # gpg decryption enabled.
436     # both virtual attributes specified, no rounds option
437     # both hashes stored in supplementalCredentials
438     # Should get values
439     def test_gpg_both_hashes_no_rounds_stored_hashes(self):
440         self.add_user("CryptSHA512 CryptSHA256")
441
442         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
443
444         self.assertTrue("virtualCryptSHA256:" in out)
445         self.assertTrue("virtualCryptSHA512:" in out)
446         self.assertTrue("rounds=" not in out)
447
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")
452
453         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
454         self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
455         self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
456
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")
463
464         out = self._get_password("virtualCryptSHA256;rounds=2561," +
465                                  "virtualCryptSHA512;rounds=5129",
466                                  True)
467
468         self.assertTrue("virtualCryptSHA256:" in out)
469         self.assertTrue("virtualCryptSHA512:" in out)
470         self.assertTrue("rounds=" in out)
471
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")
476
477         out = self._get_password("virtualCryptSHA256;rounds=2561," +
478                                  "virtualCryptSHA512;rounds=5129",
479                                  True)
480         self.assertFalse(sha256 == _get_attribute(out, "virtualCryptSHA256"))
481         self.assertFalse(sha512 == _get_attribute(out, "virtualCryptSHA512"))
482
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"))
486
487     # gpg decryption enabled.
488     # both virtual attributes specified, rounds specified
489     # both hashes stored in supplementalCredentials, with rounds
490     # Should get values
491     def test_gpg_both_hashes_rounds_stored_hashes_with_rounds(self):
492         self.add_user("CryptSHA512 " +
493                       "CryptSHA256 " +
494                       "CryptSHA512:rounds=5129 " +
495                       "CryptSHA256:rounds=2561")
496
497         out = self._get_password("virtualCryptSHA256;rounds=2561," +
498                                  "virtualCryptSHA512;rounds=5129",
499                                  True)
500
501         self.assertTrue("virtualCryptSHA256:" in out)
502         self.assertTrue("virtualCryptSHA512:" in out)
503         self.assertTrue("rounds=" in out)
504
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")
509
510         out = self._get_password("virtualCryptSHA256;rounds=2561," +
511                                  "virtualCryptSHA512;rounds=5129",
512                                  True)
513         self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
514         self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
515
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"))
519
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 " +
527                       "CryptSHA256 " +
528                       "CryptSHA512:rounds=5129 " +
529                       "CryptSHA256:rounds=2561")
530
531         out = self._get_password("virtualCryptSHA256;rounds=4000," +
532                                  "virtualCryptSHA512;rounds=5000",
533                                  True)
534
535         self.assertTrue("virtualCryptSHA256:" in out)
536         self.assertTrue("virtualCryptSHA512:" in out)
537         self.assertTrue("rounds=" in out)
538
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")
543
544         out = self._get_password("virtualCryptSHA256;rounds=4000," +
545                                  "virtualCryptSHA512;rounds=5000",
546                                  True)
547         self.assertFalse(sha256 == _get_attribute(out, "virtualCryptSHA256"))
548         self.assertFalse(sha512 == _get_attribute(out, "virtualCryptSHA512"))
549
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"))