From 802d30e899dd98c7c1a5d8dd0569b55e473ca59e 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 | 149 ++++++++++++++++++++- base/ca/src/com/netscape/ca/KeyRetriever.java | 55 ++++++++ .../src/netscape/security/pkcs/PKCS12Util.java | 3 + 3 files changed, 201 insertions(+), 6 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 d96b8841449f4a19e652cc2512f834fed87f64e5..d91f623055e97a74e459c4a7e03668e2c3c37622 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.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Enumeration; @@ -66,6 +67,7 @@ import org.mozilla.jss.crypto.SignatureAlgorithm; import org.mozilla.jss.crypto.TokenException; import org.mozilla.jss.pkix.cert.Extension; import org.mozilla.jss.pkix.primitive.Name; +import org.mozilla.jss.util.Password; import com.netscape.certsrv.apps.CMS; import com.netscape.certsrv.authentication.IAuthToken; @@ -165,6 +167,8 @@ import netscape.ldap.controls.LDAPPersistSearchControl; import netscape.ldap.util.DN; import netscape.security.pkcs.PKCS10; +import netscape.security.pkcs.PKCS12; +import netscape.security.pkcs.PKCS12Util; import netscape.security.util.DerOutputStream; import netscape.security.util.DerValue; import netscape.security.x509.AlgorithmId; @@ -206,6 +210,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; private ECAException signingUnitException = null; @@ -341,6 +346,7 @@ public class CertificateAuthority AuthorityID aid, AuthorityID parentAID, String signingKeyNickname, + Collection authorityKeyHosts, String authorityDescription, boolean authorityEnabled ) throws EBaseException { @@ -356,6 +362,7 @@ public class CertificateAuthority this.authorityDescription = authorityDescription; this.authorityEnabled = authorityEnabled; mNickname = signingKeyNickname; + this.authorityKeyHosts = authorityKeyHosts; init(hostCA.mOwner, hostCA.mConfig); } @@ -505,7 +512,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) { @@ -1447,7 +1454,7 @@ public class CertificateAuthority /** * init CA signing unit & cert chain. */ - private void initSigUnit() + private boolean initSigUnit(boolean retrieveKeys) throws EBaseException { try { // init signing unit @@ -1477,7 +1484,14 @@ public class CertificateAuthority } catch (CAMissingCertException | CAMissingKeyException e) { CMS.debug("CA signing key and cert not (yet) present in NSSDB"); signingUnitException = e; - return; + if (retrieveKeys == true) { + CMS.debug("Starting KeyRetrieverRunner thread"); + new Thread( + new KeyRetrieverRunner(this), + "KeyRetrieverRunner-" + authorityID + ).start(); + } + return false; } CMS.debug("CA signing unit inited"); @@ -1602,6 +1616,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")); @@ -2528,11 +2544,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) @@ -2613,7 +2632,9 @@ public class CertificateAuthority return new CertificateAuthority( hostCA, subjectX500Name, - aid, this.authorityID, nickname, description, true); + aid, this.authorityID, + nickname, Collections.singleton(thisHost), + description, true); } /** @@ -2786,6 +2807,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)); + modifyAuthorityEntry(mods); + authorityKeyHosts.add(hostname); + } + public synchronized void deleteAuthority() throws EBaseException { if (isHostAuthority()) throw new CATypeException("Cannot delete the host CA"); @@ -2934,7 +2972,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"); @@ -2991,6 +3028,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"); @@ -3047,6 +3085,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) @@ -3062,7 +3110,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); @@ -3112,4 +3160,93 @@ 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; + } + + KeyRetriever.Result krr = null; + try { + krr = kr.retrieveKey(ca.mNickname, ca.authorityKeyHosts); + } catch (Throwable e) { + CMS.debug("Caught exception during execution of KeyRetriever.retrieveKey"); + CMS.debug(e); + } + + boolean importSucceeded = false; + if (krr != null) { + CMS.debug("Importing key and cert"); + PKCS12Util p12util = new PKCS12Util(); + Password password = new Password(krr.getPassword().toCharArray()); + try { + PKCS12 p12 = p12util.loadFromByteArray(krr.getPKCS12(), password); + p12util.storeCertIntoNSS(p12, ca.mNickname); + importSucceeded = true; + } catch (Throwable e) { + CMS.debug("Caught exception during PKCS12 import: " + e); + } + } + + boolean initSigUnitSucceeded = false; + if (importSucceeded) { + try { + CMS.debug("Reinitialising SigningUnit"); + // 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("Caught exception during SigningUnit re-init"); + CMS.debug(e); + } + } else { + CMS.debug("Failed to import key and cert"); + } + + if (initSigUnitSucceeded == true) { + CMS.debug("Adding self to authorityKeyHosts attribute"); + try { + ca.addInstanceToAuthorityKeyHosts(); + } catch (Throwable e) { + CMS.debug("Failed to add self to authorityKeyHosts"); + CMS.debug(e); + } + } else { + CMS.debug("Failed to re-init SigningUnit"); + } + } + } + } 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..deb51b8c3a857c4b57b0fe3c705e304768ebb08b --- /dev/null +++ b/base/ca/src/com/netscape/ca/KeyRetriever.java @@ -0,0 +1,55 @@ +// --- 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 + * return to the KeyRetrieverRunner. + * + * A KeyRetriever MUST NOT import the cert and key to the NSSDB + * itself. It SHALL, if successful in retrieving the key and + * certificate, return a Result which contains a DER-encoded + * PKCS #12 object and the password for importing the + * certificates and keys contained within the PKCS #12 object. + * + * Upon failure the KeyRetriever SHALL return null. + */ + Result retrieveKey(String nickname, Collection hostname); + + class Result { + private String password; + private byte[] pkcs12; + + public Result(String password, byte[] pkcs12) { + this.password = password; + this.pkcs12 = pkcs12; + } + + public String getPassword() { + return password; + } + + public byte[] getPKCS12() { + return pkcs12; + } + } +} diff --git a/base/util/src/netscape/security/pkcs/PKCS12Util.java b/base/util/src/netscape/security/pkcs/PKCS12Util.java index 43435c822c9400248fe556bf066cd2659e18ae17..9931027dacfe88e636b694b7c490ffc6804068dd 100644 --- a/base/util/src/netscape/security/pkcs/PKCS12Util.java +++ b/base/util/src/netscape/security/pkcs/PKCS12Util.java @@ -536,7 +536,10 @@ public class PKCS12Util { Path path = Paths.get(filename); byte[] b = Files.readAllBytes(path); + return loadFromByteArray(b, password); + } + public PKCS12 loadFromByteArray(byte[] b, Password password) throws Exception { ByteArrayInputStream bis = new ByteArrayInputStream(b); PFX pfx = (PFX) (new PFX.Template()).decode(bis); -- 2.5.5