]> eyrie.org Git - kerberos/krb5-strength.git/commitdiff
Add support for requiring a number of character classes
authorToby Blake <toby@inf.ed.ac.uk>
Mon, 19 Dec 2016 21:17:44 +0000 (13:17 -0800)
committerRuss Allbery <eagle@eyrie.org>
Mon, 19 Dec 2016 21:17:44 +0000 (13:17 -0800)
Extend the required_classes config option to allow requiring at
least a given number of different character classes (whatever they
are).

docs/krb5-strength.pod
plugin/classes.c
plugin/config.c
plugin/internal.h

index 96e5cace09ed4adb9b874f0ddb03d72cccccef9c..0db76e858ba731d81e1cae713a3a93cb28a1515a 100644 (file)
@@ -235,10 +235,11 @@ whitespace-separated rule.  Each rule has the syntax:
     [<min>-<max>:]<class>[,<class>...]
 
 where <class> is one of C<upper>, C<lower>, C<digit>, or C<symbol>
-(without quote marks).  The symbol class includes all characters other
-than alphanumeric characters, including space.  The listed classes must
-appear in the password.  Separate multiple required classes with a comma
-(and no space).
+(without quote marks), or an integer representing a minimum number of
+character classes.  The symbol class includes all characters other than
+alphanumeric characters, including space.  The listed classes must appear
+in the password.  Separate multiple required classes with a comma (and no
+space).
 
 The character class checks will be done in whatever locale the plugin or
 password check program is run in, which will normally be the POSIX C
@@ -265,6 +266,32 @@ contain both upper and lower case.  Passwords longer than 20 characters
 have no character class restrictions.  (This example is probably used in
 conjunction with C<minimum_length = 8>.)
 
+C<require_classes> also supports specifying the minimum number of
+character classes a password should contain.  For example:
+
+    require_classes = 3
+
+would require all passwords to have a minimum of any three of the
+character classes.
+
+This can also be used with <min> and <max> ranges, as above.  For example:
+
+    require_classes = 8-11:3 12-19:2
+
+requires all passwords from 8 to 11 characters contain at least three
+different character classes, and passwords from 12 to 19 characters
+contain at least two different character classes.  Ranges can overlap, as
+in the examples above, but this makes less sense when specifying a minimum
+number of classes.
+
+Minimum numbers of character classes can be combined with specific
+character classes.  For example:
+
+    require_classes = symbol,3
+
+requires all passwords contain three distinct character classes and must
+contain a symbol character.
+
 =item require_non_letter
 
 If set to a true boolean value, the password must contain at least one
index b9b7467d663c9521dfe11f2aa8d7281c50ce0878..866caa145283a0bb5459fa3d4ab50849f5806640 100644 (file)
@@ -23,6 +23,7 @@ struct password_classes {
     bool upper;
     bool digit;
     bool symbol;
+    unsigned long num_classes;
 };
 
 
@@ -46,6 +47,15 @@ analyze_password(const char *password, struct password_classes *classes)
         else
             classes->symbol = true;
     }
+
+    if (classes->lower)
+       classes->num_classes++;
+    if (classes->upper)
+       classes->num_classes++;
+    if (classes->digit)
+       classes->num_classes++;
+    if (classes->symbol)
+       classes->num_classes++;
 }
 
 
@@ -60,6 +70,12 @@ check_rule(krb5_context ctx, struct class_rule *rule, size_t length,
 {
     if (length < rule->min || (rule->max > 0 && length > rule->max))
         return 0;
+
+    if (classes->num_classes < rule->num_classes)
+        return strength_error_class((ctx),
+                                   "password must have %lu character classes",
+                                   rule->num_classes);
+    
     if (rule->lower && !classes->lower)
         return strength_error_class((ctx), ERROR_CLASS_LOWER);
     if (rule->upper && !classes->upper)
index 6aaa21a40b31062cc2ce1d918d53c8ebf52d4ed7..7fc3f66cd2a318b841584056a9abcc6cd6a18c77 100644 (file)
@@ -22,6 +22,9 @@
 #include <plugin/internal.h>
 #include <util/macros.h>
 
+/* maximum number of character classes */
+#define MAX_CLASSES 4
+
 /* The representation of the realm differs between MIT and Kerberos. */
 #ifdef HAVE_KRB5_REALM
 typedef krb5_realm realm_type;
@@ -169,10 +172,10 @@ parse_class(krb5_context ctx, const char *spec, struct class_rule **rule)
         return strength_error_system(ctx, "cannot allocate memory");
 
     /*
-     * If the rule starts with a digit, it starts with a range of affected
-     * password lengths.  Parse that range.
+     * If the rule starts with a digit and contains a '-', it starts
+     * with a range of affected password lengths.  Parse that range.
      */
-    if (isdigit((unsigned char) *spec)) {
+    if (isdigit((unsigned char) *spec) && strchr(spec, '-') != NULL) {
         okay = parse_number(spec, &(*rule)->min, &end);
         if (okay)
             okay = (*end == '-');
@@ -209,6 +212,17 @@ parse_class(krb5_context ctx, const char *spec, struct class_rule **rule)
             (*rule)->digit = true;
         else if (strcmp(classes->strings[i], "symbol") == 0)
             (*rule)->symbol = true;
+       else if (isdigit((unsigned char) *classes->strings[i])) {
+           okay = parse_number(classes->strings[i],
+                               &(*rule)->num_classes, &end);
+           if (!okay || *end != '\0' || (*rule)->num_classes > MAX_CLASSES) {
+                code = strength_error_config(ctx, "bad character class"
+                                             " requirement in configuration:"
+                                             " %s",
+                                            classes->strings[i]);
+               goto fail;
+           }
+       }
         else {
             code = strength_error_config(ctx, "unknown character class %s",
                                          classes->strings[i]);
index bbd20ae24f14cffdaeb94cd87c5bb09e088f89cb..94d73c436085e007cb3efc223d08b8af45246f5b 100644 (file)
@@ -58,6 +58,7 @@ struct class_rule {
     bool upper;
     bool digit;
     bool symbol;
+    unsigned long num_classes;
     struct class_rule *next;
 };