// --- 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 statement // /////////////////////// package com.netscape.cms.authentication; /////////////////////// // import statements // /////////////////////// /* cert server imports */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; import java.security.MessageDigest; import java.security.PublicKey; import java.util.Enumeration; import java.util.Hashtable; import java.util.Locale; import java.util.Vector; import netscape.security.pkcs.PKCS10; import netscape.security.x509.X500Name; import netscape.security.x509.X509CertImpl; import netscape.security.x509.X509CertInfo; import netscape.security.x509.X509Key; import org.mozilla.jss.CryptoManager; import org.mozilla.jss.asn1.ASN1Util; import org.mozilla.jss.asn1.INTEGER; import org.mozilla.jss.asn1.InvalidBERException; import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; import org.mozilla.jss.asn1.OCTET_STRING; import org.mozilla.jss.asn1.SEQUENCE; import org.mozilla.jss.asn1.SET; import org.mozilla.jss.crypto.DigestAlgorithm; import org.mozilla.jss.crypto.PrivateKey; import org.mozilla.jss.pkcs10.CertificationRequest; import org.mozilla.jss.pkcs11.PK11PubKey; import org.mozilla.jss.pkix.cert.Certificate; import org.mozilla.jss.pkix.cert.CertificateInfo; import org.mozilla.jss.pkix.cmc.PKIData; import org.mozilla.jss.pkix.cmc.TaggedAttribute; import org.mozilla.jss.pkix.cmc.TaggedCertificationRequest; import org.mozilla.jss.pkix.cmc.TaggedRequest; import org.mozilla.jss.pkix.cms.EncapsulatedContentInfo; import org.mozilla.jss.pkix.cms.IssuerAndSerialNumber; import org.mozilla.jss.pkix.cms.SignedData; import org.mozilla.jss.pkix.cms.SignerIdentifier; import org.mozilla.jss.pkix.crmf.CertReqMsg; import org.mozilla.jss.pkix.crmf.CertRequest; import org.mozilla.jss.pkix.crmf.CertTemplate; import org.mozilla.jss.pkix.primitive.AlgorithmIdentifier; import org.mozilla.jss.pkix.primitive.Name; import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authentication.AuthToken; import com.netscape.certsrv.authentication.EInvalidCredentials; import com.netscape.certsrv.authentication.EMissingCredential; import com.netscape.certsrv.authentication.IAuthCredentials; import com.netscape.certsrv.authentication.IAuthManager; import com.netscape.certsrv.authentication.IAuthSubsystem; import com.netscape.certsrv.authentication.IAuthToken; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.IExtendedPluginInfo; import com.netscape.certsrv.base.SessionContext; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.profile.EProfileException; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.property.Descriptor; import com.netscape.certsrv.property.IDescriptor; import com.netscape.certsrv.request.IRequest; import com.netscape.cmsutil.util.Utils; //import com.netscape.cmscore.util.*; ////////////////////// // class definition // ////////////////////// /** * UID/CMC authentication plug-in *
* * @version $Revision$, $Date$ */ public class CMCAuth implements IAuthManager, IExtendedPluginInfo, IProfileAuthenticator { // ////////////////////// // default parameters // // ////////////////////// // /////////////////////////// // IAuthManager parameters // // /////////////////////////// /* authentication plug-in configuration store */ private IConfigStore mConfig; private static final String HEADER = "-----BEGIN NEW CERTIFICATE REQUEST-----"; private static final String TRAILER = "-----END NEW CERTIFICATE REQUEST-----"; public static final String TOKEN_CERT_SERIAL = "certSerialToRevoke"; public static final String REASON_CODE = "reasonCode"; /* authentication plug-in name */ private String mImplName = null; /* authentication plug-in instance name */ private String mName = null; /* authentication plug-in fields */ /* * Holds authentication plug-in fields accepted by this implementation. This * list is passed to the configuration console so configuration for * instances of this implementation can be configured through the console. */ protected static String[] mConfigParams = new String[] {}; /* authentication plug-in values */ /* authentication plug-in properties */ /* required credentials to authenticate. UID and CMC are strings. */ public static final String CRED_CMC = "cmcRequest"; protected static String[] mRequiredCreds = {}; // ////////////////////////////////// // IExtendedPluginInfo parameters // // ////////////////////////////////// /* Vector of extendedPluginInfo strings */ protected static Vector mExtendedPluginInfo = null; // public static final String AGENT_AUTHMGR_ID = "agentAuthMgr"; // public static final String AGENT_PLUGIN_ID = "agentAuthPlugin"; /* actual help messages */ static { mExtendedPluginInfo = new Vector(); mExtendedPluginInfo .add(IExtendedPluginInfo.HELP_TEXT + ";Authenticate the CMC request. The signer must be an agent. The \"Authentication Instance ID\" must be named \"CMCAuth\""); mExtendedPluginInfo.add(IExtendedPluginInfo.HELP_TOKEN + ";configuration-authentication"); } // ///////////////////// // Logger parameters // // ///////////////////// /* the system's logger */ private ILogger mLogger = CMS.getLogger(); /* signed audit parameters */ private ILogger mSignedAuditLogger = CMS.getSignedAuditLogger(); private final static String SIGNED_AUDIT_ENROLLMENT_REQUEST_TYPE = "enrollment"; private final static String SIGNED_AUDIT_REVOCATION_REQUEST_TYPE = "revocation"; private final static String LOGGING_SIGNED_AUDIT_CMC_SIGNED_REQUEST_SIG_VERIFY = "LOGGING_SIGNED_AUDIT_CMC_SIGNED_REQUEST_SIG_VERIFY_5"; // /////////////////// // default methods // // /////////////////// /** * Default constructor, initialization must follow. */ public CMCAuth() { } // //////////////////////// // IAuthManager methods // // //////////////////////// /** * Initializes the CMCAuth authentication plug-in. *
* * @param name The name for this authentication plug-in instance. * @param implName The name of the authentication plug-in. * @param config - The configuration store for this instance. * @exception EBaseException If an error occurs during initialization. */ public void init(String name, String implName, IConfigStore config) throws EBaseException { mName = name; mImplName = implName; mConfig = config; log(ILogger.LL_INFO, "Initialization complete!"); } /** * Authenticates user by their CMC; resulting AuthToken sets a TOKEN_SUBJECT * for the subject name. *
* *
* * @return String array of configuration parameter names. */ public String[] getConfigParams() { return (mConfigParams); } /** * gets the configuration substore used by this authentication plug-in *
* * @return configuration store */ public IConfigStore getConfigStore() { return mConfig; } /** * gets the plug-in name of this authentication plug-in. */ public String getImplName() { return mImplName; } /** * gets the name of this authentication plug-in instance */ public String getName() { return mName; } /** * get the list of required credentials. *
* * @return list of required credentials as strings. */ public String[] getRequiredCreds() { return (mRequiredCreds); } /** * prepares for shutdown. */ public void shutdown() { } // /////////////////////////////// // IExtendedPluginInfo methods // // /////////////////////////////// /** * Activate the help system. *
* * @return help messages */ public String[] getExtendedPluginInfo() { CMS.debug("CMCAuth: getExtendedPluginInfo()"); String[] s = Utils.getStringArrayFromVector(mExtendedPluginInfo); CMS.debug("CMCAuth: s.length = " + s.length); for (int i = 0; i < s.length; i++) { CMS.debug("" + i + " " + s[i]); } return s; } // ////////////////// // Logger methods // // ////////////////// /** * Logs a message for this class in the system log file. *
* * @param level The log level. * @param msg The message to log. * @see com.netscape.certsrv.logging.ILogger */ protected void log(int level, String msg) { if (mLogger == null) return; mLogger.log(ILogger.EV_SYSTEM, null, ILogger.S_AUTHENTICATION, level, "CMC Authentication: " + msg); } protected IAuthToken verifySignerInfo(AuthToken authToken, SignedData cmcFullReq) throws EInvalidCredentials { EncapsulatedContentInfo ci = cmcFullReq.getContentInfo(); OBJECT_IDENTIFIER id = ci.getContentType(); OCTET_STRING content = ci.getContent(); try { ByteArrayInputStream s = new ByteArrayInputStream( content.toByteArray()); PKIData pkiData = (PKIData) (new PKIData.Template()).decode(s); SET dais = cmcFullReq.getDigestAlgorithmIdentifiers(); int numDig = dais.size(); Hashtable digs = new Hashtable(); // if request key is used for signing, there MUST be only one // signerInfo // object in the signedData object. for (int i = 0; i < numDig; i++) { AlgorithmIdentifier dai = (AlgorithmIdentifier) dais .elementAt(i); String name = DigestAlgorithm.fromOID(dai.getOID()).toString(); MessageDigest md = MessageDigest.getInstance(name); byte[] digest = md.digest(content.toByteArray()); digs.put(name, digest); } SET sis = cmcFullReq.getSignerInfos(); int numSis = sis.size(); for (int i = 0; i < numSis; i++) { org.mozilla.jss.pkix.cms.SignerInfo si = (org.mozilla.jss.pkix.cms.SignerInfo) sis .elementAt(i); String name = si.getDigestAlgorithm().toString(); byte[] digest = (byte[]) digs.get(name); if (digest == null) { MessageDigest md = MessageDigest.getInstance(name); ByteArrayOutputStream ostream = new ByteArrayOutputStream(); pkiData.encode((OutputStream) ostream); digest = md.digest(ostream.toByteArray()); } // signed by previously certified signature key SignerIdentifier sid = si.getSignerIdentifier(); if (sid.getType().equals( SignerIdentifier.ISSUER_AND_SERIALNUMBER)) { IssuerAndSerialNumber issuerAndSerialNumber = sid .getIssuerAndSerialNumber(); // find from the certs in the signedData java.security.cert.X509Certificate cert = null; if (cmcFullReq.hasCertificates()) { SET certs = cmcFullReq.getCertificates(); int numCerts = certs.size(); java.security.cert.X509Certificate[] x509Certs = new java.security.cert.X509Certificate[1]; byte[] certByteArray = new byte[0]; for (int j = 0; j < numCerts; j++) { Certificate certJss = (Certificate) certs .elementAt(j); CertificateInfo certI = certJss.getInfo(); Name issuer = certI.getIssuer(); byte[] issuerB = ASN1Util.encode(issuer); INTEGER sn = certI.getSerialNumber(); // if this cert is the signer cert, not a cert in // the chain if (new String(issuerB) .equals(new String(ASN1Util .encode(issuerAndSerialNumber .getIssuer()))) && sn.toString().equals( issuerAndSerialNumber .getSerialNumber() .toString())) { ByteArrayOutputStream os = new ByteArrayOutputStream(); certJss.encode(os); certByteArray = os.toByteArray(); X509CertImpl tempcert = new X509CertImpl( os.toByteArray()); cert = tempcert; x509Certs[0] = cert; // xxx validate the cert length } } CMS.debug("CMCAuth: start checking signature"); if (cert == null) { // find from certDB CMS.debug("CMCAuth: verifying signature"); si.verify(digest, id); } else { PublicKey signKey = cert.getPublicKey(); PrivateKey.Type keyType = null; String alg = signKey.getAlgorithm(); if (alg.equals("RSA")) { keyType = PrivateKey.RSA; } else if (alg.equals("DSA")) { keyType = PrivateKey.DSA; } PK11PubKey pubK = PK11PubKey.fromRaw(keyType, ((X509Key) signKey).getKey()); CMS.debug("CMCAuth: verifying signature with public key"); si.verify(digest, id, pubK); } CMS.debug("CMCAuth: finished checking signature"); // verify signer's certificate using the revocator CryptoManager cm = CryptoManager.getInstance(); if (!cm.isCertValid(certByteArray, true, CryptoManager.CertUsage.SSLClient)) throw new EInvalidCredentials( CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); // authenticate signer's certificate using the userdb IAuthSubsystem authSS = (IAuthSubsystem) CMS .getSubsystem(CMS.SUBSYSTEM_AUTH); IAuthManager agentAuth = authSS .getAuthManager(IAuthSubsystem.CERTUSERDB_AUTHMGR_ID);// AGENT_AUTHMGR_ID); IAuthCredentials agentCred = new com.netscape.certsrv.authentication.AuthCredentials(); agentCred.set(IAuthManager.CRED_SSL_CLIENT_CERT, x509Certs); IAuthToken tempToken = agentAuth .authenticate(agentCred); netscape.security.x509.X500Name tempPrincipal = (X500Name) x509Certs[0] .getSubjectDN(); String CN = (String) tempPrincipal.getCommonName();// tempToken.get("userid"); BigInteger agentCertSerial = x509Certs[0] .getSerialNumber(); authToken.set(IAuthManager.CRED_SSL_CLIENT_CERT, agentCertSerial.toString()); tempToken.set("cn", CN); return tempToken; } // find from internaldb if it's ca. (ra does not have that.) // find from internaldb usrgrp info // find from certDB si.verify(digest, id); } // } } catch (InvalidBERException e) { CMS.debug("CMCAuth: " + e.toString()); } catch (IOException e) { CMS.debug("CMCAuth: " + e.toString()); } catch (Exception e) { throw new EInvalidCredentials( CMS.getUserMessage("CMS_AUTHENTICATION_INVALID_CREDENTIAL")); } return (IAuthToken) null; } public String[] getExtendedPluginInfo(Locale locale) { return null; } // Profile-related methods public void init(IProfile profile, IConfigStore config) throws EProfileException { } /** * Retrieves the localizable name of this policy. */ public String getName(Locale locale) { return CMS.getUserMessage(locale, "CMS_AUTHENTICATION_CMS_SIGN_NAME"); } /** * Retrieves the localizable description of this policy. */ public String getText(Locale locale) { return CMS.getUserMessage(locale, "CMS_AUTHENTICATION_CMS_SIGN_TEXT"); } /** * Retrieves a list of names of the value parameter. */ public Enumeration getValueNames() { Vector v = new Vector(); v.addElement("cert_request"); return v.elements(); } public boolean isValueWriteable(String name) { return false; } /** * Retrieves the descriptor of the given value parameter by name. */ public IDescriptor getValueDescriptor(Locale locale, String name) { if (name.equals(CRED_CMC)) { return new Descriptor(IDescriptor.STRING_LIST, null, null, "CMC request"); } return null; } public void populate(IAuthToken token, IRequest request) throws EProfileException { request.setExtData(IProfileAuthenticator.AUTHENTICATED_NAME, token.getInString(AuthToken.TOKEN_CERT_SUBJECT)); } public boolean isSSLClientRequired() { return false; } /** * Signed Audit Log * * This method is called to store messages to the signed audit log. *
* * @param msg signed audit log message */ private void audit(String msg) { // in this case, do NOT strip preceding/trailing whitespace // from passed-in String parameters if (mSignedAuditLogger == null) { return; } mSignedAuditLogger.log(ILogger.EV_SIGNED_AUDIT, null, ILogger.S_SIGNED_AUDIT, ILogger.LL_SECURITY, msg); } /** * Signed Audit Log Subject ID * * This method is called to obtain the "SubjectID" for a signed audit log * message. *
* * @return id string containing the signed audit log message SubjectID */ private String auditSubjectID() { // if no signed audit object exists, bail if (mSignedAuditLogger == null) { return null; } String subjectID = null; // Initialize subjectID SessionContext auditContext = SessionContext.getExistingContext(); if (auditContext != null) { subjectID = (String) auditContext.get(SessionContext.USER_ID); if (subjectID != null) { subjectID = subjectID.trim(); } else { subjectID = ILogger.NONROLEUSER; } } else { subjectID = ILogger.UNIDENTIFIED; } return subjectID; } }