2 * Password strength checks based on the principal.
4 * Performs various checks of the password against the principal for which the
5 * password is being changed, trying to detect and reject passwords based on
6 * components of the principal.
8 * Developed by Daria Phoebe Brashear and Ken Hornstein of Sine Nomine
9 * Associates, on behalf of Stanford University Extensive modifications by Russ
10 * Allbery <eagle@eyrie.org> Copyright 2020 Russ Allbery <eagle@eyrie.org>
11 * Copyright 2006-2007, 2009, 2012-2014
12 * The Board of Trustees of the Leland Stanford Junior University
14 * SPDX-License-Identifier: MIT
18 #include <portable/system.h>
22 #include <plugin/internal.h>
23 #include <util/macros.h>
27 * Given a string taken from the principal, check if the password matches that
28 * string or is that string with leading or trailing digits added. If so,
29 * sets the Kerberos error and returns a non-zero error code. Otherwise,
32 static krb5_error_code
33 check_component(krb5_context ctx, const char *component, const char *password)
36 size_t i, j, complength, passlength;
39 /* Check if the password is a simple match for the component. */
40 if (strcasecmp(component, password) == 0)
41 return strength_error_generic(ctx, ERROR_USERNAME);
44 * If the length of the password matches the length of the component,
45 * check for a reversed match.
47 complength = strlen(component);
48 passlength = strlen(password);
49 if (complength == passlength) {
50 copy = strdup(component);
52 return strength_error_system(ctx, "cannot allocate memory");
53 for (i = 0, j = complength - 1; i < j; i++, j--) {
58 if (strcasecmp(copy, password) == 0) {
59 explicit_bzero(copy, strlen(copy));
61 return strength_error_generic(ctx, ERROR_USERNAME);
67 * We've checked everything we care about unless the password is longer
70 if (passlength <= complength)
74 * Check whether the user just added leading or trailing digits to the
75 * component of the principal to form the password.
77 for (i = 0; i <= passlength - complength; i++) {
78 if (strncasecmp(password + i, component, complength) != 0)
82 * For this to be a match, all characters from 0 to i - 1 must be
83 * digits, and all characters from strlen(component) + i to
84 * strlen(password) - 1 must be digits.
86 for (j = 0; j < i; j++)
87 if (!isdigit((unsigned char) password[j]))
89 for (j = complength + i; j < passlength; j++)
90 if (!isdigit((unsigned char) password[j]))
93 /* The password was formed by adding digits to this component. */
94 return strength_error_generic(ctx, ERROR_USERNAME);
97 /* No similarity to component detected. */
103 * Returns true if a given character is a separator character for forming
104 * components, and false otherwise.
107 is_separator(unsigned char c)
109 if (c == '-' || c == '_')
118 * Check whether the password is based in some way on the principal. We do
119 * this by scanning the principal (in string form) and checking both each
120 * component of that password (defined as the alphanumeric, hyphen, and
121 * underscore bits between other characters) and the remaining principal from
122 * that point forward (to catch, for example, the entire realm). Returns 0 if
123 * it is not and some non-zero error code if it appears to be.
126 strength_check_principal(krb5_context ctx, krb5_pwqual_moddata data UNUSED,
127 const char *principal, const char *password)
129 krb5_error_code code;
134 if (principal == NULL)
137 /* Start with checking the entire principal. */
138 code = check_component(ctx, principal, password);
143 * Make a copy of the principal and scan forward past any leading
146 length = strlen(principal);
147 copy = strdup(principal);
149 return strength_error_system(ctx, "cannot allocate memory");
151 while (copy[i] != '\0' && is_separator(copy[i]))
155 * Now loop for each component. At the start of each loop, check against
156 * the component formed by the rest of the principal string.
160 code = check_component(ctx, copy + i, password);
162 explicit_bzero(copy, strlen(copy));
168 /* Set the component start and then scan for a separator. */
170 while (i < length && !is_separator(copy[i]))
173 /* At end of string or a separator. Truncate the component. */
176 /* Check the current component. */
177 code = check_component(ctx, start, password);
179 explicit_bzero(copy, strlen(copy));
184 /* Scan forward past any more separators. */
185 while (i < length && is_separator(copy[i]))
187 } while (i < length);
189 /* Password does not appear to be based on the principal. */
190 explicit_bzero(copy, strlen(copy));