Ade, thanks for the review. I address your comments inline. Updated patches are attached, and there are new patches implementing schema changes and the key acquisition framework. Pursuant to comment (4), I have split patch 0084 into six different patches. To keep things in order I retire patch numbers 0084-0086 and start over from 0087:
/-0087 add CAMissingKeyException class / 0088 use static db connection factory 0084 ___/ 0089 avoid repeat definition of authorities DN \ 0090 move host authority creation out of load method \ 0091 extract LDAP commit/delete methods \_0092 monitor database for changes 0085 -> 0093 set DN based on data from LDAP 0086 -> 0094 indicate when CA does not yet have keys (new) -> 0095 authority schema changes (new) -> 0096 add key retrieval framework On Thu, Mar 24, 2016 at 12:29:24PM -0400, Ade Lee wrote: > A few comments. > > 1. One of the first things that struck me as odd was making > CertificateAuthority implement Runnable. I think it would be cleaner > to have a static inner class called AuthorityMonitor or similar to > which we pass in the CertificateAuthority. > This was originally the way that LDAPProfileSubsystem was structured, and the review feedback there was that we accessed so many members of the enclosing class that it would be better to make the enclosing class Runnable and migrate the methods there. So... grass is always greener? :) Based on the LDAPProfileSubsystem experience, I'm not inclined to make this change. > 2. I do like the fact that the caMap updates are done through a static > database connection factory created by the hostCA. How do you ensure > that the database connection factory is created before being used by > other CAs? > It is initialised before other CertificateAuthority instances are instantiated. I added a comment. > 3. I'm not sure I understand how the initialLoadDone counter is > supposed to work. Are all the CA's supposed to stop until the hostCA > has completed its initial load? Because it looks like only the hostCA > calls await(). > Yep, it's used to block only the host CA's 'init' method while lightweight CAs are loaded by the monitor thread. I added a comment. > 4. There is a lot of code in that initial patch. It would help review > to split that off into at least two patches, say one in which you add > the functions in CertificateAuthority that handle modifications in the > caMap based on persistent search results, and one which adds the new > monitor thread. > Split into six patches, as discussed above. > 5. Some in-code documentation would not go amiss. For instance, I have > no idea why this code is correct -- > > String[] objectClasses = > > entry.getAttribute("objectClass").getStringValueArray(); > if > (Arrays.asList(objectClasses).contains("organizationalUnit")) { > initialNumAuthorities = new Integer( > entry.getAttribute("numSubordinates") > .getStringValueArray()[0]); > checkInitialLoadDone(); > continue; > } > organizationalUnit? > I added detailed commentary about what's going on here. > There are lots of different variables like initialNumAuthorities etc. which > could > potentially be hidden in an inner class, making this more understandable. > The declarations are grouped together, and hopefully things are made clearer by the split patches. If you still feel the updated patches would benefit substantially from grouping tracking vars in an inner record type, I will make the change. Many thanks again for the review! Cheers, Fraser > Ade > > On Tue, 2016-03-22 at 16:00 +1000, Fraser Tweedale wrote: > > On Fri, Mar 18, 2016 at 02:30:24PM +1000, Fraser Tweedale wrote: > > > Hi all, > > > > > > The attached patches implement replication support for lightweight > > > CAs. These patches do not implement key replication via Custodia > > > (my next task) but they do implement the persistent search thread > > > and appropriate** API behaviour when the signing keys are not yet > > > available. > > > > > > ** In most cases, we respond 503 Service Unavailable; this is open > > > for discussion. ca-authority-find and ca-authority-show include > > > a boolean field indicating whether the CA is ready to sign. > > > There might be (probably are) endpoints I've missed. > > > > > > Cheers, > > > Fraser > > > > > Updated patches attached - small change in patch 0084 to fix a race > > condition when deleting an authority that can cause NPE. > > > > Thanks, > > Fraser > > _______________________________________________ > > Pki-devel mailing list > > Pki-devel@redhat.com > > https://www.redhat.com/mailman/listinfo/pki-devel
From 882216d262924a36d7f9b0d77a001472f1d3b39e Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 31 Mar 2016 12:46:03 +1100 Subject: [PATCH 87/96] Lightweight CAs: add CAMissingKeyException class Add the CAMissingKeyException class and throw this exception when signing unit initialisation fails due to missing key. Also add the private 'hasKeys' field for internal use. Part of: https://fedorahosted.org/pki/ticket/1625 --- base/ca/src/com/netscape/ca/CertificateAuthority.java | 10 +++++++++- base/ca/src/com/netscape/ca/SigningUnit.java | 3 ++- .../com/netscape/certsrv/ca/CAMissingKeyException.java | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 2 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..06920ead002b14f0438b4031fa71875b46e291f5 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -101,6 +101,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; @@ -188,6 +189,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; @@ -1354,7 +1356,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 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
From 3dbc455a2f5dea3452d67c4e4f94c83379a9489e Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 31 Mar 2016 12:51:18 +1100 Subject: [PATCH 88/96] Lightweight CAs: use static db connection factory Use a static database connection factory that is initialised by the host authority and used by all CertificateAuthority instances. Part of: https://fedorahosted.org/pki/ticket/1625 --- .../src/com/netscape/ca/CertificateAuthority.java | 25 +++++++++------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 06920ead002b14f0438b4031fa71875b46e291f5..26ddc0501fd768d6261931e3a02434cce13d33c7 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -182,6 +182,11 @@ 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"); + /* The static conn factory is initialised by the host authority's + * 'init' method, before any lightweight CAs are instantiated + */ + private static ILdapConnFactory dbFactory = null; + private static final Map<AuthorityID, ICertificateAuthority> caMap = Collections.synchronizedSortedMap(new TreeMap<AuthorityID, ICertificateAuthority>()); protected CertificateAuthority hostCA = null; @@ -424,6 +429,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(); @@ -1964,8 +1974,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori * 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() @@ -2051,7 +2059,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } finally { dbFactory.returnConn(conn); - dbFactory.reset(); } if (haveLightweightCAsContainer && !foundHostAuthority) { @@ -2535,8 +2542,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori 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 { @@ -2604,7 +2609,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori throw new EBaseException("Error adding authority entry to database: " + e); } finally { dbFactory.returnConn(conn); - dbFactory.reset(); } return new CertificateAuthority( @@ -2652,8 +2656,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori 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 { @@ -2662,7 +2664,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori throw new ELdapException("Error adding host authority entry to database: " + e); } finally { dbFactory.returnConn(conn); - dbFactory.reset(); } this.authorityID = aid; @@ -2721,8 +2722,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori + 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); @@ -2730,7 +2729,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori throw new EBaseException("Error adding authority entry to database: " + e); } finally { dbFactory.returnConn(conn); - dbFactory.reset(); } // update was successful; update CA's state @@ -2761,8 +2759,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori 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(); @@ -2772,7 +2768,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori throw new ELdapException("Error deleting authority entry '" + dn + "': " + e); } finally { dbFactory.returnConn(conn); - dbFactory.reset(); } CryptoManager cryptoManager; -- 2.5.5
From 06d9386d15395b6e9e99e539d4f4af796cbf1664 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 31 Mar 2016 13:08:48 +1100 Subject: [PATCH 89/96] Lightweight CAs: avoid repeat definition of authorities DN Part of: https://fedorahosted.org/pki/ticket/1625 --- .../src/com/netscape/ca/CertificateAuthority.java | 23 +++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 26ddc0501fd768d6261931e3a02434cce13d33c7..8d9d8936c5a09f79d9725027aaff80aaa332a03f 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -523,6 +523,11 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } } + private String authorityBaseDN() { + return "ou=authorities,ou=" + getId() + + "," + getDBSubsystem().getBaseDN(); + } + private void initCRLPublisher() throws EBaseException { // instantiate CRL publisher if (!isHostAuthority()) { @@ -1976,14 +1981,12 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori private void loadLightweightCAs() throws EBaseException { 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, + authorityBaseDN(), LDAPConnection.SCOPE_ONE, "(objectclass=authority)", null, false); while (results.hasMoreElements()) { @@ -2051,7 +2054,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } catch (LDAPException e) { if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) { CMS.debug( - "Missing lightweight CAs container '" + searchDN + "Missing lightweight CAs container '" + authorityBaseDN() + "'. Disabling lightweight CAs."); haveLightweightCAsContainer = false; } else { @@ -2514,8 +2517,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 { @@ -2633,8 +2635,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(); @@ -2718,8 +2719,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } if (mods.size() > 0) { - String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou=" - + getId() + "," + getDBSubsystem().getBaseDN(); + String dn = "cn=" + authorityID.toString() + "," + authorityBaseDN(); // connect to database LDAPConnection conn = dbFactory.getConn(); @@ -2760,8 +2760,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori // delete ldap entry LDAPConnection conn = dbFactory.getConn(); - String dn = "cn=" + authorityID.toString() + ",ou=authorities,ou=" - + getId() + "," + getDBSubsystem().getBaseDN(); + String dn = "cn=" + authorityID.toString() + "," + authorityBaseDN(); try { conn.delete(dn); } catch (LDAPException e) { -- 2.5.5
From 15c807b311cda0be43a523cd3b91d3fdf94c6b03 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 31 Mar 2016 13:35:49 +1100 Subject: [PATCH 90/96] Lightweight CAs: move host authority creation out of load method To reduce the amount of code that would be run in the persistent search thread, extract the host authority entry creation out of the 'loadLightweightCAs' method, into 'CertificateAuthority.init'. Part of: https://fedorahosted.org/pki/ticket/1625 --- .../src/com/netscape/ca/CertificateAuthority.java | 42 +++++++++++++--------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 8d9d8936c5a09f79d9725027aaff80aaa332a03f..798b310668332bf58e2a656cf948065f0cf25d96 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -290,6 +290,8 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori private boolean mUseNonces = true; private int mMaxNonces = 100; + private static boolean foundHostAuthority = false; + /** * Constructs a CA subsystem. */ @@ -512,9 +514,17 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori // being functional. initCRL(); - if (isHostAuthority()) + if (isHostAuthority() && haveLightweightCAsContainer()) { loadLightweightCAs(); + 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; @@ -528,6 +538,19 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori + "," + 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()) { @@ -1982,8 +2005,6 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori LDAPConnection conn = dbFactory.getConn(); LDAPSearchResults results = null; - boolean foundHostAuthority = false; - boolean haveLightweightCAsContainer = true; try { results = conn.search( authorityBaseDN(), LDAPConnection.SCOPE_ONE, @@ -2052,23 +2073,10 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori caMap.put(aid, ca); } } catch (LDAPException e) { - if (e.getLDAPResultCode() == LDAPException.NO_SUCH_OBJECT) { - CMS.debug( - "Missing lightweight CAs container '" + authorityBaseDN() - + "'. Disabling lightweight CAs."); - haveLightweightCAsContainer = false; - } else { - throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e); - } + throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e); } finally { dbFactory.returnConn(conn); } - - 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() { -- 2.5.5
From 468f1e73e15a86861ee65ebe929527600ce37942 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Thu, 31 Mar 2016 15:37:51 +1100 Subject: [PATCH 91/96] Lightweight CAs: extract LDAP commit/delete methods LDAP code to add, modify and delete authority entries exists in multiple places. Extract these methods to remove this duplication and provide a cleaner basis for upcoming implementation of replication handling. Part of: https://fedorahosted.org/pki/ticket/1625 --- .../src/com/netscape/ca/CertificateAuthority.java | 210 +++++++++++---------- 1 file changed, 115 insertions(+), 95 deletions(-) diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 798b310668332bf58e2a656cf948065f0cf25d96..814e7e9297de5899ff2de3345d8ca1fee4f9350c 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -2551,74 +2551,70 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori attrSet.add(new LDAPAttribute("description", description)); LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); - // connect to database - 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<String, Object> 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<String, Object> 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); + throw new ECAException("Error creating lightweight CA certificate: " + e); } return new CertificateAuthority( @@ -2664,22 +2660,45 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori LDAPAttributeSet attrSet = new LDAPAttributeSet(attrs); LDAPEntry ldapEntry = new LDAPEntry(dn, attrSet); - // connect to database - 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); - } + commitAuthority(aid, ldapEntry); this.authorityID = aid; this.authorityDescription = desc; return aid; } + private void commitAuthority(AuthorityID aid, LDAPEntry entry) + throws ELdapException { + LDAPConnection conn = dbFactory.getConn(); + synchronized (hostCA) { + try { + conn.add(entry); + } catch (LDAPException e) { + throw new ELdapException("commitAuthority: failed to add entry", e); + } finally { + dbFactory.returnConn(conn); + } + } + } + + /** + * Modify _this_ authority with the given modification set. + */ + private void commitModifyAuthority(LDAPModificationSet mods) + throws ELdapException { + String dn = "cn=" + authorityID.toString() + "," + authorityBaseDN(); + LDAPConnection conn = dbFactory.getConn(); + synchronized (hostCA) { + try { + conn.modify(dn, mods); + } catch (LDAPException e) { + throw new ELdapException("commitAuthority: failed to add entry", e); + } finally { + dbFactory.returnConn(conn); + } + } + } + /** * Update lightweight authority attributes. * @@ -2727,17 +2746,7 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori } if (mods.size() > 0) { - String dn = "cn=" + authorityID.toString() + "," + authorityBaseDN(); - - // connect to database - 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); - } + commitModifyAuthority(mods); // update was successful; update CA's state authorityEnabled = nextEnabled; @@ -2745,7 +2754,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"); @@ -2763,19 +2772,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 - LDAPConnection conn = dbFactory.getConn(); - String dn = "cn=" + authorityID.toString() + "," + authorityBaseDN(); - try { - conn.delete(dn); - } catch (LDAPException e) { - throw new ELdapException("Error deleting authority entry '" + dn + "': " + e); - } finally { - dbFactory.returnConn(conn); - } + deleteAuthorityEntry(authorityID); CryptoManager cryptoManager; try { @@ -2812,4 +2812,24 @@ 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); + } + + forgetAuthority(aid); + } + } + + private void forgetAuthority(AuthorityID aid) { + caMap.remove(aid); + } + } -- 2.5.5
From e0495d2e3dd6c7c3fceaa2a93af8a3ae5b0bc205 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Wed, 9 Mar 2016 02:18:41 -0500 Subject: [PATCH 92/96] 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 | 429 ++++++++++++++++----- 1 file changed, 342 insertions(+), 87 deletions(-) diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 814e7e9297de5899ff2de3345d8ca1fee4f9350c..734ad14043550c1978905e76a4de2c0fefef6e34 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; @@ -151,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; @@ -177,7 +188,8 @@ 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"); @@ -290,7 +302,26 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori private boolean mUseNonces = true; private int mMaxNonces = 100; + /* Variables to manage loading and tracking of lightweight CAs + * + * The initialLoadDone latch causes the host authority's 'init' + * method to block until the monitor thread has finished the + * initial loading of lightweight CAs. + * + * In other words: the "server startup" cannot complete until + * all the lightweight CAs that exist at start time are loaded. + */ + private static 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<AuthorityID,Integer> entryUSNs = new TreeMap<>(); + private static TreeMap<AuthorityID,String> nsUniqueIds = new TreeMap<>(); + private static TreeSet<String> deletedNsUniqueIds = new TreeSet<>(); /** * Constructs a CA subsystem. @@ -515,7 +546,13 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori initCRL(); if (isHostAuthority() && haveLightweightCAsContainer()) { - loadLightweightCAs(); + 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"); @@ -736,6 +773,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. + } } /** @@ -1996,89 +2049,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 { - LDAPConnection conn = dbFactory.getConn(); - - LDAPSearchResults results = null; - try { - results = conn.search( - authorityBaseDN(), 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) { - throw new ECAException("Failed to execute LDAP search for lightweight CAs: " + e); - } finally { - dbFactory.returnConn(conn); - } - } - public String getOfficialName() { return OFFICIAL_NAME; } @@ -2669,15 +2639,18 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori private void commitAuthority(AuthorityID aid, LDAPEntry entry) throws ELdapException { + LDAPControl[] responseControls; LDAPConnection conn = dbFactory.getConn(); synchronized (hostCA) { try { - conn.add(entry); + 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); } } @@ -2687,15 +2660,49 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori 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); + 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); } } @@ -2824,12 +2831,260 @@ public class CertificateAuthority implements ICertificateAuthority, ICertAuthori 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(); + + /* This behaviour requires detailed explanation. + * + * We want to block startup until all the + * lightweight CAs existing at startup time are + * loaded. To do this, we need to know how many + * authority entries there are. And we must do + * this atomically - we cannot issue two LDAP + * searches in case things change. + * + * Therefore, we do a subtree search from the + * authority container. When we find the + * container (objectClass=organizationalUnit), + * we set initialNumAuthorities to the value of + * its numSubordinates attribute. + * + * We increment numAuthoritiesLoaded for each + * authority entry. When numAuthoritiesLoaded + * equals initialNumAuthorities, we unlock the + * initialLoadDone latch. + */ + 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); + } } } -- 2.5.5
From 4f2ebd1b986959075408c2608e525c0602ac76c8 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Wed, 16 Mar 2016 13:07:43 +1100 Subject: [PATCH 93/96] Lightweight CAs: set DN based on data from LDAP When initialising a lightweight CA, if we do not have the signing cert and key in the NSSDB yet, we do not initialise the DN. This causes NPE in other code that expects getX500Name() to return a value, e.g. REST API to list or show CA. To work around this, when loading lightweight CAs set the DN based on the 'authorityDN' value stored in its LDAP entry. Part of: https://fedorahosted.org/pki/ticket/1625 --- base/ca/src/com/netscape/ca/CertificateAuthority.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 734ad14043550c1978905e76a4de2c0fefef6e34..8f9c6a9a62d7ebd9d2397267df37f6094ff40251 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -335,6 +335,7 @@ public class CertificateAuthority */ private CertificateAuthority( CertificateAuthority hostCA, + X500Name dn, AuthorityID aid, AuthorityID parentAID, String signingKeyNickname, @@ -343,6 +344,11 @@ public class CertificateAuthority ) throws EBaseException { setId(hostCA.getId()); this.hostCA = hostCA; + + // cert and key may not have been replicated to local nssdb + // yet, so set DN based on data from LDAP + this.mName = dn; + this.authorityID = aid; this.authorityParentID = parentAID; this.authorityDescription = authorityDescription; @@ -2588,7 +2594,8 @@ public class CertificateAuthority } return new CertificateAuthority( - hostCA, aid, this.authorityID, nickname, description, true); + hostCA, subjectX500Name, + aid, this.authorityID, nickname, description, true); } /** @@ -3037,7 +3044,7 @@ public class CertificateAuthority try { CertificateAuthority ca = new CertificateAuthority( - hostCA, aid, parentAID, keyNick, desc, enabled); + hostCA, dn, aid, parentAID, keyNick, desc, enabled); caMap.put(aid, ca); entryUSNs.put(aid, newEntryUSN); nsUniqueIds.put(aid, nsUniqueId); -- 2.5.5
From 61fa9694c34c3839a8b3a107d168fa63155b7f55 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Wed, 16 Mar 2016 16:48:43 +1100 Subject: [PATCH 94/96] Lightweight CAs: indicate when CA does not yet have keys When a lightweight CA is created, clones will initialise a local object when the LDAP replication takes place, however, the signing keys will not yet have been replicated. Therefore, indicate CA readiness in authority data and respond appropriately (HTTP 503) when signing operations are attempted. Part of: https://fedorahosted.org/pki/ticket/1625 --- base/ca/src/com/netscape/ca/CertificateAuthority.java | 12 +++++++++--- .../org/dogtagpki/server/ca/rest/AuthorityService.java | 11 ++++++++--- .../dogtagpki/server/ca/rest/CertRequestService.java | 4 ++++ .../com/netscape/certsrv/authority/AuthorityData.java | 17 ++++++++++++++++- .../certsrv/base/ServiceUnavailableException.java | 17 +++++++++++++++++ .../com/netscape/certsrv/ca/ICertificateAuthority.java | 5 +++++ .../com/netscape/cmstools/authority/AuthorityCLI.java | 1 + .../netscape/cmstools/authority/AuthorityCreateCLI.java | 2 +- .../cmstools/authority/AuthorityDisableCLI.java | 2 +- .../netscape/cmstools/authority/AuthorityEnableCLI.java | 2 +- .../com/netscape/cms/servlet/cert/RequestProcessor.java | 3 +++ 11 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 base/common/src/com/netscape/certsrv/base/ServiceUnavailableException.java diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 8f9c6a9a62d7ebd9d2397267df37f6094ff40251..9d96d64fcfe4112dfb524e099f343bab6e6f7dbe 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -361,9 +361,16 @@ public class CertificateAuthority return hostCA == this; } - private void ensureEnabled() throws CADisabledException { + private void ensureEnabled() + throws CADisabledException, CAMissingKeyException { if (!authorityEnabled) throw new CADisabledException("Authority is disabled"); + if (!isReady()) + throw new CAMissingKeyException("Authority does not yet have keys"); + } + + public boolean isReady() { + return hasKeys; } public boolean getAuthorityEnabled() { @@ -2482,8 +2489,7 @@ public class CertificateAuthority String subjectDN, String description) throws EBaseException { - if (!authorityEnabled) - throw new CADisabledException("Parent CA is disabled"); + ensureEnabled(); // check requested DN X500Name subjectX500Name = null; diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java index fa9e1038b7b3ca718a7593052a852c31f48545f7..78c97abc7e456ad4929a88ce5a93f41783ae0517 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/AuthorityService.java @@ -43,9 +43,11 @@ 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.base.ServiceUnavailableException; import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.CAEnabledException; import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CAMissingKeyException; import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.CANotLeafException; import com.netscape.certsrv.ca.CATypeException; @@ -207,6 +209,8 @@ public class AuthorityService extends PKIService implements AuthorityResource { auditParams.put("exception", e.toString()); audit(ILogger.FAILURE, OpDef.OP_ADD, "<unknown>", auditParams); throw new ConflictingOperationException(e.toString()); + } catch (CAMissingKeyException e) { + throw new ServiceUnavailableException(e.toString()); } catch (Exception e) { CMS.debug(e); auditParams.put("exception", e.toString()); @@ -261,14 +265,14 @@ public class AuthorityService extends PKIService implements AuthorityResource { public Response enableCA(String aidString) { return modifyCA( aidString, - new AuthorityData(null, null, null, null, true, null)); + new AuthorityData(null, null, null, null, true, null, null)); } @Override public Response disableCA(String aidString) { return modifyCA( aidString, - new AuthorityData(null, null, null, null, false, null)); + new AuthorityData(null, null, null, null, false, null, null)); } @Override @@ -322,7 +326,8 @@ public class AuthorityService extends PKIService implements AuthorityResource { ca.getAuthorityID().toString(), parentAID != null ? parentAID.toString() : null, ca.getAuthorityEnabled(), - ca.getAuthorityDescription() + ca.getAuthorityDescription(), + ca.isReady() ); } 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 cddbeb1ba47741673ab5eb3d22e2bf7c53c4c33d..6e7c7ae7c57bf72f15cb6e3cab33e47abfe96914 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/CertRequestService.java @@ -43,9 +43,11 @@ import com.netscape.certsrv.base.ConflictingOperationException; import com.netscape.certsrv.base.EBaseException; import com.netscape.certsrv.base.PKIException; import com.netscape.certsrv.base.ResourceNotFoundException; +import com.netscape.certsrv.base.ServiceUnavailableException; import com.netscape.certsrv.base.UnauthorizedException; import com.netscape.certsrv.ca.AuthorityID; import com.netscape.certsrv.ca.CADisabledException; +import com.netscape.certsrv.ca.CAMissingKeyException; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfo; @@ -252,6 +254,8 @@ public class CertRequestService extends PKIService implements CertRequestResourc } catch (CADisabledException e) { CMS.debug("changeRequestState: CA disabled: " + e); throw new ConflictingOperationException(e.toString()); + } catch (CAMissingKeyException e) { + throw new ServiceUnavailableException(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 index 2312c39895c09a4b7dbf994d43c2c068eeaec2d4..84679567eb527cbf9fedd21705a72ca9c1a34a93 100644 --- a/base/common/src/com/netscape/certsrv/authority/AuthorityData.java +++ b/base/common/src/com/netscape/certsrv/authority/AuthorityData.java @@ -95,6 +95,19 @@ public class AuthorityData { } + /** + * Whether the CA is ready to perform signing operations. + * + * This is a read-only attribute; it cannot be set by the user. + */ + @XmlAttribute + protected Boolean ready; + + public Boolean getReady() { + return ready; + } + + protected Link link; public Link getLink() { @@ -111,13 +124,15 @@ public class AuthorityData { public AuthorityData( Boolean isHostAuthority, String dn, String id, String parentID, - Boolean enabled, String description) { + Boolean enabled, String description, + Boolean ready) { this.isHostAuthority = isHostAuthority; this.dn = dn; this.id = id; this.parentID = parentID; this.enabled = enabled; this.description = description; + this.ready = ready; } } diff --git a/base/common/src/com/netscape/certsrv/base/ServiceUnavailableException.java b/base/common/src/com/netscape/certsrv/base/ServiceUnavailableException.java new file mode 100644 index 0000000000000000000000000000000000000000..0ee9c8a08d5144bf9cd7e13c1c4bf7d870a7d846 --- /dev/null +++ b/base/common/src/com/netscape/certsrv/base/ServiceUnavailableException.java @@ -0,0 +1,17 @@ +package com.netscape.certsrv.base; + +import javax.ws.rs.core.Response; + +public class ServiceUnavailableException extends PKIException { + + private static final long serialVersionUID = -9160776882517621347L; + + public ServiceUnavailableException(String message) { + super(Response.Status.SERVICE_UNAVAILABLE, message); + } + + public ServiceUnavailableException(String message, Throwable cause) { + super(Response.Status.SERVICE_UNAVAILABLE, message, cause); + } + +} diff --git a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java index 16d4fc2df62ca0e4ad0eb1c814affcad357503c3..30f697b6685654e8fb86bdfa6f3a4cb033d9cdd6 100644 --- a/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java +++ b/base/common/src/com/netscape/certsrv/ca/ICertificateAuthority.java @@ -545,6 +545,11 @@ public interface ICertificateAuthority extends ISubsystem { public boolean getAuthorityEnabled(); /** + * Return whether CA is ready to perform signing operations. + */ + public boolean isReady(); + + /** * Return CA description. May be null. */ public String getAuthorityDescription(); diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java index 4fbcfef760086928b2e0e75fe4fc56f1b249b5fd..ac06ea24ce824ad1b4be29a4176658caa9302e89 100644 --- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java @@ -45,6 +45,7 @@ public class AuthorityCLI extends CLI { if (parentAID != null) System.out.println(" Parent ID: " + data.getParentID()); System.out.println(" Enabled: " + data.getEnabled()); + System.out.println(" Ready to sign: " + data.getReady()); String desc = data.getDescription(); if (desc != null) System.out.println(" Description: " + desc); diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java index d1688fbd1933a567940164d86ac726df1489f7d2..3c36ac756aeedde8d89505be871da3555b548434 100644 --- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCreateCLI.java @@ -81,7 +81,7 @@ public class AuthorityCreateCLI extends CLI { String dn = cmdArgs[0]; AuthorityData data = new AuthorityData( - null, dn, null, parentAIDString, true /* enabled */, desc); + null, dn, null, parentAIDString, true /* enabled */, desc, null); AuthorityData newData = authorityCLI.authorityClient.createCA(data); AuthorityCLI.printAuthorityData(newData); } diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java index fc4cbf30be947233a9289089bb25bef70d532bb6..85b38f0810a6cff3a8c2293feab3153c85e8fee2 100644 --- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityDisableCLI.java @@ -48,7 +48,7 @@ public class AuthorityDisableCLI extends CLI { } AuthorityData data = new AuthorityData( - null, null, cmdArgs[0], null, false, null); + null, null, cmdArgs[0], null, false, null, null); data = authorityCLI.authorityClient.modifyCA(data); AuthorityCLI.printAuthorityData(data); } diff --git a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java index f6fdab12f527975d2d0688b65968bfb992b5a97a..936edca599b7d6391370284535584953f0180bc8 100644 --- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityEnableCLI.java @@ -48,7 +48,7 @@ public class AuthorityEnableCLI extends CLI { } AuthorityData data = new AuthorityData( - null, null, cmdArgs[0], null, true, null); + null, null, cmdArgs[0], null, true, null, null); data = authorityCLI.authorityClient.modifyCA(data); AuthorityCLI.printAuthorityData(data); } 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 8558ec23f6489407dc5f41951a363d22548851c0..ca88a74839f81f10cebbf2c80f5819940b9fb5fe 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 @@ -38,6 +38,7 @@ 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.CAMissingKeyException; import com.netscape.certsrv.ca.CANotFoundException; import com.netscape.certsrv.ca.ICertificateAuthority; import com.netscape.certsrv.cert.CertReviewResponse; @@ -353,6 +354,8 @@ public class RequestProcessor extends CertProcessor { if (!ca.getAuthorityEnabled()) // authority was disabled after request was accepted throw new CADisabledException("CA '" + aidString + "' is disabled"); + if (!ca.isReady()) + throw new CAMissingKeyException("CA does not yet have signing key"); } /** -- 2.5.5
From d23d1f1eaf3e6a6dfafcffb492d2f2767af6924b Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Wed, 30 Mar 2016 16:06:25 +1100 Subject: [PATCH 95/96] Lightweight CAs: authority schema changes Add the 'authorityKeyHost' attribute which will contain names of hosts that possess the authority's signing keys. Change other attributes to be single-valued. Part of: https://fedorahosted.org/pki/ticket/1625 --- base/server/share/conf/schema-authority.ldif | 15 ++++++++------- base/server/share/conf/schema.ldif | 15 ++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/base/server/share/conf/schema-authority.ldif b/base/server/share/conf/schema-authority.ldif index 7d261f18fbc9475983bf93b1cddcc184d7f9d178..2e7c0d7af13b6af7131d6444261b09f452aa4b12 100644 --- a/base/server/share/conf/schema-authority.ldif +++ b/base/server/share/conf/schema-authority.ldif @@ -1,8 +1,9 @@ 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' ) +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE 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 SINGLE-VALUE 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 SINGLE-VALUE X-ORIGIN 'user defined' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'user defined' ) +attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Parent DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyHost-oid NAME 'authorityKeyHost' DESC 'Authority Key Hosts' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'user defined' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ authorityKeyHost $ description ) X-ORIGIN 'user defined' ) diff --git a/base/server/share/conf/schema.ldif b/base/server/share/conf/schema.ldif index a15601ae7a362635bc398b92b9bfda1c72f0dfc8..5e4118d328ebe1fcac2743b3f51fb5ca9d57f9eb 100644 --- a/base/server/share/conf/schema.ldif +++ b/base/server/share/conf/schema.ldif @@ -671,12 +671,13 @@ objectClasses: ( certProfile-oid NAME 'certProfile' DESC 'Certificate profile' S 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' ) +attributeTypes: ( authorityID-oid NAME 'authorityID' DESC 'Authority ID' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE 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 SINGLE-VALUE 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 SINGLE-VALUE X-ORIGIN 'user defined' ) +attributeTypes: ( authorityEnabled-oid NAME 'authorityEnabled' DESC 'Authority Enabled' SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE X-ORIGIN 'user defined' ) +attributeTypes: ( authorityDN-oid NAME 'authorityDN' DESC 'Authority DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'user defined' ) +attributeTypes: ( authorityParentDN-oid NAME 'authorityParentDN' DESC 'Authority Parent DN' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE X-ORIGIN 'user defined' ) +attributeTypes: ( authorityKeyHost-oid NAME 'authorityKeyHost' DESC 'Authority Key Hosts' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 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' ) +objectClasses: ( authority-oid NAME 'authority' DESC 'Certificate Authority' SUP top STRUCTURAL MUST ( cn $ authorityID $ authorityKeyNickname $ authorityEnabled $ authorityDN ) MAY ( authorityParentID $ authorityParentDN $ authorityKeyHost $ description ) X-ORIGIN 'user defined' ) -- 2.5.5
From f679d7774f5b6bfac7ccbe9493cfce5e067f08e0 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <ftwee...@redhat.com> Date: Wed, 30 Mar 2016 12:38:24 +1100 Subject: [PATCH 96/96] Lightweight CAs: add key retrieval framework Add the framework for key retrieval when a lightweight CA is missing its signing key. This includes all the bits for loading a KeyRetriever implementation, initiating retrieval in a thread and updating the record of which clones possess the key if retrieval was successful. It does not include a KeyRetriever implementation. A subsequent commit will provide this. Part of: https://fedorahosted.org/pki/ticket/1625 --- .../src/com/netscape/ca/CertificateAuthority.java | 128 +++++++++++++++++++-- base/ca/src/com/netscape/ca/KeyRetriever.java | 31 +++++ 2 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 base/ca/src/com/netscape/ca/KeyRetriever.java diff --git a/base/ca/src/com/netscape/ca/CertificateAuthority.java b/base/ca/src/com/netscape/ca/CertificateAuthority.java index 9d96d64fcfe4112dfb524e099f343bab6e6f7dbe..b29b3c31c5184a8dfd431b4622152be4a91e4181 100644 --- a/base/ca/src/com/netscape/ca/CertificateAuthority.java +++ b/base/ca/src/com/netscape/ca/CertificateAuthority.java @@ -35,6 +35,7 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; import java.util.Arrays; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Enumeration; @@ -205,6 +206,7 @@ public class CertificateAuthority protected AuthorityID authorityID = null; protected AuthorityID authorityParentID = null; protected String authorityDescription = null; + protected Collection<String> authorityKeyHosts = null; protected boolean authorityEnabled = true; private boolean hasKeys = false; @@ -339,6 +341,7 @@ public class CertificateAuthority AuthorityID aid, AuthorityID parentAID, String signingKeyNickname, + Collection<String> authorityKeyHosts, String authorityDescription, boolean authorityEnabled ) throws EBaseException { @@ -354,6 +357,7 @@ public class CertificateAuthority this.authorityDescription = authorityDescription; this.authorityEnabled = authorityEnabled; mNickname = signingKeyNickname; + this.authorityKeyHosts = authorityKeyHosts; init(hostCA.mOwner, hostCA.mConfig); } @@ -499,7 +503,7 @@ public class CertificateAuthority // init signing unit & CA cert. try { - initSigUnit(); + initSigUnit(/* retrieveKeys */ true); // init default CA attributes like cert version, validity. initDefCaAttrs(); } catch (EBaseException e) { @@ -1437,7 +1441,7 @@ public class CertificateAuthority /** * init CA signing unit & cert chain. */ - private void initSigUnit() + private boolean initSigUnit(boolean retrieveKeys) throws EBaseException { try { // init signing unit @@ -1464,8 +1468,15 @@ public class CertificateAuthority 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 key not (yet) available in NSSDB"); + if (retrieveKeys == true) { + CMS.debug("Starting KeyRetrieverRunner thread"); + new Thread( + new KeyRetrieverRunner(this), + "KeyRetrieverRunner-" + authorityID + ).start(); + } + return false; } CMS.debug("CA signing unit inited"); @@ -1590,6 +1601,8 @@ public class CertificateAuthority mNickname = mSigningUnit.getNickname(); CMS.debug("in init - got CA name " + mName); + return true; + } catch (CryptoManager.NotInitializedException e) { log(ILogger.LL_FAILURE, CMS.getLogMessage("CMSCORE_CA_CA_OCSP_SIGNING", e.toString())); throw new ECAException(CMS.getUserMessage("CMS_CA_CRYPTO_NOT_INITIALIZED")); @@ -2516,11 +2529,14 @@ public class CertificateAuthority throw new EBaseException("Failed to convert issuer DN to string: " + e); } + String thisHost = CMS.getEEHost(); + LDAPAttribute[] attrs = { new LDAPAttribute("objectclass", "authority"), new LDAPAttribute("cn", aidString), new LDAPAttribute("authorityID", aidString), new LDAPAttribute("authorityKeyNickname", nickname), + new LDAPAttribute("authorityKeyHost", thisHost), new LDAPAttribute("authorityEnabled", "TRUE"), new LDAPAttribute("authorityDN", subjectDN), new LDAPAttribute("authorityParentDN", parentDNString) @@ -2601,7 +2617,9 @@ public class CertificateAuthority return new CertificateAuthority( hostCA, subjectX500Name, - aid, this.authorityID, nickname, description, true); + aid, this.authorityID, + nickname, Collections.singleton(thisHost), + description, true); } /** @@ -2774,6 +2792,23 @@ public class CertificateAuthority } } + /** + * Add this instance to the authorityKeyHosts + */ + private void addInstanceToAuthorityKeyHosts() throws ELdapException { + String hostname = CMS.getEEHost(); + if (authorityKeyHosts.contains(hostname)) { + // already there; nothing to do + return; + } + LDAPModificationSet mods = new LDAPModificationSet(); + mods.add( + LDAPModification.ADD, + new LDAPAttribute("authorityKeyHost", hostname)); + commitModifyAuthority(mods); + authorityKeyHosts.add(hostname); + } + public synchronized void deleteAuthority() throws EBaseException { if (isHostAuthority()) throw new CATypeException("Cannot delete the host CA"); @@ -2922,7 +2957,6 @@ public class CertificateAuthority 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"); @@ -2979,6 +3013,7 @@ public class CertificateAuthority LDAPAttribute aidAttr = entry.getAttribute("authorityID"); LDAPAttribute nickAttr = entry.getAttribute("authorityKeyNickname"); + LDAPAttribute keyHostsAttr = entry.getAttribute("authorityKeyHost"); LDAPAttribute dnAttr = entry.getAttribute("authorityDN"); LDAPAttribute parentAIDAttr = entry.getAttribute("authorityParentID"); LDAPAttribute parentDNAttr = entry.getAttribute("authorityParentDN"); @@ -3035,6 +3070,16 @@ public class CertificateAuthority } String keyNick = (String) nickAttr.getStringValues().nextElement(); + + Collection<String> keyHosts; + if (keyHostsAttr == null) { + keyHosts = Collections.emptyList(); + } else { + @SuppressWarnings("unchecked") + Enumeration<String> keyHostsEnum = keyHostsAttr.getStringValues(); + keyHosts = Collections.list(keyHostsEnum); + } + AuthorityID parentAID = null; if (parentAIDAttr != null) parentAID = new AuthorityID((String) @@ -3050,7 +3095,7 @@ public class CertificateAuthority try { CertificateAuthority ca = new CertificateAuthority( - hostCA, dn, aid, parentAID, keyNick, desc, enabled); + hostCA, dn, aid, parentAID, keyNick, keyHosts, desc, enabled); caMap.put(aid, ca); entryUSNs.put(aid, newEntryUSN); nsUniqueIds.put(aid, nsUniqueId); @@ -3100,4 +3145,73 @@ public class CertificateAuthority } } + private class KeyRetrieverRunner implements Runnable { + private CertificateAuthority ca; + + public KeyRetrieverRunner(CertificateAuthority ca) { + this.ca = ca; + } + + public void run() { + String KR_CLASS_KEY = "features.authority.keyRetrieverClass"; + String className = null; + try { + className = CMS.getConfigStore().getString(KR_CLASS_KEY); + } catch (EBaseException e) { + CMS.debug("Unable to read key retriever class from CS.cfg: " + e); + return; + } + + KeyRetriever kr = null; + try { + kr = Class.forName(className) + .asSubclass(KeyRetriever.class) + .newInstance(); + } catch (ClassNotFoundException e) { + CMS.debug("Could not find class: " + className); + CMS.debug(e); + return; + } catch (ClassCastException e) { + CMS.debug("Class is not an instance of KeyRetriever: " + className); + CMS.debug(e); + return; + } catch (InstantiationException | IllegalAccessException e) { + CMS.debug("Could not instantiate class: " + className); + CMS.debug(e); + return; + } + + boolean gotKey = false; + try { + gotKey = kr.retrieveKey(ca.mNickname, ca.authorityKeyHosts); + } catch (Throwable e) { + CMS.debug("Caught exception during execution of KeyRetriever.retrieveKey"); + CMS.debug(e); + } + + boolean initSigUnitSucceeded = false; + if (gotKey == true) { + try { + // re-init signing unit, but avoid triggering + // key replication if initialisation fails again + // for some reason + // + initSigUnitSucceeded = ca.initSigUnit(/* retrieveKeys */ false); + } catch (Throwable e) { + CMS.debug("Failed to re-init signing unit for authority: " + ca.authorityID); + CMS.debug(e); + } + } + + if (initSigUnitSucceeded == true) { + try { + ca.addInstanceToAuthorityKeyHosts(); + } catch (Throwable e) { + CMS.debug("Failed to add self to authorityKeyHosts"); + CMS.debug(e); + } + } + } + } + } diff --git a/base/ca/src/com/netscape/ca/KeyRetriever.java b/base/ca/src/com/netscape/ca/KeyRetriever.java new file mode 100644 index 0000000000000000000000000000000000000000..5ae8d17989f0e746f22052bf6903bb549c5d80fd --- /dev/null +++ b/base/ca/src/com/netscape/ca/KeyRetriever.java @@ -0,0 +1,31 @@ +// --- 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) 2016 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- + +package com.netscape.ca; + +import java.util.Collection; + +public interface KeyRetriever { + /** + * Retrieve the specified signing key from specified host and + * store in local NSSDB. + * + * @return true if the retrieval was successful, otherwise false + */ + boolean retrieveKey(String nickname, Collection<String> hostname); +} -- 2.5.5
_______________________________________________ Pki-devel mailing list Pki-devel@redhat.com https://www.redhat.com/mailman/listinfo/pki-devel