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.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.
  • Loading branch information
fmarco76 committed Feb 6, 2025
1 parent bc2aa36 commit c0d104b
Show file tree
Hide file tree
Showing 12 changed files with 470 additions and 46 deletions.
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
11 changes: 10 additions & 1 deletion base/ca/shared/profiles/ca/caServerKeygen_DirUserCert.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ input.i1.class_id=serverKeygenInputImpl
output.list=o1
output.o1.class_id=pkcs12OutputImpl
policyset.list=userCertSet
policyset.userCertSet.list=1,10,2,3,4,5,6,7,8,9
policyset.userCertSet.list=1,10,2,3,4,5,6,7,8,9,11
policyset.userCertSet.1.constraint.class_id=subjectNameConstraintImpl
policyset.userCertSet.1.constraint.name=Subject Name Constraint
policyset.userCertSet.1.constraint.params.pattern=UID=.*
Expand Down Expand Up @@ -100,3 +100,12 @@ policyset.userCertSet.9.constraint.params.signingAlgsAllowed=SHA256withRSA,SHA51
policyset.userCertSet.9.default.class_id=signingAlgDefaultImpl
policyset.userCertSet.9.default.name=Signing Alg
policyset.userCertSet.9.default.params.signingAlg=-
policyset.userCertSet.11.constraint.class_id=p12ExportPasswordConstraintImpl
policyset.userCertSet.11.constraint.name=PKCS12 Password Constraint
policyset.userCertSet.11.constraint.params.password.minSize=20
policyset.userCertSet.11.constraint.params.password.minCharCategory=2,2,2,2
policyset.userCertSet.11.constraint.params.password.substringMatch=6
policyset.userCertSet.11.constraint.params.password.maxRepeatedChar=3
policyset.userCertSet.11.constraint.params.password.cracklibCheck=false
policyset.userCertSet.11.default.class_id=noDefaultImpl
policyset.userCertSet.11.default.name=No Default
11 changes: 10 additions & 1 deletion base/ca/shared/profiles/ca/caServerKeygen_UserCert.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ input.i3.class_id=submitterInfoInputImpl
output.list=o1
output.o1.class_id=pkcs12OutputImpl
policyset.list=userCertSet
policyset.userCertSet.list=1,10,2,3,4,5,6,7,8,9
policyset.userCertSet.list=1,10,2,3,4,5,6,7,8,9,11
policyset.userCertSet.1.constraint.class_id=subjectNameConstraintImpl
policyset.userCertSet.1.constraint.name=Subject Name Constraint
policyset.userCertSet.1.constraint.params.pattern=UID=.*
Expand Down Expand Up @@ -102,3 +102,12 @@ policyset.userCertSet.9.constraint.params.signingAlgsAllowed=SHA256withRSA,SHA51
policyset.userCertSet.9.default.class_id=signingAlgDefaultImpl
policyset.userCertSet.9.default.name=Signing Alg
policyset.userCertSet.9.default.params.signingAlg=-
policyset.userCertSet.11.constraint.class_id=p12ExportPasswordConstraintImpl
policyset.userCertSet.11.constraint.name=PKCS12 Password Constraint
policyset.userCertSet.11.constraint.params.password.minSize=20
policyset.userCertSet.11.constraint.params.password.minCharCategory=2,2,2,2
policyset.userCertSet.11.constraint.params.password.substringMatch=6
policyset.userCertSet.11.constraint.params.password.maxRepeatedChar=3
policyset.userCertSet.11.constraint.params.password.cracklibCheck=false
policyset.userCertSet.11.default.class_id=noDefaultImpl
policyset.userCertSet.11.default.name=No Default
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ public void execute(Request request) throws EProfileException, ERejectException
logger.info("CAEnrollProfile: Processing server-side keygen enrollment");
request.setExtData(Request.SSK_STAGE, Request.SSK_STAGE_KEYGEN);

Map<String, byte[]> passwordsGenerated = convertP12Password(request);
transWrappedSessionKey = passwordsGenerated.get("serverSideKeygenP12PasswdTransSession");
Map<String, byte[]> p12PasswordInfo = processP12Password(request);
transWrappedSessionKey = p12PasswordInfo.get("serverSideKeygenP12PasswdTransSession");

