New version of 0097 attached (0097-4). The only change is some minor improvements to the pki-ipa-retrieve-key Python program.
Cheers, Fraser On Tue, Apr 19, 2016 at 07:32:16PM +1000, Fraser Tweedale wrote: > Both issues addressed in latest patchset. Two new patches in the > mix; the order is: > > 0095-4, 0098, 0099, 0096-4, 0097-3 (tip) > > I also added another attribute to schema for the authority > certificate serial number. It is not used in current code but I > have a hunch it may be needed for renewal, so I'm adding it now. > > Thanks, > Fraser > > On Thu, Apr 14, 2016 at 05:34:45PM -0400, Ade Lee wrote: > > Couple of points on 96/97. > > > > 1. First off, I'm not sure you followed my concern about being able to > > distinguish between CA instances. > > > > On an IPA system, this is not an issue because there is only one CA on > > the server. In this case, I imagine there will be a well known > > directory which custodia would work with. > > > > In general though, we have to imagine that someone could end up > > installing two different dogtag ca instances on the same server. > > CMS.getEEHost() would result in the same value (the hostname) for both > > CAs. How does your helper program (or custodia) know which key to > > retrieve? > > > > The way to distinguish Dogtag instances is host AND port. > > > > 2. So, we're very careful that the signing keys are never in memory in > > the server. All accesses to the system certs are through JSS/NSS which > > essentially provides us handles to the keys. > > > > Now, I see a case where we import PKCS12 data AND the password into > > memory, so that we can import it into NSS? Say it ain't so .. > > > > With custodia, we have a secure mechanism of transferring the keys from > > one server to another. It makes more sense to me to have the server > > kick off the custodia transfer and then have that process also import > > into the NSS db. The server would then need to await status from the > > custodia/retriever process - and then initialize the signing unit from > > the NSS DB. Or am I completely confused? > > > > Ade > > > > > > > > On Thu, 2016-04-14 at 16:35 -0400, Ade Lee wrote: > > > Still reviewing .. ACK on 87-95 (inclusive). > > > > > > On Thu, 2016-04-14 at 16:18 +1000, Fraser Tweedale wrote: > > > > On Thu, Apr 14, 2016 at 09:04:31AM +1000, Fraser Tweedale wrote: > > > > > On Wed, Apr 13, 2016 at 05:26:44PM -0400, Ade Lee wrote: > > > > > > Still reviewing .. > > > > > > > > > > > > See comment on 87. ACK on 88,89,90,91,92,93, 94, 95. > > > > > > > > > > > > Ade > > > > > > > > > > > > On Mon, 2016-04-11 at 12:32 +1000, Fraser Tweedale wrote: > > > > > > > Thanks for review, Ade. Comments to specific feedback > > > > > > > inline. > > > > > > > Rebased and updated patches attached. The substantive > > > > > > > changes > > > > > > > are: > > > > > > > > > > > > > > - KeyRetriever implementations are now required NOT to import > > > > > > > the > > > > > > > key themselves. Instead the API is updated with > > > > > > > KeyRetriever.retrieveKey returning a Result, which contains > > > > > > > PKCS > > > > > > > #12 data and password for same. > > > > > > > > > > > > > > - KeyRetrieverRunner reads the Result and imports the PKCS > > > > > > > #12 > > > > > > > into > > > > > > > NSSDB. > > > > > > > > > > > > > > - Added new patch 0097 which provides the > > > > > > > IPACustodiaKeyRetriever > > > > > > > and assoicated Python helper script. It depends on an > > > > > > > unmerged > > > > > > > FreeIPA patch[1] as well as a particular principal and > > > > > > > associated > > > > > > > keytab and Custodia keys existing. I'm working on FreeIPA > > > > > > > updates > > > > > > > to satisfy these requirements automatically on install or > > > > > > > upgrade > > > > > > > but if you want to test this patch LMK and I'll provide > > > > > > > detailed > > > > > > > instructions. > > > > > > > > > > > > > > [1] > > > > > > > https://www.redhat.com/archives/freeipa-devel/2016-April/msg0 > > > > > > > 00 > > > > > > > 55.html > > > > > > > > > > > > > > Other comments inline. > > > > > > > > > > > > > > Cheers, > > > > > > > Fraser > > > > > > > > > > > > > > On Fri, Apr 08, 2016 at 11:16:19AM -0400, Ade Lee wrote: > > > > > > > > > > > > > > > > 0087 > > > > > > > > > > > > > > > > 1. In SigningUnit.java -- you catch an ObjectNotFound > > > > > > > > exception and > > > > > > > > rethrow that as a CAMissingKey exception. Is that the only > > > > > > > > way the > > > > > > > > ObjectNotFound exception can be thrown? What if the key is > > > > > > > > present > > > > > > > > but > > > > > > > > the cert is not? Can we refactor here to ensure that the > > > > > > > > correct > > > > > > > > exception is thrown? > > > > > > > > > > > > > > > One can't get additional info out of ObjectNotFound without > > > > > > > inspecting the String message, which I'm not comfortable > > > > > > > doing. > > > > > > > The > > > > > > > key retrieval system should import key and cert at same time > > > > > > > so > > > > > > > I've > > > > > > > renamed the exception to CAMissingKeyOrCert for clarity. > > > > > > > > > > > > > > > > > > > Well, you can always nest exceptions like so : > > > > > > > > > > > > mToken.login(cb); // ONE_TIME by default. > > > > > > > > > > > > try { > > > > > > mCert = mManager.findCertByNickname(mNickname); > > > > > > CMS.debug("Found cert by nickname: '" + > > > > > > mNickname > > > > > > + "' with serial number: " + mCert.getSerialNumber()); > > > > > > > > > > > > mCertImpl = new > > > > > > X509CertImpl(mCert.getEncoded()); > > > > > > CMS.debug("converted to x509CertImpl"); > > > > > > } catch (ObjectNotFoundException e) { > > > > > > throw new CAMissingCertException(); > > > > > > } > > > > > > > > > > > > try { > > > > > > mPrivk = mManager.findPrivKeyByCert(mCert); > > > > > > CMS.debug("Got private key from cert"); > > > > > > } catch (ObjectNotFoundException e) { > > > > > > throw new CAMissingKeyException(); > > > > > > } > > > > > > .... > > > > > > > > > > > > The only reason that I suggest this is that I could imagine > > > > > > this > > > > > > kind > > > > > > of differentiation being useful in debugging failed custodia > > > > > > replications. If you think otherwise, I'm prepare to be > > > > > > convinced > > > > > > otherwise. > > > > > > > > > > > I think a scenario where we get key but not cert, or vice versa, > > > > > is > > > > > unlikely (Custodia gives us a PKCS #12 file with both). However, > > > > > your suggestion should work and it is a relatively small change. > > > > > I'll cut a new patchset with this change today, along with the > > > > > rebase. > > > > > > > > > Updated and rebased patches attached. > > > > > > > > The suggested changes were made to 0087. This also resulted in > > > > changes to patch 0094 (indicate when CA does not yet have keys). > > > > > > > > No substantive changes to any other patches. > > > > > > > > Cheers, > From f5322fdf885196275a93696cc31a489c9b4aab1d Mon Sep 17 00:00:00 2001 > From: Fraser Tweedale <[email protected]> > Date: Wed, 30 Mar 2016 16:06:25 +1100 > Subject: [PATCH] Lightweight CAs: authority schema changes > > Add the 'authorityKeyHost' attribute which will contain names of > hosts that possess the authority's signing keys. > > Add the 'authoritySerial' attribute which may contain the serial > number of the certificate most recently issued for the authority. > > Change other attributes to be single-valued. > > Part of: https://fedorahosted.org/pki/ticket/1625 > --- > base/server/share/conf/schema-authority.ldif | 16 +++++++++------- > base/server/share/conf/schema.ldif | 15 ++++++++------- > 2 files changed, 17 insertions(+), 14 deletions(-) > > diff --git a/base/server/share/conf/schema-authority.ldif > b/base/server/share/conf/schema-authority.ldif > index > 7d261f18fbc9475983bf93b1cddcc184d7f9d178..fd3c4fa225b036142a9aa4e99c65697365160dfd > 100644 > --- a/base/server/share/conf/schema-authority.ldif > +++ b/base/server/share/conf/schema-authority.ldif > @@ -1,8 +1,10 @@ > 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: ( authoritySerial-oid NAME 'authoritySerial' DESC 'Authority > certificate serial number' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 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 ( authoritySerial $ 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 3d42d650abf5b4862570620ef7b130bb511bb17d Mon Sep 17 00:00:00 2001 > From: Fraser Tweedale <[email protected]> > Date: Tue, 19 Apr 2016 13:25:12 +1000 > Subject: [PATCH 98/99] Add method CryptoUtil.importPKIArchiveOptions > > Add the method CryptoUtil.importPKIArchiveOptions for importing a > wrapped key from a PKIArchiveOptions object. Also add another > variant of the createPKIArchiveOptions method, with a narrower API. > > Part of: https://fedorahosted.org/pki/ticket/1625 > --- > .../com/netscape/cmsutil/crypto/CryptoUtil.java | 58 > ++++++++++++++++++++++ > 1 file changed, 58 insertions(+) > > diff --git a/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java > b/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java > index > 06caa0242ab192c5bbc14845dd7abc772601bd58..7ac3e4e9ad2702e09c704638b6ab773bb2dccb49 > 100644 > --- a/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java > +++ b/base/util/src/com/netscape/cmsutil/crypto/CryptoUtil.java > @@ -80,7 +80,9 @@ import org.mozilla.jss.CryptoManager; > import org.mozilla.jss.CryptoManager.NotInitializedException; > import org.mozilla.jss.NoSuchTokenException; > import org.mozilla.jss.SecretDecoderRing.KeyManager; > +import org.mozilla.jss.asn1.ANY; > import org.mozilla.jss.asn1.ASN1Util; > +import org.mozilla.jss.asn1.ASN1Value; > import org.mozilla.jss.asn1.BIT_STRING; > import org.mozilla.jss.asn1.InvalidBERException; > import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; > @@ -1971,6 +1973,31 @@ public class CryptoUtil { > // wrap session key using transport key > byte[] session_data = wrapSymmetricKey(manager, token, > transportCert, sk); > > + return createPKIArchiveOptions(IV, session_data, key_data); > + } > + > + public static byte[] createPKIArchiveOptions( > + CryptoToken token, PublicKey wrappingKey, PrivateKey toBeWrapped, > + KeyGenAlgorithm keyGenAlg, int symKeySize, IVParameterSpec IV) > + throws TokenException, NoSuchAlgorithmException, > + InvalidAlgorithmParameterException, InvalidKeyException, > + IOException, InvalidBERException { > + SymmetricKey sessionKey = CryptoUtil.generateKey(token, keyGenAlg, > symKeySize); > + > + KeyWrapper wrapper = > token.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD); > + wrapper.initWrap(sessionKey, IV); > + byte[] key_data = wrapper.wrap(toBeWrapped); > + > + wrapper = token.getKeyWrapper(KeyWrapAlgorithm.RSA); > + wrapper.initWrap(wrappingKey, null); > + byte session_data[] = wrapper.wrap(sessionKey); > + > + return createPKIArchiveOptions(IV, session_data, key_data); > + } > + > + private static byte[] createPKIArchiveOptions( > + IVParameterSpec IV, byte[] session_data, byte[] key_data) > + throws IOException, InvalidBERException { > // create PKIArchiveOptions structure > AlgorithmIdentifier algS = new AlgorithmIdentifier(new > OBJECT_IDENTIFIER("1.2.840.113549.3.7"), > new OCTET_STRING(IV.getIV())); > @@ -1996,6 +2023,37 @@ public class CryptoUtil { > return encoded; > } > > + public static PrivateKey importPKIArchiveOptions( > + CryptoToken token, PrivateKey unwrappingKey, > + PublicKey pubkey, byte[] data) > + throws InvalidBERException, Exception { > + ByteArrayInputStream in = new ByteArrayInputStream(data); > + PKIArchiveOptions options = (PKIArchiveOptions) > + (new PKIArchiveOptions.Template()).decode(in); > + EncryptedKey encKey = options.getEncryptedKey(); > + EncryptedValue encVal = encKey.getEncryptedValue(); > + AlgorithmIdentifier algId = encVal.getSymmAlg(); > + BIT_STRING encSymKey = encVal.getEncSymmKey(); > + BIT_STRING encPrivKey = encVal.getEncValue(); > + > + KeyWrapper wrapper = token.getKeyWrapper(KeyWrapAlgorithm.RSA); > + wrapper.initUnwrap(unwrappingKey, null); > + SymmetricKey sk = wrapper.unwrapSymmetric( > + encSymKey.getBits(), SymmetricKey.Type.DES3, 0); > + > + ASN1Value v = algId.getParameters(); > + v = ((ANY) v).decodeWith(new OCTET_STRING.Template()); > + byte iv[] = ((OCTET_STRING) v).toByteArray(); > + IVParameterSpec ivps = new IVParameterSpec(iv); > + > + wrapper = token.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD); > + wrapper.initUnwrap(sk, ivps); > + PrivateKey.Type keyType = pubkey.getAlgorithm().equals("EC") > + ? PrivateKey.Type.EC > + : PrivateKey.Type.RSA; > + return wrapper.unwrapPrivate(encPrivKey.getBits(), keyType, pubkey); > + } > + > public static boolean sharedSecretExists(String nickname) throws > NotInitializedException, TokenException { > CryptoManager cm = CryptoManager.getInstance(); > CryptoToken token = cm.getInternalKeyStorageToken(); > -- > 2.5.5 > > From ba32660ee26fc00132882809ce9365690f375c6c Mon Sep 17 00:00:00 2001 > From: Fraser Tweedale <[email protected]> > Date: Tue, 19 Apr 2016 13:28:56 +1000 > Subject: [PATCH 99/99] Add ca-authority-key-export command > > Add the 'pki ca-authority-key-export' CLI command for exporting a > PKIArchiveOptions object containing a nominated target key, wrapped > by a nominated wrapping key. This command is to be used by Custodia > to export key data for transmission to a requesting clone. > > Part of: https://fedorahosted.org/pki/ticket/1625 > --- > .../netscape/cmstools/authority/AuthorityCLI.java | 1 + > .../cmstools/authority/AuthorityKeyExportCLI.java | 109 > +++++++++++++++++++++ > 2 files changed, 110 insertions(+) > create mode 100644 > base/java-tools/src/com/netscape/cmstools/authority/AuthorityKeyExportCLI.java > > 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 > ac06ea24ce824ad1b4be29a4176658caa9302e89..f42660d6727059bc76ab7ccd0bd0b22a87bc5f9a > 100644 > --- a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java > +++ b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityCLI.java > @@ -18,6 +18,7 @@ public class AuthorityCLI extends CLI { > addModule(new AuthorityDisableCLI(this)); > addModule(new AuthorityEnableCLI(this)); > addModule(new AuthorityRemoveCLI(this)); > + addModule(new AuthorityKeyExportCLI(this)); > } > > public String getFullName() { > diff --git > a/base/java-tools/src/com/netscape/cmstools/authority/AuthorityKeyExportCLI.java > > b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityKeyExportCLI.java > new file mode 100644 > index > 0000000000000000000000000000000000000000..a3dee82c8d7ef3ad923aa53635b0825f3d272998 > --- /dev/null > +++ > b/base/java-tools/src/com/netscape/cmstools/authority/AuthorityKeyExportCLI.java > @@ -0,0 +1,109 @@ > +package com.netscape.cmstools.authority; > + > +import java.nio.file.Files; > +import java.nio.file.Paths; > +import java.security.PublicKey; > + > +import org.apache.commons.cli.CommandLine; > +import org.apache.commons.cli.Option; > +import org.apache.commons.cli.ParseException; > + > +import org.mozilla.jss.CryptoManager; > +import org.mozilla.jss.crypto.CryptoToken; > +import org.mozilla.jss.crypto.IVParameterSpec; > +import org.mozilla.jss.crypto.KeyGenAlgorithm; > +import org.mozilla.jss.crypto.PrivateKey; > +import org.mozilla.jss.crypto.X509Certificate; > + > +import com.netscape.cmstools.cli.CLI; > +import com.netscape.cmsutil.crypto.CryptoUtil; > + > +public class AuthorityKeyExportCLI extends CLI { > + > + public AuthorityCLI authorityCLI; > + > + public AuthorityKeyExportCLI(AuthorityCLI authorityCLI) { > + super("key-export", "Export wrapped CA signing key", authorityCLI); > + this.authorityCLI = authorityCLI; > + > + options.addOption(null, "help", false, "Show usage"); > + > + Option option = new Option("o", "output", true, "Output file"); > + option.setArgName("filename"); > + options.addOption(option); > + > + option = new Option(null, "wrap-nickname", true, "Nickname of > wrapping key"); > + option.setArgName("nickname"); > + options.addOption(option); > + > + option = new Option(null, "target-nickname", true, "Nickname of > target key"); > + option.setArgName("nickname"); > + options.addOption(option); > + } > + > + public void printHelp() { > + formatter.printHelp(getFullName() + "--wrap-nickname NICKNAME > --target-nickname NICKNAME -o FILENAME", options); > + } > + > + public void execute(String[] args) throws Exception { > + CommandLine cmd = null; > + > + try { > + cmd = parser.parse(options, args); > + } catch (ParseException e) { > + System.err.println("Error: " + e.getMessage()); > + printHelp(); > + System.exit(-1); > + } > + > + if (cmd.hasOption("help")) { > + // Display usage > + printHelp(); > + System.exit(0); > + } > + > + String filename = cmd.getOptionValue("output"); > + if (filename == null) { > + System.err.println("Error: No output file specified."); > + printHelp(); > + System.exit(-1); > + } > + > + String wrapNick = cmd.getOptionValue("wrap-nickname"); > + if (wrapNick == null) { > + System.err.println("Error: no wrapping key nickname specified."); > + printHelp(); > + System.exit(-1); > + } > + > + String targetNick = cmd.getOptionValue("target-nickname"); > + if (targetNick == null) { > + System.err.println("Error: no target key nickname specified."); > + printHelp(); > + System.exit(-1); > + } > + > + try { > + CryptoManager cm = CryptoManager.getInstance(); > + X509Certificate wrapCert = cm.findCertByNickname(wrapNick); > + X509Certificate targetCert = cm.findCertByNickname(targetNick); > + > + PublicKey wrappingKey = wrapCert.getPublicKey(); > + PrivateKey toBeWrapped = cm.findPrivKeyByCert(targetCert); > + CryptoToken token = cm.getInternalKeyStorageToken(); > + > + byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }; > + IVParameterSpec ivps = new IVParameterSpec(iv); > + > + byte[] data = CryptoUtil.createPKIArchiveOptions( > + token, wrappingKey, toBeWrapped, > + KeyGenAlgorithm.DES3, 0, ivps); > + > + Files.newOutputStream(Paths.get(filename)).write(data); > + } catch (Throwable e) { > + e.printStackTrace(); > + System.exit(-1); > + } > + > + } > +} > -- > 2.5.5 > > From 4c8fdc1b11e052d469aee944e491cd8725b7a1e9 Mon Sep 17 00:00:00 2001 > From: Fraser Tweedale <[email protected]> > Date: Wed, 30 Mar 2016 12:38:24 +1100 > Subject: [PATCH] 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 | 162 > ++++++++++++++++++++- > base/ca/src/com/netscape/ca/KeyRetriever.java | 56 +++++++ > .../src/netscape/security/pkcs/PKCS12Util.java | 3 + > 3 files changed, 215 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 > 37f1e95fc97f3d21ec6dc379962e27b42fb5b074..ac45141662aea732389353814e5a7e7b3ba516a7 > 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; > @@ -62,8 +63,10 @@ import org.mozilla.jss.crypto.CryptoToken; > import org.mozilla.jss.crypto.KeyPairAlgorithm; > import org.mozilla.jss.crypto.KeyPairGenerator; > import org.mozilla.jss.crypto.NoSuchItemOnTokenException; > +import org.mozilla.jss.crypto.PrivateKey; > import org.mozilla.jss.crypto.SignatureAlgorithm; > import org.mozilla.jss.crypto.TokenException; > +import org.mozilla.jss.crypto.X509Certificate; > import org.mozilla.jss.pkix.cert.Extension; > import org.mozilla.jss.pkix.primitive.Name; > > @@ -205,6 +208,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; > private ECAException signingUnitException = null; > @@ -340,6 +344,7 @@ public class CertificateAuthority > AuthorityID aid, > AuthorityID parentAID, > String signingKeyNickname, > + Collection<String> authorityKeyHosts, > String authorityDescription, > boolean authorityEnabled > ) throws EBaseException { > @@ -355,6 +360,7 @@ public class CertificateAuthority > this.authorityDescription = authorityDescription; > this.authorityEnabled = authorityEnabled; > mNickname = signingKeyNickname; > + this.authorityKeyHosts = authorityKeyHosts; > init(hostCA.mOwner, hostCA.mConfig); > } > > @@ -504,7 +510,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) { > @@ -1446,7 +1452,7 @@ public class CertificateAuthority > /** > * init CA signing unit & cert chain. > */ > - private void initSigUnit() > + private boolean initSigUnit(boolean retrieveKeys) > throws EBaseException { > try { > // init signing unit > @@ -1476,7 +1482,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"); > > @@ -1601,6 +1614,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")); > @@ -2527,11 +2542,14 @@ public class CertificateAuthority > throw new EBaseException("Failed to convert issuer DN to string: > " + e); > } > > + String thisClone = CMS.getEEHost() + ":" + CMS.getEESSLPort(); > + > LDAPAttribute[] attrs = { > new LDAPAttribute("objectclass", "authority"), > new LDAPAttribute("cn", aidString), > new LDAPAttribute("authorityID", aidString), > new LDAPAttribute("authorityKeyNickname", nickname), > + new LDAPAttribute("authorityKeyHost", thisClone), > new LDAPAttribute("authorityEnabled", "TRUE"), > new LDAPAttribute("authorityDN", subjectDN), > new LDAPAttribute("authorityParentDN", parentDNString) > @@ -2612,7 +2630,9 @@ public class CertificateAuthority > > return new CertificateAuthority( > hostCA, subjectX500Name, > - aid, this.authorityID, nickname, description, true); > + aid, this.authorityID, > + nickname, Collections.singleton(thisClone), > + description, true); > } > > /** > @@ -2785,6 +2805,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"); > @@ -2933,7 +2970,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"); > @@ -2990,6 +3026,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"); > @@ -3052,6 +3089,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) > @@ -3067,7 +3114,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); > @@ -3117,4 +3164,107 @@ 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); > + return; > + } > + > + if (krr == null) { > + CMS.debug("KeyRetriever did not return a result."); > + return; > + } > + > + CMS.debug("Importing key and cert"); > + byte[] certBytes = krr.getCertificate(); > + byte[] paoData = krr.getPKIArchiveOptions(); > + try { > + CryptoManager manager = CryptoManager.getInstance(); > + CryptoToken token = manager.getInternalKeyStorageToken(); > + > + X509Certificate cert = > manager.importCACertPackage(certBytes); > + PublicKey pubkey = cert.getPublicKey(); > + token.getCryptoStore().deleteCert(cert); > + > + PrivateKey unwrappingKey = > hostCA.mSigningUnit.getPrivateKey(); > + > + CryptoUtil.importPKIArchiveOptions( > + token, unwrappingKey, pubkey, paoData); > + > + cert = manager.importUserCACertPackage(certBytes, > ca.mNickname); > + } catch (Throwable e) { > + CMS.debug("Caught exception during cert/key import"); > + CMS.debug(e); > + return; > + } > + > + boolean initSigUnitSucceeded = false; > + 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); > + return; > + } > + > + if (!initSigUnitSucceeded) { > + CMS.debug("Failed to re-init SigningUnit"); > + return; > + } > + > + CMS.debug("Adding self to authorityKeyHosts attribute"); > + try { > + ca.addInstanceToAuthorityKeyHosts(); > + } catch (Throwable e) { > + CMS.debug("Failed to add self to authorityKeyHosts"); > + CMS.debug(e); > + return; > + } > + } > + } > + > } > 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..7c0df0bf56578b81062de77de47aa516b5c9d949 > --- /dev/null > +++ b/base/ca/src/com/netscape/ca/KeyRetriever.java > @@ -0,0 +1,56 @@ > +// --- 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 clone 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 PEM-encoded > + * X.509 certificate and a DER-encoded PKIArchiveOptions object > + * containing an EncryptedValue of the target private key > + * wrapped by the host authority's public key. > + * > + * Upon failure the KeyRetriever SHALL return null. > + */ > + Result retrieveKey(String nickname, Collection<String> hostPorts); > + > + class Result { > + private byte[] certificate; > + private byte[] pkiArchiveOptions; > + > + public Result(byte[] certificate, byte[] pkiArchiveOptions) { > + this.certificate = certificate; > + this.pkiArchiveOptions = pkiArchiveOptions; > + } > + > + public byte[] getCertificate() { > + return certificate; > + } > + > + public byte[] getPKIArchiveOptions() { > + return pkiArchiveOptions; > + } > + } > +} > 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 > > From d2e57290b2d3392082335955be72dfaf3111afd0 Mon Sep 17 00:00:00 2001 > From: Fraser Tweedale <[email protected]> > Date: Fri, 8 Apr 2016 22:23:42 +1000 > Subject: [PATCH] Lightweight CAs: add IPACustodiaKeyRetriever > > Add 'IPACustodiaKeyRetriever', a 'KeyRetriever' implementation for > use when Dogtag is deployed as a FreeIPA CA. The Java class invokes > 'pki-ipa-key-retriever', a Python script that retrieves lightweight > CA keys from the Custodia server on a replica that possesses the > keys. 'pki-ipa-key-retriever' depends on FreeIPA libraries, FreeIPA > server configuration, and Kerberos and Custodia keys owned by > 'pkiuser'. > > Part of: https://fedorahosted.org/pki/ticket/1625 > --- > base/ca/src/CMakeLists.txt | 9 ++- > .../com/netscape/ca/IPACustodiaKeyRetriever.java | 75 > ++++++++++++++++++++++ > base/server/CMakeLists.txt | 11 ++++ > base/server/libexec/pki-ipa-retrieve-key | 42 ++++++++++++ > specs/pki-core.spec | 1 + > 5 files changed, 137 insertions(+), 1 deletion(-) > create mode 100644 base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java > create mode 100755 base/server/libexec/pki-ipa-retrieve-key > > diff --git a/base/ca/src/CMakeLists.txt b/base/ca/src/CMakeLists.txt > index > 5b805e1b3a5eddb46d17178ca2ac204c36ae5680..1817dacfbacaeb2635db2550e32ff62c26d628ef > 100644 > --- a/base/ca/src/CMakeLists.txt > +++ b/base/ca/src/CMakeLists.txt > @@ -24,6 +24,13 @@ find_file(COMMONS_CODEC_JAR > /usr/share/java > ) > > +find_file(COMMONS_IO_JAR > + NAMES > + commons-io.jar > + PATHS > + /usr/share/java > +) > + > find_file(COMMONS_LANG_JAR > NAMES > commons-lang.jar > @@ -73,7 +80,7 @@ javac(pki-ca-classes > com/netscape/ca/*.java > org/dogtagpki/server/ca/*.java > CLASSPATH > - ${COMMONS_CODEC_JAR} ${COMMONS_LANG_JAR} > + ${COMMONS_CODEC_JAR} ${COMMONS_IO_JAR} ${COMMONS_LANG_JAR} > ${JSS_JAR} ${SYMKEY_JAR} > ${LDAPJDK_JAR} > ${SERVLET_JAR} ${TOMCAT_CATALINA_JAR} > diff --git a/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java > b/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java > new file mode 100644 > index > 0000000000000000000000000000000000000000..4a162d3702fccc19dfef792a5213c653286930f3 > --- /dev/null > +++ b/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java > @@ -0,0 +1,75 @@ > +// --- 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.lang.Process; > +import java.lang.ProcessBuilder; > +import java.util.Collection; > +import java.util.Stack; > + > +import org.apache.commons.io.IOUtils; > +import org.apache.commons.lang.ArrayUtils; > + > +import com.netscape.certsrv.apps.CMS; > + > +public class IPACustodiaKeyRetriever implements KeyRetriever { > + public Result retrieveKey(String nickname, Collection<String> hostPorts) > { > + CMS.debug("Running IPACustodiaKeyRetriever"); > + > + Stack<String> command = new Stack<>(); > + command.push("/usr/libexec/pki-ipa-retrieve-key"); > + command.push(nickname); > + > + for (String hostPort : hostPorts) { > + String host = hostPort.split(":")[0]; > + command.push(host); > + CMS.debug("About to execute command: " + command); > + ProcessBuilder pb = new ProcessBuilder(command); > + try { > + Process p = pb.start(); > + int exitValue = p.waitFor(); > + if (exitValue != 0) > + continue; > + > + /* Custodia returns a PEM-encoded certificate and a > + * base64-encoded PKIArchiveOptions containing the > + * wrapped private key. These values are output by > + * the Python 'pki-ipa-retrieve-key' program, > + * separated by a null byte (password first) > + */ > + byte[] output = IOUtils.toByteArray(p.getInputStream()); > + int splitIndex = ArrayUtils.indexOf(output, (byte) 0); > + if (splitIndex == ArrayUtils.INDEX_NOT_FOUND) { > + CMS.debug("Invalid output: null byte not found"); > + continue; > + } > + return new Result( > + ArrayUtils.subarray(output, 0, splitIndex), > + ArrayUtils.subarray(output, splitIndex + 1, > output.length) > + ); > + } catch (Throwable e) { > + CMS.debug("Caught exception while executing command: " + e); > + } finally { > + command.pop(); > + } > + } > + CMS.debug("Failed to retrieve key from any host."); > + return null; > + } > +} > diff --git a/base/server/CMakeLists.txt b/base/server/CMakeLists.txt > index > 5a6aea96a2317655fb454967f9f218020443bcb8..9e5b27833c8d023e63320c43d64ad64b0055c254 > 100644 > --- a/base/server/CMakeLists.txt > +++ b/base/server/CMakeLists.txt > @@ -81,6 +81,17 @@ install( > > install( > DIRECTORY > + libexec/ > + DESTINATION > + ${LIBEXEC_INSTALL_DIR} > + FILE_PERMISSIONS > + OWNER_EXECUTE OWNER_WRITE OWNER_READ > + GROUP_EXECUTE GROUP_READ > + WORLD_EXECUTE WORLD_READ > +) > + > +install( > + DIRECTORY > upgrade > DESTINATION > ${DATA_INSTALL_DIR}/server/ > diff --git a/base/server/libexec/pki-ipa-retrieve-key > b/base/server/libexec/pki-ipa-retrieve-key > new file mode 100755 > index > 0000000000000000000000000000000000000000..9305150a446bb4deed339b89daad821a19b9e7df > --- /dev/null > +++ b/base/server/libexec/pki-ipa-retrieve-key > @@ -0,0 +1,42 @@ > +#!/usr/bin/python > + > +from __future__ import print_function > + > +import ConfigParser > +import base64 > +import sys > + > +from jwcrypto.common import json_decode > + > +from ipaplatform.paths import paths > +from ipapython.secrets.client import CustodiaClient > + > +conf = ConfigParser.ConfigParser() > +conf.read(paths.IPA_DEFAULT_CONF) > +hostname = conf.get('global', 'host') > +realm = conf.get('global', 'realm') > + > +keyname = "ca_wrapped/" + sys.argv[1] > +servername = sys.argv[2] > + > +client_keyfile = "/etc/pki/pki-tomcat/dogtag-ipa-custodia.keys" > +client_keytab = "/etc/pki/pki-tomcat/dogtag-ipa-custodia.keytab" > + > +client = CustodiaClient( > + client=hostname, server=servername, realm=realm, > + ldap_uri="ldaps://" + hostname, > + client_servicename='dogtag-ipa-custodia', > + keyfile=client_keyfile, keytab=client_keytab, > + ) > + > +result_json = client.fetch_key(keyname, store=False) > +result = json_decode(result_json) > +certificate = result["certificate"] > +wrapped_key = base64.b64decode(result["wrapped_key"]) > + > +# Custodia returns a PEM-encoded certificate and a base64-encoded > +# DER PKIArchiveOptions object. Output these values, separated by a > +# null byte (certificate first), to be read by the Java > +# IPACustodiaKeyRetriever that invoked this program. > + > +print(certificate, wrapped_key, sep='\0', end='') > diff --git a/specs/pki-core.spec b/specs/pki-core.spec > index > bce6bd2d298bc8e1ad5cb40618f982b5ba23b27d..509ecdafa4e5d0e651ce22497db06420abcdf259 > 100644 > --- a/specs/pki-core.spec > +++ b/specs/pki-core.spec > @@ -1007,6 +1007,7 @@ systemctl daemon-reload > %{_sbindir}/pki-server > %{_sbindir}/pki-server-nuxwdog > %{_sbindir}/pki-server-upgrade > +%{_libexecdir}/pki-ipa-retrieve-key > %{python2_sitelib}/pki/server/ > %dir %{_datadir}/pki/deployment > %{_datadir}/pki/deployment/config/ > -- > 2.5.5 > > _______________________________________________ > Pki-devel mailing list > [email protected] > https://www.redhat.com/mailman/listinfo/pki-devel
From 66e0b84fabe4175be8077a5f587f84ba999dd074 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale <[email protected]> Date: Fri, 8 Apr 2016 22:23:42 +1000 Subject: [PATCH] Lightweight CAs: add IPACustodiaKeyRetriever Add 'IPACustodiaKeyRetriever', a 'KeyRetriever' implementation for use when Dogtag is deployed as a FreeIPA CA. The Java class invokes 'pki-ipa-retrieve-key', a Python script that retrieves lightweight CA keys from the Custodia server on a replica that possesses the keys. 'pki-ipa-retrieve-key' depends on FreeIPA libraries, FreeIPA server configuration, and Kerberos and Custodia keys owned by 'pkiuser'. Part of: https://fedorahosted.org/pki/ticket/1625 --- base/ca/src/CMakeLists.txt | 9 ++- .../com/netscape/ca/IPACustodiaKeyRetriever.java | 75 ++++++++++++++++++++++ base/server/CMakeLists.txt | 11 ++++ base/server/libexec/pki-ipa-retrieve-key | 45 +++++++++++++ specs/pki-core.spec | 1 + 5 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java create mode 100755 base/server/libexec/pki-ipa-retrieve-key diff --git a/base/ca/src/CMakeLists.txt b/base/ca/src/CMakeLists.txt index 5b805e1b3a5eddb46d17178ca2ac204c36ae5680..1817dacfbacaeb2635db2550e32ff62c26d628ef 100644 --- a/base/ca/src/CMakeLists.txt +++ b/base/ca/src/CMakeLists.txt @@ -24,6 +24,13 @@ find_file(COMMONS_CODEC_JAR /usr/share/java ) +find_file(COMMONS_IO_JAR + NAMES + commons-io.jar + PATHS + /usr/share/java +) + find_file(COMMONS_LANG_JAR NAMES commons-lang.jar @@ -73,7 +80,7 @@ javac(pki-ca-classes com/netscape/ca/*.java org/dogtagpki/server/ca/*.java CLASSPATH - ${COMMONS_CODEC_JAR} ${COMMONS_LANG_JAR} + ${COMMONS_CODEC_JAR} ${COMMONS_IO_JAR} ${COMMONS_LANG_JAR} ${JSS_JAR} ${SYMKEY_JAR} ${LDAPJDK_JAR} ${SERVLET_JAR} ${TOMCAT_CATALINA_JAR} diff --git a/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java b/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java new file mode 100644 index 0000000000000000000000000000000000000000..4a162d3702fccc19dfef792a5213c653286930f3 --- /dev/null +++ b/base/ca/src/com/netscape/ca/IPACustodiaKeyRetriever.java @@ -0,0 +1,75 @@ +// --- 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.lang.Process; +import java.lang.ProcessBuilder; +import java.util.Collection; +import java.util.Stack; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.ArrayUtils; + +import com.netscape.certsrv.apps.CMS; + +public class IPACustodiaKeyRetriever implements KeyRetriever { + public Result retrieveKey(String nickname, Collection<String> hostPorts) { + CMS.debug("Running IPACustodiaKeyRetriever"); + + Stack<String> command = new Stack<>(); + command.push("/usr/libexec/pki-ipa-retrieve-key"); + command.push(nickname); + + for (String hostPort : hostPorts) { + String host = hostPort.split(":")[0]; + command.push(host); + CMS.debug("About to execute command: " + command); + ProcessBuilder pb = new ProcessBuilder(command); + try { + Process p = pb.start(); + int exitValue = p.waitFor(); + if (exitValue != 0) + continue; + + /* Custodia returns a PEM-encoded certificate and a + * base64-encoded PKIArchiveOptions containing the + * wrapped private key. These values are output by + * the Python 'pki-ipa-retrieve-key' program, + * separated by a null byte (password first) + */ + byte[] output = IOUtils.toByteArray(p.getInputStream()); + int splitIndex = ArrayUtils.indexOf(output, (byte) 0); + if (splitIndex == ArrayUtils.INDEX_NOT_FOUND) { + CMS.debug("Invalid output: null byte not found"); + continue; + } + return new Result( + ArrayUtils.subarray(output, 0, splitIndex), + ArrayUtils.subarray(output, splitIndex + 1, output.length) + ); + } catch (Throwable e) { + CMS.debug("Caught exception while executing command: " + e); + } finally { + command.pop(); + } + } + CMS.debug("Failed to retrieve key from any host."); + return null; + } +} diff --git a/base/server/CMakeLists.txt b/base/server/CMakeLists.txt index 5a6aea96a2317655fb454967f9f218020443bcb8..9e5b27833c8d023e63320c43d64ad64b0055c254 100644 --- a/base/server/CMakeLists.txt +++ b/base/server/CMakeLists.txt @@ -81,6 +81,17 @@ install( install( DIRECTORY + libexec/ + DESTINATION + ${LIBEXEC_INSTALL_DIR} + FILE_PERMISSIONS + OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_READ + WORLD_EXECUTE WORLD_READ +) + +install( + DIRECTORY upgrade DESTINATION ${DATA_INSTALL_DIR}/server/ diff --git a/base/server/libexec/pki-ipa-retrieve-key b/base/server/libexec/pki-ipa-retrieve-key new file mode 100755 index 0000000000000000000000000000000000000000..a7c1396a2fce6b3572a897cdf760d5264eefaab4 --- /dev/null +++ b/base/server/libexec/pki-ipa-retrieve-key @@ -0,0 +1,45 @@ +#!/usr/bin/python + +from __future__ import print_function + +import ConfigParser +import base64 +import os +import sys + +from jwcrypto.common import json_decode + +from ipaplatform.constants import constants +from ipaplatform.paths import paths +from ipapython.secrets.client import CustodiaClient + +conf = ConfigParser.ConfigParser() +conf.read(paths.IPA_DEFAULT_CONF) +hostname = conf.get('global', 'host') +realm = conf.get('global', 'realm') + +keyname = "ca_wrapped/" + sys.argv[1] +servername = sys.argv[2] + +service = constants.PKI_GSSAPI_SERVICE_NAME +client_keyfile = os.path.join(paths.PKI_TOMCAT, service + '.keys') +client_keytab = os.path.join(paths.PKI_TOMCAT, service + '.keytab') + +client = CustodiaClient( + client=hostname, server=servername, realm=realm, + ldap_uri="ldaps://" + hostname, + client_servicename=service, + keyfile=client_keyfile, keytab=client_keytab, + ) + +result_json = client.fetch_key(keyname, store=False) +result = json_decode(result_json) +certificate = result["certificate"] +wrapped_key = base64.b64decode(result["wrapped_key"]) + +# Custodia returns a PEM-encoded certificate and a base64-encoded +# DER PKIArchiveOptions object. Output these values, separated by a +# null byte (certificate first), to be read by the Java +# IPACustodiaKeyRetriever that invoked this program. + +print(certificate, wrapped_key, sep='\0', end='') diff --git a/specs/pki-core.spec b/specs/pki-core.spec index bce6bd2d298bc8e1ad5cb40618f982b5ba23b27d..509ecdafa4e5d0e651ce22497db06420abcdf259 100644 --- a/specs/pki-core.spec +++ b/specs/pki-core.spec @@ -1007,6 +1007,7 @@ systemctl daemon-reload %{_sbindir}/pki-server %{_sbindir}/pki-server-nuxwdog %{_sbindir}/pki-server-upgrade +%{_libexecdir}/pki-ipa-retrieve-key %{python2_sitelib}/pki/server/ %dir %{_datadir}/pki/deployment %{_datadir}/pki/deployment/config/ -- 2.5.5
_______________________________________________ Pki-devel mailing list [email protected] https://www.redhat.com/mailman/listinfo/pki-devel
