Extend password policy to evaluate passwords using libpwpolicy

Enable checking:

maxrepeat - reject passwrods which contain more than N consecutive
            characters.
maxsequence - rejected passwords which contain character sequences
              (abcde).
dictcheck - check passwords using cracklib
usercheck - check whether the password contains the user name.

The class checking provided by libpwpolicy is not used because this
overlaps with the existing IPA checking. This includes the options
dcredit, ucredit, lcredit, ocredit, minclass and maxclassrepeat.

The pwquality min length is fixed at 6 so if there is a conflict between
the system policy and pwquality log that length is enforced at 6.

https://pagure.io/freeipa/issue/6964
https://pagure.io/freeipa/issue/5948
https://pagure.io/freeipa/issue/2445
https://pagure.io/freeipa/issue/298

Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Christian Heimes <cheimes@redhat.com>
This commit is contained in:
Rob Crittenden 2020-09-24 15:43:59 -04:00
parent 3fc2eda4e1
commit c4cca53e88
3 changed files with 107 additions and 5 deletions

View File

@ -322,7 +322,9 @@ int ipapwd_getPolicy(const char *dn,
Slapi_PBlock *pb = NULL;
char *attrs[] = { "krbMaxPwdLife", "krbMinPwdLife",
"krbPwdMinDiffChars", "krbPwdMinLength",
"krbPwdHistoryLength", NULL};
"krbPwdHistoryLength", "ipaPwdMaxRepeat",
"ipaPwdMaxSequence", "ipaPwdDictCheck",
"ipaPwdUserCheck", NULL};
Slapi_Entry **es = NULL;
Slapi_Entry *pe = NULL;
int ret, res, scope, i;
@ -402,6 +404,10 @@ int ipapwd_getPolicy(const char *dn,
policy->min_complexity = slapi_entry_attr_get_int(pe,
"krbPwdMinDiffChars");
policy->max_repeat = slapi_entry_attr_get_int(pe, "ipaPwdMaxRepeat");
policy->max_sequence = slapi_entry_attr_get_int(pe, "ipaPwdMaxSequence");
policy->dictcheck = slapi_entry_attr_get_bool(pe, "ipaPwdDictCheck");
policy->usercheck = slapi_entry_attr_get_bool(pe, "ipaPwdUserCheck");
ret = 0;
@ -576,6 +582,7 @@ int ipapwd_CheckPolicy(struct ipapwd_data *data)
time_t pwd_expiration;
time_t last_pwd_change;
char **pwd_history;
char *uid;
char *tmpstr;
int ret;
@ -641,9 +648,11 @@ int ipapwd_CheckPolicy(struct ipapwd_data *data)
pwd_history = slapi_entry_attr_get_charray(data->target,
"passwordHistory");
uid = slapi_entry_attr_get_charptr(data->target, "uid");
/* check policy */
ret = ipapwd_check_policy(&pol, data->password,
uid,
data->timeNow,
acct_expiration,
pwd_expiration,
@ -651,6 +660,7 @@ int ipapwd_CheckPolicy(struct ipapwd_data *data)
pwd_history);
slapi_ch_array_free(pwd_history);
slapi_ch_free_string(&uid);
if (data->expireTime == 0) {
if (pol.max_pwd_life > 0) {

View File

@ -28,8 +28,10 @@
#include <time.h>
#include <ctype.h>
#include <fcntl.h>
#include <syslog.h>
#include <unistd.h>
#include <errno.h>
#include <pwquality.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
@ -406,6 +408,7 @@ cleanup:
*/
int ipapwd_check_policy(struct ipapwd_policy *policy,
char *password,
char *user,
time_t cur_time,
time_t acct_expiration,
time_t pwd_expiration,
@ -414,6 +417,11 @@ int ipapwd_check_policy(struct ipapwd_policy *policy,
{
int pwdlen, blen;
int ret;
pwquality_settings_t *pwq;
int check_pwquality = 0;
int entropy;
char buf[PWQ_MAX_ERROR_MESSAGE_LEN];
void *auxerror;
if (!policy || !password) {
return IPAPWD_POLICY_ERROR;
@ -462,7 +470,7 @@ int ipapwd_check_policy(struct ipapwd_policy *policy,
char *p, *n;
int size, len;
/* we want the actual lenght in bytes here */
/* we want the actual length in bytes here */
len = blen;
p = password;
@ -526,6 +534,74 @@ int ipapwd_check_policy(struct ipapwd_policy *policy,
}
}
/* Only call into libpwquality if at least one setting is made
* because there are a number of checks that don't have knobs
* so preserve the previous behavior.
*/
check_pwquality = policy->max_repeat + policy->max_sequence + policy->dictcheck + policy->usercheck;
if (check_pwquality > 0) {
/* Call libpwquality */
openlog(NULL, LOG_CONS | LOG_NDELAY, LOG_DAEMON);
pwq = pwquality_default_settings();
if (pwq == NULL) {
syslog(LOG_ERR, "Not able to set pwquality defaults\n");
return IPAPWD_POLICY_ERROR;
}
if (policy->min_pwd_length < 6)
syslog(LOG_WARNING, "password policy min length is < 6. Will be enforced as 6\n");
pwquality_set_int_value(pwq, PWQ_SETTING_MIN_LENGTH, policy->min_pwd_length);
pwquality_set_int_value(pwq, PWQ_SETTING_MAX_REPEAT, policy->max_repeat);
pwquality_set_int_value(pwq, PWQ_SETTING_MAX_SEQUENCE, policy->max_sequence);
pwquality_set_int_value(pwq, PWQ_SETTING_DICT_CHECK, policy->dictcheck);
pwquality_set_int_value(pwq, PWQ_SETTING_USER_CHECK, policy->usercheck);
entropy = pwquality_check(pwq, password, NULL, user, &auxerror);
pwquality_free_settings(pwq);
#ifdef TEST
if (user != NULL) {
fprintf(stderr, "Checking password for %s\n", user);
} else {
fprintf(stderr, "No user provided\n");
}
fprintf(stderr, "min length %d\n", policy->min_pwd_length);
fprintf(stderr, "max repeat %d\n", policy->max_repeat);
fprintf(stderr, "max sequence %d\n", policy->max_sequence);
fprintf(stderr, "dict check %d\n", policy->dictcheck);
fprintf(stderr, "user check %d\n", policy->usercheck);
#endif
if (entropy < 0) {
#ifdef TEST
fprintf(stderr, "Bad password '%s': %s\n", password, pwquality_strerror(buf, sizeof(buf), entropy, auxerror));
#endif
syslog(LOG_ERR, "Password is rejected with error %d: %s\n", entropy, pwquality_strerror(buf, sizeof(buf), entropy, auxerror));
switch (entropy) {
case PWQ_ERROR_MIN_LENGTH:
return IPAPWD_POLICY_PWD_TOO_SHORT;
case PWQ_ERROR_PALINDROME:
return IPAPWD_POLICY_PWD_PALINDROME;
case PWQ_ERROR_MAX_CONSECUTIVE:
return IPAPWD_POLICY_PWD_CONSECUTIVE;
case PWQ_ERROR_MAX_SEQUENCE:
return IPAPWD_POLICY_PWD_SEQUENCE;
case PWQ_ERROR_CRACKLIB_CHECK:
return IPAPWD_POLICY_PWD_DICT_WORD;
case PWQ_ERROR_USER_CHECK:
return IPAPWD_POLICY_PWD_USER;
default:
return IPAPWD_POLICY_PWD_COMPLEXITY;
}
#ifdef TEST
} else {
fprintf(stderr, "Password '%s' is ok, entropy is %d\n", password, entropy);
#endif
}
}
if (pwd_history) {
char *hash;
int i;
@ -549,13 +625,18 @@ char * IPAPWD_ERROR_STRINGS[] = {
"Too soon to change password",
"Password is too short",
"Password reuse not permitted",
"Password is too simple"
"Password is too simple",
"Password has too many consecutive characters",
"Password contains a monotonic sequence",
"Password is based on a dictionary word",
"Password is a palindrone",
"Password contains username"
};
char * IPAPWD_ERROR_STRING_GENERAL = "Password does not meet the policy requirements";
char * ipapwd_error2string(enum ipapwd_error err) {
if (err < 0 || err > IPAPWD_POLICY_PWD_COMPLEXITY) {
if (err < 0 || err > IPAPWD_POLICY_PWD_USER) {
/* IPAPWD_POLICY_ERROR or out of boundary, return general error */
return IPAPWD_ERROR_STRING_GENERAL;
}

View File

@ -44,7 +44,12 @@ enum ipapwd_error {
IPAPWD_POLICY_PWD_TOO_YOUNG = 2,
IPAPWD_POLICY_PWD_TOO_SHORT = 3,
IPAPWD_POLICY_PWD_IN_HISTORY = 4,
IPAPWD_POLICY_PWD_COMPLEXITY = 5
IPAPWD_POLICY_PWD_COMPLEXITY = 5,
IPAPWD_POLICY_PWD_CONSECUTIVE = 6,
IPAPWD_POLICY_PWD_SEQUENCE = 7,
IPAPWD_POLICY_PWD_DICT_WORD = 8,
IPAPWD_POLICY_PWD_PALINDROME = 9,
IPAPWD_POLICY_PWD_USER = 10
};
struct ipapwd_policy {
@ -56,6 +61,11 @@ struct ipapwd_policy {
int max_fail;
int failcnt_interval;
int lockout_duration;
int max_repeat;
int max_sequence;
int max_classrepeat;
int dictcheck;
int usercheck;
};
time_t ipapwd_gentime_to_time_t(char *timestr);
@ -68,6 +78,7 @@ int ipapwd_hash_password(char *password,
int ipapwd_check_policy(struct ipapwd_policy *policy,
char *password,
char *user,
time_t cur_time,
time_t acct_expiration,
time_t pwd_expiration,