< prev index next >

src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java

Print this page

        

@@ -67,22 +67,25 @@
 import sun.security.util.DerInputStream;
 import sun.security.util.DerOutputStream;
 import sun.security.util.DerValue;
 import sun.security.util.ObjectIdentifier;
 import sun.security.pkcs.ContentInfo;
+import sun.security.util.SecurityProperties;
 import sun.security.x509.AlgorithmId;
 import sun.security.pkcs.EncryptedPrivateKeyInfo;
 
 
 /**
  * This class provides the keystore implementation referred to as "PKCS12".
  * Implements the PKCS#12 PFX protected using the Password privacy mode.
  * The contents are protected using Password integrity mode.
  *
- * Currently we support following PBE algorithms:
- *  - pbeWithSHAAnd3KeyTripleDESCBC to encrypt private keys
- *  - pbeWithSHAAnd40BitRC2CBC to encrypt certificates
+ * Currently these PBE algorithms are used by default:
+ *  - PBEWithSHA1AndDESede to encrypt private keys, iteration count 50000.
+ *  - PBEWithSHA1AndRC2_40 to encrypt certificates, iteration count 50000.
+ *
+ * The default Mac algorithm is  HmacPBESHA1, iteration count 100000.
  *
  * Supported encryption of various implementations :
  *
  * Software and mode.     Certificate encryption  Private key encryption
  * ---------------------------------------------------------------------

@@ -121,28 +124,17 @@
  *
  * @author Seema Malkani
  * @author Jeff Nisewanger
  * @author Jan Luehe
  *
- * @see KeyProtector
  * @see java.security.KeyStoreSpi
- * @see KeyTool
- *
- *
  */
 public final class PKCS12KeyStore extends KeyStoreSpi {
 
     public static final int VERSION_3 = 3;
 
-    private static final String[] KEY_PROTECTION_ALGORITHM = {
-        "keystore.pkcs12.keyProtectionAlgorithm",
-        "keystore.PKCS12.keyProtectionAlgorithm"
-    };
-
     private static final int MAX_ITERATION_COUNT = 5000000;
-    private static final int PBE_ITERATION_COUNT = 50000; // default
-    private static final int MAC_ITERATION_COUNT = 100000; // default
     private static final int SALT_LEN = 20;
 
     // friendlyName, localKeyId, trustedKeyUsage
     private static final String[] CORE_ATTRIBUTES = {
         "1.2.840.113549.1.9.20",

@@ -159,35 +151,29 @@
     private static final int pkcs9Name[]  = {1, 2, 840, 113549, 1, 9, 20};
     private static final int pkcs9KeyId[] = {1, 2, 840, 113549, 1, 9, 21};
 
     private static final int pkcs9certType[] = {1, 2, 840, 113549, 1, 9, 22, 1};
 
-    private static final int pbeWithSHAAnd40BitRC2CBC[] =
-                                        {1, 2, 840, 113549, 1, 12, 1, 6};
-    private static final int pbeWithSHAAnd3KeyTripleDESCBC[] =
-                                        {1, 2, 840, 113549, 1, 12, 1, 3};
     private static final int pbes2[] = {1, 2, 840, 113549, 1, 5, 13};
     // TODO: temporary Oracle OID
     /*
      * { joint-iso-itu-t(2) country(16) us(840) organization(1) oracle(113894)
      *   jdk(746875) crypto(1) id-at-trustedKeyUsage(1) }
      */
     private static final int TrustedKeyUsage[] =
                                         {2, 16, 840, 1, 113894, 746875, 1, 1};
     private static final int AnyExtendedKeyUsage[] = {2, 5, 29, 37, 0};
 
-    private static ObjectIdentifier PKCS8ShroudedKeyBag_OID;
-    private static ObjectIdentifier CertBag_OID;
-    private static ObjectIdentifier SecretBag_OID;
-    private static ObjectIdentifier PKCS9FriendlyName_OID;
-    private static ObjectIdentifier PKCS9LocalKeyId_OID;
-    private static ObjectIdentifier PKCS9CertType_OID;
-    private static ObjectIdentifier pbeWithSHAAnd40BitRC2CBC_OID;
-    private static ObjectIdentifier pbeWithSHAAnd3KeyTripleDESCBC_OID;
-    private static ObjectIdentifier pbes2_OID;
-    private static ObjectIdentifier TrustedKeyUsage_OID;
-    private static ObjectIdentifier[] AnyUsage;
+    private static final ObjectIdentifier PKCS8ShroudedKeyBag_OID;
+    private static final ObjectIdentifier CertBag_OID;
+    private static final ObjectIdentifier SecretBag_OID;
+    private static final ObjectIdentifier PKCS9FriendlyName_OID;
+    private static final ObjectIdentifier PKCS9LocalKeyId_OID;
+    private static final ObjectIdentifier PKCS9CertType_OID;
+    private static final ObjectIdentifier pbes2_OID;
+    private static final ObjectIdentifier TrustedKeyUsage_OID;
+    private static final ObjectIdentifier[] AnyUsage;
 
     private int counter = 0;
 
     // private key count
     // Note: This is a workaround to allow null localKeyID attribute

@@ -198,10 +184,21 @@
     private int secretKeyCount = 0;
 
     // certificate count
     private int certificateCount = 0;
 
+    // Alg/params used for *this* keystore. Initialized as -1 for ic and
+    // null for algorithm names. When an existing file is read, they will be
+    // assigned inside engineLoad() so storing an existing keystore uses the
+    // old alg/params. This makes sure if a keystore is created password-less
+    // it will be password-less forever. Otherwise, engineStore() will read
+    // the default values. These fields are always reset when load() is called.
+    private String certProtectionAlgorithm = null;
+    private int certPbeIterationCount = -1;
+    private String macAlgorithm = null;
+    private int macIterationCount = -1;
+
     // the source of randomness
     private SecureRandom random;
 
     static {
         try {

@@ -209,20 +206,16 @@
             CertBag_OID = new ObjectIdentifier(certBag);
             SecretBag_OID = new ObjectIdentifier(secretBag);
             PKCS9FriendlyName_OID = new ObjectIdentifier(pkcs9Name);
             PKCS9LocalKeyId_OID = new ObjectIdentifier(pkcs9KeyId);
             PKCS9CertType_OID = new ObjectIdentifier(pkcs9certType);
-            pbeWithSHAAnd40BitRC2CBC_OID =
-                        new ObjectIdentifier(pbeWithSHAAnd40BitRC2CBC);
-            pbeWithSHAAnd3KeyTripleDESCBC_OID =
-                        new ObjectIdentifier(pbeWithSHAAnd3KeyTripleDESCBC);
             pbes2_OID = new ObjectIdentifier(pbes2);
             TrustedKeyUsage_OID = new ObjectIdentifier(TrustedKeyUsage);
             AnyUsage = new ObjectIdentifier[]{
                 new ObjectIdentifier(AnyExtendedKeyUsage)};
         } catch (IOException ioe) {
-            // should not happen
+            throw new AssertionError("OID not initialized", ioe);
         }
     }
 
     // A keystore entry and associated attributes
     private static class Entry {

@@ -379,11 +372,11 @@
                     throw new IOException("Invalid PBE algorithm parameters");
                 }
                 ic = pbeSpec.getIterationCount();
 
                 if (ic > MAX_ITERATION_COUNT) {
-                    throw new IOException("PBE iteration count too large");
+                    throw new IOException("key PBE iteration count too large");
                 }
             } else {
                 ic = 0;
             }
 

@@ -415,11 +408,11 @@
                     Key tmp = kfac.generatePrivate(kspec);
 
                     if (debug != null) {
                         debug.println("Retrieved a protected private key at alias" +
                                 " '" + alias + "' (" +
-                                new AlgorithmId(algOid).getName() +
+                                mapPBEParamsToAlgorithm(algOid, algParams) +
                                 " iterations: " + ic + ")");
                     }
                     return tmp;
                     // decode secret key
                 } else {

@@ -440,11 +433,11 @@
                     }
 
                     if (debug != null) {
                         debug.println("Retrieved a protected secret key at alias " +
                                 "'" + alias + "' (" +
-                                new AlgorithmId(algOid).getName() +
+                                mapPBEParamsToAlgorithm(algOid, algParams) +
                                 " iterations: " + ic + ")");
                     }
                     return tmp;
                 }
             }, password);

