samba-tool user: add rounds option to virtualCryptSHAxxx
authorGary Lockyer <gary@catalyst.net.nz>
Mon, 8 May 2017 23:20:15 +0000 (11:20 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 25 May 2017 00:25:12 +0000 (02:25 +0200)
Allow the number of rounds to be specified when calculating the
virtualCryptSHA256 and virtualCryptSHA512 attributes.

i.e. --attributes="virtualCryptSHA256;rounds=3000" will calculate the
hash using 3,000 rounds.

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 ae1c0aa59674c69df7a5200b3d59edff96706e62..15595881e0c80b8fb3eb80fbbfba3881c1b807bd 100644 (file)
@@ -103,7 +103,7 @@ def get_random_bytes(num):
         raise ImportError(random_reason)
     return get_random_bytes_fn(num)
 
-def get_crypt_value(alg, utf8pw):
+def get_crypt_value(alg, utf8pw, rounds=0):
     algs = {
         "5": {"length": 43},
         "6": {"length": 86},
@@ -116,8 +116,13 @@ def get_crypt_value(alg, utf8pw):
     # we can ignore the possible == at the end
     # of the base64 string
     # we just need to replace '+' by '.'
-    b64salt = base64.b64encode(salt)
-    crypt_salt = "$%s$%s$" % (alg, b64salt[0:16].replace('+', '.'))
+    b64salt = base64.b64encode(salt)[0:16].replace('+', '.')
+    crypt_salt = ""
+    if rounds != 0:
+        crypt_salt = "$%s$rounds=%s$%s$" % (alg, rounds, b64salt)
+    else:
+        crypt_salt = "$%s$%s$" % (alg, b64salt)
+
     crypt_value = crypt.crypt(utf8pw, crypt_salt)
     if crypt_value is None:
         raise NotImplementedError("crypt.crypt(%s) returned None" % (crypt_salt))
@@ -127,6 +132,24 @@ def get_crypt_value(alg, utf8pw):
             crypt_salt, len(crypt_value), expected_len))
     return crypt_value
 
+# Extract the rounds value from the options of a virtualCrypt attribute
+# i.e. options = "rounds=20;other=ignored;" will return 20
+# if the rounds option is not found or the value is not a number, 0 is returned
+# which indicates that the default number of rounds should be used.
+def get_rounds(options):
+    if not options:
+        return 0
+
+    opts = options.split(';')
+    for o in opts:
+        if o.lower().startswith("rounds="):
+            (key, _, val) = o.partition('=')
+            try:
+                return int(val)
+            except ValueError:
+                return 0
+    return 0
+
 try:
     random_reason = check_random()
     if random_reason is not None:
@@ -882,9 +905,19 @@ class GetPasswordCommand(Command):
     def get_account_attributes(self, samdb, username, basedn, filter, scope,
                                attrs, decrypt):
 
-        require_supplementalCredentials = False
-        search_attrs = attrs[:]
+        raw_attrs = attrs[:]
+        search_attrs = []
+        attr_opts = {}
+        for a in raw_attrs:
+            (attr, _, opts) = a.partition(';')
+            if opts:
+                attr_opts[attr] = opts
+            else:
+                attr_opts[attr] = None
+            search_attrs.append(attr)
         lower_attrs = [x.lower() for x in search_attrs]
+
+        require_supplementalCredentials = False
         for a in virtual_attributes.keys():
             if a.lower() in lower_attrs:
                 require_supplementalCredentials = True
@@ -1124,6 +1157,22 @@ class GetPasswordCommand(Command):
             except IndexError:
                 return None
 
+        def get_userPassword_hash(blob, scheme, prefix):
+            up = ndr_unpack(drsblobs.package_PrimaryUserPasswordBlob, blob)
+
+            # Check that the NT hash has not been changed without updating
+            # the user password hashes.
+            if unicodePwd != bytearray(up.current_nt_hash.hash):
+                return 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
+
+            return None
+
         # We use sort here in order to have a predictable processing order
         for a in sorted(virtual_attributes.keys()):
             if not a.lower() in lower_attrs:
