Skip to content

Commit

Permalink
A new policy constraint to enforce p12 password quality.
Browse files Browse the repository at this point in the history
The new *p12ExportPasswordConstraintImpl* constraint allows to check:

* `password.minSize` - the minimum size for the password;
* `password.minCharCategory` - a csv of minimum characters in each category, they are _capital_, _lower_, _digit_ and _punctuation_;
* `password.substringMatch` - 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).
  • Loading branch information
fmarco76 committed Feb 4, 2025
1 parent bc2aa36 commit 7cec1df
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 1 deletion.
5 changes: 4 additions & 1 deletion base/ca/shared/conf/registry.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
types=profile,defaultPolicy,constraintPolicy,profileInput,profileOutput,profileUpdater
constraintPolicy.ids=noConstraintImpl,subjectNameConstraintImpl,uniqueSubjectNameConstraintImpl,userSubjectNameConstraintImpl,cmcSharedTokenSubjectNameConstraintImpl,cmcUserSignedSubjectNameConstraintImpl,caValidityConstraintImpl,validityConstraintImpl,keyUsageExtConstraintImpl,nsCertTypeExtConstraintImpl,extendedKeyUsageExtConstraintImpl,keyConstraintImpl,basicConstraintsExtConstraintImpl,extensionConstraintImpl,signingAlgConstraintImpl,uniqueKeyConstraintImpl,renewGracePeriodConstraintImpl,authzRealmConstraintImpl,externalProcessConstraintImpl
constraintPolicy.ids=noConstraintImpl,subjectNameConstraintImpl,uniqueSubjectNameConstraintImpl,userSubjectNameConstraintImpl,cmcSharedTokenSubjectNameConstraintImpl,cmcUserSignedSubjectNameConstraintImpl,caValidityConstraintImpl,validityConstraintImpl,keyUsageExtConstraintImpl,nsCertTypeExtConstraintImpl,extendedKeyUsageExtConstraintImpl,keyConstraintImpl,basicConstraintsExtConstraintImpl,extensionConstraintImpl,signingAlgConstraintImpl,uniqueKeyConstraintImpl,renewGracePeriodConstraintImpl,authzRealmConstraintImpl,externalProcessConstraintImpl,p12ExportPasswordConstraintImpl
constraintPolicy.signingAlgConstraintImpl.class=com.netscape.cms.profile.constraint.SigningAlgConstraint
constraintPolicy.signingAlgConstraintImpl.desc=Signing Algorithm Constraint
constraintPolicy.signingAlgConstraintImpl.name=Signing Algorithm Constraint
Expand Down Expand Up @@ -27,6 +27,9 @@ constraintPolicy.nsCertTypeExtConstraintImpl.name=Netscape Certificate Type Exte
constraintPolicy.noConstraintImpl.class=com.netscape.cms.profile.constraint.NoConstraint
constraintPolicy.noConstraintImpl.desc=No Constraint
constraintPolicy.noConstraintImpl.name=No Constraint
constraintPolicy.p12ExportPasswordConstraintImpl.class=com.netscape.cms.profile.constraint.P12ExportPasswordConstraint
constraintPolicy.p12ExportPasswordConstraintImpl.desc=Generated PKCS12 Constraint
constraintPolicy.p12ExportPasswordConstraintImpl.name=Generated PKCS12 Constraint
constraintPolicy.subjectNameConstraintImpl.class=com.netscape.cms.profile.constraint.SubjectNameConstraint
constraintPolicy.subjectNameConstraintImpl.desc=Subject Name Constraint
constraintPolicy.subjectNameConstraintImpl.name=Subject Name Constraint
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// --- BEGIN COPYRIGHT BLOCK ---
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// 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.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package com.netscape.cms.profile.constraint;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.mozilla.jss.netscape.security.x509.X509CertInfo;

import com.netscape.certsrv.profile.ERejectException;
import com.netscape.certsrv.property.EPropertyException;
import com.netscape.certsrv.property.IDescriptor;
import com.netscape.cms.profile.def.NoDefault;
import com.netscape.cms.profile.def.PolicyDefault;
import com.netscape.cmscore.apps.CMS;
import com.netscape.cmscore.request.Request;

