From 284cdb6fccaff4e5c5fbebe008857783d3419da2 Mon Sep 17 00:00:00 2001 From: Russ Allbery Date: Wed, 26 Feb 2014 18:52:21 -0800 Subject: [PATCH] Add minimum_different configuration option A new configuration option, minimum_different, can be set to require that passwords contain at least that many unique characters. This can be used to reject long strings of identical characters or short patterns, which may pass other checks but still be too easy to guess. --- NEWS | 5 +++ README | 8 +++++ plugin/general.c | 43 +++++++++++++++++++++-- plugin/internal.h | 4 ++- tests/data/passwords/letter.json | 59 ++++++++++++++++++++++++++++++++ tests/plugin/heimdal-t.c | 14 ++++---- tests/plugin/mit-t.c | 14 ++++---- tests/tools/heimdal-strength-t | 3 +- 8 files changed, 134 insertions(+), 16 deletions(-) diff --git a/NEWS b/NEWS index e8c73f1..3acab41 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,11 @@ krb5-strength 3.0 (unreleased) mechanism.) This program has more extensive Perl module dependencies than the other programs in this distribution. + A new configuration option, minimum_different, can be set to require + that passwords contain at least that many unique characters. This can + be used to reject long strings of identical characters or short + patterns, which may pass other checks but still be too easy to guess. + krb5-strength 2.2 (2013-12-16) More complex character class requirements can be specified with the diff --git a/README b/README index 7ca26f0..3ad8765 100644 --- a/README +++ b/README @@ -378,6 +378,14 @@ CONFIGURATION The following additional settings are supported in the [appdefaults] section of krb5.conf when running under either Heimdal or MIT Kerberos. + minimum_different + + If set to a numeric value, passwords with fewer than this number of + unique characters will be rejected. This can be used to reject, for + example, passwords that are long strings of the same character or + repetitions of small numbers of characters, which may be too easy to + guess. + minimum_length If set to a numeric value, passwords with fewer than that number of diff --git a/plugin/general.c b/plugin/general.c index 22690b1..bd8d56f 100644 --- a/plugin/general.c +++ b/plugin/general.c @@ -9,7 +9,7 @@ * Developed by Derrick Brashear and Ken Hornstein of Sine Nomine Associates, * on behalf of Stanford University * Extensive modifications by Russ Allbery - * Copyright 2006, 2007, 2009, 2012, 2013 + * Copyright 2006, 2007, 2009, 2012, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * See LICENSE for licensing terms. @@ -47,7 +47,8 @@ strength_init(krb5_context ctx, const char *dictionary, return strength_error_system(ctx, "cannot allocate memory"); data->cdb_fd = -1; - /* Get minimum length information from krb5.conf. */ + /* Get minimum length and character information from krb5.conf. */ + strength_config_number(ctx, "minimum_different", &data->minimum_different); strength_config_number(ctx, "minimum_length", &data->minimum_length); /* Get simple character class restrictions from krb5.conf. */ @@ -113,6 +114,39 @@ only_alpha_space(const char *password) } +/* + * Check if a password has a sufficient number of unique characters. Takes + * the password and the required number of characters. + */ +static bool +has_minimum_different(const char *password, long minimum) +{ + size_t unique; + const char *p; + + /* Special cases for passwords of length 0 and a minimum <= 1. */ + if (password == NULL || password[0] == '\0') + return minimum <= 0; + if (minimum <= 1) + return true; + + /* + * Count the number of unique characters by incrementing the count if each + * subsequent character is not found in the previous password characters. + * This algorithm is O(n^2), but passwords are short enough it shouldn't + * matter. + */ + unique = 1; + for (p = password + 1; *p != '\0'; p++) + if (memchr(password, *p, p - password) == NULL) { + unique++; + if (unique >= (size_t) minimum) + return true; + } + return false; +} + + /* * Check a given password. Takes a Kerberos context, our module data, the * password, the principal the password is for, and a buffer and buffer length @@ -144,6 +178,11 @@ strength_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data, if (data->nonletter && only_alpha_space(password)) return strength_error_class(ctx, ERROR_LETTER); + /* If desired, check for enough unique characters. */ + if (data->minimum_different > 0) + if (!has_minimum_different(password, data->minimum_different)) + return strength_error_class(ctx, ERROR_MINDIFF); + /* * If desired, check that the password satisfies character class * restrictions. diff --git a/plugin/internal.h b/plugin/internal.h index 7eebf67..545f7be 100644 --- a/plugin/internal.h +++ b/plugin/internal.h @@ -4,7 +4,7 @@ * Developed by Derrick Brashear and Ken Hornstein of Sine Nomine Associates, * on behalf of Stanford University * Extensive modifications by Russ Allbery - * Copyright 2006, 2007, 2009, 2012, 2013 + * Copyright 2006, 2007, 2009, 2012, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * See LICENSE for licensing terms. @@ -32,6 +32,7 @@ typedef struct krb5_pwqual_moddata_st *krb5_pwqual_moddata; #define ERROR_ASCII "password contains non-ASCII or control characters" #define ERROR_DICT "password found in list of common passwords" #define ERROR_LETTER "password is only letters and spaces" +#define ERROR_MINDIFF "password does not contain enough unique characters" #define ERROR_SHORT "password is too short" #define ERROR_USERNAME "password based on username or principal" @@ -65,6 +66,7 @@ struct vector { * checking for at least the MIT plugin. */ struct krb5_pwqual_moddata_st { + long minimum_different; /* Minimum number of different characters */ long minimum_length; /* Minimum password length */ bool ascii; /* Whether to require printable ASCII */ bool nonletter; /* Whether to require a non-letter */ diff --git a/tests/data/passwords/letter.json b/tests/data/passwords/letter.json index 198c90f..4a221fd 100644 --- a/tests/data/passwords/letter.json +++ b/tests/data/passwords/letter.json @@ -43,5 +43,64 @@ "name": "digits", "principal": "test@EXAMPLE.ORG", "password": "the perils 0of all good dogs" + }, + { + "name": "mindiff (1 character)", + "principal": "test@EXAMPLE.ORG", + "password": "11111111111111111111", + "code": "KADM5_PASS_Q_CLASS", + "error": "password does not contain enough unique characters" + }, + { + "name": "mindiff (2 characters)", + "principal": "test@EXAMPLE.ORG", + "password": "1b1b1b1b1b1b1b1b1b1b", + "code": "KADM5_PASS_Q_CLASS", + "error": "password does not contain enough unique characters" + }, + { + "name": "mindiff (3 characters)", + "principal": "test@EXAMPLE.ORG", + "password": "1bc1bc1bc1bc1bc1bc1b", + "code": "KADM5_PASS_Q_CLASS", + "error": "password does not contain enough unique characters" + }, + { + "name": "mindiff (4 characters)", + "principal": "test@EXAMPLE.ORG", + "password": "1bcd1bcd1bcd1bcd1bcd", + "code": "KADM5_PASS_Q_CLASS", + "error": "password does not contain enough unique characters" + }, + { + "name": "mindiff (5 characters)", + "principal": "test@EXAMPLE.ORG", + "password": "1bcde1bcde1bcde1bcde", + "code": "KADM5_PASS_Q_CLASS", + "error": "password does not contain enough unique characters" + }, + { + "name": "mindiff (6 characters)", + "principal": "test@EXAMPLE.ORG", + "password": "1bcdef1bcdef1bcdef1b", + "code": "KADM5_PASS_Q_CLASS", + "error": "password does not contain enough unique characters" + }, + { + "name": "mindiff (7 characters)", + "principal": "test@EXAMPLE.ORG", + "password": "1cdbfge1cdbeg1fcdbef", + "code": "KADM5_PASS_Q_CLASS", + "error": "password does not contain enough unique characters" + }, + { + "name": "mindiff (8 characters)", + "principal": "test@EXAMPLE.ORG", + "password": "1dbegchf1cdbfgh1ebcd" + }, + { + "name": "mindiff (9 characters)", + "principal": "test@EXAMPLE.ORG", + "password": "bcd1fgei1bhdefchig1b" } ] diff --git a/tests/plugin/heimdal-t.c b/tests/plugin/heimdal-t.c index bd5c259..b32c944 100644 --- a/tests/plugin/heimdal-t.c +++ b/tests/plugin/heimdal-t.c @@ -2,7 +2,7 @@ * Test for the Heimdal shared module API. * * Written by Russ Allbery - * Copyright 2009, 2013 + * Copyright 2009, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * See LICENSE for licensing terms. @@ -138,7 +138,7 @@ int main(void) { char *path, *krb5_config, *krb5_config_empty, *tmpdir; - char *setup_argv[10]; + char *setup_argv[12]; size_t i, count; struct kadm5_pw_policy_verifier *verifier; void *handle; @@ -190,11 +190,13 @@ main(void) is_password_test(verifier, &principal_tests[i]); /* Add simple character class restrictions. */ - setup_argv[5] = (char *) "require_ascii_printable"; - setup_argv[6] = (char *) "true"; - setup_argv[7] = (char *) "require_non_letter"; + setup_argv[5] = (char *) "minimum_different"; + setup_argv[6] = (char *) "8"; + setup_argv[7] = (char *) "require_ascii_printable"; setup_argv[8] = (char *) "true"; - setup_argv[9] = NULL; + setup_argv[9] = (char *) "require_non_letter"; + setup_argv[10] = (char *) "true"; + setup_argv[11] = NULL; run_setup((const char **) setup_argv); /* Run the simple character class tests. */ diff --git a/tests/plugin/mit-t.c b/tests/plugin/mit-t.c index f2c02dd..7026f44 100644 --- a/tests/plugin/mit-t.c +++ b/tests/plugin/mit-t.c @@ -2,7 +2,7 @@ * Test for the MIT Kerberos shared module API. * * Written by Russ Allbery - * Copyright 2010, 2013 + * Copyright 2010, 2013, 2014 * The Board of Trustees of the Leland Stanford Junior University * * See LICENSE for licensing terms. @@ -148,7 +148,7 @@ int main(void) { char *path, *dictionary, *krb5_config, *krb5_config_empty, *tmpdir; - char *setup_argv[10]; + char *setup_argv[12]; const char*build; size_t i, count; krb5_context ctx; @@ -243,11 +243,13 @@ main(void) vtable->close(ctx, data); /* Add simple character class configuration to krb5.conf. */ - setup_argv[5] = (char *) "require_ascii_printable"; - setup_argv[6] = (char *) "true"; - setup_argv[7] = (char *) "require_non_letter"; + setup_argv[5] = (char *) "minimum_different"; + setup_argv[6] = (char *) "8"; + setup_argv[7] = (char *) "require_ascii_printable"; setup_argv[8] = (char *) "true"; - setup_argv[9] = NULL; + setup_argv[9] = (char *) "require_non_letter"; + setup_argv[10] = (char *) "true"; + setup_argv[11] = NULL; run_setup((const char **) setup_argv); /* Obtain a new Kerberos context with that krb5.conf file. */ diff --git a/tests/tools/heimdal-strength-t b/tests/tools/heimdal-strength-t index 8a70bad..ca25815 100755 --- a/tests/tools/heimdal-strength-t +++ b/tests/tools/heimdal-strength-t @@ -3,7 +3,7 @@ # Test suite for basic Heimdal external strength checking functionality. # # Written by Russ Allbery -# Copyright 2009, 2012, 2013 +# Copyright 2009, 2012, 2013, 2014 # The Board of Trustees of the Leland Stanford Junior University # # See LICENSE for licensing terms. @@ -192,6 +192,7 @@ for my $test (@{ $tests{length} }) { # Install the krb5.conf file for simple character class restrictions. $krb5_conf = create_krb5_conf( { + minimum_different => 8, require_ascii_printable => 'true', require_non_letter => 'true', } -- 2.39.2