From 037e62d46fa38d56c558291202b62f0e73e0b5d7 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 28 Jan 2015 02:41:10 -0500 Subject: [PATCH] Lightweight CAs: initial support This commit adds initial support for "lightweight CAs" - CAs that inhabit an existing CA instance and share the request queue and certificate database of the "top-level CA". We initially support only sub-CAs under the top-level CA - either direct sub-CAs or nested. The general design will support hosting unrelated CAs but creation or import of unrelated CAs is not yet implemented. Part of: https://fedorahosted.org/pki/ticket/1213 --- base/ca/shared/conf/acl.ldif | 2 + base/ca/shared/conf/acl.properties | 4 + base/ca/shared/conf/auth-method.properties | 1 + base/ca/shared/conf/db.ldif | 5 + base/ca/shared/webapps/ca/WEB-INF/web.xml | 10 + base/ca/src/com/netscape/ca/CAService.java | 53 +- .../src/com/netscape/ca/CertificateAuthority.java | 682 ++++++++++++++++++--- base/ca/src/com/netscape/ca/SigningUnit.java | 22 +- .../dogtagpki/server/ca/rest/AuthorityService.java | 282 +++++++++ .../dogtagpki/server/ca/rest/CAApplication.java | 3 + .../server/ca/rest/CertRequestService.java | 5 + .../netscape/certsrv/authority/AuthorityData.java | 123 ++++ .../certsrv/authority/AuthorityResource.java | 96 +++ .../src/com/netscape/certsrv/ca/AuthorityID.java | 36 ++ .../netscape/certsrv/ca/CADisabledException.java | 15 + .../netscape/certsrv/ca/CANotFoundException.java | 14 + .../com/netscape/certsrv/ca/CATypeException.java | 16 + .../src/com/netscape/certsrv/ca/ICAService.java | 11 +- .../netscape/certsrv/ca/ICertificateAuthority.java | 68 ++ .../certsrv/ca/IssuerUnavailableException.java | 15 + .../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 | 28 +- .../netscape/cms/profile/def/CAEnrollDefault.java | 4 +- .../com/netscape/cms/servlet/base/PKIService.java | 5 +- .../netscape/cms/servlet/cert/CertRequestDAO.java | 2 +- .../cms/servlet/cert/EnrollmentProcessor.java | 15 +- .../cms/servlet/cert/RequestProcessor.java | 36 +- .../com/netscape/cms/servlet/csadmin/CertUtil.java | 38 +- base/server/share/conf/schema-authority.ldif | 8 + base/server/share/conf/schema.ldif | 13 + 33 files changed, 1507 insertions(+), 132 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/CADisabledException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CANotFoundException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/CATypeException.java create mode 100644 base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java create mode 100644 base/server/share/conf/schema-authority.ldif diff --git a/base/ca/shared/conf/acl.ldif b/base/ca/shared/conf/acl.ldif index 0da10939fc64dc88b32016c308fc13a1bab2d14f..54c9f1d5c64b6578de83f1b7ffdff922a69975f4 100644 --- a/base/ca/shared/conf/acl.ldif +++ b/base/ca/shared/conf/acl.ldif @@ -57,3 +57,5 @@ resourceACLS: certServer.ca.certs:execute:allow (execute) group="Certificate Man resourceACLS: certServer.ca.groups:execute:allow (execute) group="Administrators":Admins may execute group operations resourceACLS: certServer.ca.selftests:read,execute:allow (read,execute) group="Administrators":Only admins can access selftests. resourceACLS: certServer.ca.users:execute:allow (execute) group="Administrators":Admins may execute user operations +resourceACLS: certServer.ca.authorities:list,read:allow (list,read) user="anybody":Anybody may list and read lightweight authorities +resourceACLS: certServer.ca.authorities:create,modify:allow (create,modify) group="Administrators":Administrators may create and modify lightweight authorities diff --git a/base/ca/shared/conf/acl.properties b/base/ca/shared/conf/acl.properties index d14d1832c7cff7db055f2274f1ed30223d16cad8..f0b5b9f650ad2fc4bde531ade94347a7280d3089 100644 --- a/base/ca/shared/conf/acl.properties +++ b/base/ca/shared/conf/acl.properties @@ -21,3 +21,7 @@ securityDomain.installToken = certServer.securitydomain.domainxml,read selftests.read = certServer.ca.selftests,read selftests.execute = certServer.ca.selftests,execute users = certServer.ca.users,execute +authorities.create = certServer.ca.authorities,create +authorities.list = certServer.ca.authorities,list +authorities.modify = certServer.ca.authorities,modify +authorities.read = certServer.ca.authorities,read diff --git a/base/ca/shared/conf/auth-method.properties b/base/ca/shared/conf/auth-method.properties index a213534adcbf427c3c76b9981624edb8c93ea28d..8d67690af88d387f38fd8fcf1c2fdfa8bbb492fe 100644 --- a/base/ca/shared/conf/auth-method.properties +++ b/base/ca/shared/conf/auth-method.properties @@ -8,6 +8,7 @@ default = * account = certUserDBAuthMgr,passwdUserDBAuthMgr +authorities = certUserDBAuthMgr certs = certUserDBAuthMgr certrequests = certUserDBAuthMgr groups = certUserDBAuthMgr diff --git a/base/ca/shared/conf/db.ldif b/base/ca/shared/conf/db.ldif index 8a2e0b07274a83b317fb1ba56e8ef32b96857118..704b8d11be7dcffd7d57fb3ec90c11f3c0ef9cbc 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=authorities,ou=ca,{rootSuffix} +objectClass: top +objectClass: organizationalUnit +ou: authorities diff --git a/base/ca/shared/webapps/ca/WEB-INF/web.xml b/base/ca/shared/webapps/ca/WEB-INF/web.xml index bba40e203463ac97138e6c6749bca8f979711444..628eea2a2d78147560e2ce4bd5eddaf634dd11ea 100644 --- a/base/ca/shared/webapps/ca/WEB-INF/web.xml +++ b/base/ca/shared/webapps/ca/WEB-INF/web.xml @@ -2417,6 +2417,16 @@ + Authority Services + /rest/authorities/* + + + CONFIDENTIAL + + + + + Security Domain Services /rest/securityDomain/installToken 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..e9ac6a9712f58e8024623956d3a6dce6a718a9db 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -23,36 +23,26 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.math.BigInteger; +import java.security.KeyPair; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; 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.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; import javax.servlet.http.HttpSession; -import netscape.security.util.DerOutputStream; -import netscape.security.util.DerValue; -import netscape.security.x509.AlgorithmId; -import netscape.security.x509.CertificateChain; -import netscape.security.x509.CertificateIssuerName; -import netscape.security.x509.CertificateSubjectName; -import netscape.security.x509.CertificateVersion; -import netscape.security.x509.X500Name; -import netscape.security.x509.X509CRLImpl; -import netscape.security.x509.X509CertImpl; -import netscape.security.x509.X509CertInfo; -import netscape.security.x509.X509ExtensionException; -import netscape.security.x509.X509Key; - import org.mozilla.jss.CryptoManager; import org.mozilla.jss.asn1.ASN1Util; import org.mozilla.jss.asn1.GeneralizedTime; @@ -60,6 +50,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 +66,21 @@ 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.CADisabledException; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.CATypeException; 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.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 +95,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 +107,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 +125,29 @@ import com.netscape.cmsutil.ocsp.SingleResponse; import com.netscape.cmsutil.ocsp.TBSRequest; import com.netscape.cmsutil.ocsp.UnknownInfo; +import netscape.ldap.LDAPAttribute; +import netscape.ldap.LDAPAttributeSet; +import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPEntry; +import netscape.ldap.LDAPException; +import netscape.ldap.LDAPModification; +import netscape.ldap.LDAPModificationSet; +import netscape.ldap.LDAPSearchResults; +import netscape.security.util.DerOutputStream; +import netscape.security.util.DerValue; +import netscape.security.x509.AlgorithmId; +import netscape.security.x509.CertificateChain; +import netscape.security.x509.CertificateIssuerName; +import netscape.security.x509.CertificateSubjectName; +import netscape.security.x509.CertificateVersion; +import netscape.security.x509.X500Name; +import netscape.security.x509.X509CRLImpl; +import netscape.security.x509.X509CertImpl; +import netscape.security.x509.X509CertInfo; +import netscape.security.x509.X509ExtensionException; +import netscape.security.x509.X509Key; + + /** * A class represents a Certificate Authority that is * responsible for certificate specific operations. @@ -136,6 +161,13 @@ 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 hostCA = null; + protected AuthorityID authorityID = null; + protected AuthorityID authorityParentID = null; + protected String authorityDescription = null; + protected boolean authorityEnabled = true; + protected ISubsystem mOwner = null; protected IConfigStore mConfig = null; protected ILogger mLogger = CMS.getLogger(); @@ -234,6 +266,41 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori * Constructs a CA subsystem. */ public CertificateAuthority() { + hostCA = this; + } + + /** + * Construct and initialise a lightweight authority + */ + private CertificateAuthority( + CertificateAuthority hostCA, + AuthorityID aid, + AuthorityID parentAID, + String signingKeyNickname, + String authorityDescription, + boolean authorityEnabled + ) throws EBaseException { + setId(hostCA.getId()); + this.hostCA = hostCA; + this.authorityID = aid; + this.authorityParentID = parentAID; + this.authorityDescription = authorityDescription; + this.authorityEnabled = authorityEnabled; + mNickname = signingKeyNickname; + init(hostCA.mOwner, hostCA.mConfig); + } + + public boolean isHostAuthority() { + return hostCA == this; + } + + private void ensureEnabled() throws CADisabledException { + if (!authorityEnabled) + throw new CADisabledException("Authority is disabled"); + } + + public boolean getAuthorityEnabled() { + return authorityEnabled; } /** @@ -334,8 +401,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 (isHostAuthority()) { + 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 = hostCA.mReplicaRepot; + } // init signing unit & CA cert. try { @@ -358,51 +439,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori if (CMS.isPreOpMode()) return; - // set certificate status to 10 minutes - mCertRepot.setCertStatusUpdateInterval( + /* The host CA owns these resources so skip these + * steps for lightweight CAs. + */ + if (isHostAuthority()) { + /* 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 +479,9 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori // being functional. initCRL(); + if (isHostAuthority()) + loadLightweightCAs(); + } catch (EBaseException e) { if (CMS.isPreOpMode()) return; @@ -420,6 +490,37 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + private void initCRLPublisher() throws EBaseException { + // instantiate CRL publisher + if (!isHostAuthority()) { + mByName = hostCA.mByName; + mCRLPublisher = hostCA.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 +641,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mService.startup(); mRequestQueue.recover(); - // Note that this could be null. - - // setup Admin operations - - initNotificationListeners(); - - startPublish(); - // startCRL(); + if (isHostAuthority()) { + // setup Admin operations + initNotificationListeners(); + startPublish(); + } } /** @@ -555,6 +653,10 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori *

