From b4d1dd0ac041eab66028661d6c6ab5117ca3980c Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 28 Jan 2015 02:41:10 -0500 Subject: [PATCH] Add lightweight sub-CA support --- base/ca/shared/conf/db.ldif | 5 + base/ca/src/com/netscape/ca/CAService.java | 53 ++- .../src/com/netscape/ca/CertificateAuthority.java | 443 ++++++++++++++++++--- base/ca/src/com/netscape/ca/SigningUnit.java | 22 +- .../dogtagpki/server/ca/rest/AuthorityService.java | 183 +++++++++ .../dogtagpki/server/ca/rest/CAApplication.java | 3 + .../netscape/certsrv/authority/AuthorityData.java | 109 +++++ .../certsrv/authority/AuthorityResource.java | 36 ++ .../src/com/netscape/certsrv/ca/AuthorityID.java | 36 ++ .../netscape/certsrv/ca/CANotFoundException.java | 12 + .../src/com/netscape/certsrv/ca/ICAService.java | 11 +- .../netscape/certsrv/ca/ICertificateAuthority.java | 44 ++ .../certsrv/ca/IssuerUnavailableException.java | 13 + .../netscape/certsrv/profile/IEnrollProfile.java | 5 + .../netscape/certsrv/security/ISigningUnit.java | 8 + .../cms/profile/common/CAEnrollProfile.java | 11 +- .../netscape/cms/profile/common/EnrollProfile.java | 3 + .../def/AuthorityKeyIdentifierExtDefault.java | 19 +- .../netscape/cms/profile/def/CAEnrollDefault.java | 4 +- .../cms/servlet/cert/EnrollmentProcessor.java | 22 + .../com/netscape/cms/servlet/csadmin/CertUtil.java | 38 +- base/server/share/conf/schema-subCA.ldif | 5 + base/server/share/conf/schema.ldif | 10 + 23 files changed, 988 insertions(+), 107 deletions(-) create mode 100644 base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityData.java create mode 100644 base/common/src/com/netscape/certsrv/authority/AuthorityResource.java create mode 100644 base/common/src/com/netscape/certsrv/ca/AuthorityID.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CANotFoundException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java create mode 100644 base/server/share/conf/schema-subCA.ldif diff --git a/base/ca/shared/conf/db.ldif b/base/ca/shared/conf/db.ldif index 8a2e0b07274a83b317fb1ba56e8ef32b96857118..8918c5a22566dd5f6bd52a7dd33da46169060e54 100644 --- a/base/ca/shared/conf/db.ldif +++ b/base/ca/shared/conf/db.ldif @@ -164,3 +164,8 @@ dn: ou=certificateProfiles,ou=ca,{rootSuffix} objectClass: top objectClass: organizationalUnit ou: certificateProfiles + +dn: ou=subCAs,ou=ca,{rootSuffix} +objectClass: top +objectClass: organizationalUnit +ou: subCAs diff --git a/base/ca/src/com/netscape/ca/CAService.java b/base/ca/src/com/netscape/ca/CAService.java index 36f0bd592e333a276da84662c1e64a2921c5e7d2..a49d641cec839b4dac33fe7a6be49bf86c3560a8 100644 --- a/base/ca/src/com/netscape/ca/CAService.java +++ b/base/ca/src/com/netscape/ca/CAService.java @@ -65,7 +65,9 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.MetaInfo; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ECAException; +import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; @@ -565,18 +567,15 @@ public class CAService implements ICAService, IService { /// CA related routines. /// - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException { - return issueX509Cert(certi, null, null); - } - /** * issue cert for enrollment. */ - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException { CMS.debug("issueX509Cert"); - X509CertImpl certImpl = issueX509Cert("", certi, false, null); + X509CertImpl certImpl = issueX509Cert(aid, "", certi, false, null); CMS.debug("storeX509Cert " + certImpl.getSerialNumber()); storeX509Cert(profileId, rid, certImpl); @@ -615,9 +614,21 @@ public class CAService implements ICAService, IService { * renewal is expected to have original cert serial no. in cert info * field. */ - X509CertImpl issueX509Cert(String rid, X509CertInfo certi, - boolean renewal, BigInteger oldSerialNo) - throws EBaseException { + X509CertImpl issueX509Cert( + String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + return issueX509Cert(null, rid, certi, renewal, oldSerialNo); + } + + private X509CertImpl issueX509Cert( + AuthorityID aid, String rid, X509CertInfo certi, + boolean renewal, BigInteger oldSerialNo + ) throws EBaseException { + ICertificateAuthority ca = mCA.getCA(aid); + if (ca == null) + throw new CANotFoundException("No such CA: " + aid); + String algname = null; X509CertImpl cert = null; @@ -642,7 +653,7 @@ public class CAService implements ICAService, IService { // set default cert version. If policies added a extensions // the version would already be set to version 3. if (certi.get(X509CertInfo.VERSION) == null) { - certi.set(X509CertInfo.VERSION, mCA.getDefaultCertVersion()); + certi.set(X509CertInfo.VERSION, ca.getDefaultCertVersion()); } // set default validity if not set. @@ -665,7 +676,7 @@ public class CAService implements ICAService, IService { } begin = CMS.getCurrentDate(); - end = new Date(begin.getTime() + mCA.getDefaultValidity()); + end = new Date(begin.getTime() + ca.getDefaultValidity()); certi.set(CertificateValidity.NAME, new CertificateValidity(begin, end)); } @@ -705,7 +716,7 @@ public class CAService implements ICAService, IService { } Date caNotAfter = - mCA.getSigningUnit().getCertImpl().getNotAfter(); + ca.getSigningUnit().getCertImpl().getNotAfter(); if (begin.after(caNotAfter)) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_PAST_VALIDITY")); @@ -714,7 +725,7 @@ public class CAService implements ICAService, IService { if (end.after(caNotAfter)) { if (!is_ca) { - if (!mCA.isEnablePastCATime()) { + if (!ca.isEnablePastCATime()) { end = caNotAfter; certi.set(CertificateValidity.NAME, new CertificateValidity(begin, caNotAfter)); @@ -734,7 +745,7 @@ public class CAService implements ICAService, IService { certi.get(X509CertInfo.ALGORITHM_ID); if (algor == null || algor.toString().equals(CertInfo.SERIALIZE_ALGOR.toString())) { - algname = mCA.getSigningUnit().getDefaultAlgorithm(); + algname = ca.getSigningUnit().getDefaultAlgorithm(); algid = AlgorithmId.get(algname); certi.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algid)); @@ -820,16 +831,16 @@ public class CAService implements ICAService, IService { } try { - if (mCA.getIssuerObj() != null) { + if (ca.getIssuerObj() != null) { // this ensures the isserDN has the same encoding as the // subjectDN of the CA signing cert CMS.debug("CAService: issueX509Cert: setting issuerDN using exact CA signing cert subjectDN encoding"); certi.set(X509CertInfo.ISSUER, - mCA.getIssuerObj()); + ca.getIssuerObj()); } else { - CMS.debug("CAService: issueX509Cert: mCA.getIssuerObj() is null, creating new CertificateIssuerName"); + CMS.debug("CAService: issueX509Cert: ca.getIssuerObj() is null, creating new CertificateIssuerName"); certi.set(X509CertInfo.ISSUER, - new CertificateIssuerName(mCA.getX500Name())); + new CertificateIssuerName(ca.getX500Name())); } } catch (CertificateException e) { mCA.log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_SET_ISSUER", e.toString())); @@ -861,8 +872,8 @@ public class CAService implements ICAService, IService { } } - CMS.debug("About to mCA.sign cert."); - cert = mCA.sign(certi, algname); + CMS.debug("About to ca.sign cert."); + cert = ca.sign(certi, algname); return cert; } diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index acf07b9bde2a05f7c62740293a0c66cf92f50771..3b57f5b209160d7f35d1ebfbeeb325eccf29a072 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -25,15 +25,20 @@ import java.io.IOException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.KeyPair; import java.security.PublicKey; import java.security.cert.CRLException; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; +import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.Vector; import javax.servlet.http.HttpServletRequest; @@ -53,6 +58,13 @@ import netscape.security.x509.X509CertInfo; import netscape.security.x509.X509ExtensionException; import netscape.security.x509.X509Key; +import netscape.ldap.LDAPAttribute; +import netscape.ldap.LDAPAttributeSet; +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPEntry; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPSearchResults; + import org.mozilla.jss.CryptoManager; import org.mozilla.jss.asn1.ASN1Util; import org.mozilla.jss.asn1.GeneralizedTime; @@ -60,6 +72,9 @@ 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.crypto.CryptoToken; +import org.mozilla.jss.crypto.KeyPairAlgorithm; +import org.mozilla.jss.crypto.KeyPairGenerator; import org.mozilla.jss.crypto.SignatureAlgorithm; import org.mozilla.jss.crypto.TokenException; import org.mozilla.jss.pkix.cert.Extension; @@ -73,15 +88,20 @@ import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.ISubsystem; import com.netscape.certsrv.base.Nonces; import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ECAException; import com.netscape.certsrv.ca.ICRLIssuingPoint; import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; import com.netscape.certsrv.dbs.IDBSubsystem; +import com.netscape.certsrv.dbs.IDBSSession; import com.netscape.certsrv.dbs.certdb.ICertRecord; import com.netscape.certsrv.dbs.certdb.ICertificateRepository; import com.netscape.certsrv.dbs.crldb.ICRLRepository; import com.netscape.certsrv.dbs.replicadb.IReplicaIDRepository; import com.netscape.certsrv.ldap.ELdapException; +import com.netscape.certsrv.ldap.ILdapConnFactory; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.ocsp.IOCSPService; import com.netscape.certsrv.policy.IPolicyProcessor; @@ -96,6 +116,8 @@ import com.netscape.certsrv.request.IRequestScheduler; import com.netscape.certsrv.request.IService; import com.netscape.certsrv.security.ISigningUnit; import com.netscape.certsrv.util.IStatsSubsystem; +import com.netscape.cms.servlet.csadmin.CertUtil; +import com.netscape.cmscore.base.PropConfigStore; import com.netscape.cmscore.dbs.CRLRepository; import com.netscape.cmscore.dbs.CertRecord; import com.netscape.cmscore.dbs.CertificateRepository; @@ -106,6 +128,7 @@ import com.netscape.cmscore.listeners.ListenerPlugin; import com.netscape.cmscore.request.RequestSubsystem; import com.netscape.cmscore.security.KeyCertUtil; import com.netscape.cmscore.util.Debug; +import com.netscape.cmsutil.crypto.CryptoUtil; import com.netscape.cmsutil.ocsp.BasicOCSPResponse; import com.netscape.cmsutil.ocsp.CertID; import com.netscape.cmsutil.ocsp.CertStatus; @@ -123,6 +146,9 @@ import com.netscape.cmsutil.ocsp.SingleResponse; import com.netscape.cmsutil.ocsp.TBSRequest; import com.netscape.cmsutil.ocsp.UnknownInfo; +import org.apache.commons.lang.StringUtils; + + /** * A class represents a Certificate Authority that is * responsible for certificate specific operations. @@ -136,6 +162,12 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori public final static OBJECT_IDENTIFIER OCSP_NONCE = new OBJECT_IDENTIFIER("1.3.6.1.5.5.7.48.1.2"); + private static final Map caMap = new TreeMap(); + protected CertificateAuthority topCA = null; + protected AuthorityID authorityID = null; + protected AuthorityID authorityParentID = null; + protected String authorityDescription = null; + protected ISubsystem mOwner = null; protected IConfigStore mConfig = null; protected ILogger mLogger = CMS.getLogger(); @@ -234,6 +266,33 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori * Constructs a CA subsystem. */ public CertificateAuthority() { + topCA = this; + } + + /** + * Construct and initialise a sub-CA + */ + public CertificateAuthority( + String subsystemId, + ISubsystem owner, + IConfigStore config, + CertificateAuthority topCA, + AuthorityID aid, + AuthorityID parentAID, + String signingKeyNickname, + String authorityDescription + ) throws EBaseException { + setId(subsystemId); + this.topCA = topCA; + this.authorityID = aid; + this.authorityParentID = parentAID; + this.authorityDescription = authorityDescription; + mNickname = signingKeyNickname; + init(owner, config); + } + + private boolean isTopCA() { + return authorityID == null; } /** @@ -334,8 +393,22 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mOwner = owner; mConfig = config; - // init cert & crl database. - initCaDatabases(); + // init cert & crl database + initCertDatabase(); + initCrlDatabase(); + + // init replica id repository + if (isTopCA()) { + String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); + if (replicaReposDN == null) { + replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); + } + mReplicaRepot = new ReplicaIDRepository( + DBSubsystem.getInstance(), 1, replicaReposDN); + CMS.debug("Replica Repot inited"); + } else { + mReplicaRepot = topCA.mReplicaRepot; + } // init signing unit & CA cert. try { @@ -358,51 +431,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori if (CMS.isPreOpMode()) return; - // set certificate status to 10 minutes - mCertRepot.setCertStatusUpdateInterval( + /* The top-level CA owns these resources so skip these + * steps for sub-CAs. + */ + if (isTopCA()) { + /* These methods configure and start threads related to + * CertificateRepository. Ideally all of the config would + * be pushed into CertificateRepository constructor and a + * single 'start' method would start the threads. + */ + // set certificate status to 10 minutes + mCertRepot.setCertStatusUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("certStatusUpdateInterval", 10 * 60), mConfig.getBoolean("listenToCloneModifications", false)); - mCertRepot.setConsistencyCheck( + mCertRepot.setConsistencyCheck( mConfig.getBoolean("ConsistencyCheck", false)); - mCertRepot.setSkipIfInConsistent( + mCertRepot.setSkipIfInConsistent( mConfig.getBoolean("SkipIfInConsistent", false)); - // set serial number update task to run every 10 minutes - mCertRepot.setSerialNumberUpdateInterval( + // set serial number update task to run every 10 minutes + mCertRepot.setSerialNumberUpdateInterval( mRequestQueue.getRequestRepository(), mConfig.getInteger("serialNumberUpdateInterval", 10 * 60)); - mService.init(config.getSubStore("connector")); + mService.init(config.getSubStore("connector")); - initMiscellaneousListeners(); - - // instantiate CRL publisher - IConfigStore cpStore = null; - - mByName = config.getBoolean("byName", true); - - cpStore = config.getSubStore("crlPublisher"); - if (cpStore != null && cpStore.size() > 0) { - String publisherClass = cpStore.getString("class"); - - if (publisherClass != null) { - try { - @SuppressWarnings("unchecked") - Class pc = (Class) Class.forName(publisherClass); - - mCRLPublisher = pc.newInstance(); - mCRLPublisher.init(this, cpStore); - } catch (ClassNotFoundException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (IllegalAccessException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } catch (InstantiationException ee) { - log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); - } - } + initMiscellaneousListeners(); } + initCRLPublisher(); + // initialize publisher processor (publish remote admin // rely on this subsystem, so it has to be initialized) initPublish(); @@ -412,6 +471,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori // being functional. initCRL(); + if (isTopCA()) + loadSubCAs(); + } catch (EBaseException e) { if (CMS.isPreOpMode()) return; @@ -420,6 +482,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + private void initCRLPublisher() throws EBaseException { + // instantiate CRL publisher + if (!isTopCA()) { + mByName = topCA.mByName; + mCRLPublisher = topCA.mCRLPublisher; + return; + } + + mByName = mConfig.getBoolean("byName", true); + IConfigStore cpStore = mConfig.getSubStore("crlPublisher"); + if (cpStore != null && cpStore.size() > 0) { + String publisherClass = cpStore.getString("class"); + + if (publisherClass != null) { + try { + @SuppressWarnings("unchecked") + Class pc = (Class) Class.forName(publisherClass); + + mCRLPublisher = pc.newInstance(); + mCRLPublisher.init(this, cpStore); + } catch (ClassNotFoundException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (IllegalAccessException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } catch (InstantiationException ee) { + log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_NO_PUBLISHER", ee.toString())); + } + } + } + } + /** * return CA's request queue processor */ @@ -540,14 +633,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mService.startup(); mRequestQueue.recover(); - // Note that this could be null. - - // setup Admin operations - - initNotificationListeners(); - - startPublish(); - // startCRL(); + if (isTopCA()) { + // setup Admin operations + initNotificationListeners(); + startPublish(); + } } /** @@ -555,6 +645,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori *

*/ public void shutdown() { + if (!isTopCA()) return; // sub-CAs don't own these resources + Enumeration enums = mCRLIssuePoints.elements(); while (enums.hasMoreElements()) { CRLIssuingPoint point = (CRLIssuingPoint) enums.nextElement(); @@ -1228,13 +1320,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mIssuerObj = new CertificateIssuerName((X500Name)mSubjectObj.get(CertificateIssuerName.DN_NAME)); } - mSigningUnit.init(this, caSigningCfg); + mSigningUnit.init(this, caSigningCfg, mNickname); CMS.debug("CA signing unit inited"); // for identrus IConfigStore CrlStore = mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE); - if (CrlStore != null && CrlStore.size() > 0) { + if (isTopCA() && CrlStore != null && CrlStore.size() > 0) { mCRLSigningUnit = new SigningUnit(); mCRLSigningUnit.init(this, mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE)); } else { @@ -1304,7 +1396,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori IConfigStore OCSPStore = mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE); - if (OCSPStore != null && OCSPStore.size() > 0) { + if (isTopCA() && OCSPStore != null && OCSPStore.size() > 0) { mOCSPSigningUnit = new SigningUnit(); mOCSPSigningUnit.init(this, mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE)); CMS.debug("Separate OCSP signing unit inited"); @@ -1443,8 +1535,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori /** * init cert & crl database */ - private void initCaDatabases() + private void initCertDatabase() throws EBaseException { + if (!isTopCA()) { + mCertRepot = topCA.mCertRepot; + return; + } + int certdb_inc = mConfig.getInteger(PROP_CERTDB_INC, 5); String certReposDN = mConfig.getString(PROP_CERT_REPOS_DN, null); @@ -1471,8 +1568,17 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mCertRepot.setTransitRecordPageSize(transitRecordPageSize); CMS.debug("Cert Repot inited"); + } - // init crl repot. + /** + * init cert & crl database + */ + private void initCrlDatabase() + throws EBaseException { + if (!isTopCA()) { + mCRLRepot = topCA.mCRLRepot; + return; + } int crldb_inc = mConfig.getInteger(PROP_CRLDB_INC, 5); @@ -1482,14 +1588,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori "ou=crlIssuingPoints, ou=" + getId() + ", " + getDBSubsystem().getBaseDN()); CMS.debug("CRL Repot inited"); - - String replicaReposDN = mConfig.getString(PROP_REPLICAID_DN, null); - if (replicaReposDN == null) { - replicaReposDN = "ou=Replica," + getDBSubsystem().getBaseDN(); - } - mReplicaRepot = new ReplicaIDRepository( - DBSubsystem.getInstance(), 1, replicaReposDN); - CMS.debug("Replica Repot inited"); } private void startPublish() @@ -1513,6 +1611,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initPublish() throws EBaseException { + if (!isTopCA()) { + mPublisherProcessor = topCA.mPublisherProcessor; + return; + } + IConfigStore c = null; try { @@ -1676,6 +1779,15 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initRequestQueue() throws EBaseException { + if (!isTopCA()) { + mPolicy = topCA.mPolicy; + mService = topCA.mService; + mNotify = topCA.mNotify; + mPNotify = topCA.mPNotify; + mRequestQueue = topCA.mRequestQueue; + return; + } + mPolicy = new CAPolicy(); mPolicy.init(this, mConfig.getSubStore(PROP_POLICY)); CMS.debug("CA policy inited"); @@ -1734,6 +1846,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori @SuppressWarnings("unchecked") private void initCRL() throws EBaseException { + if (!isTopCA()) { + mCRLIssuePoints = topCA.mCRLIssuePoints; + mMasterCRLIssuePoint = topCA.mMasterCRLIssuePoint; + return; + } IConfigStore crlConfig = mConfig.getSubStore(PROP_CRL_SUBSTORE); if ((crlConfig == null) || (crlConfig.size() <= 0)) { @@ -1799,6 +1916,64 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori log(ILogger.LL_INFO, "CRL Issuing Points inited"); } + /** + * Find, instantiate and register sub-CAs. + * + * This method must only be called by the top-level CA. + */ + private synchronized void loadSubCAs() throws EBaseException { + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("loadSubCAs"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + String searchDN = "ou=authorities,ou=" + getId() + + "," + getDBSubsystem().getBaseDN(); + LDAPSearchResults results = null; + try { + results = conn.search( + searchDN, LDAPConnection.SCOPE_ONE, + "(objectclass=authority)", null, false); + + while (results.hasMoreElements()) { + LDAPEntry entry = results.next(); + LDAPAttribute aidAttr = entry.getAttribute("authorityID"); + LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID"); + LDAPAttribute nickAttr = entry.getAttribute("authorityKeyNickname"); + if (aidAttr == null || nickAttr == null) + throw new ECAException("Malformed sub-CA object: " + entry.getDN()); + String keyNick = (String) nickAttr.getStringValues().nextElement(); + + AuthorityID aid = new AuthorityID((String) + aidAttr.getStringValues().nextElement()); + AuthorityID parentAID = null; + if (parentAIDAttr != null) + parentAID = new AuthorityID((String) + parentAIDAttr.getStringValues().nextElement()); + + String desc = null; + LDAPAttribute descAttr = entry.getAttribute("description"); + if (descAttr != null) + desc = (String) descAttr.getStringValues().nextElement(); + + CertificateAuthority subCA = new CertificateAuthority( + getId(), mOwner, mConfig, this, + aid, parentAID, keyNick, desc); + caMap.put(aid, subCA); + } + } catch (LDAPException e) { + if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) { + CMS.debug( + "Missing lightweight CAs container '" + searchDN + + "'. Disabling lightweight CAs."); + } else { + throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e); + } + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + } + public String getOfficialName() { return OFFICIAL_NAME; } @@ -2083,4 +2258,160 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori return new SingleResponse(cid, certStatus, thisUpdate, nextUpdate); } + + /** + * Enumerate all sub-CAs. + */ + public synchronized List getCAs() { + List cas = new ArrayList(); + for (ICertificateAuthority ca : caMap.values()) { + cas.add(ca); + } + return cas; + } + + /** + * Get the sub-CA by ID. + * + * @param aid The ID of the CA to retrieve, or null + * to retreive the top-level CA. + * + * @return the authority, or null if not found + */ + public ICertificateAuthority getCA(AuthorityID aid) { + return aid == null ? topCA : caMap.get(aid); + } + + public AuthorityID getAuthorityID() { + return authorityID; + } + + public AuthorityID getAuthorityParentID() { + return authorityParentID; + } + + public String getAuthorityDescription() { + return authorityDescription; + } + + /** + * Create a new sub-CA. + * + * @param subjectDN Subject DN for new CA + * @param parentAID ID of parent CA, or null to create + * sub-CA immediately below top-level CA + * @param description Optional string description of CA + */ + public ICertificateAuthority createCA( + String subjectDN, AuthorityID parentAID, + String description) + throws EBaseException { + ICertificateAuthority parentCA = getCA(parentAID); + if (parentCA == null) + throw new CANotFoundException( + "Parent CA \"" + parentAID + "\" does not exist"); + + ICertificateAuthority subCA = parentCA.createSubCA( + subjectDN, description); + synchronized (this) { + caMap.put(subCA.getAuthorityID(), subCA); + } + return subCA; + } + + /** + * Create a new sub-CA IMMEDIATELY beneath this one. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String subjectDN, String description) + throws EBaseException { + + // check uniqueness of DN + X500Name subjectX500Name = null; + try { + subjectX500Name = new X500Name(subjectDN); + } catch (IOException e) { + throw new IllegalArgumentException( + "Invalid Subject DN: " + subjectDN); + } + for (ICertificateAuthority ca : caMap.values()) { + if (ca.getX500Name().equals(subjectX500Name)) + throw new IssuerUnavailableException( + "CA with Subject DN '" + subjectDN + "' already exists"); + } + + // generate authority ID and nickname + AuthorityID aid = new AuthorityID(); + String aidString = aid.toString(); + String nickname = topCA.getNickname() + " " + aidString; + + // build database entry + String dn = "cn=" + aidString + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + CMS.debug("createSubCA: DN = " + dn); + LDAPAttribute[] attrs = { + new LDAPAttribute("objectclass", "authority"), + new LDAPAttribute("cn", aidString), + new LDAPAttribute("authorityID", aidString), + new LDAPAttribute("authorityKeyNickname", nickname) + }; + LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); + if (this.authorityID != null) + attrSet.add(new LDAPAttribute( + "authorityParentID", this.authorityID.toString())); + if (description != null) + attrSet.add(new LDAPAttribute("description", description)); + LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("createSubCA"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + try { + // add entry to database + conn.add(ldapEntry); + + try { + // Generate sub-CA signing key + CryptoManager cryptoManager = CryptoManager.getInstance(); + // TODO read PROP_TOKEN_NAME config + CryptoToken token = cryptoManager.getInternalKeyStorageToken(); + // TODO algorithm parameter + KeyPairGenerator gen = token.getKeyPairGenerator(KeyPairAlgorithm.RSA); + gen.initialize(2048); + KeyPair keypair = gen.genKeyPair(); + PublicKey pub = keypair.getPublic(); + X509Key x509key = CryptoUtil.convertPublicKeyToX509Key(pub); + + // Sign certificate + String algName = mSigningUnit.getDefaultAlgorithm(); + IConfigStore cs = new PropConfigStore("cs"); + cs.put(".profile", "caCert.profile"); + cs.put(".dn", subjectDN); + cs.put(".keyalgorithm", algName); + X509CertImpl cert = + CertUtil.createLocalCertWithCA(cs, x509key, "", "", "local", this); + + // Add certificate to nssdb + cryptoManager.importCertPackage(cert.getEncoded(), nickname); + } catch (Exception e) { + // something went wrong; delete just-added entry + conn.delete(dn); + throw new ECAException("Error creating sub-CA certificate: " + e); + } + } catch (LDAPException e) { + throw new EBaseException("Error adding sub-CA entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + return new CertificateAuthority( + getId(), mOwner, mConfig, topCA, aid, this.authorityID, + nickname, description); + } } diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java b/base/ca/src/com/netscape/ca/SigningUnit.java index 2466fb652a46a3b5faede616cb397d18e592f5a0..0410bd2909bc71ca7d7443b3c9db0b085d62eaea 100644 --- a/base/ca/src/com/netscape/ca/SigningUnit.java +++ b/base/ca/src/com/netscape/ca/SigningUnit.java @@ -123,16 +123,14 @@ public final class SigningUnit implements ISigningUnit { return mConfig.getString(PROP_TOKEN_NAME); } - public String getNickName() throws EBaseException { - try { - return mConfig.getString(PROP_RENAMED_CERT_NICKNAME); - } catch (EBaseException e) { - return mConfig.getString(PROP_CERT_NICKNAME); - } - } public void init(ISubsystem owner, IConfigStore config) throws EBaseException { + init(owner, config, null); + } + + public void init(ISubsystem owner, IConfigStore config, String nickname) + throws EBaseException { mOwner = owner; mConfig = config; @@ -140,7 +138,15 @@ public final class SigningUnit implements ISigningUnit { try { mManager = CryptoManager.getInstance(); - mNickname = getNickName(); + if (nickname == null) { + try { + mNickname = mConfig.getString(PROP_RENAMED_CERT_NICKNAME); + } catch (EBaseException e) { + mNickname = mConfig.getString(PROP_CERT_NICKNAME); + } + } else { + mNickname = nickname; + } tokenname = config.getString(PROP_TOKEN_NAME); if (tokenname.equalsIgnoreCase(Constants.PR_INTERNAL_TOKEN) || diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java new file mode 100644 index 0000000000000000000000000000000000000000..5ca21892d441e4b0e611d117e4dd490df05cf6e6 --- /dev/null +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -0,0 +1,183 @@ +//--- 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) 2015 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- + +package org.dogtagpki.server.ca.rest; + +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.GenericEntity; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.lang.StringUtils; +import org.jboss.resteasy.plugins.providers.atom.Link; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; +import com.netscape.certsrv.base.ResourceNotFoundException; +import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; +import com.netscape.certsrv.common.NameValuePairs; +import com.netscape.certsrv.common.OpDef; +import com.netscape.certsrv.common.ScopeDef; +import com.netscape.certsrv.logging.ILogger; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.authority.AuthorityResource; +import com.netscape.cms.realm.PKIPrincipal; +import com.netscape.cms.servlet.base.PKIService; + +/** + * @author ftweedal + */ +public class AuthorityService extends PKIService implements AuthorityResource { + + ICertificateAuthority topCA; + + public AuthorityService() { + topCA = (ICertificateAuthority) CMS.getSubsystem("ca"); + } + + @Context + private UriInfo uriInfo; + + @Context + private HttpHeaders headers; + + @Context + private Request request; + + @Context + private HttpServletRequest servletRequest; + + /* + private final static String LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL = + "LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL_4"; + private final static String LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE = + "LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE_3"; + */ + + @Override + public Response listCAs() { + List results = new ArrayList(); + for (ICertificateAuthority ca : topCA.getCAs()) + results.add(readAuthorityData(ca)); + + GenericEntity entity = new GenericEntity>(results) {}; + return Response.ok(entity).build(); + } + + @Override + public Response getCA(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = topCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + return createOKResponse(readAuthorityData(ca)); + } + + @Override + public Response createCA(AuthorityData data) { + AuthorityID parentAID = null; + String parentAIDString = data.getParentID(); + if (parentAIDString != null) { + try { + parentAID = new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + parentAIDString); + } + } + + try { + ICertificateAuthority subCA = topCA.createCA( + data.getDN(), parentAID, data.getDescription()); + return createOKResponse(readAuthorityData(subCA)); + } catch (IllegalArgumentException e) { + throw new BadRequestException(e.toString()); + } catch (CANotFoundException e) { + throw new ResourceNotFoundException(e.toString()); + } catch (IssuerUnavailableException e) { + throw new ConflictingOperationException(e.toString()); + } catch (Exception e) { + CMS.debug(e); + throw new PKIException("Error creating sub-CA: " + e.toString()); + } + } + + private static AuthorityData readAuthorityData(ICertificateAuthority ca) + throws PKIException { + String dn; + try { + dn = ca.getX500Name().toLdapDNString(); + } catch (IOException e) { + throw new PKIException("Error reading CA data: could not determine Issuer DN"); + } + + AuthorityID parentAID = ca.getAuthorityParentID(); + return new AuthorityData( + dn, + ca.getAuthorityID().toString(), + parentAID != null ? parentAID.toString() : null, + ca.getAuthorityDescription() + ); + } + + /* TODO work out what audit messages are needed + public void auditProfileChangeState(String profileId, String op, String status) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CERT_PROFILE_APPROVAL, + auditor.getSubjectID(), + status, + profileId, + op); + auditor.log(msg); + } + + public void auditProfileChange(String scope, String type, String id, String status, Map params) { + String msg = CMS.getLogMessage( + LOGGING_SIGNED_AUDIT_CONFIG_CERT_PROFILE, + auditor.getSubjectID(), + status, + auditor.getParamString(scope, type, id, params)); + auditor.log(msg); + } + */ + +} diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java index 16eae7877059c7dc42479276b3111db1ce7f582d..235ea105bef0c738bccd53276a744b95a76f0627 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java @@ -34,6 +34,9 @@ public class CAApplication extends Application { // installer classes.add(CAInstallerService.class); + // sub-ca management + classes.add(AuthorityService.class); + // certs and requests classes.add(CertService.class); classes.add(CertRequestService.class); diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityData.java b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java new file mode 100644 index 0000000000000000000000000000000000000000..64f2af83720311e3eac0ee7a9197a05ff0e7198a --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java @@ -0,0 +1,109 @@ +// --- 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) 2015 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- + +/** + * @author ftweedal + */ +package com.netscape.certsrv.authority; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import org.jboss.resteasy.plugins.providers.atom.Link; + +@XmlRootElement(name = "ca") +@XmlAccessorType(XmlAccessType.FIELD) +public class AuthorityData { + + public static Marshaller marshaller; + public static Unmarshaller unmarshaller; + + static { + try { + marshaller = JAXBContext.newInstance(AuthorityData.class).createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + unmarshaller = JAXBContext.newInstance(AuthorityData.class).createUnmarshaller(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + @XmlAttribute + protected String aid; + + public String getID() { + return aid; + } + + + @XmlAttribute + protected String parentAID; + + public String getParentID() { + return parentAID; + } + + + @XmlAttribute + protected String dn; + + public String getDN() { + return dn; + } + + + @XmlAttribute + protected String description; + + public String getDescription() { + return description; + } + + + protected Link link; + + public Link getLink() { + return link; + } + + public void setLink(Link link) { + this.link = link; + } + + protected AuthorityData() { + } + + public AuthorityData( + String dn, String aid, String parentAID, + String description) { + this.dn = dn; + this.aid = aid; + this.parentAID = parentAID; + this.description = description; + } + +} diff --git a/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java new file mode 100644 index 0000000000000000000000000000000000000000..817885687b04d1b10ea35dca9a1942fe5ce201f4 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java @@ -0,0 +1,36 @@ +package com.netscape.certsrv.authority; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.annotations.ClientResponseType; + +import com.netscape.certsrv.acls.ACLMapping; +import com.netscape.certsrv.authentication.AuthMethodMapping; + +@Path("authorities") +public interface AuthorityResource { + + @GET + public Response listCAs(); + /* + @QueryParam("start") Integer start, + @QueryParam("size") Integer size); + */ + + @GET + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + public Response getCA(@PathParam("id") String caIDString); + + @POST + @ClientResponseType(entityType=AuthorityData.class) + //@ACLMapping("certs") + //@AuthMethodMapping("certs") + public Response createCA(AuthorityData data); + +} diff --git a/base/common/src/com/netscape/certsrv/ca/AuthorityID.java b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java new file mode 100644 index 0000000000000000000000000000000000000000..86f3870ab8cec700ab27cd6a8555e8e8d6e84c01 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/AuthorityID.java @@ -0,0 +1,36 @@ +package com.netscape.certsrv.ca; + +import java.util.UUID; + +/** + * Identifier for a CertificateAuthority. + */ +public class AuthorityID implements Comparable { + + protected UUID uuid; + + /** + * Parse a AuthorityID from the given string + */ + public AuthorityID(String s) { + uuid = UUID.fromString(s); + } + + /** + * Construct a random AuthorityID + */ + public AuthorityID() { + uuid = UUID.randomUUID(); + } + + public String toString() { + return uuid.toString(); + } + + public int compareTo(Object o) { + if (o instanceof AuthorityID) + return uuid.compareTo(((AuthorityID) o).uuid); + throw new ClassCastException("Invalid comparsion operand for AuthorityID"); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..b7574d4243d9941e30aba506b4ad5fd7b8394386 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java @@ -0,0 +1,12 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot be found. + */ +public class CANotFoundException extends ECAException { + + public CANotFoundException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/ICAService.java b/base/common/src/com/netscape/certsrv/ca/ICAService.java index 1d179e07692eee2f32780b33489975a571670685..a4b4a63038872fbf6d97cfc3fbcadce5234208a6 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICAService.java +++ b/base/common/src/com/netscape/certsrv/ca/ICAService.java @@ -23,6 +23,7 @@ import netscape.security.x509.X509CertInfo; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.connector.IConnector; import com.netscape.certsrv.request.IRequest; @@ -59,13 +60,15 @@ public interface ICAService { * Issues certificate base on enrollment information, * creates certificate record, and stores all necessary data. * + * @param caID CA ID * @param certi information obtain from revocation request + * @param profileId Name of profile used + * @param rid Request ID * @exception EBaseException failed to issue certificate or create certificate record */ - public X509CertImpl issueX509Cert(X509CertInfo certi) - throws EBaseException; - - public X509CertImpl issueX509Cert(X509CertInfo certi, String profileId, String rid) + public X509CertImpl issueX509Cert( + AuthorityID aid, X509CertInfo certi, + String profileId, String rid) throws EBaseException; /** diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java index f87f15420b3ea6e02e5ce47b5c350e86f67ccc9f..d707bf848ba0973c2a817c45abeba8219bff574f 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java +++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java @@ -18,6 +18,7 @@ package com.netscape.certsrv.ca; import java.util.Enumeration; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -515,4 +516,47 @@ public interface ICertificateAuthority extends ISubsystem { public CertificateIssuerName getIssuerObj(); public CertificateSubjectName getSubjectObj(); + + /** + * Enumerate all sub-CA handles. + */ + public List getCAs(); + + /** + * Get the CA ID of this CA. Returns null for the top-level CA. + */ + public AuthorityID getAuthorityID(); + + /** + * Get the CA ID of this CA's parent CA. Returns null for + * authorities signed by the top-level CA. + */ + public AuthorityID getAuthorityParentID(); + + /** + * Return CA description. May be null. + */ + public String getAuthorityDescription(); + + /** + * Get the CA by ID. Returns null if CA not found. + */ + public ICertificateAuthority getCA(AuthorityID aid); + + /** + * Create a new sub-CA under the specified parent CA. + */ + public ICertificateAuthority createCA( + String dn, AuthorityID parentAID, String desc) + throws EBaseException; + + /** + * Create a new sub-CA IMMEDIATELY beneath this one. + * + * This method DOES NOT add the new CA to caMap; it is the + * caller's responsibility. + */ + public ICertificateAuthority createSubCA( + String dn, String desc) + throws EBaseException; } diff --git a/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java new file mode 100644 index 0000000000000000000000000000000000000000..541df308d413676da51e5a1bce27232464118920 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java @@ -0,0 +1,13 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw during CA creation when requested CA + * (issuer DN) already exists. + */ +public class IssuerUnavailableException extends ECAException { + + public IssuerUnavailableException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java index 69a39d7e23232a1a0cc6e2fe98305d452234bb8c..a861a2e73a2e361971f010f63bd0ca615ba08e80 100644 --- a/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java +++ b/base/common/src/com/netscape/certsrv/profile/IEnrollProfile.java @@ -175,6 +175,11 @@ public interface IEnrollProfile extends IProfile { public static final String REQUEST_ALGORITHM_PARAMS = "req_algorithm_params"; /** + * ID of requested certificate authority (unused for top-level CA) + */ + public static final String REQUEST_AUTHORITY_ID = "req_authority_id"; + + /** * Set Default X509CertInfo in the request. * * @param request profile-based certificate request. diff --git a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java index 34d2a5109170c560b5a449d08f43eeeda5035b88..75b45bb8b31cde22881e0ddc310c0bdb51a66338 100644 --- a/base/common/src/com/netscape/certsrv/security/ISigningUnit.java +++ b/base/common/src/com/netscape/certsrv/security/ISigningUnit.java @@ -17,6 +17,7 @@ // --- END COPYRIGHT BLOCK --- package com.netscape.certsrv.security; +import java.security.PrivateKey; import java.security.PublicKey; import netscape.security.x509.X509CertImpl; @@ -161,4 +162,11 @@ public interface ISigningUnit { * @return public key */ public PublicKey getPublicKey(); + + /** + * Retrieves the public key associated in this unit. + * + * @return public key + */ + public PrivateKey getPrivateKey(); } diff --git a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java index d0bfdb8a64ee857a3f5ff544e41de905b4660f55..53edca3a93c28a4fdd6c476bbdd2dc3d83869505 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/CAEnrollProfile.java @@ -29,6 +29,7 @@ import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authority.IAuthority; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.SessionContext; +import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.ICAService; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.connector.IConnector; @@ -95,8 +96,8 @@ public class CAEnrollProfile extends EnrollProfile { CMS.debug("CAEnrollProfile: execute reqId=" + request.getRequestId().toString()); ICertificateAuthority ca = (ICertificateAuthority) getAuthority(); + ICAService caService = (ICAService) ca.getCAService(); - if (caService == null) { throw new EProfileException("No CA Service"); } @@ -190,9 +191,13 @@ public class CAEnrollProfile extends EnrollProfile { if (setId != null) { sc.put("profileSetId", setId); } + AuthorityID aid = null; + String aidString = request.getExtDataInString(REQUEST_AUTHORITY_ID); + if (aidString != null) + aid = new AuthorityID(aidString); try { - theCert = caService.issueX509Cert(info, getId() /* profileId */, - id /* requestId */); + theCert = caService.issueX509Cert( + aid, info, getId() /* profileId */, id /* requestId */); } catch (EBaseException e) { CMS.debug(e.toString()); diff --git a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java index fe3b424a4b8e13215d4029d328d4a1e280be63ff..523e0117a55567d2f807dd3eb2e69c48d7eb7344 100644 --- a/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java +++ b/base/server/cms/src/com/netscape/cms/profile/common/EnrollProfile.java @@ -190,6 +190,9 @@ public abstract class EnrollProfile extends BasicProfile if (locale != null) { result[i].setExtData(REQUEST_LOCALE, locale.getLanguage()); } + + // set requested CA + result[i].setExtData(REQUEST_AUTHORITY_ID, ctx.get(REQUEST_AUTHORITY_ID)); } return result; } diff --git a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java index 095f8bb5ffa2f950b58c868a6daee99991a80daa..54cfafed22a0654dd993c9c67f247eefac09c1a3 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/AuthorityKeyIdentifierExtDefault.java @@ -26,8 +26,12 @@ import netscape.security.x509.PKIXExtensions; 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.ca.AuthorityID; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.profile.EProfileException; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.property.Descriptor; import com.netscape.certsrv.property.EPropertyException; @@ -161,18 +165,27 @@ public class AuthorityKeyIdentifierExtDefault extends CAEnrollDefault { */ public void populate(IRequest request, X509CertInfo info) throws EProfileException { - AuthorityKeyIdentifierExtension ext = createExtension(info); + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + String aidString = request.getExtDataInString( + IEnrollProfile.REQUEST_AUTHORITY_ID); + if (aidString != null) + ca = ca.getCA(new AuthorityID(aidString)); + if (ca == null) + throw new EProfileException("Could not reach requested CA"); + AuthorityKeyIdentifierExtension ext = createExtension(ca, info); addExtension(PKIXExtensions.AuthorityKey_Id.toString(), ext, info); } - public AuthorityKeyIdentifierExtension createExtension(X509CertInfo info) { + public AuthorityKeyIdentifierExtension createExtension( + ICertificateAuthority ca, X509CertInfo info) { KeyIdentifier kid = null; String localKey = getConfig("localKey"); if (localKey != null && localKey.equals("true")) { kid = getKeyIdentifier(info); } else { - kid = getCAKeyIdentifier(); + kid = getCAKeyIdentifier(ca); } if (kid == null) diff --git a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java index 1d1d05ed55ef30114781521ac607eae118546250..696830ead842767892f77bd8f8c9ea6f667225aa 100644 --- a/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java +++ b/base/server/cms/src/com/netscape/cms/profile/def/CAEnrollDefault.java @@ -68,9 +68,7 @@ public abstract class CAEnrollDefault extends EnrollDefault { return null; } - public KeyIdentifier getCAKeyIdentifier() { - ICertificateAuthority ca = (ICertificateAuthority) - CMS.getSubsystem(CMS.SUBSYSTEM_CA); + public KeyIdentifier getCAKeyIdentifier(ICertificateAuthority ca) { X509CertImpl caCert = ca.getCACert(); if (caCert == null) { // during configuration, we dont have the CA certificate diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java index 960f997cd4badd18bdd25393e9175fc935d52edb..7e358b87f35b8aef2d3ef9de3f8dfd4c7a2b7053 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/EnrollmentProcessor.java @@ -30,6 +30,10 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.SessionContext; import com.netscape.certsrv.cert.CertEnrollmentRequest; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileContext; @@ -146,6 +150,24 @@ public class EnrollmentProcessor extends CertProcessor { } IProfileContext ctx = profile.createContext(); + + // Insert AuthorityID into request context + // + String aidString = request.getParameter("authority"); + if (aidString != null) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestDataException("EnrollmentProcessor: invalid AuthorityID: " + aidString); + } + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem(CMS.SUBSYSTEM_CA); + if (ca.getCA(aid) == null) + throw new CANotFoundException("CA not found: " + aidString); + ctx.set(IEnrollProfile.REQUEST_AUTHORITY_ID, aidString); + } + CMS.debug("EnrollmentProcessor: set Inputs into profile Context"); setInputsIntoContext(data, profile, ctx); diff --git a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java index 36b0e4d0d44ec8987856ebaaa3f4919c4a3f7071..c0729d88100e64d06c099bc4f1d73a14bcb9918f 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java +++ b/base/server/cms/src/com/netscape/cms/servlet/csadmin/CertUtil.java @@ -434,8 +434,19 @@ public class CertUtil { (signingKeyType.equals("dsa") && algorithm.contains("DSA"))); } + public static X509CertImpl createLocalCertWithCA(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, ICertificateAuthority ca) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, ca, null); + } + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, String prefix, String certTag, String type, Context context) throws IOException { + return createLocalCert(config, x509key, prefix, certTag, type, null, context); + } + + public static X509CertImpl createLocalCert(IConfigStore config, X509Key x509key, + String prefix, String certTag, String type, + ICertificateAuthority ca, Context context) throws IOException { CMS.debug("Creating local certificate... certTag=" + certTag); String profile = null; @@ -446,13 +457,14 @@ public class CertUtil { } X509CertImpl cert = null; - ICertificateAuthority ca = null; ICertificateRepository cr = null; RequestId reqId = null; String profileId = null; IRequestQueue queue = null; IRequest req = null; + boolean caProvided = ca != null; + try { Boolean injectSAN = config.getBoolean( "service.injectSAN", false); @@ -468,7 +480,8 @@ public class CertUtil { } else { keyAlgorithm = config.getString(prefix + certTag + ".keyalgorithm"); } - ca = (ICertificateAuthority) CMS.getSubsystem( + if (!caProvided) + ca = (ICertificateAuthority) CMS.getSubsystem( ICertificateAuthority.ID); cr = ca.getCertificateRepository(); BigInteger serialNo = cr.getNextSerialNumber(); @@ -496,9 +509,9 @@ public class CertUtil { } CMS.debug("Cert Template: " + info.toString()); - String instanceRoot = config.getString("instanceRoot"); + String instanceRoot = CMS.getConfigStore().getString("instanceRoot"); - String configurationRoot = config.getString("configurationRoot"); + String configurationRoot = CMS.getConfigStore().getString("configurationRoot"); CertInfoProfile processor = new CertInfoProfile( instanceRoot + configurationRoot + profile); @@ -541,11 +554,18 @@ public class CertUtil { processor.populate(req, info); - String caPriKeyID = config.getString( - prefix + "signing" + ".privkey.id"); - byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); - PrivateKey caPrik = CryptoUtil.findPrivateKeyFromID( - keyIDb); + PrivateKey caPrik = null; + if (caProvided) { + java.security.PrivateKey pk = ca.getSigningUnit().getPrivateKey(); + if (!(pk instanceof PrivateKey)) + throw new IOException("CA Private key must be a JSS PrivateKey"); + caPrik = (PrivateKey) pk; + } else { + String caPriKeyID = config.getString( + prefix + "signing" + ".privkey.id"); + byte[] keyIDb = CryptoUtil.string2byte(caPriKeyID); + caPrik = CryptoUtil.findPrivateKeyFromID(keyIDb); + } if (caPrik == null) { CMS.debug("CertUtil::createSelfSignedCert() - " diff --git a/base/server/share/conf/schema-subCA.ldif b/base/server/share/conf/schema-subCA.ldif new file mode 100644 index 0000000000000000000000000000000000000000..d03ca70ab466a6229f0efbbe2df6c587d8d5ea5c --- /dev/null +++ b/base/server/share/conf/schema-subCA.ldif @@ -0,0 +1,5 @@ +dn: cn=schema +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif index 475758c5d66bf681e589995505a561bf4e4c40ef..3a692cac9370e9bb115a35fd0fe56be1d49b9ce9 100644 --- a/base/server/share/conf/schema.ldif +++ b/base/server/share/conf/schema.ldif @@ -667,3 +667,13 @@ dn: cn=schema changetype: modify add: objectClasses objectClasses: ( certProfile-oid NAME 'certProfile' DESC 'Certificate profile' SUP top STRUCTURAL MUST cn MAY ( classId $ certProfileConfig ) X-ORIGIN 'user defined' ) + +dn: cn=schema +changetype: modify +add: attributeTypes +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyNickname-oid NAME 'authorityKeyNickname' DESC 'Authority key nickname' SYNTAX 1.3.6.1.4.1.1466.115.121.1.44 X-ORIGIN 'user-defined' ) +attributeTypes: ( authorityParentID-oid NAME 'authorityParentID' DESC 'Authority Parent ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 X-ORIGIN 'user defined' ) +- +add: objectClasses +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname ) MAY ( authorityParentID $ description ) X-ORIGIN 'user defined' ) -- 2.4.3