-
Notifications
You must be signed in to change notification settings - Fork 139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Server side key gen password complexity #4950
base: master
Are you sure you want to change the base?
Server side key gen password complexity #4950
Conversation
Key derivation from pkcs12 password was implemented during the default population operation and then the password was deleted. To implement a new constraint for the password this operation has to move to a later step so no computation is wasted in case the password do not satisfy its constraints.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some comments and questions below but there's no major issue. I'll defer the final review to @ladycfu.
Do we need to add these params into the profiles that we ship?
Do we need an upgrade script to update the registry.cfg
in existing instances?
Is there a doc/wiki page that shows how to modify the existing registry/profile files in existing instances?
It might be nice if we have a unit test or CI test for this, but that's optional.
String transportNickname = kraConnectorConfig.getString("transportCertNickname", "KRA Transport Certificate"); | ||
transCert = cm.findCertByNickname(transportNickname); | ||
} catch (Exception e) { | ||
logger.debug(method + "'KRA transport certificate' not found in nssdb; need to be manually setup for Server-Side keygen enrollment"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a misconfiguration so I think we should log the error message using logger.error()
.
/* future; cert nickname can't be controlled yet at import in jss | ||
logger.debug(method + "KRA transport certificate not found in nssdb; getting from CS.cfg"); | ||
transportCertStr = connectorsConfig.getString("KRA.transportCert", ""); | ||
logger.debug(method + "transportCert found in CS.cfg: " + transportCertStr); | ||
|
||
byte[] transportCertB = Utils.base64decode(transportCertStr); | ||
logger.debug(method + "transportCertB.length=" + transportCertB.length); | ||
// hmmm, can't yet control the nickname | ||
transCert = cm.importCACertPackage(transportCertB); | ||
logger.debug(method + "KRA transport certificate imported"); | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should support storing the transport cert in CS.cfg
anymore. The transport cert should already be stored in NSS database when the server is started and we should not assume we can modify the NSS database when the server is running.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@edewata The majority of the code in convertP12Password was actually moved from ServerKeygenUserKeyDefault.java (see lines 286-386 in this PR below) so that the code is not run before the p12 password gets checked by the constraint. I'm therefore to be blamed for the "future" comment (wink). I agree that we shouldn't assume that we can modify the NSS db on the fly. The comment should be removed.
if (ct == null) | ||
logger.debug(method + "crypto token null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably should throw an exception if the token is not available. Alternatively, we probably can assume that the internal token is always available so no need to check for null
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, there is no action beside the log. If the token is null there will be exceptions later in the code.
I have investigated some other places where the token is retrieved and this is never verified. I am removing this line. If a log or exception is needed then it can be in the getCryptoToken(<name>)
which log or raise an exception if it cannot be retrieved.
boolean useOAEP = caCfg.getUseOAEPKeyWrap(); | ||
|
||
KeyWrapAlgorithm wrapAlgorithm = KeyWrapAlgorithm.RSA; | ||
if(useOAEP == true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The == true
is redundant.
* parameters: graceBefore and graceAfter | ||
* | ||
* @author Christina Fu | ||
* @version $Revision$, $Date$ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't use $Revision$
or $Date$
so I think we can remove the @version
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feel free to remove or replace the @author too! lol. Also, replace the "This class supports renewal grace period, which has two parameters: graceBefore and graceAfter" with 5 params and their names.
} | ||
|
||
} catch (IOException e) { | ||
logger.error(method + "impossible check password with cracklib.", e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If cracklib check is enabled and it's failing I think we should throw an exception here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some suggestions for now.
@@ -531,4 +532,113 @@ public void execute(Request request) throws EProfileException, ERejectException | |||
} | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how you group the code into a method to handle processing of the p12 password and return in a Map collection. The method "convertP12Password" could use a comment briefly describing what it does. Also, I think instead of "convertP12Password", it might be more appropriate to call it "processP12Password" since you are not changing or generating the password. It's just been processed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, it appears that the code in this method was moved from def/ServerKeygenUserKeyDefault.java. I assume this is to avoid the P12 password being "processed" unnecessarily if it were to fail the password checks in the constraint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how you group the code into a method to handle processing of the p12 password and return in a Map collection. The method "convertP12Password" could use a comment briefly describing what it does. Also, I think instead of "convertP12Password", it might be more appropriate to call it "processP12Password" since you are not changing or generating the password. It's just been processed.
I was using convert
because in my mind it convert from a String to a corresponding key but I agree it is not a real conversion to I have update the names.
/* future; cert nickname can't be controlled yet at import in jss | ||
logger.debug(method + "KRA transport certificate not found in nssdb; getting from CS.cfg"); | ||
transportCertStr = connectorsConfig.getString("KRA.transportCert", ""); | ||
logger.debug(method + "transportCert found in CS.cfg: " + transportCertStr); | ||
|
||
byte[] transportCertB = Utils.base64decode(transportCertStr); | ||
logger.debug(method + "transportCertB.length=" + transportCertB.length); | ||
// hmmm, can't yet control the nickname | ||
transCert = cm.importCACertPackage(transportCertB); | ||
logger.debug(method + "KRA transport certificate imported"); | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@edewata The majority of the code in convertP12Password was actually moved from ServerKeygenUserKeyDefault.java (see lines 286-386 in this PR below) so that the code is not run before the p12 password gets checked by the constraint. I'm therefore to be blamed for the "future" comment (wink). I agree that we shouldn't assume that we can modify the NSS db on the fly. The comment should be removed.
* They will be put back at SSK_STAGE_KEY_RETRIEVE below | ||
*/ | ||
transWrappedSessionKey = request.getExtDataInByteArray("serverSideKeygenP12PasswdTransSession"); | ||
Map<String, byte[]> passwordsGenerated = convertP12Password(request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see my comment below at the method "convertP12Password". For the same reason (password was not changed or generated), how about changing the name "passwordsGenerated" to something like "p12PasswordInfo"?
// with this program; if not, write to the Free Software Foundation, Inc., | ||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
// | ||
// (C) 2007 Red Hat, Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to change the copy right to a more recent year? ;-)
* parameters: graceBefore and graceAfter | ||
* | ||
* @author Christina Fu | ||
* @version $Revision$, $Date$ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feel free to remove or replace the @author too! lol. Also, replace the "This class supports renewal grace period, which has two parameters: graceBefore and graceAfter" with 5 params and their names.
public static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(P12ExportPasswordConstraint.class); | ||
|
||
public static final String CONFIG_PASSWORD_MIN_SIZE = "password.minSize"; | ||
public static final String CONFIG_PASSWORD_MIN_CHAR_CATEGORY = "password.minCharCategory"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be simpler (to the users to understand, and to the coding) to have individual config parameter for each category? e.g. CONFIG_PASSWORD_MIN_UPPER_LETTER, CONFIG_PASSWORD_MIN_LOWER_LETTER, CONFIG_PASSWORD_MIN_NUMBER, CONFIG_PASSWORD_MIN_SPECIAL_CHAR, and CONFIG_PASSWORD_MIN_TOTAL_CHAR ?
catChars[0] = passwordSubset.split("(?<=\\p{Upper})", -6).length -1; | ||
catChars[1] = passwordSubset.split("(?<=\\p{Lower})", -6).length -1; | ||
catChars[2] = passwordSubset.split("(?<=\\d)", -6).length -1; | ||
catChars[3] = passwordSubset.split("(?<=\\p{Punct})", -6).length -1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you were to have separate config param for each category, you could have easier (to read) and less error-prone coding here, Something like:
Pattern upper = Pattern.compile("[A-Z]");
Pattern lower = Pattern.compile("[a-z]");
Pattern digit = Pattern.compile("[0-9]");
Pattern special = Pattern.compile("[^a-zA-Z0-9]");
then
if (countIt(password, upper) < minUpper ||
countIt(password, lower) < minLower ||
countIt(password, digit) < minDigit ||
countIt(password, special) < minSpecial) {
return false; (or throw exception)
}
private static int countIt(String str, Pattern pattern) {
Matcher matched = pattern.matcher(str);
int c = 0;
while (matched.find()) {
c++;
}
return c;
}
* | ||
* @author Christina Fu | ||
* @version $Revision$, $Date$ | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a thought. Could we maybe use a password checker somewhere else later? Like maybe KRA key recovery? If so, maybe we could extract the relevant code into a generic PasswordChecker class and apply it in this constraint instead?
@fmarco76 Actually, I think it's best to have defaults set in the constraint class itself as well as the profile. Also, see my comment regarding having separate config for each of the 4 elements in minCharCategory. Shouldn't the minSize be 20? |
7cec1df
to
ffbabfc
Compare
058435f
to
bf717d1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update. Most comments for your consideration.
CMS_PASSWORD_MISSING_LOWER_CASE_1=The password requires at least {0} lower case letter(s) | ||
CMS_PASSWORD_SEQUENCE=PKCS12 password cannot have repeated sequences (check also inverted). | ||
CMS_PASSWORD_REPEATED_CHAR=PKCS12 password cannot contain the same character more then {0} times. | ||
CMS_PASSWORD_CRACKLIB_FAILS=PKCS12 password did not pass cracklib test. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the PasswordChecker is now a generic password quality checker, perhaps drop the word "PKCS12" from the above 3 CMS_PASSWORD_ messages?
StringBuilder base = new StringBuilder(password.replaceAll("^\\p{Upper}*", "")); | ||
base.reverse(); | ||
basePassword = base.toString().replaceAll("^\\d*", ""); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might have missed something, but it looks like it's skipping over the leading upper letters and ending digits? Don't they need to be checked too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Investigating other password checker there are several consideration on the initial and final letters. As an example, from passwdc readme (it is an alternative to cracklib in many distros):
When calculating the number of character classes, upper-case letters
used as the first character and digits used as the last character of a
password are not counted.
I guess it is to avoid putting a password without digit and add a counter at the end at each password update.
However, to start simple we could remove this for now and count all the characters, is it OK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think it's safer that way. Simpler too.
private int minPunctuationChar = 0; | ||
private int substringMatch = 0; | ||
private int maxRepeatedChar = 0; | ||
private boolean cracklibCheck = false; | ||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something to consider (maybe later?). Since there are existing calls to the previous version of PasswordChecker, we could leave these default values here. However, I'm thinking maybe it's a good idea to allow overwriting these values in maybe CS.cfg instead of individual profiles so that the password quality restriction is uniformly configured, even at places where profiles are not involved. Maybe just add a comment for now if to be punted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on second thought, if we are more leaning towards setting these in CS.cfg, it might be a good idea to do it now while the class being being introduced so as to avoid backward compatibility and profile regression support etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added now in both. The configuration in CS.cfg is the default. The profile can overwrite the value.
return PasswordQuality.CMS_PASSWORD_SEQUENCE; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still thinking about defaults. For a password length of 20, it might be a good idea to set substringMatch to 5 or 6? Anyway, we can think about it later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, it appears that substringMatch is to detect repeated sequences of a length, would something like "seqLength" be a more telling name?
int mostRepeatedChar = password.chars().mapToObj(x -> (char) x) | ||
.collect(Collectors.groupingBy(x -> x, Collectors.counting())) | ||
.entrySet().stream().max(Map.Entry.comparingByValue()) | ||
.get().getValue().intValue(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm not mistaken, if this is the attempt to catch " repeated characters", I suspect that's misunderstood. I think "repeated characters" means something like "aaa", "bb" etc. instead of, say, "takuaba", where 'a' appears 3 times but it's not a meaningful word and doesn't have consecutive repeated characters so should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact, I think the previous check (substringMatch) should catch repeated characters already, right? If so, maybe remove this check. I think we can call this "Phase 1" and add improvements later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think the substringMatch capture repeated characters. If a password contains aaaaabbbbb
and we check with substring of 6 then it is OK but we could have max repeated of 3 which should fails. I'll update the check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, maybe you can change the logic so that the value assigned to substringMatch means that it will check with substringMatch, substringMatch-1, and substringMatch-2, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think we have to check with 1 character but we could go to min(3, maxRepeated).
crackIn.write(password); | ||
crackIn.close(); | ||
|
||
String crackResult = crackOut.readLine().substring(password.length() + 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does cracklib check for substrings of the password? If not, perhaps you could do some minimal processing first to extract consecutive string of characters from password to pass into cracklib.
We can do more complex checks in phase 2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does some substring check but not sure how much it is effective but get consecutive characters does not work then I replace a character with a symbol. The word matches to dictionary if taken entirely. I think this extraction should go to phase 2.
return pwd != null && pwd.length() >= MIN_LEN; | ||
try { | ||
return checkPassword(pwd) == PasswordQuality.CMS_PASSWORD_GOOD; | ||
} catch (EPasswordCheckException e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only have a few places that call isGoodPassword now. Could we maybe just throw an exception with the reason instead, and get rid of getReason()? See comment for getReason() below.
EPasswordCheckException e = new EPasswordCheckException( | ||
CMS.getUserMessage("CMS_PASSWORD_INVALID_LEN", "" + MIN_LEN)); | ||
try { | ||
PasswordQuality pq = checkPassword(pwd); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is making the existing calls to isGoodPassword() and then getReason() call checkPassword() twice. See comment at isGoodPassword. If we change it so that isGoodPassword throws password quality failure reason in an exception it would be more efficient. It would require some minor code changes to the callers, but I think it's only at a few places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is not optimal. My idea was not to modify the code currently using the checker and update/improve in a second step but we can go fix all in one.
In my idea was not to return the reason and the output but caching latest check so the reason can be retrieved without a check. If reason is not needed the boolean for the check is better. I'll update the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ladycfu I have updated the code. I still have to fully test, add update version script and eventually CI. But you can get a look if it is in the right direction
bf717d1
to
3e9d347
Compare
The new *p12ExportPasswordConstraintImpl* constraint allows to check: * `password.minSize` - the minimum size for the password; * `password.minUpperLetter` - the minimum number of capital letters; * `password.minLowerLetter` - the minimum number of lower letters; * `password.minNumber` - the minimum number of digits; * `password.minSpecialChar` - the minimum number of punctuation characters; * `password.seqLength` - the size of substring sequence which cannot be repeated; * `password.maxRepeatedChar` - maximum number of repeating for each character; * `password.cracklibCheck` - a boolean to request an additional check with *cracklib* (it has to be installed if not present). The same option can be configured in the CS.cfg replacing `password.*` with `passwordChecker.*`. The configuration in CS.cfg is used for all the passwords but the profile can overwrite to have stronger or weaker configuration.
3e9d347
to
c0d104b
Compare
Quality Gate passedIssues Measures |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is pretty much it. I only have a few very minor comments now. The rest is up to your testing. Thanks!
* - password.minLowerLetter | ||
* - password.minNumber | ||
* - password.minSpecialChar | ||
* - password.substringMatch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fmarco76 Could we unified the names between the two classes (I put the ones I prefer in bold)? so
substringMatch becomes seqLength
and
minPunctuationChar becomes minSpecialChar
This way you can document things in one place and not to confuse people.
last = c; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's much better.
CMS.getUserMessage(loc,"CMS_PASSWORD_MISSING_NUMERIC", "" + minNumber)).toString(); | ||
case CMS_PASSWORD_MISSING_PUNCTUATION -> new EPasswordCheckException( | ||
CMS.getUserMessage(loc,"CMS_PASSWORD_MISSING_PUNCTUATION", "" + minPunctuationChar)).toString(); | ||
case CMS_PASSWORD_REPEATED_CHAR -> new EPasswordCheckException( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are missing CMS_PASSWORD_SEQUENCE.
if (pwd == null || pwd.length() == 0) { | ||
EPasswordCheckException e = new EPasswordCheckException( | ||
CMS.getUserMessage("CMS_PASSWORD_EMPTY_PASSWORD")); | ||
public String getReason(Locale loc) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure where to put this, but shouldn't be have a user message completes with the usage that tells users the requirements of the passwords?
`password.*` with `passwordChecker.*`. The configuration in `CS.cfg` | ||
are used for all the passwords but each profile can overwrite to allow | ||
stronger or weaker passwords. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be put this in the Admin's guide as well for both CS.cfg and profile configuration?
I have implemented a new constraint to enforce the password for p12 export profile.
Added a description of the new options into the file docs/changes/v11.6.0/Server-Changes.adoc.
To enable this policy constraint in a profile with input
serverKeygenInputImpl
and outputpkcs12OutputImpl
, the following policy should be added to the set (the number could be different):@ladycfu and @edewata I have not modified the current policies but we could add at least the minimum size for the password. Is this OK?
This is for BZ #2196889