From 5a34e0153e42cb8dabb5d44091e2a7b67fcd2d77 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 9 Mar 2016 02:18:41 -0500 Subject: [PATCH 84/86] Lightweight CAs: monitor database for changes Implement a thread that performs an LDAP persistent search to keep a running CA's view of lightweight CAs in sync with the database. Signing key replication is not yet supported; this will be implemented in a later patch and will not use the database to propagate keys. Part of: https://fedorahosted.org/pki/ticket/1625 --- .../src/com/netscape/ca/CertificateAuthority.java | 693 ++++++++++++++------- base/ca/src/com/netscape/ca/SigningUnit.java | 3 +- .../netscape/certsrv/ca/CAMissingKeyException.java | 15 + 3 files changed, 490 insertions(+), 221 deletions(-) create mode 100644 base/common/src/com/netscape/certsrv/ca/CAMissingKeyException.java diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 63c7ca4e4a8083dc58b54196af89cc7629e9fd97..775abea7c1501ecb84836d10c78b9027851ccb20 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -43,7 +43,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; +import java.util.TreeSet; import java.util.Vector; +import java.util.concurrent.CountDownLatch; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -51,11 +53,18 @@ import javax.servlet.http.HttpSession; import netscape.ldap.LDAPAttribute; import netscape.ldap.LDAPAttributeSet; import netscape.ldap.LDAPConnection; +import netscape.ldap.LDAPConstraints; +import netscape.ldap.LDAPControl; import netscape.ldap.LDAPEntry; import netscape.ldap.LDAPException; import netscape.ldap.LDAPModification; import netscape.ldap.LDAPModificationSet; +import netscape.ldap.LDAPSearchConstraints; import netscape.ldap.LDAPSearchResults; +import netscape.ldap.controls.LDAPEntryChangeControl; +import netscape.ldap.controls.LDAPPersistSearchControl; +import netscape.ldap.util.DN; + import netscape.security.pkcs.PKCS10; import netscape.security.util.DerOutputStream; import netscape.security.util.DerValue; @@ -101,6 +110,7 @@ import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.CADisabledException; import com.netscape.certsrv.ca.CAEnabledException; +import com.netscape.certsrv.ca.CAMissingKeyException; import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.CANotLeafException; import com.netscape.certsrv.ca.CATypeException; @@ -150,6 +160,8 @@ 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.ldap.LDAPPostReadControl; +import com.netscape.cmsutil.ldap.LDAPUtil; import com.netscape.cmsutil.ocsp.BasicOCSPResponse; import com.netscape.cmsutil.ocsp.CertID; import com.netscape.cmsutil.ocsp.CertStatus; @@ -176,11 +188,13 @@ import com.netscape.cmsutil.ocsp.UnknownInfo; * @author lhsiao * @version $Revision$, $Date$ */ -public class CertificateAuthority implements ICertificateAuthority, ICertAuthority, IOCSPService { +public class CertificateAuthority + implements ICertificateAuthority, ICertAuthority, IOCSPService, Runnable { public static final String OFFICIAL_NAME = "Certificate Manager"; public final static OBJECT_IDENTIFIER OCSP_NONCE = new OBJECT_IDENTIFIER("1.3.6.1.5.5.7.48.1.2"); + private static ILdapConnFactory dbFactory = null; private static final Map caMap = Collections.synchronizedSortedMap(new TreeMap()); protected CertificateAuthority hostCA = null; @@ -188,6 +202,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori protected AuthorityID authorityParentID = null; protected String authorityDescription = null; protected boolean authorityEnabled = true; + private boolean hasKeys = false; protected ISubsystem mOwner = null; protected IConfigStore mConfig = null; @@ -283,6 +298,19 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori private boolean mUseNonces = true; private int mMaxNonces = 100; + /* Variables to manage loading and tracking of lightweight CAs */ + private boolean stopped = false; + private static boolean foundHostAuthority = false; + private static Integer initialNumAuthorities = null; + private static int numAuthoritiesLoaded = 0; + private static CountDownLatch initialLoadDone = new CountDownLatch(1); + + /* Maps and sets of entryUSNs and nsUniqueIds for avoiding race + * conditions and unnecessary reloads related to replication */ + private static TreeMap entryUSNs = new TreeMap<>(); + private static TreeMap nsUniqueIds = new TreeMap<>(); + private static TreeSet deletedNsUniqueIds = new TreeSet<>(); + /** * Constructs a CA subsystem. */ @@ -422,6 +450,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mOwner = owner; mConfig = config; + if (isHostAuthority()) { + dbFactory = CMS.getLdapBoundConnFactory("CertificateAuthority"); + dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); + } + // init cert & crl database initCertDatabase(); initCrlDatabase(); @@ -500,9 +533,23 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori // being functional. initCRL(); - if (isHostAuthority()) - loadLightweightCAs(); + if (isHostAuthority() && haveLightweightCAsContainer()) { + new Thread(this, "authorityMonitor").start(); + try { + initialLoadDone.await(); + } catch (InterruptedException e) { + CMS.debug("CertificateAuthority: caught InterruptedException " + + "while waiting for initial load of authorities."); + } + if (!foundHostAuthority) { + CMS.debug("loadLightweightCAs: no entry for host authority"); + CMS.debug("loadLightweightCAs: adding entry for host authority"); + caMap.put(addHostAuthorityEntry(), this); + } + + CMS.debug("CertificateAuthority: finished init of host authority"); + } } catch (EBaseException e) { if (CMS.isPreOpMode()) return; @@ -511,6 +558,24 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + private String authorityBaseDN() { + return "ou=authorities,ou=" + getId() + + "," + getDBSubsystem().getBaseDN(); + } + + private boolean haveLightweightCAsContainer() throws ELdapException { + LDAPConnection conn = dbFactory.getConn(); + try { + LDAPSearchResults results = conn.search( + authorityBaseDN(), LDAPConnection.SCOPE_BASE, null, null, false); + return results != null; + } catch (LDAPException e) { + return false; + } finally { + dbFactory.returnConn(conn); + } + } + private void initCRLPublisher() throws EBaseException { // instantiate CRL publisher if (!isHostAuthority()) { @@ -696,6 +761,22 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori if (mPublisherProcessor != null) { mPublisherProcessor.shutdown(); } + + /* Stop the activityMonitor thread + * + * dbFactory.reset() will disconnect all connections, + * causing the current conn.search() to throw. + * The search will not be restarted because 'stopped' has + * set, and the monitor thread will exit. + */ + stopped = true; + try { + dbFactory.reset(); + } catch (ELdapException e) { + CMS.debug("CertificateAuthority.shutdown: failed to reset " + + "dbFactory: " + e); + // not much else we can do here. + } } /** @@ -1354,7 +1435,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori mIssuerObj = new CertificateIssuerName((X500Name)mSubjectObj.get(CertificateIssuerName.DN_NAME)); } - mSigningUnit.init(this, caSigningCfg, mNickname); + try { + mSigningUnit.init(this, caSigningCfg, mNickname); + hasKeys = true; + } catch (CAMissingKeyException e) { + CMS.debug("CA signing key not (yet) present in NSSDB"); + return; + } CMS.debug("CA signing unit inited"); // for identrus @@ -1950,109 +2037,6 @@ 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 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; } @@ -2499,8 +2483,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori String nickname = hostCA.getNickname() + " " + aidString; // build database entry - String dn = "cn=" + aidString + ",ou=authorities,ou=" - + getId() + "," + getDBSubsystem().getBaseDN(); + String dn = "cn=" + aidString + "," + authorityBaseDN(); CMS.debug("createSubCA: DN = " + dn); String parentDNString = null; try { @@ -2526,77 +2509,70 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori 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(); + commitAuthority(aid, ldapEntry); try { - // add entry to database - conn.add(ldapEntry); + // 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); + // Create pkcs10 request + CMS.debug("createSubCA: creating pkcs10 request"); + PKCS10 pkcs10 = new PKCS10(x509key); + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initSign(keypair.getPrivate()); + pkcs10.encodeAndSign( + new X500Signer(signature, subjectX500Name)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + pkcs10.print(new PrintStream(out)); + String pkcs10String = out.toString(); + + // Sign certificate + Locale locale = Locale.getDefault(); + String profileId = "caCACert"; + IProfileSubsystem ps = (IProfileSubsystem) + CMS.getSubsystem(IProfileSubsystem.ID); + IProfile profile = ps.getProfile(profileId); + ArgBlock argBlock = new ArgBlock(); + argBlock.set("cert_request_type", "pkcs10"); + argBlock.set("cert_request", pkcs10String); + CertEnrollmentRequest certRequest = + CertEnrollmentRequestFactory.create(argBlock, profile, locale); + EnrollmentProcessor processor = + new EnrollmentProcessor("createSubCA", locale); + Map resultMap = processor.processEnrollment( + certRequest, null, authorityID, null, authToken); + IRequest requests[] = (IRequest[]) resultMap.get(CAProcessor.ARG_REQUESTS); + IRequest request = requests[0]; + Integer result = request.getExtDataInInteger(IRequest.RESULT); + if (result != null && !result.equals(IRequest.RES_SUCCESS)) + throw new EBaseException("createSubCA: certificate request submission resulted in error: " + result); + RequestStatus requestStatus = request.getRequestStatus(); + if (requestStatus != RequestStatus.COMPLETE) + throw new EBaseException("createSubCA: certificate request did not complete; status: " + requestStatus); + + // Add certificate to nssdb + X509CertImpl cert = request.getExtDataInCert(IEnrollProfile.REQUEST_ISSUED_CERT); + cryptoManager.importCertPackage(cert.getEncoded(), nickname); + } catch (Exception e) { + // something went wrong; delete just-added entry + CMS.debug("Error creating lightweight CA certificate"); + CMS.debug(e); 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); - - // Create pkcs10 request - CMS.debug("createSubCA: creating pkcs10 request"); - PKCS10 pkcs10 = new PKCS10(x509key); - Signature signature = Signature.getInstance("SHA256withRSA"); - signature.initSign(keypair.getPrivate()); - pkcs10.encodeAndSign( - new X500Signer(signature, subjectX500Name)); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - pkcs10.print(new PrintStream(out)); - String pkcs10String = out.toString(); - - // Sign certificate - Locale locale = Locale.getDefault(); - String profileId = "caCACert"; - IProfileSubsystem ps = (IProfileSubsystem) - CMS.getSubsystem(IProfileSubsystem.ID); - IProfile profile = ps.getProfile(profileId); - ArgBlock argBlock = new ArgBlock(); - argBlock.set("cert_request_type", "pkcs10"); - argBlock.set("cert_request", pkcs10String); - CertEnrollmentRequest certRequest = - CertEnrollmentRequestFactory.create(argBlock, profile, locale); - EnrollmentProcessor processor = - new EnrollmentProcessor("createSubCA", locale); - Map resultMap = processor.processEnrollment( - certRequest, null, authorityID, null, authToken); - IRequest requests[] = (IRequest[]) resultMap.get(CAProcessor.ARG_REQUESTS); - IRequest request = requests[0]; - Integer result = request.getExtDataInInteger(IRequest.RESULT); - if (result != null && !result.equals(IRequest.RES_SUCCESS)) - throw new EBaseException("createSubCA: certificate request submission resulted in error: " + result); - RequestStatus requestStatus = request.getRequestStatus(); - if (requestStatus != RequestStatus.COMPLETE) - throw new EBaseException("createSubCA: certificate request did not complete; status: " + requestStatus); - - // Add certificate to nssdb - X509CertImpl cert = request.getExtDataInCert(IEnrollProfile.REQUEST_ISSUED_CERT); - cryptoManager.importCertPackage(cert.getEncoded(), nickname); - } catch (Exception e) { - // something went wrong; delete just-added entry - conn.delete(dn); - CMS.debug("Error creating lightweight CA certificate"); - CMS.debug(e); - throw new ECAException("Error creating lightweight CA certificate: " + e); + deleteAuthorityEntry(aid); + } catch (ELdapException e2) { + // we are about to throw ECAException, so just + // log this error. + CMS.debug("Error deleting new authority entry after failure during certificate generation: " + e2); } - } catch (LDAPException e) { - throw new EBaseException("Error adding authority entry to database: " + e); - } finally { - dbFactory.returnConn(conn); - dbFactory.reset(); + throw new ECAException("Error creating lightweight CA certificate: " + e); } return new CertificateAuthority( @@ -2621,8 +2597,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori String aidString = aid.toString(); // build database entry - String dn = "cn=" + aidString + ",ou=authorities,ou=" - + getId() + "," + getDBSubsystem().getBaseDN(); + String dn = "cn=" + aidString + "," + authorityBaseDN(); String dnString = null; try { dnString = mName.toLdapDNString(); @@ -2643,25 +2618,82 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori 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(); - } + commitAuthority(aid, ldapEntry); this.authorityID = aid; this.authorityDescription = desc; return aid; } + private void commitAuthority(AuthorityID aid, LDAPEntry entry) + throws ELdapException { + LDAPControl[] responseControls; + LDAPConnection conn = dbFactory.getConn(); + synchronized (hostCA) { + try { + conn.add(entry, getCommitConstraints()); + responseControls = conn.getResponseControls(); + } catch (LDAPException e) { + throw new ELdapException("commitAuthority: failed to add entry", e); + } finally { + dbFactory.returnConn(conn); + } + postCommit(aid, responseControls); + } + } + + /** + * Modify _this_ authority with the given modification set. + */ + private void commitModifyAuthority(LDAPModificationSet mods) + throws ELdapException { + String dn = "cn=" + authorityID.toString() + "," + authorityBaseDN(); + LDAPControl[] responseControls; + LDAPConnection conn = dbFactory.getConn(); + synchronized (hostCA) { + try { + conn.modify(dn, mods, getCommitConstraints()); + responseControls = conn.getResponseControls(); + } catch (LDAPException e) { + throw new ELdapException("commitAuthority: failed to add entry", e); + } finally { + dbFactory.returnConn(conn); + } + postCommit(authorityID, responseControls); + } + } + + private LDAPConstraints getCommitConstraints() { + String[] attrs = {"entryUSN", "nsUniqueId"}; + LDAPConstraints cons = new LDAPConstraints(); + LDAPPostReadControl control = new LDAPPostReadControl(true, attrs); + cons.setServerControls(control); + return cons; + } + + /** + * Post-commit processing of authority to track its entryUSN and nsUniqueId + */ + private void postCommit(AuthorityID aid, LDAPControl[] responseControls) { + LDAPPostReadControl control = (LDAPPostReadControl) + LDAPUtil.getControl(LDAPPostReadControl.class, responseControls); + LDAPEntry entry = control.getEntry(); + + LDAPAttribute attr = entry.getAttribute("entryUSN"); + if (attr != null) { + Integer entryUSN = new Integer(attr.getStringValueArray()[0]); + entryUSNs.put(aid, entryUSN); + CMS.debug("postCommit: new entryUSN = " + entryUSN); + } + + attr = entry.getAttribute("nsUniqueId"); + if (attr != null) { + String nsUniqueId = attr.getStringValueArray()[0]; + nsUniqueIds.put(aid, nsUniqueId); + CMS.debug("postCommit: nsUniqueId = " + nsUniqueId); + } + } + /** * Update lightweight authority attributes. * @@ -2709,21 +2741,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } 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(); - } + commitModifyAuthority(mods); // update was successful; update CA's state authorityEnabled = nextEnabled; @@ -2731,7 +2749,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } - public void deleteAuthority() throws EBaseException { + public synchronized void deleteAuthority() throws EBaseException { if (isHostAuthority()) throw new CATypeException("Cannot delete the host CA"); @@ -2749,23 +2767,10 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori if (hasSubCAs) throw new CANotLeafException("CA with sub-CAs cannot be deleted (delete sub-CAs first)"); - caMap.remove(authorityID); shutdown(); // delete ldap entry - ILdapConnFactory dbFactory = CMS.getLdapBoundConnFactory("updateAuthority"); - dbFactory.init(CMS.getConfigStore().getSubStore("internaldb")); - LDAPConnection conn = dbFactory.getConn(); - String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou=" - + getId() + "," + getDBSubsystem().getBaseDN(); - try { - conn.delete(dn); - } catch (LDAPException e) { - throw new ELdapException("Error deleting authority entry '" + dn + "': " + e); - } finally { - dbFactory.returnConn(conn); - dbFactory.reset(); - } + deleteAuthorityEntry(authorityID); CryptoManager cryptoManager; try { @@ -2802,4 +2807,252 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + private void deleteAuthorityEntry(AuthorityID aid) throws ELdapException { + String dn = "cn=" + aid.toString() + "," + authorityBaseDN(); + LDAPConnection conn = dbFactory.getConn(); + synchronized (hostCA) { + try { + conn.delete(dn); + } catch (LDAPException e) { + throw new ELdapException("Error deleting authority entry: " + dn, e); + } finally { + dbFactory.returnConn(conn); + } + + String nsUniqueId = nsUniqueIds.get(aid); + if (nsUniqueId != null) + deletedNsUniqueIds.add(nsUniqueId); + forgetAuthority(aid); + } + } + + private void checkInitialLoadDone() { + if (initialNumAuthorities != null + && numAuthoritiesLoaded >= initialNumAuthorities) + initialLoadDone.countDown(); + } + + public void run() { + int op = LDAPPersistSearchControl.ADD + | LDAPPersistSearchControl.MODIFY + | LDAPPersistSearchControl.DELETE + | LDAPPersistSearchControl.MODDN; + LDAPPersistSearchControl persistCtrl = + new LDAPPersistSearchControl(op, false, true, true); + + CMS.debug("authorityMonitor: starting."); + + while (!stopped) { + LDAPConnection conn = null; + try { + conn = dbFactory.getConn(); + LDAPSearchConstraints cons = conn.getSearchConstraints(); + cons.setServerControls(persistCtrl); + cons.setBatchSize(1); + cons.setServerTimeLimit(0 /* seconds */); + String[] attrs = {"*", "entryUSN", "nsUniqueId", "numSubordinates"}; + LDAPSearchResults results = conn.search( + authorityBaseDN(), LDAPConnection.SCOPE_SUB, + "(objectclass=*)", attrs, false, cons); + while (!stopped && results.hasMoreElements()) { + LDAPEntry entry = results.next(); + + String[] objectClasses = + entry.getAttribute("objectClass").getStringValueArray(); + if (Arrays.asList(objectClasses).contains("organizationalUnit")) { + initialNumAuthorities = new Integer( + entry.getAttribute("numSubordinates") + .getStringValueArray()[0]); + checkInitialLoadDone(); + continue; + } + + LDAPEntryChangeControl changeControl = (LDAPEntryChangeControl) + LDAPUtil.getControl( + LDAPEntryChangeControl.class, results.getResponseControls()); + CMS.debug("authorityMonitor: Processed change controls."); + if (changeControl != null) { + int changeType = changeControl.getChangeType(); + switch (changeType) { + case LDAPPersistSearchControl.ADD: + CMS.debug("authorityMonitor: ADD"); + readAuthority(entry); + // TODO kick off signing key replication via custodia + break; + case LDAPPersistSearchControl.DELETE: + CMS.debug("authorityMonitor: DELETE"); + handleDELETE(entry); + break; + case LDAPPersistSearchControl.MODIFY: + CMS.debug("authorityMonitor: MODIFY"); + // TODO how do we handle authorityID change? + readAuthority(entry); + break; + case LDAPPersistSearchControl.MODDN: + CMS.debug("authorityMonitor: MODDN"); + handleMODDN(new DN(changeControl.getPreviousDN()), entry); + break; + default: + CMS.debug("authorityMonitor: unknown change type: " + changeType); + break; + } + } else { + CMS.debug("authorityMonitor: immediate result"); + readAuthority(entry); + numAuthoritiesLoaded += 1; + checkInitialLoadDone(); + } + } + } catch (ELdapException e) { + CMS.debug("authorityMonitor: failed to get LDAPConnection. Retrying in 1 second."); + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } catch (LDAPException e) { + CMS.debug("authorityMonitor: Failed to execute LDAP search for lightweight CAs: " + e); + } finally { + try { + dbFactory.returnConn(conn); + } catch (Exception e) { + CMS.debug("authorityMonitor: Error releasing the LDAPConnection" + e.toString()); + } + } + } + CMS.debug("authorityMonitor: stopping."); + } + + private synchronized void readAuthority(LDAPEntry entry) { + String nsUniqueId = + entry.getAttribute("nsUniqueId").getStringValueArray()[0]; + if (deletedNsUniqueIds.contains(nsUniqueId)) { + CMS.debug("readAuthority: ignoring entry with nsUniqueId '" + + nsUniqueId + "' due to deletion"); + return; + } + + 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) { + CMS.debug("Malformed authority object; required attribute(s) missing: " + entry.getDN()); + return; + } + + AuthorityID aid = new AuthorityID((String) + aidAttr.getStringValues().nextElement()); + + Integer newEntryUSN = new Integer( + entry.getAttribute("entryUSN").getStringValueArray()[0]); + CMS.debug("readAuthority: new entryUSN = " + newEntryUSN); + Integer knownEntryUSN = entryUSNs.get(aid); + if (knownEntryUSN != null) { + CMS.debug("readAuthority: known entryUSN = " + knownEntryUSN); + if (newEntryUSN <= knownEntryUSN) { + CMS.debug("readAuthority: data is current"); + return; + } + } + + X500Name dn = null; + try { + dn = new X500Name((String) dnAttr.getStringValues().nextElement()); + } catch (IOException e) { + CMS.debug("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); + return; + } + + @SuppressWarnings("unused") + X500Name parentDN = null; + if (parentDNAttr != null) { + try { + parentDN = new X500Name((String) parentDNAttr.getStringValues().nextElement()); + } catch (IOException e) { + CMS.debug("Malformed authority object; invalid authorityParentDN: " + entry.getDN()); + return; + } + } + + 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"); + } + + try { + CertificateAuthority ca = new CertificateAuthority( + hostCA, aid, parentAID, keyNick, desc, enabled); + caMap.put(aid, ca); + entryUSNs.put(aid, newEntryUSN); + nsUniqueIds.put(aid, nsUniqueId); + } catch (EBaseException e) { + CMS.debug("Error initialising lightweight CA: " + e); + } + } + + private synchronized void handleDELETE(LDAPEntry entry) { + LDAPAttribute attr = entry.getAttribute("nsUniqueId"); + String nsUniqueId = null; + if (attr != null) + nsUniqueId = attr.getStringValueArray()[0]; + + if (deletedNsUniqueIds.remove(nsUniqueId)) { + CMS.debug("handleDELETE: delete was already effected"); + return; + } + + AuthorityID aid = null; + attr = entry.getAttribute("authorityID"); + if (attr != null) { + aid = new AuthorityID((String) attr.getStringValueArray()[0]); + forgetAuthority(aid); + } + } + + private void forgetAuthority(AuthorityID aid) { + caMap.remove(aid); + entryUSNs.remove(aid); + nsUniqueIds.remove(aid); + } + + private synchronized void handleMODDN(DN oldDN, LDAPEntry entry) { + DN authorityBase = new DN(authorityBaseDN()); + + boolean wasMonitored = oldDN.isDescendantOf(authorityBase); + boolean isMonitored = (new DN(entry.getDN())).isDescendantOf(authorityBase); + if (wasMonitored && !isMonitored) { + LDAPAttribute attr = entry.getAttribute("authorityID"); + if (attr != null) { + AuthorityID aid = new AuthorityID(attr.getStringValueArray()[0]); + forgetAuthority(aid); + } + } else if (!wasMonitored && isMonitored) { + readAuthority(entry); + } + } + } diff --git a/base/ca/src/com/netscape/ca/SigningUnit.java b/base/ca/src/com/netscape/ca/SigningUnit.java index 0ac4b7a1cc640310a4fa06f5eb562218408abfa7..692842a76b9f1669385678c3143c042a58b30499 100644 --- a/base/ca/src/com/netscape/ca/SigningUnit.java +++ b/base/ca/src/com/netscape/ca/SigningUnit.java @@ -43,6 +43,7 @@ import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.IConfigStore; import com.netscape.certsrv.base.ISubsystem; import com.netscape.certsrv.ca.ECAException; +import com.netscape.certsrv.ca.CAMissingKeyException; import com.netscape.certsrv.common.Constants; import com.netscape.certsrv.logging.ILogger; import com.netscape.certsrv.security.ISigningUnit; @@ -203,7 +204,7 @@ public final class SigningUnit implements ISigningUnit { } catch (ObjectNotFoundException e) { CMS.debug("SigningUnit init: debug " + e.toString()); log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_SIGNING_CERT_NOT_FOUND", e.toString())); - throw new ECAException(CMS.getUserMessage("CMS_CA_CERT_OBJECT_NOT_FOUND")); + throw new CAMissingKeyException(CMS.getUserMessage("CMS_CA_CERT_OBJECT_NOT_FOUND")); } catch (TokenException e) { CMS.debug("SigningUnit init: debug " + e.toString()); log(ILogger.LL_FAILURE, CMS.getLogMessage("OPERATION_ERROR", e.toString())); diff --git a/base/common/src/com/netscape/certsrv/ca/CAMissingKeyException.java b/base/common/src/com/netscape/certsrv/ca/CAMissingKeyException.java new file mode 100644 index 0000000000000000000000000000000000000000..8f5e1e72a3cdb31b1f12985d9e52371277901ae1 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/ca/CAMissingKeyException.java @@ -0,0 +1,15 @@ +package com.netscape.certsrv.ca; + +/** + * Exception to throw when a (sub-)CA's signing key is not (yet) + * present in the local NSSDB. + */ +public class CAMissingKeyException extends ECAException { + + private static final long serialVersionUID = -364157165997677925L; + + public CAMissingKeyException(String msgFormat) { + super(msgFormat); + } + +} -- 2.5.5