samba-tool user: Tests for virtualWDigest attributes
authorGary Lockyer <gary@catalyst.net.nz>
Sun, 7 May 2017 22:00:09 +0000 (10:00 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 25 May 2017 00:25:11 +0000 (02:25 +0200)
Add tests for the new virtualWDigest attributes, these return the hashes
stored in supplementalCredentials Primary:WDigest in a form suitable for
use with htdigest authentication.

Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Garming Sam <garming@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/tests/samba_tool/user_wdigest.py [new file with mode: 0644]
selftest/knownfail
source4/selftest/tests.py

diff --git a/python/samba/tests/samba_tool/user_wdigest.py b/python/samba/tests/samba_tool/user_wdigest.py
new file mode 100644 (file)
index 0000000..ff08420
--- /dev/null
@@ -0,0 +1,456 @@
+# Tests for the samba-tool user sub command reading Primary:WDigest
+#
+# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
+#
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import time
+import base64
+import ldb
+import samba
+from samba.tests.samba_tool.base import SambaToolCmdTest
+from samba import (
+        credentials,
+        nttime2unix,
+        dsdb
+        )
+from samba.ndr import ndr_unpack
+from samba.dcerpc import drsblobs
+import binascii
+import md5
+import re
+
+USER_NAME = "WdigestTestUser"
+USER_PASS = samba.generate_random_password(32, 32)
+
+# Calculate the MD5 password digest from the supplied user, realm and password
+#
+def calc_digest(user, realm, password):
+    data = "%s:%s:%s" % (user, realm, password)
+    return "%s:%s:%s" % (user, realm, binascii.hexlify(md5.new(data).digest()))
+
+
+
+class UserCmdWdigestTestCase(SambaToolCmdTest):
+    """Tests for samba-tool user subcommands extraction of the wdigest values
+       Test results validated against Windows Server 2012 R2.
+       NOTE: That as at 22-05-2017 the values Documented at
+             3.1.1.8.11.3.1 WDIGEST_CREDENTIALS Construction
+             are incorrect.
+    """
+    users = []
+    samdb = None
+
+    def setUp(self):
+        super(UserCmdWdigestTestCase, self).setUp()
+        self.lp = samba.tests.env_loadparm()
+        self.samdb = self.getSamDB(
+            "-H", "ldap://%s" % os.environ["DC_SERVER"],
+            "-U%s%%%s" % (os.environ["DC_USERNAME"],
+            os.environ["DC_PASSWORD"]))
+        self.dns_domain = self.samdb.domain_dns_name()
+        res = self.samdb.search(
+            base=self.samdb.get_config_basedn(),
+            expression="ncName=%s" % self.samdb.get_default_basedn(),
+            attrs=["nETBIOSName"])
+        self.netbios_domain = res[0]["nETBIOSName"][0]
+        self.runsubcmd("user",
+                       "create",
+                       USER_NAME,
+                       USER_PASS,
+                       "-H",
+                       "ldap://%s" % os.environ["DC_SERVER"],
+                       "-U%s%%%s" % (
+                            os.environ["DC_USERNAME"],
+                            os.environ["DC_PASSWORD"]))
+
+    def tearDown(self):
+        super(UserCmdWdigestTestCase, self).tearDown()
+        self.runsubcmd("user", "delete", USER_NAME)
+
+    def _testWDigest(self,  attribute, expected, missing=False):
+
+        (result, out, err) = self.runsubcmd("user",
+                                            "getpassword",
+                                            USER_NAME,
+                                            "--attributes",
+                                            attribute)
+        self.assertCmdSuccess(result,
+                              out,
+                              err,
+                              "Ensure getpassword runs")
+        self.assertEqual(err, "", "getpassword")
+        self.assertMatch(out,
+                         "Got password OK",
+                         "getpassword out[%s]" % out)
+
+        if missing:
+            self.assertTrue(attribute not in out)
+        else:
+            result = re.sub(r"\n\s*", '', out)
+            self.assertMatch(result, "%s: %s" % (attribute, expected))
+
+    def test_Wdigest_no_suffix(self):
+        attribute = "virtualWDigest"
+        self._testWDigest(attribute, None, True)
+
+    def test_Wdigest_non_numeric_suffix(self):
+        attribute = "virtualWDigestss"
+        self._testWDigest(attribute, None, True)
+
+    def test_Wdigest00(self):
+        attribute = "virtualWDigest00"
+        self._testWDigest(attribute, None, True)
+
+    # Hash01  MD5(sAMAccountName,
+    #            NETBIOSDomainName,
+    #            password)
+    #
+    def test_Wdigest01(self):
+        attribute = "virtualWDigest01"
+        expected = calc_digest(USER_NAME,
+                               self.netbios_domain,
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash02 MD5(LOWER(sAMAccountName),
+    #            LOWER(NETBIOSDomainName),
+    #            password)
+    #
+    def test_Wdigest02(self):
+        attribute = "virtualWDigest02"
+        expected = calc_digest(USER_NAME.lower(),
+                               self.netbios_domain.lower(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash03 MD5(UPPER(sAMAccountName),
+    #            UPPER(NETBIOSDomainName),
+    #            password)
+    #
+    def test_Wdigest03(self):
+        attribute = "virtualWDigest03"
+        expected = calc_digest(USER_NAME.upper(),
+                               self.netbios_domain.upper(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash04 MD5(sAMAccountName,
+    #            UPPER(NETBIOSDomainName),
+    #            password)
+    #
+    def test_Wdigest04(self):
+        attribute = "virtualWDigest04"
+        expected = calc_digest(USER_NAME,
+                               self.netbios_domain.upper(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash05 MD5(sAMAccountName,
+    #            LOWER(NETBIOSDomainName),
+    #            password)
+    #
+    def test_Wdigest05(self):
+        attribute = "virtualWDigest05"
+        expected = calc_digest(USER_NAME,
+                               self.netbios_domain.lower(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash06 MD5(UPPER(sAMAccountName),
+    #            LOWER(NETBIOSDomainName),
+    #            password)
+    #
+    def test_Wdigest06(self):
+        attribute = "virtualWDigest06"
+        expected = calc_digest(USER_NAME.upper(),
+                               self.netbios_domain.lower(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash07 MD5(LOWER(sAMAccountName),
+    #            UPPER(NETBIOSDomainName),
+    #            password)
+    #
+    def test_Wdigest07(self):
+        attribute = "virtualWDigest07"
+        expected = calc_digest(USER_NAME.lower(),
+                               self.netbios_domain.upper(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash08 MD5(sAMAccountName,
+    #            DNSDomainName,
+    #            password)
+    #
+    # Note: Samba lowercases the DNSDomainName at provision time,
+    #       Windows preserves the case. This means that the WDigest08 values
+    #       calculated byt Samba and Windows differ.
+    #
+    def test_Wdigest08(self):
+        attribute = "virtualWDigest08"
+        expected = calc_digest(USER_NAME,
+                               self.dns_domain,
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash09 MD5(LOWER(sAMAccountName),
+    #            LOWER(DNSDomainName),
+    #            password)
+    #
+    def test_Wdigest09(self):
+        attribute = "virtualWDigest09"
+        expected = calc_digest(USER_NAME.lower(),
+                               self.dns_domain.lower(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash10 MD5(UPPER(sAMAccountName),
+    #            UPPER(DNSDomainName),
+    #            password)
+    #
+    def test_Wdigest10(self):
+        attribute = "virtualWDigest10"
+        expected = calc_digest(USER_NAME.upper(),
+                               self.dns_domain.upper(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash11 MD5(sAMAccountName,
+    #            UPPER(DNSDomainName),
+    #            password)
+    #
+    def test_Wdigest11(self):
+        attribute = "virtualWDigest11"
+        expected = calc_digest(USER_NAME,
+                               self.dns_domain.upper(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash12 MD5(sAMAccountName,
+    #            LOWER(DNSDomainName),
+    #            password)
+    #
+    def test_Wdigest12(self):
+        attribute = "virtualWDigest12"
+        expected = calc_digest(USER_NAME,
+                               self.dns_domain.lower(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash13 MD5(UPPER(sAMAccountName),
+    #            LOWER(DNSDomainName),
+    #            password)
+    #
+    def test_Wdigest13(self):
+        attribute = "virtualWDigest13"
+        expected = calc_digest(USER_NAME.upper(),
+                               self.dns_domain.lower(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+
+    # Hash14 MD5(LOWER(sAMAccountName),
+    #            UPPER(DNSDomainName),
+    #            password)
+    #
+    def test_Wdigest14(self):
+        attribute = "virtualWDigest14"
+        expected = calc_digest(USER_NAME.lower(),
+                               self.dns_domain.upper(),
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash15 MD5(userPrincipalName,
+    #            password)
+    #
+    def test_Wdigest15(self):
+        attribute = "virtualWDigest15"
+        name = "%s@%s" % (USER_NAME, self.dns_domain)
+        expected = calc_digest(name,
+                               "",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash16 MD5(LOWER(userPrincipalName),
+    #            password)
+    #
+    def test_Wdigest16(self):
+        attribute = "virtualWDigest16"
+        name = "%s@%s" % (USER_NAME.lower(), self.dns_domain.lower())
+        expected = calc_digest(name,
+                               "",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash17 MD5(UPPER(userPrincipalName),
+    #            password)
+    #
+    def test_Wdigest17(self):
+        attribute = "virtualWDigest17"
+        name = "%s@%s" % (USER_NAME.upper(), self.dns_domain.upper())
+        expected = calc_digest(name,
+                               "",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash18 MD5(NETBIOSDomainName\sAMAccountName,
+    #            password)
+    #
+    def test_Wdigest18(self):
+        attribute = "virtualWDigest18"
+        name = "%s\\%s" % (self.netbios_domain, USER_NAME)
+        expected = calc_digest(name,
+                               "",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash19 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
+    #            password)
+    #
+    def test_Wdigest19(self):
+        attribute = "virtualWDigest19"
+        name = "%s\\%s" % (self.netbios_domain, USER_NAME)
+        expected = calc_digest(name.lower(),
+                               "",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash20 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
+    #            password)
+    #
+    def test_Wdigest20(self):
+        attribute = "virtualWDigest20"
+        name = "%s\\%s" % (self.netbios_domain, USER_NAME)
+        expected = calc_digest(name.upper(),
+                               "",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash21 MD5(sAMAccountName,
+    #            "Digest",
+    #            password)
+    #
+    def test_Wdigest21(self):
+        attribute = "virtualWDigest21"
+        expected = calc_digest(USER_NAME,
+                               "Digest",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash22 MD5(LOWER(sAMAccountName),
+    #            "Digest",
+    #            password)
+    #
+    def test_Wdigest22(self):
+        attribute = "virtualWDigest22"
+        expected = calc_digest(USER_NAME.lower(),
+                               "Digest",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash23 MD5(UPPER(sAMAccountName),
+    #            "Digest",
+    #            password)
+    #
+    def test_Wdigest23(self):
+        attribute = "virtualWDigest23"
+        expected = calc_digest(USER_NAME.upper(),
+                               "Digest",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash24  MD5(userPrincipalName),
+    #             "Digest",
+    #              password)
+    #
+    def test_Wdigest24(self):
+        attribute = "virtualWDigest24"
+        name = "%s@%s" % (USER_NAME, self.dns_domain)
+        expected = calc_digest(name,
+                               "Digest",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash25 MD5(LOWER(userPrincipalName),
+    #            "Digest",
+    #            password)
+    #
+    def test_Wdigest25(self):
+        attribute = "virtualWDigest25"
+        name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
+        expected = calc_digest(name.lower(),
+                               "Digest",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash26 MD5(UPPER(userPrincipalName),
+    #            "Digest",
+    #             password)
+    #
+    def test_Wdigest26(self):
+        attribute = "virtualWDigest26"
+        name = "%s@%s" % (USER_NAME, self.dns_domain.lower())
+        expected = calc_digest(name.upper(),
+                               "Digest",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+    # Hash27 MD5(NETBIOSDomainName\sAMAccountName,
+    #            "Digest",
+    #            password)
+    #
+    def test_Wdigest27(self):
+        attribute = "virtualWDigest27"
+        name = "%s\\%s" % (self.netbios_domain, USER_NAME)
+        expected = calc_digest(name,
+                               "Digest",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash28 MD5(LOWER(NETBIOSDomainName\sAMAccountName),
+    #            "Digest",
+    #            password)
+    #
+    def test_Wdigest28(self):
+        attribute = "virtualWDigest28"
+        name = "%s\\%s" % (self.netbios_domain.lower(), USER_NAME.lower())
+        expected = calc_digest(name,
+                               "Digest",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    # Hash29 MD5(UPPER(NETBIOSDomainName\sAMAccountName),
+    #            "Digest",
+    #             password)
+    #
+    def test_Wdigest29(self):
+        attribute = "virtualWDigest29"
+        name = "%s\\%s" % (self.netbios_domain.upper(), USER_NAME.upper())
+        expected = calc_digest(name,
+                               "Digest",
+                               USER_PASS)
+        self._testWDigest(attribute, expected)
+
+    def test_Wdigest30(self):
+        attribute = "virtualWDigest30"
+        self._testWDigest(attribute, None, True)
+
+    # Check digest calculation against an known htdigest value
+    def test_calc_digest(self):
+        htdigest = "gary:fred:2204fcc247cb47ded249ef2fe0013255"
+        digest = calc_digest("gary", "fred", "password")
+        self.assertEqual(htdigest, digest)
index b16ff520e4274f693f690e306d4645f8d9f5a62c..23e6d6fe3ed849a9083a5ef75570757474e9e7a5 100644 (file)
 # We currently don't send referrals for LDAP modify of non-replicated attrs
 ^samba4.ldap.rodc.python\(rodc\).__main__.RodcTests.test_modify_nonreplicated.*
 ^samba4.ldap.rodc_rwdc.python.*.__main__.RodcRwdcTests.test_change_password_reveal_on_demand_kerberos
+#
+# Tests for the retrieval of the virtualWDigest attributes
+# by the samba-tool user sub command
+^samba.tests.samba_tool.user_wdigest.samba.tests.samba_tool.user_wdigest.UserCmdWdigestTestCase.test_Wdigest01.*
+^samba.tests.samba_tool.user_wdigest.samba.tests.samba_tool.user_wdigest.UserCmdWdigestTestCase.test_Wdigest02.*
+^samba.tests.samba_tool.user_wdigest.samba.tests.samba_tool.user_wdigest.UserCmdWdigestTestCase.test_Wdigest03.*
+^samba.tests.samba_tool.user_wdigest.samba.tests.samba_tool.user_wdigest.UserCmdWdigestTestCase.test_Wdigest04.*
+^samba.tests.samba_tool.user_wdigest.samba.tests.samba_tool.user_wdigest.UserCmdWdigestTestCase.test_Wdigest05.*
+^samba.tests.samba_tool.user_wdigest.samba.tests.samba_tool.user_wdigest.UserCmdWdigestTestCase.test_Wdigest06.*
+^samba.tests.samba_tool.user_wdigest.samba.tests.samba_tool.user_wdigest.UserCmdWdigestTestCase.test_Wdigest07.*
+^samba.tests.samba_tool.user_wdigest.samba.tests.samba_tool.user_wdigest.UserCmdWdigestTestCase.test_Wdigest08.*
+^samba.tests.samba_tool.user_wdigest.samba.tests.samba_tool.user_wdigest.UserCmdWdigestTestCase.test_Wdigest09.*
+^samba.tests.samba_tool.user_wdigest.samba.tests.samba_tool.user_wdigest.UserCmdWdigestTestCase.test_Wdigest1.*
+^samba.tests.samba_tool.user_wdigest.samba.tests.samba_tool.user_wdigest.UserCmdWdigestTestCase.test_Wdigest2.*
index c173beafca3dfc7b49953f8975a63b4733d52d4f..a492bb796cdc9776cd467bd38e1e268c9e6ab4e9 100755 (executable)
@@ -592,6 +592,7 @@ planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.gpo")
 
 planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.samba_tool.processes")
 planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.samba_tool.user")
+planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.samba_tool.user_wdigest")
 planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.user")
 planpythontestsuite("chgdcpass:local", "samba.tests.samba_tool.user_check_password_script")
 planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.samba_tool.group")