]> eyrie.org Git - kerberos/krb5-strength.git/commitdiff
Add minimum_different configuration option
authorRuss Allbery <eagle@eyrie.org>
Thu, 27 Feb 2014 02:52:21 +0000 (18:52 -0800)
committerRuss Allbery <eagle@eyrie.org>
Thu, 27 Feb 2014 02:52:21 +0000 (18:52 -0800)
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
README
plugin/general.c
plugin/internal.h
tests/data/passwords/letter.json
tests/plugin/heimdal-t.c
tests/plugin/mit-t.c
tests/tools/heimdal-strength-t

diff --git a/NEWS b/NEWS
index e8c73f17c1af04c3e9b4d1b933a9b2ceb0c0b180..3acab414c3c8bc917f6821087bc5de5c42a11025 100644 (file)
--- 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 7ca26f0b275dc91ac04dbf83bc0eba0cd1d9b6bc..3ad8765a6efa43d30ab2bf05353168536a9b814d 100644 (file)
--- 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
index 22690b1a78e17a711ea1cf4a646442a5c290a10a..bd8d56f13779d099df340d3fe01c7e9e0e4cdc5f 100644 (file)
@@ -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 <eagle@eyrie.org>
- * 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.
index 7eebf67ae42cf2f3c54498f6ea4c923b77c02b34..545f7bee7efde7d3a0ba4ce2035a46eeb326699c 100644 (file)
@@ -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 <eagle@eyrie.org>
- * 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 */
index 198c90f2ec72fc3b7d6c02939a5c954f5e41b444..4a221fd54a32f41869dc5507b8610fb7ab99252f 100644 (file)
         "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"
     }
 ]
index bd5c2596a644f08bb5f0384b7ed51ef4b17dd5f8..b32c944fbebaf8c94841b0e4cbea438d5fb5d03f 100644 (file)
@@ -2,7 +2,7 @@
  * Test for the Heimdal shared module API.
  *
  * Written by Russ Allbery <eagle@eyrie.org>
- * 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. */
index f2c02dd82cdc78f65d2e62eb9ad054bfbcad66b9..7026f441a8451ae5cc2c290a9eac3638202de24d 100644 (file)
@@ -2,7 +2,7 @@
  * Test for the MIT Kerberos shared module API.
  *
  * Written by Russ Allbery <eagle@eyrie.org>
- * 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. */
index 8a70bade35879f600fa050d37af75501fb8a03e1..ca258155c7616a46016dc411a400ddd2055ef3c7 100755 (executable)
@@ -3,7 +3,7 @@
 # Test suite for basic Heimdal external strength checking functionality.
 #
 # Written by Russ Allbery <eagle@eyrie.org>
-# 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',
     }