From f679d7774f5b6bfac7ccbe9493cfce5e067f08e0 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale 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 authorityKeyHosts = null; protected boolean authorityEnabled = true; private boolean hasKeys = false; @@ -339,6 +341,7 @@ public class CertificateAuthority AuthorityID aid, AuthorityID parentAID, String signingKeyNickname, + Collection 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 keyHosts; + if (keyHostsAttr == null) { + keyHosts = Collections.emptyList(); + } else { + @SuppressWarnings("unchecked") + Enumeration 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 hostname); +} -- 2.5.5