lib/util: improve check_password_quality() to handle utf8
authorStefan Metzmacher <metze@samba.org>
Mon, 4 Feb 2013 07:45:48 +0000 (08:45 +0100)
committerMichael Adam <obnox@samba.org>
Mon, 4 Feb 2013 16:14:22 +0000 (17:14 +0100)
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Michael Adam <obnox@samba.org>
lib/util/genrand.c
lib/util/tests/genrand.c

index 57884ef791fb6a14ba02abd44fc260da9e81a20b..3dfaf089d16c4afdf25ead27391ce3835ba4b082 100644 (file)
@@ -298,29 +298,127 @@ _PUBLIC_ uint32_t generate_random(void)
 
 
 /**
-  very basic password quality checker
+  Microsoft composed the following rules (among others) for quality
+  checks. This is an abridgment from
+  http://msdn.microsoft.com/en-us/subscriptions/cc786468%28v=ws.10%29.aspx:
+
+  Passwords must contain characters from three of the following five
+  categories:
+
+   - Uppercase characters of European languages (A through Z, with
+     diacritic marks, Greek and Cyrillic characters)
+   - Lowercase characters of European languages (a through z, sharp-s,
+     with diacritic marks, Greek and Cyrillic characters)
+   - Base 10 digits (0 through 9)
+   - Nonalphanumeric characters: ~!@#$%^&*_-+=`|\(){}[]:;"'<>,.?/
+   - Any Unicode character that is categorized as an alphabetic character
+     but is not uppercase or lowercase. This includes Unicode characters
+     from Asian languages.
+
+ Note: for now do not check if the unicode category is
+       alphabetic character
 **/
-_PUBLIC_ bool check_password_quality(const char *s)
+_PUBLIC_ bool check_password_quality(const char *pwd)
 {
-       int has_digit=0, has_capital=0, has_lower=0, has_special=0, has_high=0;
-       const char* reals = s;
-       while (*s) {
-               if (isdigit((unsigned char)*s)) {
-                       has_digit |= 1;
-               } else if (isupper((unsigned char)*s)) {
-                       has_capital |= 1;
-               } else if (islower((unsigned char)*s)) {
-                       has_lower |= 1;
-               } else if (isascii((unsigned char)*s)) {
-                       has_special |= 1;
-               } else {
-                       has_high++;
+       size_t ofs = 0;
+       size_t num_chars = 0;
+       size_t num_digits = 0;
+       size_t num_upper = 0;
+       size_t num_lower = 0;
+       size_t num_nonalpha = 0;
+       size_t num_unicode = 0;
+       size_t num_categories = 0;
+
+       if (pwd == NULL) {
+               return false;
+       }
+
+       while (true) {
+               const char *s = &pwd[ofs];
+               size_t len = 0;
+               codepoint_t c;
+
+               c = next_codepoint(s, &len);
+               if (c == INVALID_CODEPOINT) {
+                       return false;
+               } else if (c == 0) {
+                       break;
+               }
+               ofs += len;
+               num_chars += 1;
+
+               if (len == 1) {
+                       const char *na = "~!@#$%^&*_-+=`|\\(){}[]:;\"'<>,.?/";
+
+                       if (isdigit(c)) {
+                               num_digits += 1;
+                               continue;
+                       }
+
+                       if (isupper(c)) {
+                               num_upper += 1;
+                               continue;
+                       }
+
+                       if (islower(c)) {
+                               num_lower += 1;
+                               continue;
+                       }
+
+                       if (strchr(na, c)) {
+                               num_nonalpha += 1;
+                               continue;
+                       }
+
+                       /*
+                        * the rest does not belong to
+                        * a category.
+                        */
+                       continue;
                }
-               s++;
+
+               if (isupper_m(c)) {
+                       num_upper += 1;
+                       continue;
+               }
+
+               if (islower_m(c)) {
+                       num_lower += 1;
+                       continue;
+               }
+
+               /*
+                * Note: for now do not check if the unicode category is
+                *       alphabetic character
+                *
+                * We would have to import the details from
+                * ftp://ftp.unicode.org/Public/6.3.0/ucd/UnicodeData-6.3.0d1.txt
+                */
+               num_unicode += 1;
+               continue;
+       }
+
+       if (num_digits > 0) {
+               num_categories += 1;
+       }
+       if (num_upper > 0) {
+               num_categories += 1;
+       }
+       if (num_lower > 0) {
+               num_categories += 1;
+       }
+       if (num_nonalpha > 0) {
+               num_categories += 1;
+       }
+       if (num_unicode > 0) {
+               num_categories += 1;
+       }
+
+       if (num_categories >= 3) {
+               return true;
        }
 
-       return ((has_digit + has_lower + has_capital + has_special) >= 3
-               || (has_high > strlen(reals)/2));
+       return false;
 }
 
 /**
index 50d77bb03ad89b5ad0737163aa5f82caed0fa157..ff608cd9254121bf2cdba836b90581af1a2af6cc 100644 (file)
@@ -41,7 +41,10 @@ static bool test_check_password_quality(struct torture_context *tctx)
        torture_assert(tctx, !check_password_quality("BLA"), "multiple upcases password");
        torture_assert(tctx, !check_password_quality("123"), "digits only");
        torture_assert(tctx, !check_password_quality("matthiéu"), "not enough high symbols");
-       torture_assert(tctx, check_password_quality("abcdééàçè"), "valid");
+       torture_assert(tctx, !check_password_quality("abcdééàçè"), "only lower case");
+       torture_assert(tctx, !check_password_quality("abcdééàçè+"), "only lower and symbols");
+       torture_assert(tctx, check_password_quality("abcdééàçè+ढ"), "valid");
+       torture_assert(tctx, check_password_quality("ç+ढ"), "valid");
        torture_assert(tctx, check_password_quality("A2e"), "valid");
        torture_assert(tctx, check_password_quality("BA2eLi443"), "valid");
        return true;