Commit message: Issue 4817 - BUG - locked crypt accounts on import may allow all passwords (#4819)

Bug Description: Due to mishanding of short dbpwd hashes, the
crypt_r algorithm was misused and was only comparing salts
in some cases, rather than checking the actual content
of the password.

Fix Description: Stricter checks on dbpwd lengths to ensure
that content passed to crypt_r has at least 2 salt bytes and
1 hash byte, as well as stricter checks on ct_memcmp to ensure
that compared values are the same length, rather than potentially
allowing overruns/short comparisons.

fixes: https://github.com/389ds/389-ds-base/issues/4817

Author: William Brown <william@blackhats.net.au>

Review by: @mreynolds389
CVE: CVE-2021-3652
CWE: CWE-284
Vulnerability description: A flaw was found in 389-ds-base. If an asterisk is imported as password hashes, either accidentally or maliciously, then instead of being inactive, any password will successfully match during authentication. This flaw allows an attacker to successfully authenticate as a user whose password was disabled.

Project: 389-ds-base
File: crypt_pwd.c

crypt_pw_md5_enc(const char *pwd)
{
    return crypt_pw_enc_by_hash(pwd, CRYPT_MD5);
}

Project: 389-ds-base
File: crypt_pwd.c

crypt_pw_enc_by_hash(const char *pwd, int hash_algo)
{
    char salt[3];
    char *algo_salt = NULL;
    char *cry;
    char *enc = NULL;
    long v;
    static unsigned int seed = 0;
    struct crypt_data data;
    data.initialized = 0;

    if (seed == 0) {
        seed = (unsigned int)slapi_rand();
    }
    v = slapi_rand_r(&seed);

    salt[0] = itoa64[v & 0x3f];
    v >>= 6;
    salt[1] = itoa64[v & 0x3f];
    salt[2] = '\0';

    /* Prepare our salt based on the hashing algorithm */
    if (hash_algo == CRYPT_UNIX) {
        algo_salt = strdup(salt);
    } else if (hash_algo == CRYPT_MD5) {
        algo_salt = slapi_ch_smprintf("$1$%s", salt);
    } else if (hash_algo == CRYPT_SHA256) {
        algo_salt = slapi_ch_smprintf("$5$%s", salt);
    } else if (hash_algo == CRYPT_SHA512) {
        algo_salt = slapi_ch_smprintf("$6$%s", salt);
    } else {
        /* default to CRYPT_UNIX */
        algo_salt = strdup(salt);
    }

    cry = crypt_r(pwd, algo_salt, &data);
    if (cry != NULL) {
        enc = slapi_ch_smprintf("%c%s%c%s", PWD_HASH_PREFIX_START, CRYPT_SCHEME_NAME, PWD_HASH_PREFIX_END, cry);
    }
    slapi_ch_free_string(&algo_salt);

    return (enc);
}

Project: 389-ds-base
File: crypt_pwd.c

crypt_pw_cmp(const char *userpwd, const char *dbpwd)
{
    int rc = -1;
    char *cp = NULL;
    size_t dbpwd_len = strlen(dbpwd);
    struct crypt_data data;
    data.initialized = 0;

    /*
     * there MUST be at least 2 chars of salt and some pw bytes, else this is INVALID and will
     * allow any password to bind as we then only compare SALTS.
     */
    if (dbpwd_len >= 3) {
        /* we use salt (first 2 chars) of encoded password in call to crypt_r() */
        cp = crypt_r(userpwd, dbpwd, &data);
    }
    /* If these are not the same length, we can not proceed safely with memcmp. */
    if (cp && dbpwd_len == strlen(cp)) {
        rc = slapi_ct_memcmp(dbpwd, cp, dbpwd_len);
    } else {
        rc = -1;
    }
    return rc;
}

Project: 389-ds-base
File: crypt_pwd.c

crypt_pw_sha256_enc(const char *pwd)
{
    return crypt_pw_enc_by_hash(pwd, CRYPT_SHA256);
}

Project: 389-ds-base
File: crypt_pwd.c

crypt_pw_sha512_enc(const char *pwd)
{
    return crypt_pw_enc_by_hash(pwd, CRYPT_SHA512);
}

Project: 389-ds-base
File: crypt_pwd.c

crypt_pw_enc(const char *pwd)
{
    return crypt_pw_enc_by_hash(pwd, CRYPT_UNIX);
}