// --- 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.policy.extensions;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.Hashtable;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.Vector;
import netscape.security.util.BitArray;
import netscape.security.x509.CRLDistributionPoint;
import netscape.security.x509.CRLDistributionPointsExtension;
import netscape.security.x509.CRLDistributionPointsExtension.Reason;
import netscape.security.x509.CertificateExtensions;
import netscape.security.x509.CertificateVersion;
import netscape.security.x509.GeneralName;
import netscape.security.x509.GeneralNames;
import netscape.security.x509.GeneralNamesException;
import netscape.security.x509.RDN;
import netscape.security.x509.URIName;
import netscape.security.x509.X500Name;
import netscape.security.x509.X509CertInfo;
import com.netscape.certsrv.apps.CMS;
import com.netscape.certsrv.base.EBaseException;
import com.netscape.certsrv.base.IConfigStore;
import com.netscape.certsrv.base.IExtendedPluginInfo;
import com.netscape.certsrv.base.ISubsystem;
import com.netscape.certsrv.logging.ILogger;
import com.netscape.certsrv.policy.IEnrollmentPolicy;
import com.netscape.certsrv.request.IRequest;
import com.netscape.certsrv.request.PolicyResult;
import com.netscape.cms.policy.APolicyRule;
/**
* The type of the distribution point or issuer name. The name is expressed
* as a simple string in the configuration file, so this attribute is needed
* to tell whether the simple string should be stored in an X.500 Name,
* a URL, or an RDN.
*
*
*
* NOTE: The Policy Framework has been replaced by the Profile Framework.
*
*
*
* @deprecated
* @version $Revision$, $Date$
*/
class NameType {
private NameType() {
} // no default constructor
private String stringRep; // string representation of this type
private NameType(String s) {
map.put(s, this);
stringRep = s;
}
private static Hashtable map = new Hashtable();
/**
* Looks up a NameType from its string representation. Returns null
* if no matching NameType was found.
*/
public static NameType fromString(String s) {
return map.get(s);
}
public String toString() {
return stringRep;
}
public static final NameType DIRECTORY_NAME = new NameType("DirectoryName");
public static final NameType URI = new NameType("URI");
public static final NameType RELATIVE_TO_ISSUER =
new NameType("RelativeToIssuer");
}
/**
* These are the parameters that may be given in the configuration file
* for each distribution point. They are parsed by DPParamsToDP().
* Any of them may be null.
*/
class DistPointParams {
public String pointName;
public String pointType;
public String reasons;
public String issuerName;
public String issuerType;
public DistPointParams() {
}
public DistPointParams(DistPointParams old) {
pointName = old.pointName;
pointType = old.pointType;
reasons = old.reasons;
issuerName = old.issuerName;
issuerType = old.issuerType;
}
}
/**
* CRL Distribution Points policy.
* Adds the CRL Distribution Points extension to the certificate.
*/
public class CRLDistributionPointsExt extends APolicyRule
implements IEnrollmentPolicy, IExtendedPluginInfo {
public static final String PROP_IS_CRITICAL = "critical";
public static final String PROP_NUM_POINTS = "numPoints";
public static final String PROP_POINT_TYPE = "pointType";
public static final String PROP_POINT_NAME = "pointName";
public static final String PROP_REASONS = "reasons";
public static final String PROP_ISSUER_NAME = "issuerName";
public static final String PROP_ISSUER_TYPE = "issuerType";
private static final int MAX_POINTS = 10;
private static final int DEFAULT_NUM_BLANK_POINTS = 3;
private int mNumPoints = DEFAULT_NUM_BLANK_POINTS;
// PKIX specifies the that the extension SHOULD NOT be critical
public static final boolean DEFAULT_CRITICALITY = false;
private Vector defaultParams = new Vector();
private Vector mParams = new Vector();
private String mExtParams[] = null;
private CRLDistributionPointsExtension mCrldpExt = null;
public CRLDistributionPointsExt() {
NAME = "CRLDistributionPointsExt";
DESC = "Sets CRL distribution points extension";
defaultParams.addElement(PROP_IS_CRITICAL + "=" + DEFAULT_CRITICALITY);
defaultParams.addElement(PROP_NUM_POINTS + "=0");
for (int i = 0; i < DEFAULT_NUM_BLANK_POINTS; i++) {
defaultParams.addElement(PROP_POINT_NAME + i + "=");
defaultParams.addElement(PROP_POINT_TYPE + i + "=");
defaultParams.addElement(PROP_REASONS + i + "=");
defaultParams.addElement(PROP_ISSUER_NAME + i + "=");
defaultParams.addElement(PROP_ISSUER_TYPE + i + "=");
}
}
private void setExtendedPluginInfo() {
Vector v = new Vector();
// should replace MAX_POINTS with mNumPoints if bug 385118 is fixed
for (int i = 0; i < MAX_POINTS; i++) {
v.addElement(PROP_POINT_TYPE + Integer.toString(i) + ";choice(" +
"DirectoryName,URI,RelativeToIssuer);" +
"The type of the CRL distribution point.");
v.addElement(PROP_POINT_NAME + Integer.toString(i) + ";string;" +
"The name of the CRL distribution point depending on the CRLDP type.");
v.addElement(PROP_REASONS
+ Integer.toString(i)
+ ";string;"
+
"The revocation reasons for the CRL maintained at this distribution point. It's a comma-seperated list of the following constants: unused, keyCompromise, cACompromise, affiliationChanged, superseded, cessationOfOperation, certificateHold.");
v.addElement(PROP_ISSUER_TYPE + Integer.toString(i) + ";choice(" +
"DirectoryName,URI);" +
"The type of the issuer that has signed the CRL maintained at this distribution point.");
v.addElement(PROP_ISSUER_NAME
+ Integer.toString(i)
+ ";string;"
+
"The name of the issuer that has signed the CRL maintained at this distribution point. The value depends on the issuer type.");
}
v.addElement(PROP_NUM_POINTS +
";number;The total number of CRL distribution points to be contained or allowed in the extension.");
v.addElement(PROP_IS_CRITICAL
+
";boolean;RFC 2459 recommendation: SHOULD be non-critical. But recommends support for this extension by CAs and applications.");
v.addElement(IExtendedPluginInfo.HELP_TOKEN +
";configuration-policyrules-crldistributionpoints");
v.addElement(IExtendedPluginInfo.HELP_TEXT +
";This policy inserts the CRL Distribution Points " +
"Extension into the certificate. See RFC 2459 (4.2.1.14). "
);
mExtParams = com.netscape.cmsutil.util.Utils.getStringArrayFromVector(v);
}
public String[] getExtendedPluginInfo(Locale locale) {
if (mExtParams == null) {
setExtendedPluginInfo();
}
return mExtParams;
}
/**
* Performs one-time initialization of the policy.
*/
public void init(ISubsystem owner, IConfigStore config)
throws EBaseException {
// Register the CRL Distribution Points extension.
try {
netscape.security.x509.OIDMap.addAttribute(
CRLDistributionPointsExtension.class.getName(),
CRLDistributionPointsExtension.OID,
CRLDistributionPointsExtension.NAME);
} catch (CertificateException e) {
// ignore, just means it has already been added
}
// assemble the list of Distribution Points from the config file
int numPoints = config.getInteger(PROP_NUM_POINTS, 0);
mParams.addElement(PROP_NUM_POINTS + "=" + numPoints);
mNumPoints = numPoints;
for (int i = 0; i < numPoints; i++) {
// construct a distribution point from the parameters
DistPointParams params = new DistPointParams();
params.pointType = config.getString(PROP_POINT_TYPE + i, "");
params.pointName = config.getString(PROP_POINT_NAME + i, "");
params.reasons = config.getString(PROP_REASONS + i, "");
params.issuerType = config.getString(PROP_ISSUER_TYPE + i, "");
params.issuerName = config.getString(PROP_ISSUER_NAME + i, "");
DistPointParams configparams = new DistPointParams(params);
CRLDistributionPoint crldp = DPParamsToDP(params);
mParams.addElement(PROP_POINT_TYPE + i + "=" + configparams.pointType);
mParams.addElement(PROP_POINT_NAME + i + "=" + configparams.pointName);
mParams.addElement(PROP_REASONS + i + "=" + configparams.reasons);
mParams.addElement(PROP_ISSUER_TYPE + i + "=" + configparams.issuerType);
mParams.addElement(PROP_ISSUER_NAME + i + "=" + configparams.issuerName);
// add the distribution point to the extension
if (mCrldpExt == null) {
mCrldpExt = new CRLDistributionPointsExtension(crldp);
} else {
mCrldpExt.addPoint(crldp);
}
}
boolean crit = config.getBoolean(PROP_IS_CRITICAL,
DEFAULT_CRITICALITY);
mParams.addElement(PROP_IS_CRITICAL + "=" + crit);
if (mCrldpExt != null) {
// configure the extension itself
mCrldpExt.setCritical(crit);
}
setExtendedPluginInfo();
}
/**
* Parses the parameters in the config file to create an
* actual CRL Distribution Point object.
*/
private CRLDistributionPoint DPParamsToDP(DistPointParams params)
throws EBaseException {
CRLDistributionPoint crlDP = new CRLDistributionPoint();
try {
if (params.pointName != null && params.pointName.length() == 0) {
params.pointName = null;
}
if (params.pointType != null && params.pointType.length() == 0) {
params.pointType = null;
}
if (params.reasons != null && params.reasons.length() == 0) {
params.reasons = null;
}
if (params.issuerName != null && params.issuerName.length() == 0) {
params.issuerName = null;
}
if (params.issuerType != null && params.issuerType.length() == 0) {
params.issuerType = null;
}
// deal with the distribution point name
if (params.pointName != null && params.pointType != null) {
// decode the type of the name
NameType nType = NameType.fromString(params.pointType);
if (nType == null) {
String err = "Unknown name type: " + params.pointType;
log(ILogger.LL_FAILURE, CMS.getLogMessage("CA_UNKNOWN_NAME_TYPE", params.pointType));
throw new EBaseException(err);
}
if (nType == NameType.DIRECTORY_NAME) {
GeneralNames gen = new GeneralNames();
gen.addElement(new GeneralName(new X500Name(params.pointName)));
crlDP.setFullName(gen);
} else if (nType == NameType.URI) {
GeneralNames gen = new GeneralNames();
gen.addElement(new GeneralName(new URIName(params.pointName)));
crlDP.setFullName(gen);
} else if (nType == NameType.RELATIVE_TO_ISSUER) {
crlDP.setRelativeName(new RDN(params.pointName));
} else {
String err = "Unknown name type: " + nType.toString();
log(ILogger.LL_FAILURE, CMS.getLogMessage("CA_UNKNOWN_NAME_TYPE", nType.toString()));
throw new EBaseException(err);
}
}
// deal with the reasons
if (params.reasons != null) {
StringTokenizer tok = new StringTokenizer(params.reasons, ", \t");
byte reasonBits = 0;
while (tok.hasMoreTokens()) {
String s = tok.nextToken();
Reason r = Reason.fromString(s);
if (r == null) {
log(ILogger.LL_FAILURE, CMS.getLogMessage("CA_UNKNOWN_REASON", s));
throw new EBaseException("Unknown reason: " + s);
} else {
reasonBits |= r.getBitMask();
}
}
if (reasonBits != 0) {
BitArray ba = new BitArray(8, new byte[] { reasonBits }
);
crlDP.setReasons(ba);
}
}
// deal with the issuer name
if (params.issuerName != null && params.issuerType != null) {
// decode the type of the name
NameType nType = NameType.fromString(params.issuerType);
if (nType == null) {
String err = "Unknown name type: " + params.issuerType;
log(ILogger.LL_FAILURE, CMS.getLogMessage("CA_UNKNOWN_NAME_TYPE", params.issuerType));
throw new EBaseException(err);
}
if (nType == NameType.DIRECTORY_NAME) {
GeneralNames gen = new GeneralNames();
gen.addElement(new GeneralName(new X500Name(params.issuerName)));
crlDP.setCRLIssuer(gen);
} else if (nType == NameType.URI) {
GeneralNames gen = new GeneralNames();
gen.addElement(new GeneralName(new URIName(params.issuerName)));
crlDP.setCRLIssuer(gen);
} else {
String err = "Unknown name type: " + nType.toString();
log(ILogger.LL_FAILURE, CMS.getLogMessage("CA_UNKNOWN_NAME_TYPE", nType.toString()));
throw new EBaseException(err);
}
}
} catch (GeneralNamesException e) {
throw new EBaseException(e.getMessage());
} catch (IOException e) {
throw new EBaseException(e.getMessage());
}
// done, return this distribution point
return crlDP;
}
/**
* Applies the policy to the given request.
*/
public PolicyResult apply(IRequest req) {
// if the extension was not configured correctly, just skip it
if (mCrldpExt == null) {
return PolicyResult.ACCEPTED;
}
X509CertInfo[] ci = req.getExtDataInCertInfoArray(IRequest.CERT_INFO);
if (ci == null || ci[0] == null) {
setError(req, CMS.getUserMessage("CMS_POLICY_NO_CERT_INFO"), NAME);
return PolicyResult.REJECTED;
}
for (int i = 0; i < ci.length; i++) {
PolicyResult certRes = applyCert(req, ci[i]);
if (certRes == PolicyResult.REJECTED)
return certRes;
}
return PolicyResult.ACCEPTED;
}
public PolicyResult applyCert(IRequest req, X509CertInfo certInfo) {
try {
// find the extensions in the certInfo
CertificateExtensions extensions = (CertificateExtensions)
certInfo.get(X509CertInfo.EXTENSIONS);
// prepare the extensions data structure
if (extensions == null) {
certInfo.set(X509CertInfo.VERSION,
new CertificateVersion(CertificateVersion.V3));
extensions = new CertificateExtensions();
certInfo.set(X509CertInfo.VERSION,
new CertificateVersion(CertificateVersion.V3));
certInfo.set(X509CertInfo.EXTENSIONS, extensions);
} else {
// remove any previously computed version of the extension
try {
extensions.delete(CRLDistributionPointsExtension.NAME);
} catch (IOException e) {
// extension isn't there
}
}
extensions.set(CRLDistributionPointsExtension.NAME, mCrldpExt);
return PolicyResult.ACCEPTED;
} catch (IOException e) {
log(ILogger.LL_FAILURE, CMS.getLogMessage("POLICY_UNEXPECTED_POLICY_ERROR", NAME, e.getMessage()));
setError(req, CMS.getUserMessage("CMS_POLICY_UNEXPECTED_POLICY_ERROR"), NAME,
e.getMessage());
return PolicyResult.REJECTED;
} catch (CertificateException e) {
log(ILogger.LL_FAILURE, CMS.getLogMessage("CA_CERT_INFO_ERROR",
e.getMessage()));
setError(req, CMS.getUserMessage("CMS_POLICY_UNEXPECTED_POLICY_ERROR"), NAME,
e.getMessage());
return PolicyResult.REJECTED;
}
}
// parameters must be entered in the config file
public Vector getDefaultParams() {
for (int i = DEFAULT_NUM_BLANK_POINTS; i < mNumPoints; i++) {
defaultParams.addElement(PROP_POINT_NAME + i + "=");
defaultParams.addElement(PROP_POINT_TYPE + i + "=");
defaultParams.addElement(PROP_REASONS + i + "=");
defaultParams.addElement(PROP_ISSUER_NAME + i + "=");
defaultParams.addElement(PROP_ISSUER_TYPE + i + "=");
}
return defaultParams;
}
/**
* Return configured parameters for a policy rule instance.
*
* @return nvPairs A Vector of name/value pairs.
*/
public Vector getInstanceParams() {
return mParams;
}
}