0a707628dcf6ef2b562014722dffb432bd2f092b
[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
55
56 def _get_attribute(out, name):
57     p = re.compile("^" + name + ":\s+(\S+)")
58     for line in out.split("\n"):
59         m = p.match(line)
60         if m:
61             return m.group(1)
62     return ""
63
64
65 class UserCmdCryptShaTestCase(SambaToolCmdTest):
66     """
67     Tests for samba-tool user subcommands generation of the virtualCryptSHA256
68     and virtualCryptSHA512 attributes
69     """
70     users = []
71     samdb = None
72
73     def setUp(self):
74         super(UserCmdCryptShaTestCase, self).setUp()
75
76     def add_user(self, hashes=""):
77         self.lp = samba.tests.env_loadparm()
78
79         # set the extra hashes to be calculated
80         self.lp.set(HASH_OPTION, hashes)
81
82         self.creds = Credentials()
83         self.session = system_session()
84         self.ldb = SamDB(
85             session_info=self.session,
86             credentials=self.creds,
87             lp=self.lp)
88
89         self.runsubcmd("user",
90                        "create",
91                        USER_NAME,
92                        USER_PASS)
93
94     def tearDown(self):
95         super(UserCmdCryptShaTestCase, self).tearDown()
96         self.runsubcmd("user", "delete", USER_NAME)
97
98     def _get_password(self, attributes, decrypt=False):
99         command = ["user",
100                    "getpassword",
101                    USER_NAME,
102                    "--attributes",
103                    attributes]
104         if decrypt:
105             command.append("--decrypt-samba-gpg")
106
107         (result, out, err) = self.runsubcmd(*command)
108         self.assertCmdSuccess(result,
109                               out,
110                               err,
111                               "Ensure getpassword runs")
112         self.assertEqual(err, "", "getpassword")
113         self.assertMatch(out,
114                          "Got password OK",
115                          "getpassword out[%s]" % out)
116         return out
117
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.
120     #
121     def _change_nt_hash(self):
122         res = self.ldb.search(expression = "cn=%s" % USER_NAME,
123                               scope      = ldb.SCOPE_SUBTREE)
124         msg = ldb.Message()
125         msg.dn = res[0].dn
126         msg["unicodePwd"] = ldb.MessageElement(b"ABCDEF1234567890",
127                                                ldb.FLAG_MOD_REPLACE,
128                                                "unicodePwd")
129         self.ldb.modify(
130             msg,
131             controls=["local_oid:%s:0" %
132                       dsdb.DSDB_CONTROL_BYPASS_PASSWORD_HASH_OID])
133
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):
139         self.add_user()
140         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
141
142         self.assertTrue("virtualCryptSHA256:" not in out)
143         self.assertTrue("virtualCryptSHA512:" not in out)
144         self.assertTrue("rounds=" not in out)
145
146     # gpg decryption not enabled.
147     # SHA256 specified
148     # no hashes stored in supplementalCredentials
149     # No rounds
150     #
151     # Should not get values
152     def test_no_gpg_sha256_no_rounds(self):
153         self.add_user()
154         out = self._get_password("virtualCryptSHA256")
155
156         self.assertTrue("virtualCryptSHA256:" not in out)
157         self.assertTrue("virtualCryptSHA512:" not in out)
158         self.assertTrue("rounds=" not in out)
159
160     # gpg decryption not enabled.
161     # SHA512 specified
162     # no hashes stored in supplementalCredentials
163     # No rounds
164     #
165     # Should not get values
166     def test_no_gpg_sha512_no_rounds(self):
167         self.add_user()
168         out = self._get_password("virtualCryptSHA512")
169
170         self.assertTrue("virtualCryptSHA256:" not in out)
171         self.assertTrue("virtualCryptSHA512:" not in out)
172         self.assertTrue("rounds=" not in out)
173
174     # gpg decryption not enabled.
175     # SHA128 specified, i.e. invalid/unknown algorithm
176     # no hashes stored in supplementalCredentials
177     # No rounds
178     #
179     # Should not get values
180     def test_no_gpg_invalid_alg_no_rounds(self):
181         self.add_user()
182         out = self._get_password("virtualCryptSHA128")
183
184         self.assertTrue("virtualCryptSHA256:" not in out)
185         self.assertTrue("virtualCryptSHA512:" not in out)
186         self.assertTrue("rounds=" not in out)
187
188     # gpg decryption enabled.
189     # both virtual attributes specified, no rounds option
190     # no hashes stored in supplementalCredentials
191     # Should get values
192     def test_gpg_both_hashes_no_rounds(self):
193         self.add_user()
194         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
195
196         self.assertTrue("virtualCryptSHA256:" in out)
197         self.assertTrue("virtualCryptSHA512:" in out)
198         self.assertTrue("rounds=" not in out)
199
200     # gpg decryption enabled.
201     # SHA256 specified
202     # no hashes stored in supplementalCredentials
203     # No rounds
204     #
205     # Should get values
206     def test_gpg_sha256_no_rounds(self):
207         self.add_user()
208         out = self._get_password("virtualCryptSHA256", True)
209
210         self.assertTrue("virtualCryptSHA256:" in out)
211         self.assertTrue("virtualCryptSHA512:" not in out)
212         self.assertTrue("rounds=" not in out)
213
214     # gpg decryption enabled.
215     # SHA512 specified
216     # no hashes stored in supplementalCredentials
217     # No rounds
218     #
219     # Should get values
220     def test_gpg_sha512_no_rounds(self):
221         self.add_user()
222         out = self._get_password("virtualCryptSHA512", True)
223
224         self.assertTrue("virtualCryptSHA256:" not in out)
225         self.assertTrue("virtualCryptSHA512:" in out)
226         self.assertTrue("rounds=" not in out)
227
228     # gpg decryption enabled.
229     # SHA128 specified, i.e. invalid/unknown algorithm
230     # no hashes stored in supplementalCredentials
231     # No rounds
232     #
233     # Should not get values
234     def test_gpg_invalid_alg_no_rounds(self):
235         self.add_user()
236         out = self._get_password("virtualCryptSHA128", True)
237
238         self.assertTrue("virtualCryptSHA256:" not in out)
239         self.assertTrue("virtualCryptSHA512:" not in out)
240         self.assertTrue("rounds=" not in out)
241
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
246     # invalid.
247     # Should not get values
248     def test_gpg_both_hashes_no_rounds_pwd_changed(self):
249         self.add_user()
250         self._change_nt_hash()
251         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
252
253         self.assertTrue("virtualCryptSHA256:" not in out)
254         self.assertTrue("virtualCryptSHA512:" not in out)
255         self.assertTrue("rounds=" not in out)
256
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
261     # invalid.
262     # Should not get values
263     def test_gpg_sha256_no_rounds_pwd_changed(self):
264         self.add_user()
265         self._change_nt_hash()
266         out = self._get_password("virtualCryptSHA256", True)
267
268         self.assertTrue("virtualCryptSHA256:" not in out)
269         self.assertTrue("virtualCryptSHA512:" not in out)
270         self.assertTrue("rounds=" not in out)
271
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
276     # invalid.
277     # Should not get values
278     def test_gpg_sha512_no_rounds_pwd_changed(self):
279         self.add_user()
280         self._change_nt_hash()
281         out = self._get_password("virtualCryptSHA256", True)
282
283         self.assertTrue("virtualCryptSHA256:" not in out)
284         self.assertTrue("virtualCryptSHA512:" not in out)
285         self.assertTrue("rounds=" not in out)
286
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):
292         self.add_user()
293         out = self._get_password(
294             "virtualCryptSHA256;rounds=10123,virtualCryptSHA512;rounds=10456",
295             True)
296
297         self.assertTrue("virtualCryptSHA256:" in out)
298         self.assertTrue("virtualCryptSHA512:" in out)
299
300         sha256 = _get_attribute(out, "virtualCryptSHA256")
301         self.assertTrue(sha256.startswith("{CRYPT}$5$rounds=10123$"))
302
303         sha512 = _get_attribute(out, "virtualCryptSHA512")
304         self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=10456$"))
305
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):
312         self.add_user()
313         out = self._get_password(
314             "virtualCryptSHA256;rounds=invalid,virtualCryptSHA512;rounds=3125",
315             True)
316
317         self.assertTrue("virtualCryptSHA256:" in out)
318         self.assertTrue("virtualCryptSHA512:" in out)
319
320         sha256 = _get_attribute(out, "virtualCryptSHA256")
321         self.assertTrue(sha256.startswith("{CRYPT}$5$"))
322         self.assertTrue("rounds" not in sha256)
323
324         sha512 = _get_attribute(out, "virtualCryptSHA512")
325         self.assertTrue(sha512.startswith("{CRYPT}$6$rounds=3125$"))
326
327     # gpg decryption not enabled.
328     # both virtual attributes specified, no rounds option
329     # both hashes stored in supplementalCredentials
330     # Should get values
331     def test_no_gpg_both_hashes_no_rounds_stored_hashes(self):
332         self.add_user("CryptSHA512 CryptSHA256")
333
334         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
335
336         self.assertTrue("virtualCryptSHA256:" in out)
337         self.assertTrue("virtualCryptSHA512:" in out)
338         self.assertTrue("rounds=" not in out)
339
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")
344
345         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
346         self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
347         self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
348
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")
355
356         out = self._get_password("virtualCryptSHA256;rounds=2561," +
357                                  "virtualCryptSHA512;rounds=5129")
358
359         self.assertTrue("virtualCryptSHA256:" in out)
360         self.assertTrue("virtualCryptSHA512:" in out)
361         self.assertTrue("rounds=" not in out)
362
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")
367
368         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512")
369         self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
370         self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
371
372     # gpg decryption not enabled.
373     # both virtual attributes specified, rounds specified
374     # both hashes stored in supplementalCredentials, with rounds
375     # Should get values
376     def test_no_gpg_both_hashes_rounds_stored_hashes_with_rounds(self):
377         self.add_user("CryptSHA512 " +
378                       "CryptSHA256 " +
379                       "CryptSHA512:rounds=5129 " +
380                       "CryptSHA256:rounds=2561")
381
382         out = self._get_password("virtualCryptSHA256;rounds=2561," +
383                                  "virtualCryptSHA512;rounds=5129")
384
385         self.assertTrue("virtualCryptSHA256:" in out)
386         self.assertTrue("virtualCryptSHA512:" in out)
387         self.assertTrue("rounds=" in out)
388
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")
393
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"))
398
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"))
402
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 " +
410                       "CryptSHA256 " +
411                       "CryptSHA512:rounds=5129 " +
412                       "CryptSHA256:rounds=2561")
413
414         out = self._get_password("virtualCryptSHA256;rounds=4000," +
415                                  "virtualCryptSHA512;rounds=5000")
416
417         self.assertTrue("virtualCryptSHA256:" in out)
418         self.assertTrue("virtualCryptSHA512:" in out)
419         self.assertTrue("rounds=" not in out)
420
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")
425
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"))
430
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"))
437
438     # gpg decryption enabled.
439     # both virtual attributes specified, no rounds option
440     # both hashes stored in supplementalCredentials
441     # Should get values
442     def test_gpg_both_hashes_no_rounds_stored_hashes(self):
443         self.add_user("CryptSHA512 CryptSHA256")
444
445         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
446
447         self.assertTrue("virtualCryptSHA256:" in out)
448         self.assertTrue("virtualCryptSHA512:" in out)
449         self.assertTrue("rounds=" not in out)
450
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")
455
456         out = self._get_password("virtualCryptSHA256,virtualCryptSHA512", True)
457         self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
458         self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
459
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")
466
467         out = self._get_password("virtualCryptSHA256;rounds=2561," +
468                                  "virtualCryptSHA512;rounds=5129",
469                                  True)
470
471         self.assertTrue("virtualCryptSHA256:" in out)
472         self.assertTrue("virtualCryptSHA512:" in out)
473         self.assertTrue("rounds=" in out)
474
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")
479
480         out = self._get_password("virtualCryptSHA256;rounds=2561," +
481                                  "virtualCryptSHA512;rounds=5129",
482                                  True)
483         self.assertFalse(sha256 == _get_attribute(out, "virtualCryptSHA256"))
484         self.assertFalse(sha512 == _get_attribute(out, "virtualCryptSHA512"))
485
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"))
489
490     # gpg decryption enabled.
491     # both virtual attributes specified, rounds specified
492     # both hashes stored in supplementalCredentials, with rounds
493     # Should get values
494     def test_gpg_both_hashes_rounds_stored_hashes_with_rounds(self):
495         self.add_user("CryptSHA512 " +
496                       "CryptSHA256 " +
497                       "CryptSHA512:rounds=5129 " +
498                       "CryptSHA256:rounds=2561")
499
500         out = self._get_password("virtualCryptSHA256;rounds=2561," +
501                                  "virtualCryptSHA512;rounds=5129",
502                                  True)
503
504         self.assertTrue("virtualCryptSHA256:" in out)
505         self.assertTrue("virtualCryptSHA512:" in out)
506         self.assertTrue("rounds=" in out)
507
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")
512
513         out = self._get_password("virtualCryptSHA256;rounds=2561," +
514                                  "virtualCryptSHA512;rounds=5129",
515                                  True)
516         self.assertEquals(sha256, _get_attribute(out, "virtualCryptSHA256"))
517         self.assertEquals(sha512, _get_attribute(out, "virtualCryptSHA512"))
518
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"))
522
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 " +
530                       "CryptSHA256 " +
531                       "CryptSHA512:rounds=5129 " +
532                       "CryptSHA256:rounds=2561")
533
534         out = self._get_password("virtualCryptSHA256;rounds=4000," +
535                                  "virtualCryptSHA512;rounds=5000",
536                                  True)
537
538         self.assertTrue("virtualCryptSHA256:" in out)
539         self.assertTrue("virtualCryptSHA512:" in out)
540         self.assertTrue("rounds=" in out)
541
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")
546
547         out = self._get_password("virtualCryptSHA256;rounds=4000," +
548                                  "virtualCryptSHA512;rounds=5000",
549                                  True)
550         self.assertFalse(sha256 == _get_attribute(out, "virtualCryptSHA256"))
551         self.assertFalse(sha512 == _get_attribute(out, "virtualCryptSHA512"))
552
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"))