password_hash: generate and store Primary:userPassword
authorGary Lockyer <gary@catalyst.net.nz>
Tue, 4 Apr 2017 04:05:08 +0000 (16:05 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 25 May 2017 00:25:12 +0000 (02:25 +0200)
Generate sha256 and sha512 password hashes and store them in
supplementalCredentials

Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Garming Sam <garming@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
selftest/knownfail
source4/dsdb/samdb/ldb_modules/password_hash.c

index 98f747249c7d974fabc6fc2b1cd815bfb28d396b..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 password hash supplemental credentials, userPassword hashes
-# Will fail as the implementation has not been written
-#
-^samba.tests.password_hash_gpgme.samba.tests.password_hash_gpgme.PassWordHashGpgmeTests.test_userPassword_multiple_hashes\(ad_dc:local\)
-^samba.tests.password_hash_gpgme.samba.tests.password_hash_gpgme.PassWordHashGpgmeTests.test_userPassword_multiple_hashes_rounds_specified\(ad_dc:local\)
-^samba.tests.password_hash_fl2008.samba.tests.password_hash_fl2008.PassWordHashFl2008Tests.test_userPassword_cleartext_sha256\(ad_dc_ntvfs:local\)
-^samba.tests.password_hash_fl2008.samba.tests.password_hash_fl2008.PassWordHashFl2008Tests.test_userPassword_sha512\(ad_dc_ntvfs:local\)
-^samba.tests.password_hash_fl2003.samba.tests.password_hash_fl2003.PassWordHashFl2003Tests.test_userPassword_cleartext_sha512\(fl2003dc:local\)
-^samba.tests.password_hash_fl2003.samba.tests.password_hash_fl2003.PassWordHashFl2003Tests.test_userPassword_sha256\(fl2003dc:local\)
index 7d38d215f16bc9157b64b1ce86f043d67bbddfa2..22ad5b11f1575cad6d8d164edd61865049012a62 100644 (file)
@@ -113,6 +113,7 @@ struct ph_context {
        bool pwd_last_set_bypass;
        bool pwd_last_set_default;
        bool smartcard_reset;
+       const char **userPassword_schemes;
 };
 
 
@@ -1120,8 +1121,8 @@ static int setup_primary_wdigest(struct setup_password_fields_io *io,
                DATA_BLOB *nt4dom;
        } wdigest[] = {
        /*
-        * See
-        * http://technet2.microsoft.com/WindowsServer/en/library/717b450c-f4a0-4cc9-86f4-cc0633aae5f91033.mspx?mfr=true
+        * See 3.1.1.8.11.3.1 WDIGEST_CREDENTIALS Construction
+        *     https://msdn.microsoft.com/en-us/library/cc245680.aspx
         * for what precalculated hashes are supposed to be stored...
         *
         * I can't reproduce all values which should contain "Digest" as realm,
@@ -1417,6 +1418,203 @@ static int setup_primary_wdigest(struct setup_password_fields_io *io,
        return LDB_SUCCESS;
 }
 
+#define SHA_SALT_PERMITTED_CHARS "abcdefghijklmnopqrstuvwxyz" \
+                                "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+                                "0123456789./"
+#define SHA_SALT_SIZE 16
+#define SHA_256_SCHEME "CryptSHA256"
+#define SHA_512_SCHEME "CryptSHA512"
+#define CRYPT "{CRYPT}"
+#define SHA_ID_LEN 3
+#define SHA_256_ALGORITHM_ID 5
+#define SHA_512_ALGORITHM_ID 6
+#define ROUNDS_PARAMETER "rounds="
+
+/*
+ * Extract the crypt (3) algorithm number and number of hash rounds from the
+ * supplied scheme string
+ */
+static bool parse_scheme(const char *scheme, int *algorithm, int *rounds) {
+
+       const char *rp = NULL; /* Pointer to the 'rounds=' option */
+       char digits[21];       /* digits extracted from the rounds option */
+       int i = 0;             /* loop index variable */
+
+       if (strncasecmp(SHA_256_SCHEME, scheme, strlen(SHA_256_SCHEME)) == 0) {
+               *algorithm = SHA_256_ALGORITHM_ID;
+       } else if (strncasecmp(SHA_512_SCHEME, scheme, strlen(SHA_256_SCHEME))
+                  == 0) {
+               *algorithm = SHA_512_ALGORITHM_ID;
+       } else {
+               return false;
+       }
+
+       rp = strcasestr(scheme, ROUNDS_PARAMETER);
+       if (rp == NULL) {
+               /* No options specified, use crypt default number of rounds */
+               *rounds = 0;
+               return true;
+       }
+       rp += strlen(ROUNDS_PARAMETER);
+       for (i = 0; isdigit(rp[i]) && i < (sizeof(digits) - 1); i++) {
+               digits[i] = rp[i];
+       }
+       digits[i] = '\0';
+       *rounds = atoi(digits);
+       return true;
+}
+
+/*
+ * Calculate the password hash specified by scheme, and return it in
+ * hash_value
+ */
+static int setup_primary_userPassword_hash(
+       TALLOC_CTX *ctx,
+       struct setup_password_fields_io *io,
+       const char* scheme,
+       struct package_PrimaryUserPasswordValue *hash_value)
+{
+       struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+       const char *salt = NULL;        /* Randomly generated salt */
+       const char *cmd = NULL;         /* command passed to crypt */
+       const char *hash = NULL;        /* password hash generated by crypt */
+       struct crypt_data crypt_data;   /* working storage used by crypt */
+       int algorithm = 0;              /* crypt hash algorithm number */
+       int rounds = 0;                 /* The number of hash rounds */
+       DATA_BLOB *hash_blob = NULL;
+       TALLOC_CTX *frame = talloc_stackframe();
+
+       /* Genrate a random password salt */
+       salt = generate_random_str_list(frame,
+                                       SHA_SALT_SIZE,
+                                       SHA_SALT_PERMITTED_CHARS);
+       if (salt == NULL) {
+               TALLOC_FREE(frame);
+               return ldb_oom(ldb);
+       }
+
+       /* determine the hashing algoritm and number of rounds*/
+       if (!parse_scheme(scheme, &algorithm, &rounds)) {
+               ldb_asprintf_errstring(
+                       ldb,
+                       "setup_primary_userPassword: Invalid scheme of [%s] "
+                       "specified for 'password hash userPassword schemes' in "
+                       "samba.conf",
+                       scheme);
+               TALLOC_FREE(frame);
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+       hash_value->scheme = talloc_strdup(ctx, CRYPT);
+       hash_value->scheme_len = strlen(CRYPT) + 1;
+
+       /* generate the id/salt parameter used by crypt */
+       if (rounds) {
+               cmd = talloc_asprintf(frame,
+                                     "$%d$rounds=%d$%s",
+                                     algorithm,
+                                     rounds,
+                                     salt);
+       } else {
+               cmd = talloc_asprintf(frame, "$%d$%s", algorithm, salt);
+       }
+
+       /*
+        * Relies on the assertion that cleartext_utf8->data is a zero
+        * terminated UTF-8 string
+        */
+       hash = crypt_r((char *)io->n.cleartext_utf8->data, cmd, &crypt_data);
+       if (hash == NULL) {
+               char buf[1024];
+               ldb_asprintf_errstring(
+                       ldb,
+                       "setup_primary_userPassword: generation of a %s "
+                       "password hash failed: (%s)",
+                       scheme,
+                       strerror_r(errno, buf, sizeof(buf)));
+               TALLOC_FREE(frame);
+               return LDB_ERR_OPERATIONS_ERROR;
+       }
+
+       hash_blob = talloc_zero(ctx, DATA_BLOB);
+
+       if (hash_blob == NULL) {
+               TALLOC_FREE(frame);
+               return ldb_oom(ldb);
+       }
+
+       *hash_blob =  data_blob_talloc(hash_blob,
+                                      (const uint8_t *)hash,
+                                      strlen(hash));
+       if (hash_blob->data == NULL) {
+               TALLOC_FREE(frame);
+               return ldb_oom(ldb);
+       }
+       hash_value->value = hash_blob;
+       TALLOC_FREE(frame);
+       return LDB_SUCCESS;
+}
+
+/*
+ * Calculate the desired extra password hashes
+ */
+static int setup_primary_userPassword(
+       struct setup_password_fields_io *io,
+       const struct supplementalCredentialsBlob *old_scb,
+       struct package_PrimaryUserPasswordBlob *p_userPassword_b)
+{
+       struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+       TALLOC_CTX *frame = talloc_stackframe();
+       int i;
+       int ret;
+
+       /*
+        * Save the current nt_hash, use this to determine if the password
+        * has been changed by windows. Which will invalidate the userPassword
+        * hash. Note once NTLM-Strong-NOWTF becomes available it should be
+        * used in preference to the NT password hash
+        */
+       if (io->g.nt_hash == NULL) {
+               ldb_asprintf_errstring(ldb,
+                       "No NT Hash, unable to calculate userPassword hashes");
+                       return LDB_ERR_UNWILLING_TO_PERFORM;
+       }
+       p_userPassword_b->current_nt_hash = *io->g.nt_hash;
+
+       /*
+        * Determine the number of hashes
+        * Note: that currently there is no limit on the number of hashes
+        *       no checking is done on the number of schemes specified
+        *       or for uniqueness.
+        */
+       p_userPassword_b->num_hashes = 0;
+       for (i = 0; io->ac->userPassword_schemes[i]; i++) {
+               p_userPassword_b->num_hashes++;
+       }
+
+       p_userPassword_b->hashes
+               = talloc_array(io->ac,
+                              struct package_PrimaryUserPasswordValue,
+                              p_userPassword_b->num_hashes);
+       if (p_userPassword_b->hashes == NULL) {
+               TALLOC_FREE(frame);
+               return ldb_oom(ldb);
+       }
+
+       for (i = 0; io->ac->userPassword_schemes[i]; i++) {
+               ret = setup_primary_userPassword_hash(
+                       p_userPassword_b->hashes,
+                       io,
+                       io->ac->userPassword_schemes[i],
+                       &p_userPassword_b->hashes[i]);
+               if (ret != LDB_SUCCESS) {
+                       TALLOC_FREE(frame);
+                       return ret;
+               }
+       }
+       return LDB_SUCCESS;
+}
+
+
 static int setup_primary_samba_gpg(struct setup_password_fields_io *io,
                                   struct package_PrimarySambaGPGBlob *pgb)
 {
@@ -1553,7 +1751,7 @@ static int setup_primary_samba_gpg(struct setup_password_fields_io *io,
 #endif /* else ENABLE_GPGME */
 }
 
-#define NUM_PACKAGES 5
+#define NUM_PACKAGES 6
 static int setup_supplemental_field(struct setup_password_fields_io *io)
 {
        struct ldb_context *ldb;
@@ -1561,7 +1759,8 @@ static int setup_supplemental_field(struct setup_password_fields_io *io)
        struct supplementalCredentialsBlob *old_scb = NULL;
        /*
         * Packages +
-        * (Kerberos-Newer-Keys, Kerberos, WDigest, CLEARTEXT, SambaGPG)
+        * ( Kerberos-Newer-Keys, Kerberos,
+        *   WDigest, CLEARTEXT, userPassword, SambaGPG)
         */
        uint32_t num_names = 0;
        const char *names[1+NUM_PACKAGES];
@@ -1611,6 +1810,7 @@ static int setup_supplemental_field(struct setup_password_fields_io *io)
         * Primary:Kerberos
         * Primary:WDigest
         * Primary:CLEARTEXT (optional)
+        * Primary:userPassword
         * Primary:SambaGPG (optional)
         *
         * And the 'Packages' package is insert before the last
@@ -1781,6 +1981,53 @@ static int setup_supplemental_field(struct setup_password_fields_io *io)
                num_packages++;
        }
 
+       if (io->ac->userPassword_schemes) {
+               /*
+                * setup 'Primary:userPassword' element
+                */
+               struct package_PrimaryUserPasswordBlob
+                       p_userPassword_b;
+               DATA_BLOB p_userPassword_b_blob;
+               char *p_userPassword_b_hexstr;
+
+               names[num_names++] = "userPassword";
+
+               ret = setup_primary_userPassword(io,
+                                                old_scb,
+                                                &p_userPassword_b);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
+
+               ndr_err = ndr_push_struct_blob(
+                       &p_userPassword_b_blob,
+                       io->ac,
+                       &p_userPassword_b,
+                       (ndr_push_flags_fn_t)
+                       ndr_push_package_PrimaryUserPasswordBlob);
+               if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+                       NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+                       ldb_asprintf_errstring(
+                               ldb,
+                               "setup_supplemental_field: failed to push "
+                               "package_PrimaryUserPasswordBlob: %s",
+                               nt_errstr(status));
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+               p_userPassword_b_hexstr
+                       = data_blob_hex_string_upper(
+                               io->ac,
+                               &p_userPassword_b_blob);
+               if (!p_userPassword_b_hexstr) {
+                       return ldb_oom(ldb);
+               }
+               pp->name     = "Primary:userPassword";
+               pp->reserved = 1;
+               pp->data     = p_userPassword_b_hexstr;
+               pp++;
+               num_packages++;
+       }
+
        /*
         * setup 'Primary:SambaGPG' element
         */
@@ -3441,7 +3688,8 @@ static struct ph_context *ph_init_context(struct ldb_module *module,
        lp_ctx = talloc_get_type_abort(ldb_get_opaque(ldb, "loadparm"),
                                       struct loadparm_context);
        ac->gpg_key_ids = lpcfg_password_hash_gpg_key_ids(lp_ctx);
-
+       ac->userPassword_schemes
+               = lpcfg_password_hash_userpassword_schemes(lp_ctx);
        return ac;
 }