sessionWrappedPassphrase = passwordsGenerated.get("serverSideKeygenP12PasswdEnc");
sessionWrappedPassphrase = p12PasswordInfo.get("serverSideKeygenP12PasswdEnc");


try {
Expand Down Expand Up @@ -532,8 +532,17 @@ public void execute(Request request) throws EProfileException, ERejectException
}
}

private Map<String, byte[]> convertP12Password (Request request) throws EProfileException {
String method = "CAEnrollProfile: convertP12Password: ";
/**
* Read the p12 password and generate symmetric keys
*
* The password is read from the request and removed after the keys are generated.
*
* @param request
* @return symmetric keys
* @throws EProfileException
*/
private Map<String, byte[]> processP12Password (Request request) throws EProfileException {
String method = "CAEnrollProfile: processP12Password: ";
Map<String, byte[]> returnPass = null;

String p12passwd = request.getExtDataInString("serverSideKeygenP12Passwd");
Expand All @@ -551,29 +560,15 @@ private Map<String, byte[]> convertP12Password (Request request) throws EProfile
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");
logger.error(method + "'KRA transport certificate' not found in nssdb; need to be manually setup for Server-Side keygen enrollment");
throw new EProfileException(CMS.getUserMessage("CMS_MISSING_KRA_TRANSPORT_CERT_IN_CA_NSSDB"));

/* 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");
*/
}

try
{
// todo: make things configurable in CS.cfg or profile
CryptoToken ct =
CryptoUtil.getCryptoToken(CryptoUtil.INTERNAL_TOKEN_NAME);
if (ct == null)
logger.debug(method + "crypto token null");

EncryptionAlgorithm encryptAlgorithm =
EncryptionAlgorithm.AES_128_CBC_PAD;
Expand All @@ -582,7 +577,7 @@ private Map<String, byte[]> convertP12Password (Request request) throws EProfile
boolean useOAEP = caCfg.getUseOAEPKeyWrap();

KeyWrapAlgorithm wrapAlgorithm = KeyWrapAlgorithm.RSA;
if(useOAEP == true) {
if(useOAEP) {
wrapAlgorithm = KeyWrapAlgorithm.RSA_OAEP;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// --- 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) 2025 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package com.netscape.cms.profile.constraint;

import java.util.Locale;

import org.dogtagpki.server.ca.CAEngine;
import org.mozilla.jss.netscape.security.x509.X509CertInfo;

import com.netscape.certsrv.password.EPasswordCheckException;
import com.netscape.certsrv.profile.ERejectException;
import com.netscape.certsrv.property.EPropertyException;
import com.netscape.certsrv.property.IDescriptor;
import com.netscape.cms.password.PasswordChecker;
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 implement a policy constraint for the pkcs12 export password
*
* The policy has several configurations, the are:
* - password.minSize
* - password.minUpperLetter
* - password.minLowerLetter
* - password.minNumber
* - password.minSpecialChar
* - password.substringMatch
* - password.maxRepeatedChar
* - password.cracklibCheck
*
* @author Marco Fargetta {@literal <mfargett@redhat.com>}
*/
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_UPPER_LETTER = "password.minUpperLetter";
public static final String CONFIG_PASSWORD_MIN_LOWER_LETTER = "password.minLowerLetter";
public static final String CONFIG_PASSWORD_MIN_NUMBER = "password.minNumber";
public static final String CONFIG_PASSWORD_MIN_SPECIAL_CHAR = "password.minSpecialChar";
public static final String CONFIG_PASSWORD_SEQUENCE_LENGTH = "password.seqLength";
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);
addConfigName(CONFIG_PASSWORD_MIN_UPPER_LETTER);
addConfigName(CONFIG_PASSWORD_MIN_LOWER_LETTER);
addConfigName(CONFIG_PASSWORD_MIN_NUMBER);
addConfigName(CONFIG_PASSWORD_MIN_SPECIAL_CHAR);
addConfigName(CONFIG_PASSWORD_SEQUENCE_LENGTH);
addConfigName(CONFIG_PASSWORD_MAX_REPEATED_CHAR);
addConfigName(CONFIG_PASSWORD_CRACKLIB_CHECK);

}

@Override
public void setConfig(String name, String value)
throws EPropertyException {
if (name.equals(CONFIG_PASSWORD_MIN_SIZE) ||
name.equals(CONFIG_PASSWORD_MIN_UPPER_LETTER) ||
name.equals(CONFIG_PASSWORD_MIN_LOWER_LETTER) ||
name.equals(CONFIG_PASSWORD_MIN_NUMBER) ||
name.equals(CONFIG_PASSWORD_MIN_SPECIAL_CHAR) ||
name.equals(CONFIG_PASSWORD_SEQUENCE_LENGTH) ||
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");
PasswordChecker pCheck = getChecker();

try {
if (!pCheck.isGoodPassword(password)) {
throw new ERejectException(pCheck.getReason(getLocale(req)));
}
} catch (EPasswordCheckException e) {
logger.error("{password rejected because }", method, e.getMessage());
throw new ERejectException(CMS.getUserMessage(getLocale(req),
"CMS_PROFILE_P12EXPORT_PASSWORD_ERROR", e.getMessage()));
}
}

@Override
public String getText(Locale locale) {
PasswordChecker pCheck = getChecker();
String[] params = {
Integer.toString(pCheck.getMinSize()),
Integer.toString(pCheck.getMinUpperLetter()),
Integer.toString(pCheck.getMinLowerLetter()),
Integer.toString(pCheck.getMinNumber()),
Integer.toString(pCheck.getMinPunctuationChar()),
Integer.toString(pCheck.getSeqLength()),
Integer.toString(pCheck.getMaxRepeatedChar()),
Boolean.toString(pCheck.isCracklibCheck())
};
return CMS.getUserMessage(locale, "CMS_PROFILE_CONSTRAINT_P12EXPORT_PASSWORD_TEXT", params);
}

@Override
public boolean isApplicable(PolicyDefault def) {
return (def instanceof NoDefault);
}

private PasswordChecker getChecker() {
CAEngine engine = CAEngine.getInstance();
PasswordChecker pCheck = engine.getPasswordChecker();

if (!getConfig(CONFIG_PASSWORD_MIN_SIZE).isEmpty()) {
pCheck.setMinSize(Integer.parseInt(getConfig(CONFIG_PASSWORD_MIN_SIZE)));
}
if (!getConfig(CONFIG_PASSWORD_MIN_UPPER_LETTER).isEmpty()) {
pCheck.setMinUpperLetter(Integer.parseInt(getConfig(CONFIG_PASSWORD_MIN_UPPER_LETTER)));
}
if (!getConfig(CONFIG_PASSWORD_MIN_LOWER_LETTER).isEmpty()) {
pCheck.setMinLowerLetter(Integer.parseInt(getConfig(CONFIG_PASSWORD_MIN_LOWER_LETTER)));
}
if (!getConfig(CONFIG_PASSWORD_MIN_NUMBER).isEmpty()) {
pCheck.setMinNumber(Integer.parseInt(getConfig(CONFIG_PASSWORD_MIN_NUMBER)));
}
if (!getConfig(CONFIG_PASSWORD_MIN_SPECIAL_CHAR).isEmpty()) {
pCheck.setMinPunctuationChar(Integer.parseInt(getConfig(CONFIG_PASSWORD_MIN_SPECIAL_CHAR)));
}
if (!getConfig(CONFIG_PASSWORD_SEQUENCE_LENGTH).isEmpty()) {
pCheck.setSeqLength(Integer.parseInt(getConfig(CONFIG_PASSWORD_SEQUENCE_LENGTH)));
}
if (!getConfig(CONFIG_PASSWORD_MAX_REPEATED_CHAR).isEmpty()) {
pCheck.setMaxRepeatedChar(Integer.parseInt(getConfig(CONFIG_PASSWORD_MAX_REPEATED_CHAR)));
}
if (!getConfig(CONFIG_PASSWORD_MAX_REPEATED_CHAR).isEmpty()) {
pCheck.setCracklibCheck(getConfigBoolean(CONFIG_PASSWORD_CRACKLIB_CHECK));
}

return pCheck;
}
}
Loading

0 comments on commit c0d104b

Please sign in to comment.