samba-tool add support for userPassword
authorGary Lockyer <gary@catalyst.net.nz>
Mon, 15 May 2017 00:19:22 +0000 (12:19 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 25 May 2017 00:25:12 +0000 (02:25 +0200)
Changes to virtualCryptSHA256 and virtualCryptSHA512 attributes.
The values are now calculated as follows:
  1) If a value exists in 'Primary:userPassword' with
     the specified number of rounds it is returned.
  2) If 'Primary:CLEARTEXT, or 'Primary:SambaGPG' with
     '--decrypt-samba-gpg'. Calculate a hash with the specified number of rounds
  3) Return the first {CRYPT} value in 'Primary:userPassword' with a
     matching algorithm

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/netcmd/user.py
selftest/knownfail

index 15595881e0c80b8fb3eb80fbbfba3881c1b807bd..53ac39f9ee8cab284979273239739d427dc858ea 100644 (file)
@@ -1157,21 +1157,62 @@ class GetPasswordCommand(Command):
             except IndexError:
                 return None
 
-        def get_userPassword_hash(blob, scheme, prefix):
+
+        # get the value for a virtualCrypt attribute.
+        # look for an exact match on algorithm and rounds in supplemental creds
+        # if not found calculate using Primary:CLEARTEXT
+        # if no Primary:CLEARTEXT return the first supplementalCredential
+        #    that matches the algorithm.
+        def get_virtual_crypt_value(a, algorithm, rounds, username, account_name):
+            sv = None
+            fb = None
+            b = get_package("Primary:userPassword")
+            if b is not None:
+                (sv, fb) = get_userPassword_hash(b, algorithm, rounds)
+            if sv is None:
+                # No exact match on algorithm and number of rounds
+                # try and calculate one from the Primary:CLEARTEXT
+                b = get_package("Primary:CLEARTEXT")
+                if b is not None:
+                    u8 = get_utf8(a, b, username or account_name)
+                    if u8 is not None:
+                        sv = get_crypt_value(str(algorithm), u8, rounds)
+                if sv is None:
+                    # Unable to calculate a hash with the specified
+                    # number of rounds, fall back to the first hash using
+                    # the specified algorithm
+                    sv = fb
+            if sv is None:
+                return None
+            return "{CRYPT}" + sv
+
+        def get_userPassword_hash(blob, algorithm, rounds):
             up = ndr_unpack(drsblobs.package_PrimaryUserPasswordBlob, blob)
+            SCHEME = "{CRYPT}"
 
             # Check that the NT hash has not been changed without updating
-            # the user password hashes.
+            # the user password hashes. This indicates that password has been
+            # changed without updating the supplemental credentials.
             if unicodePwd != bytearray(up.current_nt_hash.hash):
                 return None
 
+            scheme_prefix = "$%d$" % algorithm
+            prefix = scheme_prefix
+            if rounds > 0:
+                prefix = "$%d$rounds=%d" % (algorithm, rounds)
+            scheme_match = None
 
-            # Return the first hash that matches scheme
             for h in up.hashes:
-                if h.scheme == scheme and h.value.startswith(prefix):
-                    return h.value
+                if (scheme_match is None and
+                      h.scheme == SCHEME and
+                      h.value.startswith(scheme_prefix)):
+                    scheme_match = h.value
+                if h.scheme == SCHEME and h.value.startswith(prefix):
+                    return (h.value, scheme_match)
 
-            return None
+            # No match on the number of rounds, return the value of the
+            # first matching scheme
+            return (None, scheme_match)
 
         # We use sort here in order to have a predictable processing order
         for a in sorted(virtual_attributes.keys()):
@@ -1204,25 +1245,17 @@ class GetPasswordCommand(Command):
                 bv = h.digest() + salt
                 v = "{SSHA}" + base64.b64encode(bv)
             elif a == "virtualCryptSHA256":
-                b = get_package("Primary:CLEARTEXT")
-                if b is None:
-                    continue
-                u8 = get_utf8(a, b, username or account_name)
-                if u8 is None:
-                    continue
                 rounds = get_rounds(attr_opts[a])
-                sv = get_crypt_value("5", u8, rounds)
-                v = "{CRYPT}" + sv
-            elif a == "virtualCryptSHA512":
-                b = get_package("Primary:CLEARTEXT")
-                if b is None:
-                    continue
-                u8 = get_utf8(a, b, username or account_name)
-                if u8 is None:
+                x = get_virtual_crypt_value(a, 5, rounds, username, account_name)
+                if x is None:
                     continue
+                v = x
+            elif a == "virtualCryptSHA512":
                 rounds = get_rounds(attr_opts[a])
-                sv = get_crypt_value("6", u8, rounds)
-                v = "{CRYPT}" + sv
+                x = get_virtual_crypt_value(a, 6, rounds, username, account_name)
+                if x is None:
+                    continue
+                v = x
             elif a == "virtualSambaGPG":
                 # Samba adds 'Primary:SambaGPG' at the end.
                 # When Windows sets the password it keeps