*/ public void shutdown() { + // lightweight authorities don't own these resources + if (!isHostAuthority()) + return; + Enumeration enums = mCRLIssuePoints.elements(); while (enums.hasMoreElements()) { CRLIssuingPoint point = (CRLIssuingPoint) enums.nextElement(); @@ -967,6 +1069,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public X509CRLImpl sign(X509CRLImpl crl, String algname) throws EBaseException { + ensureEnabled(); X509CRLImpl signedcrl = null; IStatsSubsystem statsSub = (IStatsSubsystem) CMS.getSubsystem("stats"); @@ -1039,6 +1142,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public X509CertImpl sign(X509CertInfo certInfo, String algname) throws EBaseException { + ensureEnabled(); X509CertImpl signedcert = null; @@ -1123,6 +1227,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ public byte[] sign(byte[] data, String algname) throws EBaseException { + ensureEnabled(); return mSigningUnit.sign(data, algname); } @@ -1228,13 +1333,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 (isHostAuthority() && CrlStore != null && CrlStore.size() > 0) { mCRLSigningUnit = new SigningUnit(); mCRLSigningUnit.init(this, mConfig.getSubStore(PROP_CRL_SIGNING_SUBSTORE)); } else { @@ -1304,7 +1409,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori IConfigStore OCSPStore = mConfig.getSubStore(PROP_OCSP_SIGNING_SUBSTORE); - if (OCSPStore != null && OCSPStore.size() > 0) { + if (isHostAuthority() && 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 +1548,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori /** * init cert & crl database */ - private void initCaDatabases() + private void initCertDatabase() throws EBaseException { + if (!isHostAuthority()) { + mCertRepot = hostCA.mCertRepot; + return; + } + int certdb_inc = mConfig.getInteger(PROP_CERTDB_INC, 5); String certReposDN = mConfig.getString(PROP_CERT_REPOS_DN, null); @@ -1471,8 +1581,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 (!isHostAuthority()) { + mCRLRepot = hostCA.mCRLRepot; + return; + } int crldb_inc = mConfig.getInteger(PROP_CRLDB_INC, 5); @@ -1482,14 +1601,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 +1624,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initPublish() throws EBaseException { + if (!isHostAuthority()) { + mPublisherProcessor = hostCA.mPublisherProcessor; + return; + } + IConfigStore c = null; try { @@ -1676,6 +1792,15 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori */ private void initRequestQueue() throws EBaseException { + if (!isHostAuthority()) { + mPolicy = hostCA.mPolicy; + mService = hostCA.mService; + mNotify = hostCA.mNotify; + mPNotify = hostCA.mPNotify; + mRequestQueue = hostCA.mRequestQueue; + return; + } + mPolicy = new CAPolicy(); mPolicy.init(this, mConfig.getSubStore(PROP_POLICY)); CMS.debug("CA policy inited"); @@ -1734,6 +1859,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori @SuppressWarnings("unchecked") private void initCRL() throws EBaseException { + if (!isHostAuthority()) { + mCRLIssuePoints = hostCA.mCRLIssuePoints; + mMasterCRLIssuePoint = hostCA.mMasterCRLIssuePoint; + return; + } IConfigStore crlConfig = mConfig.getSubStore(PROP_CRL_SUBSTORE); if ((crlConfig == null) || (crlConfig.size() <= 0)) { @@ -1799,6 +1929,109 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori log(ILogger.LL_INFO, "CRL Issuing Points inited"); } + /** + * Find, instantiate and register lightweight CAs. + * + * This method must only be called by the host CA. + */ + private synchronized void loadLightweightCAs() throws EBaseException { + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("loadLightweightCAs"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + String searchDN = "ou=authorities,ou=" + getId() + + "," + getDBSubsystem().getBaseDN(); + LDAPSearchResults results = null; + boolean foundHostAuthority = false; + boolean haveLightweightCAsContainer = true; + 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 nickAttr = entry.getAttribute("authorityKeyNickname"); + LDAPAttribute dnAttr = entry.getAttribute("authorityDN"); + LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID"); + LDAPAttribute parentDNAttr = entry.getAttribute("authorityParentDN"); + + if (aidAttr == null || nickAttr == null || dnAttr == null) + throw new ECAException("Malformed authority object; required attribute(s) missing: " + entry.getDN()); + + AuthorityID aid = new AuthorityID((String) + aidAttr.getStringValues().nextElement()); + + X500Name dn = null; + try { + dn = new X500Name((String) dnAttr.getStringValues().nextElement()); + } catch (IOException e) { + throw new ECAException("Malformed authority object; invalid authorityDN: " + entry.getDN()); + } + + String desc = null; + LDAPAttribute descAttr = entry.getAttribute("description"); + if (descAttr != null) + desc = (String) descAttr.getStringValues().nextElement(); + + if (dn.equals(mName)) { + foundHostAuthority = true; + this.authorityID = aid; + this.authorityDescription = desc; + caMap.put(aid, this); + continue; + } + + @SuppressWarnings("unused") + X500Name parentDN = null; + if (parentDNAttr != null) { + try { + parentDN = new X500Name((String) parentDNAttr.getStringValues().nextElement()); + } catch (IOException e) { + throw new ECAException("Malformed authority object; invalid authorityParentDN: " + entry.getDN()); + } + } + + String keyNick = (String) nickAttr.getStringValues().nextElement(); + AuthorityID parentAID = null; + if (parentAIDAttr != null) + parentAID = new AuthorityID((String) + parentAIDAttr.getStringValues().nextElement()); + + boolean enabled = true; + LDAPAttribute enabledAttr = entry.getAttribute("authorityEnabled"); + if (enabledAttr != null) { + String enabledString = (String) + enabledAttr.getStringValues().nextElement(); + enabled = enabledString.equalsIgnoreCase("TRUE"); + } + + CertificateAuthority ca = new CertificateAuthority( + this, aid, parentAID, keyNick, desc, enabled); + caMap.put(aid, ca); + } + } catch (LDAPException e) { + if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) { + CMS.debug( + "Missing lightweight CAs container '" + searchDN + + "'. Disabling lightweight CAs."); + haveLightweightCAsContainer = false; + } else { + throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e); + } + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + if (haveLightweightCAsContainer && !foundHostAuthority) { + CMS.debug("loadLightweightCAs: no entry for host authority"); + CMS.debug("loadLightweightCAs: adding entry for host authority"); + caMap.put(addHostAuthorityEntry(), this); + } + } + public String getOfficialName() { return OFFICIAL_NAME; } @@ -1960,6 +2193,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } private BasicOCSPResponse sign(ResponseData rd) throws EBaseException { + ensureEnabled(); try (DerOutputStream out = new DerOutputStream()) { DerOutputStream tmp = new DerOutputStream(); @@ -2083,4 +2317,312 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori return new SingleResponse(cid, certStatus, thisUpdate, nextUpdate); } + + /** + * Enumerate all authorities (including host authority) + */ + public synchronized List getCAs() { + List cas = new ArrayList<>(); + for (ICertificateAuthority ca : caMap.values()) { + cas.add(ca); + } + return cas; + } + + /** + * Get authority by ID. + * + * @param aid The ID of the CA to retrieve, or null + * to retreive the host authority. + * + * @return the authority, or null if not found + */ + public ICertificateAuthority getCA(AuthorityID aid) { + return aid == null ? hostCA : caMap.get(aid); + } + + public ICertificateAuthority getCA(X500Name dn) { + for (ICertificateAuthority ca : getCAs()) { + if (ca.getX500Name().equals(dn)) + return ca; + } + return null; + } + + public AuthorityID getAuthorityID() { + return authorityID; + } + + public AuthorityID getAuthorityParentID() { + return authorityParentID; + } + + public String getAuthorityDescription() { + return authorityDescription; + } + + /** + * Create a new lightweight authority. + * + * @param subjectDN Subject DN for new CA + * @param parentAID ID of parent 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 ca = parentCA.createSubCA( + subjectDN, description); + synchronized (this) { + caMap.put(ca.getAuthorityID(), ca); + } + return ca; + } + + private void ensureAuthorityDNAvailable(X500Name dn) + throws IssuerUnavailableException { + for (ICertificateAuthority ca : getCAs()) { + if (ca.getAuthorityEnabled() && ca.getX500Name().equals(dn)) + throw new IssuerUnavailableException( + "DN '" + dn + "' is in use by an enabled authority"); + } + } + + /** + * Create a new lightweight authority signed by this authority. + * + * 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 requested DN + X500Name subjectX500Name = null; + try { + subjectX500Name = new X500Name(subjectDN); + } catch (IOException e) { + throw new IllegalArgumentException( + "Invalid Subject DN: " + subjectDN); + } + ensureAuthorityDNAvailable(subjectX500Name); + + // generate authority ID and nickname + AuthorityID aid = new AuthorityID(); + String aidString = aid.toString(); + String nickname = hostCA.getNickname() + " " + aidString; + + // build database entry + String dn = "cn=" + aidString + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + CMS.debug("createSubCA: DN = " + dn); + String parentDNString = null; + try { + parentDNString = mName.toLdapDNString(); + } catch (IOException e) { + throw new EBaseException("Failed to convert issuer DN to string: " + e); + } + + LDAPAttribute[] attrs = { + new LDAPAttribute("objectclass", "authority"), + new LDAPAttribute("cn", aidString), + new LDAPAttribute("authorityID", aidString), + new LDAPAttribute("authorityKeyNickname", nickname), + new LDAPAttribute("authorityEnabled", "TRUE"), + new LDAPAttribute("authorityDN", subjectDN), + new LDAPAttribute("authorityParentDN", parentDNString) + }; + 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 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 lightweight CA certificate: " + e); + } + } catch (LDAPException e) { + throw new EBaseException("Error adding authority entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + return new CertificateAuthority( + hostCA, aid, this.authorityID, nickname, description, true); + } + + /** + * Add an LDAP entry for the host authority. + * + * This method also sets the authorityID and authorityDescription + * fields. + * + * It is the caller's responsibility to add the returned + * AuthorityID to the caMap. + */ + private AuthorityID addHostAuthorityEntry() throws EBaseException { + if (!isHostAuthority()) + throw new EBaseException("Can only invoke from host CA"); + + // generate authority ID + AuthorityID aid = new AuthorityID(); + String aidString = aid.toString(); + + // build database entry + String dn = "cn=" + aidString + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + String dnString = null; + try { + dnString = mName.toLdapDNString(); + } catch (IOException e) { + throw new EBaseException("Failed to convert issuer DN to string: " + e); + } + + String desc = "Host authority"; + LDAPAttribute[] attrs = { + new LDAPAttribute("objectclass", "authority"), + new LDAPAttribute("cn", aidString), + new LDAPAttribute("authorityID", aidString), + new LDAPAttribute("authorityKeyNickname", getNickname()), + new LDAPAttribute("authorityEnabled", "TRUE"), + new LDAPAttribute("authorityDN", dnString), + new LDAPAttribute("description", desc) + }; + LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); + LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("addHostAuthorityEntry"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + + try { + conn.add(ldapEntry); + } catch (LDAPException e) { + throw new ELdapException("Error adding host authority entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + this.authorityID = aid; + this.authorityDescription = desc; + return aid; + } + + /** + * Update lightweight authority attributes. + * + * Pass null values to exclude an attribute from the update. + * + * If a passed value matches the current value, it is excluded + * from the update. + * + * To remove optional string values, pass the empty string. + */ + public void modifyAuthority(Boolean enabled, String desc) + throws EBaseException { + if (isHostAuthority() && enabled != null && !enabled) + throw new CATypeException("Cannot disable the host CA"); + + LDAPModificationSet mods = new LDAPModificationSet(); + + boolean nextEnabled = authorityEnabled; + if (enabled != null && enabled.booleanValue() != authorityEnabled) { + if (enabled) + ensureAuthorityDNAvailable(mName); + mods.add( + LDAPModification.REPLACE, + new LDAPAttribute("authorityEnabled", enabled ? "TRUE" : "FALSE")); + nextEnabled = enabled; + } + + String nextDesc = authorityDescription; + if (desc != null) { + if (!desc.isEmpty() && authorityDescription != null + && !desc.equals(authorityDescription)) { + mods.add( + LDAPModification.REPLACE, + new LDAPAttribute("description", desc)); + nextDesc = desc; + } else if (desc.isEmpty() && authorityDescription != null) { + mods.add( + LDAPModification.DELETE, + new LDAPAttribute("description", authorityDescription)); + nextDesc = null; + } else if (!desc.isEmpty() && authorityDescription == null) { + mods.add( + LDAPModification.ADD, + new LDAPAttribute("description", desc)); + nextDesc = desc; + } + } + + if (mods.size() > 0) { + String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou=" + + getId() + "," + getDBSubsystem().getBaseDN(); + + // connect to database + ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("updateAuthority"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + LDAPConnection conn = dbFactory.getConn(); + try { + conn.modify(dn, mods); + } catch (LDAPException e) { + throw new EBaseException("Error adding authority entry to database: " + e); + } finally { + dbFactory.returnConn(conn); + dbFactory.reset(); + } + + // update was successful; update CA's state + authorityEnabled = nextEnabled; + authorityDescription = nextDesc; + } + } + } 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..820f8ab6499eed9fdb8e3d8d782df64c71ad1fc3 --- /dev/null +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -0,0 +1,282 @@ +//--- 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.ByteArrayOutputStream; +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +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.UriInfo; + +import com.netscape.certsrv.apps.CMS; +import com.netscape.certsrv.authority.AuthorityData; +import com.netscape.certsrv.authority.AuthorityResource; +import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; +import com.netscape.certsrv.base.EBaseException; +import com.netscape.certsrv.base.ForbiddenException; +import com.netscape.certsrv.base.PKIException; +import com.netscape.certsrv.base.ResourceNotFoundException; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.CATypeException; +import com.netscape.certsrv.ca.ICertificateAuthority; +import com.netscape.certsrv.ca.IssuerUnavailableException; +import com.netscape.cms.servlet.base.PKIService; +import com.netscape.cmsutil.util.Utils; + +/** + * @author ftweedal + */ +public class AuthorityService extends PKIService implements AuthorityResource { + + ICertificateAuthority hostCA; + + public AuthorityService() { + hostCA = (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 : hostCA.getCAs()) + results.add(readAuthorityData(ca)); + + GenericEntity> entity = + new GenericEntity>(results) {}; + return Response.ok(entity).build(); + } + + @Override + public Response getCA(String aidString) { + ICertificateAuthority ca = hostCA; + + if (!AuthorityResource.HOST_AUTHORITY.equals(aidString)) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ca = hostCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + } + + return createOKResponse(readAuthorityData(ca)); + } + + @Override + public Response getCert(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = hostCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + return Response.ok(ca.getCaX509Cert().getEncoded()).build(); + } catch (CertificateEncodingException e) { + // this really is a 500 Internal Server Error + throw new PKIException("Error encoding certificate: " + e); + } + } + + @Override + public Response getCertPEM(String aidString) { + byte[] der = (byte[]) getCert(aidString).getEntity(); + return Response.ok(toPem("CERTIFICATE", der)).build(); + } + + @Override + public Response getChain(String aidString) { + AuthorityID aid; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = hostCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + ca.getCACertChain().encode(out); + } catch (IOException e) { + throw new PKIException("Error encoding certificate chain: " + e); + } + + return Response.ok(out.toByteArray()).build(); + } + + @Override + public Response getChainPEM(String aidString) { + byte[] der = (byte[]) getCert(aidString).getEntity(); + return Response.ok(toPem("PKCS7", der)).build(); + } + + @Override + public Response createCA(AuthorityData data) { + String parentAIDString = data.getParentID(); + AuthorityID parentAID = null; + try { + parentAID = new AuthorityID(parentAIDString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad Authority ID: " + parentAIDString); + } + + try { + ICertificateAuthority subCA = hostCA.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 CA: " + e.toString()); + } + } + + @Override + public Response modifyCA(String aidString, AuthorityData data) { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Bad CA ID: " + aidString); + } + + ICertificateAuthority ca = hostCA.getCA(aid); + if (ca == null) + throw new ResourceNotFoundException("CA \"" + aidString + "\" not found"); + + try { + ca.modifyAuthority(data.getEnabled(), data.getDescription()); + return createOKResponse(readAuthorityData(ca)); + } catch (CATypeException e) { + throw new ForbiddenException(e.toString()); + } catch (IssuerUnavailableException e) { + throw new ConflictingOperationException(e.toString()); + } catch (EBaseException e) { + CMS.debug(e); + throw new PKIException("Error modifying authority: " + e.toString()); + } + } + + @Override + public Response enableCA(String aidString) { + return modifyCA( + aidString, + new AuthorityData(null, null, null, null, true, null)); + } + + @Override + public Response disableCA(String aidString) { + return modifyCA( + aidString, + new AuthorityData(null, null, null, null, false, null)); + } + + 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( + ca.isHostAuthority(), + dn, + ca.getAuthorityID().toString(), + parentAID != null ? parentAID.toString() : null, + ca.getAuthorityEnabled(), + ca.getAuthorityDescription() + ); + } + + private String toPem(String name, byte[] data) { + return "-----BEGIN " + name + "-----\n" + + Utils.base64encode(data) + + "-----END " + name + "-----\n"; + } + + /* 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/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java index 95f1f4c20086ddb45846f65b1db157bff238708a..1da1ce1713541e52164e9e8fbcbf39ca2332540d 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -38,9 +38,11 @@ import com.netscape.certsrv.authentication.EAuthException; import com.netscape.certsrv.authorization.EAuthzException; import com.netscape.certsrv.base.BadRequestDataException; import com.netscape.certsrv.base.BadRequestException; +import com.netscape.certsrv.base.ConflictingOperationException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.base.UnauthorizedException; +import com.netscape.certsrv.ca.CADisabledException; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; import com.netscape.certsrv.cert.CertRequestInfos; @@ -210,6 +212,9 @@ public class CertRequestService extends PKIService implements CertRequestResourc } catch (BadRequestDataException e) { CMS.debug("changeRequestState: bad request data: " + e); throw new BadRequestException(e.toString()); + } catch (CADisabledException e) { + CMS.debug("changeRequestState: CA disabled: " + e); + throw new ConflictingOperationException(e.toString()); } catch (EPropertyException e) { CMS.debug("changeRequestState: execution error " + e); throw new PKIException(CMS.getUserMessage(getLocale(headers), 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..2312c39895c09a4b7dbf994d43c2c068eeaec2d4 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java @@ -0,0 +1,123 @@ +// --- 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.XmlRootElement; +import org.jboss.resteasy.plugins.providers.atom.Link; + +@XmlRootElement(name = "authority") +@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 Boolean isHostAuthority; + + public Boolean getIsHostAuthority() { + return isHostAuthority; + } + + + @XmlAttribute + protected String id; + + public String getID() { + return id; + } + + + @XmlAttribute + protected String parentID; + + public String getParentID() { + return parentID; + } + + + @XmlAttribute + protected String dn; + + public String getDN() { + return dn; + } + + + @XmlAttribute + protected Boolean enabled; + + public Boolean getEnabled() { + return enabled; + } + + + @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( + Boolean isHostAuthority, + String dn, String id, String parentID, + Boolean enabled, String description) { + this.isHostAuthority = isHostAuthority; + this.dn = dn; + this.id = id; + this.parentID = parentID; + this.enabled = enabled; + 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..eaef903db444512dbea6c87b11800130d94a944d --- /dev/null +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityResource.java @@ -0,0 +1,96 @@ +package com.netscape.certsrv.authority; + +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +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 { + + public static final String HOST_AUTHORITY = "host-authority"; + + @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); + + @GET + @Path("{id}/cert") + @Produces("application/pkix-cert") + @ClientResponseType(entityType=byte[].class) + public Response getCert(@PathParam("id") String caIDString); + + @GET + @Path("{id}/cert") + @Produces("application/x-pem-file") + @ClientResponseType(entityType=String.class) + public Response getCertPEM(@PathParam("id") String caIDString); + + @GET + @Path("{id}/chain") + @Produces("application/pkcs7-mime") + @ClientResponseType(entityType=byte[].class) + public Response getChain(@PathParam("id") String caIDString); + + @GET + @Path("{id}/chain") + @Produces("application/x-pem-file") + @ClientResponseType(entityType=String.class) + public Response getChainPEM(@PathParam("id") String caIDString); + + @POST + @ClientResponseType(entityType=AuthorityData.class) + @AuthMethodMapping("authorities") + @ACLMapping("authorities.create") + public Response createCA(AuthorityData data); + + /** + * Modify a CA (supports partial updates). + * + * isHostEnabled, authorityID, authorityParentID and DN are + * immutable; differences in these values are ignored. + * + * Other values, if null, are ignored, otherwise they are + * set to the new value. To remove the description, use an + * empty string. + */ + @PUT + @Path("{id}") + @ClientResponseType(entityType=AuthorityData.class) + @AuthMethodMapping("authorities") + @ACLMapping("authorities.modify") + public Response modifyCA( + @PathParam("id") String caIDString, + AuthorityData data); + + @POST + @Path("{id}/enable") + @ClientResponseType(entityType=AuthorityData.class) + @AuthMethodMapping("authorities") + @ACLMapping("authorities.modify") + public Response enableCA(@PathParam("id") String caIDString); + + @POST + @Path("{id}/disable") + @ClientResponseType(entityType=AuthorityData.class) + @AuthMethodMapping("authorities") + @ACLMapping("authorities.modify") + public Response disableCA(@PathParam("id") String caIDString); + +} 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..daac587b75843f5faf75e556d4d0135c8ffc8fd7 --- /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) { + if (s == null) + throw new IllegalArgumentException("null AuthorityID string"); + uuid = UUID.fromString(s); + } + + /** + * Construct a random AuthorityID + */ + public AuthorityID() { + uuid = UUID.randomUUID(); + } + + public String toString() { + return uuid.toString(); + } + + public int compareTo(AuthorityID aid) { + return uuid.compareTo(aid.uuid); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CADisabledException.java b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java new file mode 100644 index 0000000000000000000000000000000000000000..9b3f16b90a8d28b87e993575037a2a19517e17b9 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CADisabledException.java @@ -0,0 +1,15 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot perform an operation + * because it is disabled. + */ +public class CADisabledException extends ECAException { + + private static final long serialVersionUID = -8827509070155037699L; + + public CADisabledException(String msgFormat) { + super(msgFormat); + } + +} 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..f292077ece3089d398ad0cf3a008b7e5218a6bcd --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CANotFoundException.java @@ -0,0 +1,14 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA cannot be found. + */ +public class CANotFoundException extends ECAException { + + private static final long serialVersionUID = -4618887355685066120L; + + public CANotFoundException(String msgFormat) { + super(msgFormat); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/CATypeException.java b/base/common/src/com/netscape/certsrv/ca/CATypeException.java new file mode 100644 index 0000000000000000000000000000000000000000..19eb680e88aee3baac51abd95439c46487bf5720 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CATypeException.java @@ -0,0 +1,16 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when an operation cannot be completed + * because the CA is the wrong type (e.g., an operation that + * only applies to lightweight CAs). + */ +public class CATypeException extends ECAException { + + private static final long serialVersionUID = -6004456461295692150L; + + public CATypeException(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..31d5c9277a63ca7c916f39651300b0c9a9061c1e 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,71 @@ public interface ICertificateAuthority extends ISubsystem { public CertificateIssuerName getIssuerObj(); public CertificateSubjectName getSubjectObj(); + + /** + * Enumerate all authorities, including host authority. + */ + public List getCAs(); + + /** + * Return whether this CA is the host authority (not a + * lightweight authority). + */ + public boolean isHostAuthority(); + + /** + * Get the AuthorityID of this CA. + */ + public AuthorityID getAuthorityID(); + + /** + * Get the AuthorityID of this CA's parent CA, if available. + */ + public AuthorityID getAuthorityParentID(); + + /** + * Return CA description. May be null. + */ + public boolean getAuthorityEnabled(); + + /** + * 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); + + /** + * Get the CA by DN. Returns null if CA not found. + */ + public ICertificateAuthority getCA(X500Name dn); + + /** + * 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; + + /** + * Update authority configurables. + * + * @param enabled Whether CA is enabled or disabled + * @param desc Description; null or empty removes it + */ + public void modifyAuthority(Boolean enabled, 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..75bf88251bfadb687c7bf85944a806339f6f3b9f --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/IssuerUnavailableException.java @@ -0,0 +1,15 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw during CA creation when requested CA + * (issuer DN) already exists. + */ +public class IssuerUnavailableException extends ECAException { + + private static final long serialVersionUID = -6247493607604418446L; + + 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..12667120e3d87deecb786965b4abcef492ac556d 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 (absense implies host authority) + */ + 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..a5d641e93f9aca9cd47dac3084227dbc637d83d9 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 private 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..bd71a4ef8cf710008fc861a022a553d5064c37ba 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 @@ -20,20 +20,23 @@ package com.netscape.cms.profile.def; import java.io.IOException; import java.util.Locale; -import netscape.security.x509.AuthorityKeyIdentifierExtension; -import netscape.security.x509.KeyIdentifier; -import netscape.security.x509.PKIXExtensions; -import netscape.security.x509.X509CertInfo; - import com.netscape.certsrv.apps.CMS; 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; import com.netscape.certsrv.property.IDescriptor; import com.netscape.certsrv.request.IRequest; +import netscape.security.x509.AuthorityKeyIdentifierExtension; +import netscape.security.x509.KeyIdentifier; +import netscape.security.x509.PKIXExtensions; +import netscape.security.x509.X509CertInfo; + /** * This class implements an enrollment default policy * that populates Authority Key Identifier extension @@ -161,18 +164,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/base/PKIService.java b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java index 4ebf075cb709e813fb6a919c507e9847455e70b2..fe77fd567922a49641938cde99d533c091398b75 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java +++ b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java @@ -56,7 +56,10 @@ public class PKIService { public static List MESSAGE_FORMATS = Arrays.asList( MediaType.APPLICATION_XML_TYPE, MediaType.APPLICATION_JSON_TYPE, - MediaType.APPLICATION_FORM_URLENCODED_TYPE + MediaType.APPLICATION_FORM_URLENCODED_TYPE, + MediaType.valueOf("application/pkix-cert"), + MediaType.valueOf("application/pkcs7-mime"), + MediaType.valueOf("application/x-pem-file") ); public final static int MIN_FILTER_LENGTH = 3; diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java index c94ee14961ef39681a53f506b24e4ca5ab06a27e..27d8b8262cb7bbcffa3706cba5318ca8aa0ad75b 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/CertRequestDAO.java @@ -175,7 +175,7 @@ public class CertRequestDAO extends CMSRequestDAO { results = processor.processRenewal(data, request); } else { EnrollmentProcessor processor = new EnrollmentProcessor("caProfileSubmit", locale); - results = processor.processEnrollment(data, request); + results = processor.processEnrollment(data, request, null); } IRequest reqs[] = (IRequest[]) results.get(CAProcessor.ARG_REQUESTS); 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..e5b9a14df99f29da8ad5c4f76c088c98ff766540 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,8 @@ 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.profile.IEnrollProfile; import com.netscape.certsrv.profile.IProfile; import com.netscape.certsrv.profile.IProfileAuthenticator; import com.netscape.certsrv.profile.IProfileContext; @@ -98,7 +100,7 @@ public class EnrollmentProcessor extends CertProcessor { } CertEnrollmentRequest data = CertEnrollmentRequestFactory.create(cmsReq, profile, locale); - return processEnrollment(data, cmsReq.getHttpReq()); + return processEnrollment(data, cmsReq.getHttpReq(), null); } /** @@ -118,8 +120,11 @@ public class EnrollmentProcessor extends CertProcessor { * @param cmsReq the object holding the request and response information * @exception EBaseException an error has occurred */ - public HashMap processEnrollment(CertEnrollmentRequest data, HttpServletRequest request) - throws EBaseException { + public HashMap processEnrollment( + CertEnrollmentRequest data, + HttpServletRequest request, + AuthorityID aid) + throws EBaseException { try { if (CMS.debugOn()) { @@ -146,6 +151,10 @@ public class EnrollmentProcessor extends CertProcessor { } IProfileContext ctx = profile.createContext(); + + if (aid != null) + ctx.set(IEnrollProfile.REQUEST_AUTHORITY_ID, aid.toString()); + CMS.debug("EnrollmentProcessor: set Inputs into profile Context"); setInputsIntoContext(data, profile, ctx); diff --git a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java index 2826f477e358a5e16657e985d7f13079cdb14a33..8558ec23f6489407dc5f41951a363d22548851c0 100644 --- a/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java +++ b/base/server/cms/src/com/netscape/cms/servlet/cert/RequestProcessor.java @@ -36,6 +36,10 @@ import com.netscape.certsrv.base.BadRequestException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.EPropertyNotFound; import com.netscape.certsrv.base.IConfigStore; +import com.netscape.certsrv.ca.AuthorityID; +import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CANotFoundException; +import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertReviewResponse; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.profile.EDeferException; @@ -327,6 +331,31 @@ public class RequestProcessor extends CertProcessor { } /** + * Ensure validity of AuthorityID and that CA exists and is enabled. + */ + private void ensureCAEnabled(String aidString) throws EBaseException { + AuthorityID aid = null; + try { + aid = new AuthorityID(aidString); + } catch (IllegalArgumentException e) { + // this shouldn't happen because request was already accepted + throw new BadRequestDataException("Invalid AuthorityID in request data"); + } + ICertificateAuthority ca = (ICertificateAuthority) + CMS.getSubsystem("ca"); + if (ca == null) + // this shouldn't happen + throw new CANotFoundException("Could not get host authority"); // shouldn't happen + ca = ca.getCA(aid); + if (ca == null) + // this shouldn't happen because request was already accepted + throw new CANotFoundException("Unknown CA: " + aidString); + if (!ca.getAuthorityEnabled()) + // authority was disabled after request was accepted + throw new CADisabledException("CA '" + aidString + "' is disabled"); + } + + /** * Approve request *

* @@ -346,11 +375,16 @@ public class RequestProcessor extends CertProcessor { * occurred */ private void approveRequest(IRequest req, CertReviewResponse data, IProfile profile, Locale locale) - throws EProfileException { + throws EBaseException { String auditMessage = null; String auditSubjectID = auditSubjectID(); String auditRequesterID = auditRequesterID(req); + // ensure target CA is enabled + String aidString = req.getExtDataInString(IEnrollProfile.REQUEST_AUTHORITY_ID); + if (aidString != null) + ensureCAEnabled(aidString); + try { profile.execute(req); req.setRequestStatus(RequestStatus.COMPLETE); 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-authority.ldif b/base/server/share/conf/schema-authority.ldif new file mode 100644 index 0000000000000000000000000000000000000000..7d261f18fbc9475983bf93b1cddcc184d7f9d178 --- /dev/null +++ b/base/server/share/conf/schema-authority.ldif @@ -0,0 +1,8 @@ +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' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' ) diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif index 475758c5d66bf681e589995505a561bf4e4c40ef..a15601ae7a362635bc398b92b9bfda1c72f0dfc8 100644 --- a/base/server/share/conf/schema.ldif +++ b/base/server/share/conf/schema.ldif @@ -667,3 +667,16 @@ 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' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'user defined' ) +- +add: objectClasses +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ description ) X-ORIGIN 'user defined' ) -- 2.4.3