@@ -689,11 +682,11 @@
             entry.alias = alias.toLowerCase(Locale.ENGLISH);
             // add the entry
             entries.put(alias.toLowerCase(Locale.ENGLISH), entry);
 
         } catch (Exception nsae) {
-            throw new KeyStoreException("Key protection " +
+            throw new KeyStoreException("Key protection" +
                        " algorithm not found: " + nsae, nsae);
         }
     }
 
     /**

@@ -786,18 +779,17 @@
     }
 
     /*
      * Generate PBE Algorithm Parameters
      */
-    private AlgorithmParameters getPBEAlgorithmParameters(String algorithm)
-        throws IOException
-    {
+    private AlgorithmParameters getPBEAlgorithmParameters(
+            String algorithm, int iterationCount) throws IOException {
         AlgorithmParameters algParams = null;
 
         // create PBE parameters from salt and iteration count
         PBEParameterSpec paramSpec =
-                new PBEParameterSpec(getSalt(), PBE_ITERATION_COUNT);
+                new PBEParameterSpec(getSalt(), iterationCount);
         try {
            algParams = AlgorithmParameters.getInstance(algorithm);
            algParams.init(paramSpec);
         } catch (Exception e) {
            throw new IOException("getPBEAlgorithmParameters failed: " +

@@ -856,17 +848,18 @@
         }
         return skey;
     }
 
     /*
-     * Encrypt private key using Password-based encryption (PBE)
+     * Encrypt private key or secret key using Password-based encryption (PBE)
      * as defined in PKCS#5.
      *
      * NOTE: By default, pbeWithSHAAnd3-KeyTripleDES-CBC algorithmID is
      *       used to derive the key and IV.
      *
-     * @return encrypted private key encoded as EncryptedPrivateKeyInfo
+     * @return encrypted private key or secret key encoded as
+     *         EncryptedPrivateKeyInfo
      */
     private byte[] encryptPrivateKey(byte[] data,
         KeyStore.PasswordProtection passwordProtection)
         throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException
     {

@@ -884,31 +877,18 @@
                     passwordProtection.getProtectionParameters();
                 if (algParamSpec != null) {
                     algParams = AlgorithmParameters.getInstance(algorithm);
                     algParams.init(algParamSpec);
                 } else {
-                    algParams = getPBEAlgorithmParameters(algorithm);
+                    algParams = getPBEAlgorithmParameters(algorithm,
+                            defaultKeyPbeIterationCount());
                 }
             } else {
                 // Check default key protection algorithm for PKCS12 keystores
-                algorithm = AccessController.doPrivileged(
-                    new PrivilegedAction<String>() {
-                        public String run() {
-                            String prop =
-                                Security.getProperty(
-                                    KEY_PROTECTION_ALGORITHM[0]);
-                            if (prop == null) {
-                                prop = Security.getProperty(
-                                    KEY_PROTECTION_ALGORITHM[1]);
-                            }
-                            return prop;
-                        }
-                    });
-                if (algorithm == null || algorithm.isEmpty()) {
-                    algorithm = "PBEWithSHA1AndDESede";
-                }
-                algParams = getPBEAlgorithmParameters(algorithm);
+                algorithm = defaultKeyProtectionAlgorithm();
+                algParams = getPBEAlgorithmParameters(algorithm,
+                        defaultKeyPbeIterationCount());
             }
 
             ObjectIdentifier pbeOID = mapPBEAlgorithmToOID(algorithm);
             if (pbeOID == null) {
                     throw new IOException("PBE algorithm '" + algorithm +

@@ -962,11 +942,11 @@
         AlgorithmParameters algParams) throws NoSuchAlgorithmException {
         // Check for PBES2 algorithms
         if (algorithm.equals((Object)pbes2_OID) && algParams != null) {
             return algParams.toString();
         }
-        return algorithm.toString();
+        return new AlgorithmId(algorithm).getName();
     }
 
     /**
      * Assigns the given certificate to the given alias.
      *

@@ -1187,14 +1167,10 @@
      * the keystore data could not be stored
      */
     public synchronized void engineStore(OutputStream stream, char[] password)
         throws IOException, NoSuchAlgorithmException, CertificateException
     {
-        // password is mandatory when storing
-        if (password == null) {
-           throw new IllegalArgumentException("password can't be null");
-        }
 
         // -- Create PFX
         DerOutputStream pfx = new DerOutputStream();
 
         // PFX version (always write the latest version)

@@ -1223,20 +1199,32 @@
         }
 
         // -- create EncryptedContentInfo
         if (certificateCount > 0) {
 
+            if (certProtectionAlgorithm == null) {
+                certProtectionAlgorithm = defaultCertProtectionAlgorithm();
+            }
+            if (certPbeIterationCount < 0) {
+                certPbeIterationCount = defaultCertPbeIterationCount();
+            }
+
             if (debug != null) {
                 debug.println("Storing " + certificateCount +
                     " certificate(s) in a PKCS#7 encryptedData");
             }
 
             byte[] encrData = createEncryptedData(password);
+            if (!certProtectionAlgorithm.equalsIgnoreCase("NONE")) {
             ContentInfo encrContentInfo =
                 new ContentInfo(ContentInfo.ENCRYPTED_DATA_OID,
                                 new DerValue(encrData));
             encrContentInfo.encode(authSafeContentInfo);
+            } else {
+                ContentInfo dataContentInfo = new ContentInfo(encrData);
+                dataContentInfo.encode(authSafeContentInfo);
+            }
         }
 
         // wrap as SequenceOf ContentInfos
         DerOutputStream cInfo = new DerOutputStream();
         cInfo.write(DerValue.tag_SequenceOf, authSafeContentInfo);

@@ -1247,13 +1235,20 @@
         contentInfo.encode(authSafe);
         byte[] authSafeData = authSafe.toByteArray();
         pfx.write(authSafeData);
 
         // -- MAC
+        if (macAlgorithm == null) {
+            macAlgorithm = defaultMacAlgorithm();
+        }
+        if (macIterationCount < 0) {
+            macIterationCount = defaultMacIterationCount();
+        }
+        if (!macAlgorithm.equalsIgnoreCase("NONE")) {
         byte[] macData = calculateMac(password, authenticatedSafe);
         pfx.write(macData);
-
+        }
         // write PFX to output stream
         DerOutputStream pfxout = new DerOutputStream();
         pfxout.write(DerValue.tag_Sequence, pfx);
         byte[] pfxData = pfxout.toByteArray();
         stream.write(pfxData);

@@ -1460,55 +1455,37 @@
 
         return entry.attributes;
     }
 
     /*
-     * Generate Hash.
-     */
-    private byte[] generateHash(byte[] data) throws IOException
-    {
-        byte[] digest = null;
-
-        try {
-            MessageDigest md = MessageDigest.getInstance("SHA1");
-            md.update(data);
-            digest = md.digest();
-        } catch (Exception e) {
-            throw new IOException("generateHash failed: " + e, e);
-        }
-        return digest;
-    }
-
-
-    /*
      * Calculate MAC using HMAC algorithm (required for password integrity)
      *
      * Hash-based MAC algorithm combines secret key with message digest to
      * create a message authentication code (MAC)
      */
     private byte[] calculateMac(char[] passwd, byte[] data)
         throws IOException
     {
         byte[] mData = null;
-        String algName = "SHA1";
+        String algName = macAlgorithm.substring(7);
 
         try {
             // Generate a random salt.
             byte[] salt = getSalt();
 
             // generate MAC (MAC key is generated within JCE)
-            Mac m = Mac.getInstance("HmacPBESHA1");
+            Mac m = Mac.getInstance(macAlgorithm);
             PBEParameterSpec params =
-                        new PBEParameterSpec(salt, MAC_ITERATION_COUNT);
+                        new PBEParameterSpec(salt, macIterationCount);
             SecretKey key = getPBEKey(passwd);
             m.init(key, params);
             m.update(data);
             byte[] macResult = m.doFinal();
 
             // encode as MacData
             MacData macData = new MacData(algName, macResult, salt,
-                                                MAC_ITERATION_COUNT);
+                    macIterationCount);
             DerOutputStream bytes = new DerOutputStream();
             bytes.write(macData.getEncoded());
             mData = bytes.toByteArray();
         } catch (Exception e) {
             throw new IOException("calculateMac failed: " + e, e);

@@ -1762,19 +1739,23 @@
         DerOutputStream safeBagValue = new DerOutputStream();
         safeBagValue.write(DerValue.tag_SequenceOf, out);
         byte[] safeBagData = safeBagValue.toByteArray();
 
         // encrypt the content (EncryptedContentInfo)
+        if (!certProtectionAlgorithm.equalsIgnoreCase("NONE")) {
         byte[] encrContentInfo = encryptContent(safeBagData, password);
 
         // -- SEQUENCE of EncryptedData
         DerOutputStream encrData = new DerOutputStream();
         DerOutputStream encrDataContent = new DerOutputStream();
         encrData.putInteger(0);
         encrData.write(encrContentInfo);
         encrDataContent.write(DerValue.tag_Sequence, encrData);
         return encrDataContent.toByteArray();
+        } else {
+            return safeBagData;
+        }
     }
 
     /*
      * Create SafeContent Data content type.
      * Includes encrypted secret key in a SafeBag of type SecretBag.

@@ -1879,51 +1860,56 @@
     private byte[] encryptContent(byte[] data, char[] password)
         throws IOException {
 
         byte[] encryptedData = null;
 
+
+        try {
         // create AlgorithmParameters
-        AlgorithmParameters algParams =
-                getPBEAlgorithmParameters("PBEWithSHA1AndRC2_40");
+            AlgorithmParameters algParams = getPBEAlgorithmParameters(
+                    certProtectionAlgorithm, certPbeIterationCount);
         DerOutputStream bytes = new DerOutputStream();
-        AlgorithmId algId =
-                new AlgorithmId(pbeWithSHAAnd40BitRC2CBC_OID, algParams);
-        algId.encode(bytes);
-        byte[] encodedAlgId = bytes.toByteArray();
 
-        try {
             // Use JCE
             SecretKey skey = getPBEKey(password);
-            Cipher cipher = Cipher.getInstance("PBEWithSHA1AndRC2_40");
+            Cipher cipher = Cipher.getInstance(certProtectionAlgorithm);
             cipher.init(Cipher.ENCRYPT_MODE, skey, algParams);
             encryptedData = cipher.doFinal(data);
 
+            AlgorithmId algId = new AlgorithmId(
+                    mapPBEAlgorithmToOID(certProtectionAlgorithm),
+                    cipher.getParameters());
+                    // cipher.getParameters() now has IV
+            algId.encode(bytes);
+            byte[] encodedAlgId = bytes.toByteArray();
+
             if (debug != null) {
                 debug.println("  (Cipher algorithm: " + cipher.getAlgorithm() +
                     ")");
             }
 
-        } catch (Exception e) {
-            throw new IOException("Failed to encrypt" +
-                    " safe contents entry: " + e, e);
-        }
-
         // create EncryptedContentInfo
         DerOutputStream bytes2 = new DerOutputStream();
         bytes2.putOID(ContentInfo.DATA_OID);
         bytes2.write(encodedAlgId);
 
         // Wrap encrypted data in a context-specific tag.
         DerOutputStream tmpout2 = new DerOutputStream();
         tmpout2.putOctetString(encryptedData);
         bytes2.writeImplicit(DerValue.createTag(DerValue.TAG_CONTEXT,
-                                        false, (byte)0), tmpout2);
+                    false, (byte) 0), tmpout2);
 
         // wrap EncryptedContentInfo in a Sequence
         DerOutputStream out = new DerOutputStream();
         out.write(DerValue.tag_Sequence, bytes2);
         return out.toByteArray();
+        } catch (IOException ioe) {
+            throw ioe;
+        } catch (Exception e) {
+            throw new IOException("Failed to encrypt" +
+                    " safe contents entry: " + e, e);
+        }
     }
 
     /**
      * Loads the keystore from the given input stream.
      *

@@ -1942,14 +1928,16 @@
      * keystore could not be loaded
      */
     public synchronized void engineLoad(InputStream stream, char[] password)
         throws IOException, NoSuchAlgorithmException, CertificateException
     {
-        DataInputStream dis;
-        CertificateFactory cf = null;
-        ByteArrayInputStream bais = null;
-        byte[] encoded = null;
+
+        // Reset config when loading a different keystore.
+        certProtectionAlgorithm = null;
+        certPbeIterationCount = -1;
+        macAlgorithm = null;
+        macIterationCount = -1;
 
         if (stream == null)
            return;
 
         // reset the counter

@@ -1985,10 +1973,12 @@
         // reset the counters at the start
         privateKeyCount = 0;
         secretKeyCount = 0;
         certificateCount = 0;
 
+        boolean seeEncBag = false;
+
         /*
          * Spin over the ContentInfos.
          */
         for (int i = 0; i < count; i++) {
             ContentInfo safeContents;

@@ -2010,10 +2000,25 @@
 
                     if (debug != null) {
                         debug.println("Warning: skipping PKCS#7 encryptedData" +
                             " - no password was supplied");
                     }
+                    // No password to decrypt ENCRYPTED_DATA_OID. *Skip it*.
+                    // This means user will see a PrivateKeyEntry without
+                    // certificates and a whole TrustedCertificateEntry will
+                    // be lost. This is not a perfect solution but alternative
+                    // solutions are more disruptive:
+                    //
+                    // We cannot just fail, since KeyStore.load(is, null)
+                    // has been known to never fail because of a null password.
+                    //
+                    // We cannot just throw away the whole PrivateKeyEntry,
+                    // this is too silent and no one will notice anything.
+                    //
+                    // We also cannot fail when getCertificate() on such a
+                    // PrivateKeyEntry is called, since the method has not
+                    // specified this behavior.
                     continue;
                 }
 
                 DerInputStream edi =
                                 safeContents.getContent().toDerInputStream();

@@ -2053,17 +2058,22 @@
                             "Invalid PBE algorithm parameters");
                     }
                     ic = pbeSpec.getIterationCount();
 
                     if (ic > MAX_ITERATION_COUNT) {
-                        throw new IOException("PBE iteration count too large");
+                        throw new IOException("cert PBE iteration count too large");
                     }
+
+                    certProtectionAlgorithm
+                            = mapPBEParamsToAlgorithm(algOid, algParams);
+                    certPbeIterationCount = ic;
+                    seeEncBag = true;
                 }
 
                 if (debug != null) {
                     debug.println("Loading PKCS#7 encryptedData " +
-                        "(" + new AlgorithmId(algOid).getName() +
+                        "(" + mapPBEParamsToAlgorithm(algOid, algParams) +
                         " iterations: " + ic + ")");
                 }
 
                 try {
                     RetryWithZero.run(pass -> {

@@ -2084,12 +2094,20 @@
                 throw new IOException("public key protected PKCS12" +
                                         " not supported");
             }
         }
 
+        // No ENCRYPTED_DATA_OID but see certificate. Must be passwordless.
+        if (!seeEncBag && certificateCount > 0) {
+            certProtectionAlgorithm = "NONE";
+        }
+
         // The MacData is optional.
-        if (password != null && s.available() > 0) {
+        if (s.available() > 0) {
+            // If there is no password, we cannot fail. KeyStore.load(is, null)
+            // has been known to never fail because of a null password.
+            if (password != null) {
             MacData macData = new MacData(s);
             int ic = macData.getIterations();
 
             try {
                 if (ic > MAX_ITERATION_COUNT) {

@@ -2101,12 +2119,15 @@
                         macData.getDigestAlgName().toUpperCase(Locale.ENGLISH);
 
                 // Change SHA-1 to SHA1
                 algName = algName.replace("-", "");
 
+                    macAlgorithm = "HmacPBE" + algName;
+                    macIterationCount = ic;
+
                 // generate MAC (MAC key is created within JCE)
-                Mac m = Mac.getInstance("HmacPBE" + algName);
+                    Mac m = Mac.getInstance(macAlgorithm);
                 PBEParameterSpec params =
                         new PBEParameterSpec(macData.getSalt(), ic);
 
                 RetryWithZero.run(pass -> {
                     SecretKey key = getPBEKey(pass);

@@ -2121,16 +2142,19 @@
 
                     if (!MessageDigest.isEqual(macData.getDigest(), macResult)) {
                         throw new UnrecoverableKeyException("Failed PKCS12" +
                                 " integrity checking");
                     }
-                    return (Void)null;
+                        return (Void) null;
                 }, password);
             } catch (Exception e) {
                 throw new IOException("Integrity check failed: " + e, e);
             }
         }
+        } else {
+            macAlgorithm = "NONE";
+        }
 
         /*
          * Match up private keys with certificate chains.
          */
         PrivateKeyEntry[] list =

@@ -2165,12 +2189,18 @@
                         break;
                     }
                     cert = certsMap.get(issuerDN);
                 }
                 /* Update existing KeyEntry in entries table */
-                if (chain.size() > 0)
+                if (chain.size() > 0) {
                     entry.chain = chain.toArray(new Certificate[chain.size()]);
+                } else {
+                    // Remove private key entries where there is no associated
+                    // certs. Most likely the keystore is loaded with a null
+                    // password.
+                    entries.remove(entry);
+                }
             }
         }
 
         if (debug != null) {
             debug.println("PKCS12KeyStore load: private key count: " +

@@ -2182,10 +2212,50 @@
         certsMap.clear();
         keyList.clear();
     }
 
     /**
+     * Returns if a pkcs12 file is password-less. This means no cert is
+     * encrypted and there is no Mac. Please note that the private key
+     * can be encrypted.
+     *
+     * This is a simplified version of {@link #engineLoad} that only looks
+     * at the ContentInfo types.
+     *
+     * @param f the pkcs12 file
+     * @return if it's password-less
+     * @throws IOException
+     */
+    public static boolean isPasswordless(File f) throws IOException {
+
+        try (FileInputStream stream = new FileInputStream(f)) {
+            DerValue val = new DerValue(stream);
+            DerInputStream s = val.toDerInputStream();
+
+            s.getInteger(); // skip version
+
+            ContentInfo authSafe = new ContentInfo(s);
+            DerInputStream as = new DerInputStream(authSafe.getData());
+            for (DerValue seq : as.getSequence(2)) {
+                DerInputStream sci = new DerInputStream(seq.toByteArray());
+                ContentInfo safeContents = new ContentInfo(sci);
+                if (safeContents.getContentType()
+                        .equals(ContentInfo.ENCRYPTED_DATA_OID)) {
+                    // Certificate encrypted
+                    return false;
+                }
+            }
+
+            if (s.available() > 0) {
+                // The MacData exists.
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Locates a matched CertEntry from certEntries, and returns its cert.
      * @param entry the KeyEntry to match
      * @return a certificate, null if not found
      */
     private X509Certificate findMatchedCertificate(PrivateKeyEntry entry) {

@@ -2337,21 +2407,24 @@
              * null keyId, we should skip it entirely.
              */
             if (bagItem instanceof KeyEntry) {
                 KeyEntry entry = (KeyEntry)bagItem;
 
-                if (bagItem instanceof PrivateKeyEntry) {
                     if (keyId == null) {
+                    if (bagItem instanceof PrivateKeyEntry) {
                        // Insert a localKeyID for the privateKey
                        // Note: This is a workaround to allow null localKeyID
                        // attribute in pkcs12 with one private key entry and
                        // associated cert-chain
                        if (privateKeyCount == 1) {
                             keyId = "01".getBytes("UTF8");
                        } else {
                             continue;
                        }
+                    } else {
+                        // keyId in a SecretKeyEntry is not significant
+                        keyId = "00".getBytes("UTF8");
                     }
                 }
                 entry.keyId = keyId;
                 // restore date if it exists
                 String keyIdStr = new String(keyId, "UTF8");

@@ -2418,6 +2491,85 @@
 
     private String getUnfriendlyName() {
         counter++;
         return (String.valueOf(counter));
     }
+
+    // 8076190: Customizing the generation of a PKCS12 keystore
+
+    private static String defaultCertProtectionAlgorithm() {
+        String result = SecurityProperties.privilegedGetOverridable(
+                "keystore.pkcs12.certProtectionAlgorithm");
+        return (result != null && !result.isEmpty())
+                ? result : "PBEWithSHA1AndRC2_40";
+    }
+
+    private static int defaultCertPbeIterationCount() {
+        String result = SecurityProperties.privilegedGetOverridable(
+                "keystore.pkcs12.certPbeIterationCount");
+        return (result != null && !result.isEmpty())
+                ? string2IC("certPbeIterationCount", result) : 50000;
+    }
+
+    // Read both "keystore.pkcs12.keyProtectionAlgorithm" and
+    // "keystore.PKCS12.keyProtectionAlgorithm" for compatibility.
+    private static String defaultKeyProtectionAlgorithm() {
+        String result = AccessController.doPrivileged(new PrivilegedAction<String>() {
+            public String run() {
+                String result;
+                String name1 = "keystore.pkcs12.keyProtectionAlgorithm";
+                String name2 = "keystore.PKCS12.keyProtectionAlgorithm";
+                result = System.getProperty(name1);
+                if (result != null) {
+                    return result;
+                }
+                result = System.getProperty(name2);
+                if (result != null) {
+                    return result;
+                }
+                result = Security.getProperty(name1);
+                if (result != null) {
+                    return result;
+                }
+                return Security.getProperty(name2);
+            }
+        });
+        return (result != null && !result.isEmpty())
+                ? result : "PBEWithSHA1AndDESede";
+    }
+
+    private static int defaultKeyPbeIterationCount() {
+        String result = SecurityProperties.privilegedGetOverridable(
+                "keystore.pkcs12.keyPbeIterationCount");
+        return (result != null && !result.isEmpty())
+                ? string2IC("keyPbeIterationCount", result) : 50000;
+    }
+
+    private static String defaultMacAlgorithm() {
+        String result = SecurityProperties.privilegedGetOverridable(
+                "keystore.pkcs12.macAlgorithm");
+        return (result != null && !result.isEmpty())
+                ? result : "HmacPBESHA1";
+    }
+
+    private static int defaultMacIterationCount() {
+        String result = SecurityProperties.privilegedGetOverridable(
+                "keystore.pkcs12.macIterationCount");
+        return (result != null && !result.isEmpty())
+                ? string2IC("macIterationCount", result) : 100000;
+    }
+
+    private static int string2IC(String type, String value) {
+        int number;
+        try {
+            number = Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("keystore.pkcs12." + type
+                    + " is not a number: " + value);
+        }
+        if (number <= 0 || number > MAX_ITERATION_COUNT) {
+            throw new IllegalArgumentException("Invalid keystore.pkcs12."
+                    + type + ": " + value);
+        }
+        return number;
+    }
 }
< prev index next >