From 633c7c6519c925af7e3700adff29961d72435c7f Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Thu, 23 Mar 2017 14:34:31 +1100 Subject: [PATCH] PKCS12Util: use AES to encrypt private keys Update PKCS12Util to use AES-256-CBC to encrypt private keys. Use JSS CryptoStore methods to ensure that all key wrapping and unwrapping is done on the token. Specifically, CryptoStore.getEncryptedPrivateKeyInfo replaces the previous process where a symmetric key was generated, the private key wrapped to the symmetric key, then decryted into Dogtag's memory, then re-encrypted under the supplied passphrase. Now the key gets wrapped directly to the supplied passphrase. Similarly, for import, the EncryptedPrivateKeyInfo was decrypted using the supplied passphrase, then encrypted to a freshly generated symmetric key, which was then used to unwrap the key into the token. Now, the new JSS method CryptoStore.importEncryptedPrivateKeyInfo is used to unwrap the EncryptedPrivateKeyInfo directly into the token, using the supplied passphrase. As a result, the PKCS12KeyInfo class, which previously stored unencrypted key material (a PrivateKeyInfo object), it now only deals with PrivateKey (an opaque handle to an PKCS #11 object) on export and encoded (byte[]) EncryptedPrivateKeyInfo data on import. This split suggests that PKCS12KeyInfo should be decomposed into two classes - one containing a PrivateKey and the other containing a byte[] encryptedPrivateKeyInfo - but this refactoring is left for another day. Part of: https://pagure.io/dogtagpki/issue/2610 Change-Id: I75d48de4d7040c9fb3a9a6d1e920c191aa757b70 (cherry picked from commit 2e198ddbe9ec5000ee7e14df0aa364b600d3aa92) --- .../cmstools/pkcs12/PKCS12ImportCLI.java | 4 +- .../cmstools/pkcs12/PKCS12KeyCLI.java | 1 - .../netscape/security/pkcs/PKCS12KeyInfo.java | 29 +++-- .../netscape/security/pkcs/PKCS12Util.java | 122 +++++++----------- 4 files changed, 65 insertions(+), 91 deletions(-) diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java index da5478c60bc..de432848c55 100644 --- a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java @@ -124,12 +124,12 @@ public void execute(String[] args) throws Exception { if (nicknames.length == 0) { // store all certificates - util.storeIntoNSS(pkcs12, overwrite); + util.storeIntoNSS(pkcs12, password, overwrite); } else { // load specified certificates for (String nickname : nicknames) { - util.storeCertIntoNSS(pkcs12, nickname, overwrite); + util.storeCertIntoNSS(pkcs12, password, nickname, overwrite); } } diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java index fbebddabb91..e74b63a59b4 100644 --- a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12KeyCLI.java @@ -38,6 +38,5 @@ public static void printKeyInfo(PKCS12KeyInfo keyInfo) throws Exception { System.out.println(" Key ID: " + keyInfo.getID().toString(16)); System.out.println(" Subject DN: " + keyInfo.getSubjectDN()); - System.out.println(" Algorithm: " + keyInfo.getPrivateKeyInfo().getAlgorithm()); } } diff --git a/base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java b/base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java index c7e84f01ffa..f180cf23bb4 100644 --- a/base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java +++ b/base/util/src/netscape/security/pkcs/PKCS12KeyInfo.java @@ -19,31 +19,40 @@ import java.math.BigInteger; -import org.mozilla.jss.pkix.primitive.PrivateKeyInfo; +import org.mozilla.jss.crypto.PrivateKey; public class PKCS12KeyInfo { + private PrivateKey privateKey; + private byte[] epkiBytes; BigInteger id; - PrivateKeyInfo privateKeyInfo; String subjectDN; public PKCS12KeyInfo() { } - public BigInteger getID() { - return id; + public PKCS12KeyInfo(PrivateKey k) { + this.privateKey = k; } - public void setID(BigInteger id) { - this.id = id; + public PKCS12KeyInfo(byte[] epkiBytes) { + this.epkiBytes = epkiBytes; + } + + public PrivateKey getPrivateKey() { + return this.privateKey; } - public PrivateKeyInfo getPrivateKeyInfo() { - return privateKeyInfo; + public byte[] getEncryptedPrivateKeyInfoBytes() { + return epkiBytes; } - public void setPrivateKeyInfo(PrivateKeyInfo privateKeyInfo) { - this.privateKeyInfo = privateKeyInfo; + public BigInteger getID() { + return id; + } + + public void setID(BigInteger id) { + this.id = id; } public String getSubjectDN() { diff --git a/base/util/src/netscape/security/pkcs/PKCS12Util.java b/base/util/src/netscape/security/pkcs/PKCS12Util.java index 0b164aafc55..9f9a35e1630 100644 --- a/base/util/src/netscape/security/pkcs/PKCS12Util.java +++ b/base/util/src/netscape/security/pkcs/PKCS12Util.java @@ -33,27 +33,19 @@ import org.apache.commons.lang.StringUtils; import org.mozilla.jss.CryptoManager; import org.mozilla.jss.asn1.ANY; -import org.mozilla.jss.asn1.ASN1Util; import org.mozilla.jss.asn1.ASN1Value; import org.mozilla.jss.asn1.BMPString; import org.mozilla.jss.asn1.OBJECT_IDENTIFIER; import org.mozilla.jss.asn1.OCTET_STRING; import org.mozilla.jss.asn1.SEQUENCE; import org.mozilla.jss.asn1.SET; -import org.mozilla.jss.crypto.Cipher; import org.mozilla.jss.crypto.CryptoStore; import org.mozilla.jss.crypto.CryptoToken; import org.mozilla.jss.crypto.EncryptionAlgorithm; -import org.mozilla.jss.crypto.IVParameterSpec; import org.mozilla.jss.crypto.InternalCertificate; -import org.mozilla.jss.crypto.KeyGenAlgorithm; -import org.mozilla.jss.crypto.KeyWrapAlgorithm; -import org.mozilla.jss.crypto.KeyWrapper; import org.mozilla.jss.crypto.NoSuchItemOnTokenException; import org.mozilla.jss.crypto.ObjectNotFoundException; -import org.mozilla.jss.crypto.PBEAlgorithm; import org.mozilla.jss.crypto.PrivateKey; -import org.mozilla.jss.crypto.SymmetricKey; import org.mozilla.jss.crypto.X509Certificate; import org.mozilla.jss.pkcs12.AuthenticatedSafes; import org.mozilla.jss.pkcs12.CertBag; @@ -61,14 +53,10 @@ import org.mozilla.jss.pkcs12.PasswordConverter; import org.mozilla.jss.pkcs12.SafeBag; import org.mozilla.jss.pkix.primitive.Attribute; -import org.mozilla.jss.pkix.primitive.EncryptedPrivateKeyInfo; -import org.mozilla.jss.pkix.primitive.PrivateKeyInfo; import org.mozilla.jss.util.Password; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.netscape.cmsutil.crypto.CryptoUtil; - import netscape.ldap.LDAPDN; import netscape.ldap.util.DN; import netscape.security.x509.X509CertImpl; @@ -114,41 +102,30 @@ public void setTrustFlags(X509Certificate cert, String trustFlags) throws Except icert.setObjectSigningTrust(PKCS12.decodeFlags(flags[2])); } - byte[] getEncodedKey(PrivateKey privateKey) throws Exception { - CryptoManager cm = CryptoManager.getInstance(); - CryptoToken token = cm.getInternalKeyStorageToken(); - - byte[] iv = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }; - IVParameterSpec param = new IVParameterSpec(iv); - - SymmetricKey sk = CryptoUtil.generateKey(token, KeyGenAlgorithm.DES3, 0, null, true); - byte[] enckey = CryptoUtil.wrapUsingSymmetricKey( - token, - sk, - privateKey, - param, - KeyWrapAlgorithm.DES3_CBC_PAD); - - Cipher c = token.getCipherContext(EncryptionAlgorithm.DES3_CBC_PAD); - c.initDecrypt(sk, param); - return c.doFinal(enckey); - } - public void addKeyBag(PKCS12KeyInfo keyInfo, Password password, SEQUENCE encSafeContents) throws Exception { + PrivateKey k = keyInfo.getPrivateKey(); + if (k == null) { + logger.debug("NO PRIVATE KEY for " + keyInfo.subjectDN); + return; + } logger.debug("Creating key bag for " + keyInfo.subjectDN); PasswordConverter passConverter = new PasswordConverter(); - byte salt[] = { 0x01, 0x01, 0x01, 0x01 }; - - EncryptedPrivateKeyInfo encPrivateKeyInfo = EncryptedPrivateKeyInfo.createPBE( - PBEAlgorithm.PBE_SHA1_DES3_CBC, - password, salt, 1, passConverter, keyInfo.privateKeyInfo); + byte[] epkiBytes = CryptoManager.getInstance() + .getInternalKeyStorageToken() + .getCryptoStore() + .getEncryptedPrivateKeyInfo( + /* NSS has a bug that causes any AES CBC encryption + * to use AES-256, but AlgorithmID contains chosen + * alg. To avoid mismatch, use AES_256_CBC. */ + passConverter, password, EncryptionAlgorithm.AES_256_CBC, 0, k); SET keyAttrs = createKeyBagAttrs(keyInfo); - SafeBag safeBag = new SafeBag(SafeBag.PKCS8_SHROUDED_KEY_BAG, encPrivateKeyInfo, keyAttrs); + SafeBag safeBag = new SafeBag( + SafeBag.PKCS8_SHROUDED_KEY_BAG, new ANY(epkiBytes), keyAttrs); encSafeContents.addElement(safeBag); } @@ -318,14 +295,10 @@ public void loadKeyInfoFromNSS(PKCS12 pkcs12, X509Certificate cert, BigInteger i PrivateKey privateKey = cm.findPrivKeyByCert(cert); logger.debug("Certificate \"" + nickname + "\" has private key"); - PKCS12KeyInfo keyInfo = new PKCS12KeyInfo(); + PKCS12KeyInfo keyInfo = new PKCS12KeyInfo(privateKey); keyInfo.id = id; keyInfo.subjectDN = cert.getSubjectDN().toString(); - byte[] privateData = getEncodedKey(privateKey); - keyInfo.privateKeyInfo = (PrivateKeyInfo) - ASN1Util.decode(PrivateKeyInfo.getTemplate(), privateData); - pkcs12.addKeyInfo(keyInfo); } catch (ObjectNotFoundException e) { @@ -375,11 +348,7 @@ public void storeIntoFile(PKCS12 pkcs12, String filename, Password password) thr public PKCS12KeyInfo getKeyInfo(SafeBag bag, Password password) throws Exception { - PKCS12KeyInfo keyInfo = new PKCS12KeyInfo(); - - // get private key info - EncryptedPrivateKeyInfo encPrivateKeyInfo = (EncryptedPrivateKeyInfo) bag.getInterpretedBagContent(); - keyInfo.privateKeyInfo = encPrivateKeyInfo.decrypt(password, new PasswordConverter()); + PKCS12KeyInfo keyInfo = new PKCS12KeyInfo(bag.getBagContent().getEncoded()); // get key attributes SET bagAttrs = bag.getBagAttributes(); @@ -491,7 +460,7 @@ public PKCS12CertInfo getCertInfo(SafeBag bag) throws Exception { public void getKeyInfos(PKCS12 pkcs12, PFX pfx, Password password) throws Exception { - logger.debug("Load private keys:"); + logger.debug("Load encrypted private keys:"); AuthenticatedSafes safes = pfx.getAuthSafes(); @@ -590,20 +559,12 @@ public PKCS12CertInfo getCertBySubjectDN(PKCS12 pkcs12, String subjectDN) public void importKey( PKCS12 pkcs12, + Password password, + String nickname, PKCS12KeyInfo keyInfo) throws Exception { logger.debug("Importing private key " + keyInfo.subjectDN); - byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }; - IVParameterSpec param = new IVParameterSpec(iv); - - PrivateKeyInfo privateKeyInfo = keyInfo.privateKeyInfo; - - // encode private key - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - privateKeyInfo.encode(bos); - byte[] privateKey = bos.toByteArray(); - PKCS12CertInfo certInfo = pkcs12.getCertInfoByID(keyInfo.getID()); if (certInfo == null) { logger.debug("Private key has no certificate, ignore"); @@ -619,26 +580,29 @@ public void importKey( // get public key PublicKey publicKey = cert.getPublicKey(); - // delete the cert again + byte[] epkiBytes = keyInfo.getEncryptedPrivateKeyInfoBytes(); + if (epkiBytes == null) { + logger.debug( + "No EncryptedPrivateKeyInfo for key '" + + keyInfo.subjectDN + "'; skipping key"); + } + store.importEncryptedPrivateKeyInfo( + new PasswordConverter(), password, nickname, publicKey, epkiBytes); + + // delete the cert again (it will be imported again later + // with the correct nickname) try { store.deleteCert(cert); } catch (NoSuchItemOnTokenException e) { // this is OK } - - // encrypt private key - SymmetricKey sk = CryptoUtil.generateKey(token, KeyGenAlgorithm.DES3, 0, null, true); - byte[] encpkey = CryptoUtil.encryptUsingSymmetricKey( - token, sk, privateKey, EncryptionAlgorithm.DES3_CBC_PAD, param); - - // unwrap private key to load into database - KeyWrapper wrapper = token.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD); - wrapper.initUnwrap(sk, param); - wrapper.unwrapPrivate(encpkey, getPrivateKeyType(publicKey), publicKey); } - public void storeCertIntoNSS(PKCS12 pkcs12, PKCS12CertInfo certInfo, boolean overwrite) throws Exception { - + public void storeCertIntoNSS( + PKCS12 pkcs12, Password password, + PKCS12CertInfo certInfo, boolean overwrite) + throws Exception + { CryptoManager cm = CryptoManager.getInstance(); CryptoToken ct = cm.getInternalKeyStorageToken(); CryptoStore store = ct.getCryptoStore(); @@ -656,7 +620,7 @@ public void storeCertIntoNSS(PKCS12 pkcs12, PKCS12CertInfo certInfo, boolean ove X509Certificate cert; if (keyInfo != null) { // cert has key logger.debug("Importing user key for " + certInfo.nickname); - importKey(pkcs12, keyInfo); + importKey(pkcs12, password, certInfo.nickname, keyInfo); logger.debug("Importing user certificate " + certInfo.nickname); cert = cm.importUserCACertPackage(certInfo.cert.getEncoded(), certInfo.nickname); @@ -671,19 +635,21 @@ public void storeCertIntoNSS(PKCS12 pkcs12, PKCS12CertInfo certInfo, boolean ove setTrustFlags(cert, certInfo.trustFlags); } - public void storeCertIntoNSS(PKCS12 pkcs12, String nickname, boolean overwrite) throws Exception { + public void storeCertIntoNSS(PKCS12 pkcs12, Password password, String nickname, boolean overwrite) throws Exception { Collection certInfos = pkcs12.getCertInfosByNickname(nickname); for (PKCS12CertInfo certInfo : certInfos) { - storeCertIntoNSS(pkcs12, certInfo, overwrite); + storeCertIntoNSS(pkcs12, password, certInfo, overwrite); } } - public void storeIntoNSS(PKCS12 pkcs12, boolean overwrite) throws Exception { - + public void storeIntoNSS( + PKCS12 pkcs12, Password password, boolean overwrite) + throws Exception + { logger.info("Storing data into NSS database"); for (PKCS12CertInfo certInfo : pkcs12.getCertInfos()) { - storeCertIntoNSS(pkcs12, certInfo, overwrite); + storeCertIntoNSS(pkcs12, password, certInfo, overwrite); } } }