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 Derrick Brashear and Ken Hornstein of Sine Nomine Associates,
9 * on behalf of Stanford University
10 * Extensive modifications by Russ Allbery <eagle@eyrie.org>
11 * Copyright 2020 Russ Allbery <eagle@eyrie.org>
12 * Copyright 2006-2007, 2009, 2012-2014
13 * The Board of Trustees of the Leland Stanford Junior University
15 * SPDX-License-Identifier: MIT
19 #include <portable/system.h>
23 #include <plugin/internal.h>
24 #include <util/macros.h>
28 * Given a string taken from the principal, check if the password matches that
29 * string or is that string with leading or trailing digits added. If so,
30 * sets the Kerberos error and returns a non-zero error code. Otherwise,
33 static krb5_error_code
34 check_component(krb5_context ctx, const char *component, const char *password)
37 size_t i, j, complength, passlength;
40 /* Check if the password is a simple match for the component. */
41 if (strcasecmp(component, password) == 0)
42 return strength_error_generic(ctx, ERROR_USERNAME);
45 * If the length of the password matches the length of the component,
46 * check for a reversed match.
48 complength = strlen(component);
49 passlength = strlen(password);
50 if (complength == passlength) {
51 copy = strdup(component);
53 return strength_error_system(ctx, "cannot allocate memory");
54 for (i = 0, j = complength - 1; i < j; i++, j--) {
59 if (strcasecmp(copy, password) == 0) {
60 explicit_bzero(copy, strlen(copy));
62 return strength_error_generic(ctx, ERROR_USERNAME);
68 * We've checked everything we care about unless the password is longer
71 if (passlength <= complength)
75 * Check whether the user just added leading or trailing digits to the
76 * component of the principal to form the password.
78 for (i = 0; i <= passlength - complength; i++) {
79 if (strncasecmp(password + i, component, complength) != 0)
83 * For this to be a match, all characters from 0 to i - 1 must be
84 * digits, and all characters from strlen(component) + i to
85 * strlen(password) - 1 must be digits.
87 for (j = 0; j < i; j++)
88 if (!isdigit((unsigned char) password[j]))
90 for (j = complength + i; j < passlength; j++)
91 if (!isdigit((unsigned char) password[j]))
94 /* The password was formed by adding digits to this component. */
95 return strength_error_generic(ctx, ERROR_USERNAME);
98 /* No similarity to component detected. */
104 * Returns true if a given character is a separator character for forming
105 * components, and false otherwise.
108 is_separator(unsigned char c)
110 if (c == '-' || c == '_')
119 * Check whether the password is based in some way on the principal. We do
120 * this by scanning the principal (in string form) and checking both each
121 * component of that password (defined as the alphanumeric, hyphen, and
122 * underscore bits between other characters) and the remaining principal from
123 * that point forward (to catch, for example, the entire realm). Returns 0 if
124 * it is not and some non-zero error code if it appears to be.
127 strength_check_principal(krb5_context ctx, krb5_pwqual_moddata data UNUSED,
128 const char *principal, const char *password)
130 krb5_error_code code;
135 if (principal == NULL)
138 /* Start with checking the entire principal. */
139 code = check_component(ctx, principal, password);
144 * Make a copy of the principal and scan forward past any leading
147 length = strlen(principal);
148 copy = strdup(principal);
150 return strength_error_system(ctx, "cannot allocate memory");
152 while (copy[i] != '\0' && is_separator(copy[i]))
156 * Now loop for each component. At the start of each loop, check against
157 * the component formed by the rest of the principal string.
161 code = check_component(ctx, copy + i, password);
163 explicit_bzero(copy, strlen(copy));
169 /* Set the component start and then scan for a separator. */
171 while (i < length && !is_separator(copy[i]))
174 /* At end of string or a separator. Truncate the component. */
177 /* Check the current component. */
178 code = check_component(ctx, start, password);
180 explicit_bzero(copy, strlen(copy));
185 /* Scan forward past any more separators. */
186 while (i < length && is_separator(copy[i]))
188 } while (i < length);
190 /* Password does not appear to be based on the principal. */
191 explicit_bzero(copy, strlen(copy));