@@ -1161,7 +1210,8 @@ class GetPasswordCommand(Command):
                 u8 = get_utf8(a, b, username or account_name)
                 if u8 is None:
                     continue
-                sv = get_crypt_value("5", u8)
+                rounds = get_rounds(attr_opts[a])
+                sv = get_crypt_value("5", u8, rounds)
                 v = "{CRYPT}" + sv
             elif a == "virtualCryptSHA512":
                 b = get_package("Primary:CLEARTEXT")
@@ -1170,7 +1220,8 @@ class GetPasswordCommand(Command):
                 u8 = get_utf8(a, b, username or account_name)
                 if u8 is None:
                     continue
-                sv = get_crypt_value("6", u8)
+                rounds = get_rounds(attr_opts[a])
+                sv = get_crypt_value("6", u8, rounds)
                 v = "{CRYPT}" + sv
             elif a == "virtualSambaGPG":
                 # Samba adds 'Primary:SambaGPG' at the end.
@@ -1257,10 +1308,20 @@ for which virtual attributes are supported in your environment):
    virtualCryptSHA256:    As virtualClearTextUTF8, but a salted SHA256
                           checksum, useful for OpenLDAP's '{CRYPT}' algorithm,
                           with a $5$... salt, see crypt(3) on modern systems.
+                          The number of rounds used to calculate the hash can
+                          also be specified. By appending ";rounds=x" to the
+                          attribute name i.e. virtualCryptSHA256;rounds=10000
+                          will calculate a SHA256 hash with 10,000 rounds.
+                          non numeric values for rounds are silently ignored
 
    virtualCryptSHA512:    As virtualClearTextUTF8, but a salted SHA512
                           checksum, useful for OpenLDAP's '{CRYPT}' algorithm,
                           with a $6$... salt, see crypt(3) on modern systems.
+                          The number of rounds used to calculate the hash can
+                          also be specified. By appending ";rounds=x" to the
+                          attribute name i.e. virtualCryptSHA512;rounds=10000
+                          will calculate a SHA512 hash with 10,000 rounds.
+                          non numeric values for rounds are silently ignored
 
    virtualWDigestNN:      The individual hash values stored in
                           'Primary:WDigest' where NN is the hash number in
@@ -1398,10 +1459,20 @@ for supported virtual attributes in your environment):
    virtualCryptSHA256:    As virtualClearTextUTF8, but a salted SHA256
                           checksum, useful for OpenLDAP's '{CRYPT}' algorithm,
                           with a $5$... salt, see crypt(3) on modern systems.
+                          The number of rounds used to calculate the hash can
+                          also be specified. By appending ";rounds=x" to the
+                          attribute name i.e. virtualCryptSHA256;rounds=10000
+                          will calculate a SHA256 hash with 10,000 rounds.
+                          non numeric values for rounds are silently ignored
 
    virtualCryptSHA512:    As virtualClearTextUTF8, but a salted SHA512
                           checksum, useful for OpenLDAP's '{CRYPT}' algorithm,
                           with a $6$... salt, see crypt(3) on modern systems.
+                          The number of rounds used to calculate the hash can
+                          also be specified. By appending ";rounds=x" to the
+                          attribute name i.e. virtualCryptSHA512;rounds=10000
+                          will calculate a SHA512 hash with 10,000 rounds.
+                          non numeric values for rounds are silently ignored
 
    virtualWDigestNN:      The individual hash values stored in
                           'Primary:WDigest' where NN is the hash number in
index 85b89f4b55dc1353139a3abcd485350914497180..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
-# rounds tests for samba_tool.user_virtualCryptSHA should fail until the rounds functionality is implemented
-^samba.tests.samba_tool.user_virtualCryptSHA.*.test_gpg_both_hashes_sha256_rounds_invalid\(.*
-^samba.tests.samba_tool.user_virtualCryptSHA.*.gpg_both_hashes_both_rounds\(.*