password_hash: Add tests to allow refactoring
[samba.git] / python / samba / tests / password_hash.py
1 # Tests for Tests for source4/dsdb/samdb/ldb_modules/password_hash.c
2 #
3 # Copyright (C) Catalyst IT Ltd. 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 """
20 Base class for tests for source4/dsdb/samdb/ldb_modules/password_hash.c
21 """
22
23 from samba.credentials import Credentials
24 from samba.samdb import SamDB
25 from samba.auth import system_session
26 from samba.tests import TestCase
27 from samba.ndr import ndr_unpack
28 from samba.dcerpc import drsblobs
29 from samba.dcerpc.samr import DOMAIN_PASSWORD_STORE_CLEARTEXT
30 from samba.dsdb import UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED
31 from samba.tests import delete_force
32 import ldb
33 import os
34 import samba
35 import binascii
36 import md5
37
38
39 USER_NAME = "PasswordHashTestUser"
40 USER_PASS = samba.generate_random_password(32,32)
41 UPN       = "PWHash@User.Principle"
42
43 # Get named package from the passed supplemental credentials
44 #
45 # returns the package and it's position within the supplemental credentials
46 def get_package(sc, name):
47     if sc is None:
48         return None
49
50     idx = 0
51     for p in sc.sub.packages:
52         idx += 1
53         if name == p.name:
54             return (idx, p)
55
56     return None
57
58 # Calculate the MD5 password digest from the supplied user, realm and password
59 #
60 def calc_digest(user, realm, password):
61
62     data = "%s:%s:%s" % (user, realm, password)
63     return binascii.hexlify(md5.new(data).digest())
64
65
66 class PassWordHashTests(TestCase):
67
68     def setUp(self):
69         super(PassWordHashTests, self).setUp()
70
71     # Add a user to ldb, this will exercise the password_hash code
72     # and calculate the appropriate supplemental credentials
73     def add_user(self, options=None, clear_text=False):
74         self.lp = samba.tests.env_loadparm()
75         # set any needed options
76         if options is not None:
77             for (option,value) in options:
78                 self.lp.set(option, value)
79
80         self.creds = Credentials()
81         self.session = system_session()
82         self.ldb = SamDB(
83             session_info=self.session,
84             credentials=self.creds,
85             lp=self.lp)
86
87         # Gets back the basedn
88         base_dn = self.ldb.domain_dn()
89
90         # Gets back the configuration basedn
91         configuration_dn = self.ldb.get_config_basedn().get_linearized()
92
93         # Get the old "dSHeuristics" if it was set
94         dsheuristics = self.ldb.get_dsheuristics()
95
96         # Set the "dSHeuristics" to activate the correct "userPassword"
97         # behaviour
98         self.ldb.set_dsheuristics("000000001")
99
100         # Reset the "dSHeuristics" as they were before
101         self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
102
103         # Get the old "minPwdAge"
104         minPwdAge = self.ldb.get_minPwdAge()
105
106         # Set it temporarily to "0"
107         self.ldb.set_minPwdAge("0")
108         self.base_dn = self.ldb.domain_dn()
109
110         # Reset the "minPwdAge" as it was before
111         self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
112
113         account_control = 0
114         if clear_text:
115             # get the current pwdProperties
116             pwdProperties = self.ldb.get_pwdProperties()
117             # enable clear text properties
118             props = int(pwdProperties)
119             props |= DOMAIN_PASSWORD_STORE_CLEARTEXT
120             self.ldb.set_pwdProperties(str(props))
121             # Restore the value on exit.
122             self.addCleanup(self.ldb.set_pwdProperties, pwdProperties)
123             account_control |= UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED
124
125         # (Re)adds the test user USER_NAME with password USER_PASS
126         # and userPrincipalName UPN
127         delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn)
128         self.ldb.add({
129              "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn,
130              "objectclass": "user",
131              "sAMAccountName": USER_NAME,
132              "userPassword": USER_PASS,
133              "userPrincipalName": UPN,
134              "userAccountControl": str(account_control)
135         })
136
137     # Get the supplemental credentials for the user under test
138     def get_supplemental_creds(self):
139         base = "cn=" + USER_NAME + ",cn=users," + self.base_dn
140         res = self.ldb.search(scope=ldb.SCOPE_BASE,
141                               base=base,
142                               attrs=["supplementalCredentials"])
143         self.assertIs( True, len(res) > 0)
144         obj = res[0]
145         sc_blob = obj["supplementalCredentials"][0]
146         sc = ndr_unpack(drsblobs.supplementalCredentialsBlob, sc_blob)
147         return sc
148
149     # Calculate and validate a Wdigest value
150     def check_digest(self, user, realm, password,  digest):
151         expected = calc_digest( user, realm, password)
152         actual = binascii.hexlify(bytearray(digest))
153         error = "Digest expected[%s], actual[%s], " \
154                 "user[%s], realm[%s], pass[%s]" % \
155                 (expected, actual, user, realm, password)
156         self.assertEquals(expected, actual, error)
157
158     # Check all of the 29 expected WDigest values
159     #
160     def check_wdigests(self, digests):
161
162         self.assertEquals(29, digests.num_hashes)
163
164         self.check_digest(USER_NAME,
165                           self.lp.get("workgroup"),
166                           USER_PASS,
167                           digests.hashes[0].hash)
168         self.check_digest(USER_NAME.lower(),
169                           self.lp.get("workgroup").lower(),
170                           USER_PASS,
171                           digests.hashes[1].hash)
172         self.check_digest(USER_NAME.upper(),
173                           self.lp.get("workgroup").upper(),
174                           USER_PASS,
175                           digests.hashes[2].hash)
176         self.check_digest(USER_NAME,
177                           self.lp.get("workgroup").upper(),
178                           USER_PASS,
179                           digests.hashes[3].hash)
180         self.check_digest(USER_NAME,
181                           self.lp.get("workgroup").lower(),
182                           USER_PASS,
183                           digests.hashes[4].hash)
184         self.check_digest(USER_NAME.upper(),
185                           self.lp.get("workgroup").lower(),
186                           USER_PASS,
187                           digests.hashes[5].hash)
188         self.check_digest(USER_NAME.lower(),
189                           self.lp.get("workgroup").upper(),
190                           USER_PASS,
191                           digests.hashes[6].hash)
192         self.check_digest(USER_NAME,
193                           self.lp.get("realm").lower(),
194                           USER_PASS,
195                           digests.hashes[7].hash)
196         self.check_digest(USER_NAME.lower(),
197                           self.lp.get("realm").lower(),
198                           USER_PASS,
199                           digests.hashes[8].hash)
200         self.check_digest(USER_NAME.upper(),
201                           self.lp.get("realm"),
202                           USER_PASS,
203                           digests.hashes[9].hash)
204         self.check_digest(USER_NAME,
205                           self.lp.get("realm"),
206                           USER_PASS,
207                           digests.hashes[10].hash)
208         self.check_digest(USER_NAME,
209                           self.lp.get("realm").lower(),
210                           USER_PASS,
211                           digests.hashes[11].hash)
212         self.check_digest(USER_NAME.upper(),
213                           self.lp.get("realm").lower(),
214                           USER_PASS,
215                           digests.hashes[12].hash)
216         self.check_digest(USER_NAME.lower(),
217                           self.lp.get("realm"),
218                           USER_PASS,
219                           digests.hashes[13].hash)
220         self.check_digest(UPN,
221                           "",
222                           USER_PASS,
223                           digests.hashes[14].hash)
224         self.check_digest(UPN.lower(),
225                           "",
226                           USER_PASS,
227                           digests.hashes[15].hash)
228         self.check_digest(UPN.upper(),
229                           "",
230                           USER_PASS,
231                           digests.hashes[16].hash)
232
233         name = "%s\\%s" % (self.lp.get("workgroup"), USER_NAME)
234         self.check_digest(name,
235                           "",
236                           USER_PASS,
237                           digests.hashes[17].hash)
238
239         name = "%s\\%s" % (self.lp.get("workgroup").lower(), USER_NAME.lower())
240         self.check_digest(name,
241                           "",
242                           USER_PASS,
243                           digests.hashes[18].hash)
244
245         name = "%s\\%s" % (self.lp.get("workgroup").upper(), USER_NAME.upper())
246         self.check_digest(name,
247                           "",
248                           USER_PASS,
249                           digests.hashes[19].hash)
250         self.check_digest(USER_NAME,
251                           "Digest",
252                           USER_PASS,
253                           digests.hashes[20].hash)
254         self.check_digest(USER_NAME.lower(),
255                           "Digest",
256                           USER_PASS,
257                           digests.hashes[21].hash)
258         self.check_digest(USER_NAME.upper(),
259                           "Digest",
260                           USER_PASS,
261                           digests.hashes[22].hash)
262         self.check_digest(UPN,
263                           "Digest",
264                           USER_PASS,
265                           digests.hashes[23].hash)
266         self.check_digest(UPN.lower(),
267                           "Digest",
268                           USER_PASS,
269                           digests.hashes[24].hash)
270         self.check_digest(UPN.upper(),
271                           "Digest",
272                           USER_PASS,
273                           digests.hashes[25].hash)
274         name = "%s\\%s" % (self.lp.get("workgroup"), USER_NAME)
275         self.check_digest(name,
276                           "Digest",
277                           USER_PASS,
278                           digests.hashes[26].hash)
279
280         name = "%s\\%s" % (self.lp.get("workgroup").lower(), USER_NAME.lower())
281         self.check_digest(name,
282                           "Digest",
283                           USER_PASS,
284                           digests.hashes[27].hash)
285
286         name = "%s\\%s" % (self.lp.get("workgroup").upper(), USER_NAME.upper())
287         self.check_digest(name,
288                           "Digest",
289                           USER_PASS,
290                           digests.hashes[28].hash)