/**
* This class supports renewal grace period, which has two
* parameters: graceBefore and graceAfter
*
* @author Christina Fu
* @version $Revision$, $Date$
*/
public class P12ExportPasswordConstraint extends EnrollConstraint {

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";
public static final String CONFIG_PASSWORD_SUBSTRING_MATCH = "password.substringMatch";
public static final String CONFIG_PASSWORD_MAX_REPEATED_CHAR = "password.maxRepeatedChar";
public static final String CONFIG_PASSWORD_CRACKLIB_CHECK = "password.cracklibCheck";

public P12ExportPasswordConstraint() {
super();
addConfigName(CONFIG_PASSWORD_MIN_SIZE);
}

@Override
public void setConfig(String name, String value)
throws EPropertyException {
if (name.equals(CONFIG_PASSWORD_MIN_SIZE) ||
name.equals(CONFIG_PASSWORD_SUBSTRING_MATCH) ||
name.equals(CONFIG_PASSWORD_MAX_REPEATED_CHAR)) {
try {
Integer.parseInt(value);
} catch (Exception e) {
throw new EPropertyException(CMS.getUserMessage(
"CMS_INVALID_PROPERTY", name));
}
}
super.setConfig(name, value);
}

@Override
public IDescriptor getConfigDescriptor(Locale locale, String name) {
return null;
}

@Override
public void validate(Request req, X509CertInfo info)
throws ERejectException {
String method = "P12ExportPasswordConstraint: validate: ";
String password = req.getExtDataInString("serverSideKeygenP12Passwd");
String param = getConfig(CONFIG_PASSWORD_MIN_SIZE);
if (!param.equals("") && password.length() < Integer.parseInt(param)) {
throw new ERejectException(CMS.getUserMessage(getLocale(req),
"CMS_PROFILE_P12EXPORT_PASSWORD_TOO_SHORT", param));
}

param = getConfig(CONFIG_PASSWORD_MIN_CHAR_CATEGORY);
if (!param.equals("")) {
String[] catCharSize = param.split(",");
Pattern pattern = Pattern.compile("^\\p{Upper}*(.*?)\\d*$");
Matcher matcher = pattern.matcher(password);
int[] catChars = {0, 0, 0, 0};
if (matcher.find()) {
String passwordSubset = matcher.group(1);
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;
}
for (int i = 0; i < 4; i++) {
if (catChars[i] < Integer.parseInt(catCharSize[i])) {
throw new ERejectException(CMS.getUserMessage(getLocale(req),
"CMS_PROFILE_P12EXPORT_PASSWORD_FEW_CHARS_CATEGORY", catCharSize));
}
}
}

param = getConfig(CONFIG_PASSWORD_SUBSTRING_MATCH);
if (!param.equals("")) {
int seqSize = Integer.parseInt(param);
for (int i = 0; i< password.length() - (seqSize * 2); i++) {
String seq = password.substring(i, i + seqSize);
String invSeq = new StringBuilder(seq).reverse().toString();
if(password.indexOf(seq, i + seqSize) > 0 || password.indexOf(invSeq, i + seqSize) > 0){
throw new ERejectException(CMS.getUserMessage(getLocale(req),
"CMS_PROFILE_P12EXPORT_PASSWORD_SEQUENCE"));
}
}
}

param = getConfig(CONFIG_PASSWORD_MAX_REPEATED_CHAR);
if (!param.equals("")) {
int maxRepeat = Integer.parseInt(param);
int mostRepeatedChar = password.chars().mapToObj(x -> (char) x)
.collect(Collectors.groupingBy(x -> x, Collectors.counting()))
.entrySet().stream().max(Map.Entry.comparingByValue())
.get().getValue().intValue();

if (maxRepeat < mostRepeatedChar) {
throw new ERejectException(CMS.getUserMessage(getLocale(req),
"CMS_PROFILE_P12EXPORT_PASSWORD_REPEATED_CHAR", param));
}
}

if (getConfigBoolean(CONFIG_PASSWORD_CRACKLIB_CHECK)) {
try {
Process crack = new ProcessBuilder("/usr/sbin/cracklib-check").start();
BufferedWriter crackIn = crack.outputWriter();
BufferedReader crackOut = crack.inputReader();
crackIn.write(password);
crackIn.close();

String crackResult = crackOut.readLine().substring(password.length() + 2);
if (!crackResult.equals("OK")) {
throw new ERejectException(CMS.getUserMessage(getLocale(req),
"CMS_PROFILE_P12EXPORT_PASSWORD_CRACKLIB_FAILS", crackResult));
}

} catch (IOException e) {
logger.error(method + "impossible check password with cracklib.", e);
}

}
}

@Override
public String getText(Locale locale) {
String[] params = {
getConfig(CONFIG_PASSWORD_MIN_SIZE, "-1"),
getConfig(CONFIG_PASSWORD_MIN_CHAR_CATEGORY, "-1"),
getConfig(CONFIG_PASSWORD_SUBSTRING_MATCH, "-1"),
getConfig(CONFIG_PASSWORD_MAX_REPEATED_CHAR, "-1"),
getConfig(CONFIG_PASSWORD_CRACKLIB_CHECK, "False")
};
return CMS.getUserMessage(locale, "CMS_PROFILE_CONSTRAINT_P12EXPORT_PASSWORD_TEXT", params);
}

@Override
public boolean isApplicable(PolicyDefault def) {
return (def instanceof NoDefault);
}
}
6 changes: 6 additions & 0 deletions base/server/src/main/resources/UserMessages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,12 @@ CMS_PROFILE_CONSTRAINT_VALIDITY_TEXT=This constraint rejects the validity that i
CMS_PROFILE_CONSTRAINT_RENEWAL_GRACE_PERIOD_TEXT=This constraint rejects the renewal requests that are outside of the grace period {0}
CMS_PROFILE_CONSTRAINT_VALIDITY_RENEWAL_TEXT=This constraint rejects the validity that is not between {0} days. If renewal, grace period is {1} days before and {2} days after the expiration date of the original certificate.
CMS_PROFILE_CONSTRAINT_REALM_TEXT=This constraint accepts only specified authorization realms.
CMS_PROFILE_CONSTRAINT_P12EXPORT_PASSWORD_TEXT=This constraint accept export password with minimum size {0}, minimum characters per category {1} (capital,lower,digit,others) excluding initial capital and final digits, no repeated substrings of size {2}, no more {3} repeated character and verified with cracklib {4}.
CMS_PROFILE_P12EXPORT_PASSWORD_TOO_SHORT=PKCS12 password too short. Minimum is {0}
CMS_PROFILE_P12EXPORT_PASSWORD_FEW_CHARS_CATEGORY=PKCS12 password not contain enough character variations. The minimum, beside the initial capitals and the final digits, are: {0} capital, {1} lower, {2} digit and {3} others.
CMS_PROFILE_P12EXPORT_PASSWORD_SEQUENCE=PKCS12 password cannot have repeated sequences (check also inverted).
CMS_PROFILE_P12EXPORT_PASSWORD_REPEATED_CHAR=PKCS12 password cannot contain the same character more then {0} times.
CMS_PROFILE_P12EXPORT_PASSWORD_CRACKLIB_FAILS=PKCS12 password did not pass cracklib test with the message: {0}.

CMS_PROFILE_DEF_SIA_TEXT=This default populates a Subject Info Access Extension (1.3.6.1.5.5.7.1.11) to the request. The default values are Criticality={0}, {1}
CMS_PROFILE_DEF_AIA_TEXT=This default populates a Authority Info Access Extension (1.3.6.1.5.5.7.1.1) to the request. The default values are Criticality={0}, {1}
Expand Down
10 changes: 10 additions & 0 deletions docs/changes/v11.6.0/Server-Changes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,13 @@ A new `pki_authdb_url` parameter has been added for `pkispawn` to replace the fo
* `pki_authdb_hostname`
* `pki_authdb_port`
* `pki_authdb_secure_conn`

== Implement new policy constraint for p12 password ==

A new policy constraint is defined to enforce the password quality: *p12ExportPasswordConstraintImpl*. The constraint allows to check:

* `password.minSize` - the minimum size for the password;
* `password.minCharCategory` - a csv of minimum characters in each category, they are _capital_, _lower_, _digit_ and _punctuation_;
* `password.substringMatch` - 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).

0 comments on commit 7cec1df

Please sign in to comment.