@@ -1313,6 +1346,15 @@ for which virtual attributes are supported in your environment):
                           attribute name i.e. virtualCryptSHA256;rounds=10000
                           will calculate a SHA256 hash with 10,000 rounds.
                           non numeric values for rounds are silently ignored
+                          The value is calculated as follows:
+                          1) If a value exists in 'Primary:userPassword' with
+                             the specified number of rounds it is returned.
+                          2) If 'Primary:CLEARTEXT, or 'Primary:SambaGPG' with
+                             '--decrypt-samba-gpg'. Calculate a hash with
+                             the specified number of rounds
+                          3) Return the first CryptSHA256 value in
+                             'Primary:userPassword'
+
 
    virtualCryptSHA512:    As virtualClearTextUTF8, but a salted SHA512
                           checksum, useful for OpenLDAP's '{CRYPT}' algorithm,
@@ -1322,6 +1364,14 @@ for which virtual attributes are supported in your environment):
                           attribute name i.e. virtualCryptSHA512;rounds=10000
                           will calculate a SHA512 hash with 10,000 rounds.
                           non numeric values for rounds are silently ignored
+                          The value is calculated as follows:
+                          1) If a value exists in 'Primary:userPassword' with
+                             the specified number of rounds it is returned.
+                          2) If 'Primary:CLEARTEXT, or 'Primary:SambaGPG' with
+                             '--decrypt-samba-gpg'. Calculate a hash with
+                             the specified number of rounds
+                          3) Return the first CryptSHA512 value in
+                             'Primary:userPassword'
 
    virtualWDigestNN:      The individual hash values stored in
                           'Primary:WDigest' where NN is the hash number in
@@ -1464,6 +1514,14 @@ for supported virtual attributes in your environment):
                           attribute name i.e. virtualCryptSHA256;rounds=10000
                           will calculate a SHA256 hash with 10,000 rounds.
                           non numeric values for rounds are silently ignored
+                          The value is calculated as follows:
+                          1) If a value exists in 'Primary:userPassword' with
+                             the specified number of rounds it is returned.
+                          2) If 'Primary:CLEARTEXT, or 'Primary:SambaGPG' with
+                             '--decrypt-samba-gpg'. Calculate a hash with
+                             the specified number of rounds
+                          3) Return the first CryptSHA256 value in
+                             'Primary:userPassword'
 
    virtualCryptSHA512:    As virtualClearTextUTF8, but a salted SHA512
                           checksum, useful for OpenLDAP's '{CRYPT}' algorithm,
@@ -1473,6 +1531,14 @@ for supported virtual attributes in your environment):
                           attribute name i.e. virtualCryptSHA512;rounds=10000
                           will calculate a SHA512 hash with 10,000 rounds.
                           non numeric values for rounds are silently ignored
+                          The value is calculated as follows:
+                          1) If a value exists in 'Primary:userPassword' with
+                             the specified number of rounds it is returned.
+                          2) If 'Primary:CLEARTEXT, or 'Primary:SambaGPG' with
+                             '--decrypt-samba-gpg'. Calculate a hash with
+                             the specified number of rounds
+                          3) Return the first CryptSHA512 value in
+                             'Primary:userPassword'
 
    virtualWDigestNN:      The individual hash values stored in
                           'Primary:WDigest' where NN is the hash number in
index 6eb7d0fc48797cb9e8d9d9b8cc625c02a75c6983..b16ff520e4274f693f690e306d4645f8d9f5a62c 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 samba-tool user command extracting CRYPT hashes from supplememtal
-# credetials, will fail until code implemented
-^samba.tests.samba_tool.user_virtualCryptSHA.samba.tests.samba_tool.user_virtualCryptSHA.UserCmdCryptShaTestCase.test_no_gpg_both_hashes_rounds_stored_hashes_with_rounds_no_match\(ad_dc:local\)
-^samba.tests.samba_tool.user_virtualCryptSHA.samba.tests.samba_tool.user_virtualCryptSHA.UserCmdCryptShaTestCase.test_no_gpg_both_hashes_rounds_stored_hashes\(ad_dc:local\)
-^samba.tests.samba_tool.user_virtualCryptSHA.samba.tests.samba_tool.user_virtualCryptSHA.UserCmdCryptShaTestCase.test_no_gpg_both_hashes_no_rounds_stored_hashes\(ad_dc:local\)
-^samba.tests.samba_tool.user_virtualCryptSHA.samba.tests.samba_tool.user_virtualCryptSHA.UserCmdCryptShaTestCase.test_no_gpg_both_hashes_rounds_stored_hashes_with_rounds\(ad_dc:local\)
-^samba.tests.samba_tool.user_virtualCryptSHA.samba.tests.samba_tool.user_virtualCryptSHA.UserCmdCryptShaTestCase.test_gpg_both_hashes_rounds_stored_hashes_with_rounds\(ad_dc:local\)
-^samba.tests.samba_tool.user_virtualCryptSHA.samba.tests.samba_tool.user_virtualCryptSHA.UserCmdCryptShaTestCase.test_gpg_both_hashes_no_rounds_stored_hashes\(ad_dc:local\)