# HG changeset patch # User mbalao # Date 1573529455 10800 # Node ID 438337c846fb071900ddb6922bddf8b3e895a514 # Parent b313bcb68b4c0ca76dfc232b01b835e477f239cf 8233404: System property to set the number of PBE iterations in JCEKS keystores Reviewed-by: weijun diff -r b313bcb68b4c -r 438337c846fb src/java.base/share/classes/com/sun/crypto/provider/KeyProtector.java --- a/src/java.base/share/classes/com/sun/crypto/provider/KeyProtector.java Wed Nov 20 14:51:42 2019 +0000 +++ b/src/java.base/share/classes/com/sun/crypto/provider/KeyProtector.java Tue Nov 12 00:30:55 2019 -0300 @@ -48,6 +48,7 @@ import sun.security.x509.AlgorithmId; import sun.security.util.ObjectIdentifier; +import sun.security.util.SecurityProperties; /** * This class implements a protection mechanism for private keys. In JCE, we @@ -75,14 +76,39 @@ private static final String KEY_PROTECTOR_OID = "1.3.6.1.4.1.42.2.17.1.1"; private static final int MAX_ITERATION_COUNT = 5000000; - private static final int ITERATION_COUNT = 200000; + private static final int MIN_ITERATION_COUNT = 10000; + private static final int DEFAULT_ITERATION_COUNT = 200000; private static final int SALT_LEN = 20; // the salt length private static final int DIGEST_LEN = 20; + private static final int ITERATION_COUNT; // the password used for protecting/recovering keys passed through this // key protector private char[] password; + /** + * {@systemProperty jdk.jceks.iterationCount} property indicating the + * number of iterations for password-based encryption (PBE) in JCEKS + * keystores. Values in the range 10000 to 5000000 are considered valid. + * If the value is out of this range, or is not a number, or is + * unspecified; a default of 200000 is used. + */ + static { + int iterationCount = DEFAULT_ITERATION_COUNT; + String ic = SecurityProperties.privilegedGetOverridable( + "jdk.jceks.iterationCount"); + if (ic != null && !ic.isEmpty()) { + try { + iterationCount = Integer.parseInt(ic); + if (iterationCount < MIN_ITERATION_COUNT || + iterationCount > MAX_ITERATION_COUNT) { + iterationCount = DEFAULT_ITERATION_COUNT; + } + } catch (NumberFormatException e) {} + } + ITERATION_COUNT = iterationCount; + } + KeyProtector(char[] password) { if (password == null) { throw new IllegalArgumentException("password can't be null"); diff -r b313bcb68b4c -r 438337c846fb src/java.base/share/conf/security/java.security --- a/src/java.base/share/conf/security/java.security Wed Nov 20 14:51:42 2019 +0000 +++ b/src/java.base/share/conf/security/java.security Tue Nov 12 00:30:55 2019 -0300 @@ -1066,6 +1066,16 @@ jceks.key.serialFilter = java.base/java.lang.Enum;java.base/java.security.KeyRep;\ java.base/java.security.KeyRep$Type;java.base/javax.crypto.spec.SecretKeySpec;!* +# The iteration count used for password-based encryption (PBE) in JCEKS +# keystores. Values in the range 10000 to 5000000 are considered valid. +# If the value is out of this range, or is not a number, or is unspecified; +# a default of 200000 is used. +# +# If the system property jdk.jceks.iterationCount is also specified, it +# supersedes the security property value defined here. +# +#jdk.jceks.iterationCount = 200000 + # # PKCS12 KeyStore properties # diff -r b313bcb68b4c -r 438337c846fb test/jdk/com/sun/crypto/provider/KeyProtector/IterationCount.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/com/sun/crypto/provider/KeyProtector/IterationCount.java Tue Nov 12 00:30:55 2019 -0300 @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2019, Red Hat, Inc. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8233404 + * @library /test/lib + * @run main/othervm/timeout=30 IterationCount HOST 200000 + * @run main/othervm/timeout=30 IterationCount HOST 200000 1 + * @run main/othervm/timeout=30 IterationCount HOST 200000 6000000 + * @run main/othervm/timeout=30 IterationCount HOST 200000 invalid + * @run main/othervm/timeout=30 IterationCount HOST 30000 30000 + * @run main/othervm/timeout=30 IterationCount OVERRIDE + * @author Martin Balao (mbalao@redhat.com) + */ + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class IterationCount { + private static final String clientStr = "CLIENT"; + private static final String javaBinPath = + System.getProperty("java.home", ".") + File.separator + "bin" + + File.separator + "java"; + + public static void main(String[] args) throws Throwable { + if (args[0].equals("HOST")) { + String setValue = null; + if (args.length > 2) { + setValue = args[2]; + } + testSystem(args[1], setValue); + testSecurity(args[1], setValue); + } else if (args[0].equals(clientStr)) { + int expectedIterationCount = Integer.parseInt(args[1]); + int currentIterationCount = getCurrentIterationCountValue(); + System.out.println("Expected value: " + expectedIterationCount); + System.out.println("Current value: " + currentIterationCount); + if (currentIterationCount != expectedIterationCount) { + throw new Exception("Expected value different than current"); + } + } else if (args[0].equals("OVERRIDE")) { + testSystemOverridesSecurity(); + } + System.out.println("TEST PASS - OK"); + } + + private static List getBasicCommand() { + List cmd = new ArrayList<>(); + cmd.add(javaBinPath); + cmd.add("-cp"); + cmd.add(System.getProperty("test.classes", ".")); + return cmd; + } + + private static void executeCommand(List cmd, String expectedCount) + throws Throwable { + cmd.add(IterationCount.class.getName()); + cmd.add(clientStr); + cmd.add(expectedCount); + OutputAnalyzer out = ProcessTools.executeCommand( + cmd.toArray(new String[cmd.size()])); + out.shouldHaveExitValue(0); + } + + private static void testSystem(String expectedCount, String setValue) + throws Throwable { + System.out.println("Test setting " + + (setValue != null ? setValue : "nothing") + + " as a System property"); + List cmd = getBasicCommand(); + if (setValue != null) { + cmd.add("-Djdk.jceks.iterationCount=" + setValue); + } + executeCommand(cmd, expectedCount); + System.out.println("............................."); + } + + private static void testSecurity(String expectedCount, String setValue) + throws Throwable { + testSecurity(expectedCount, setValue, getBasicCommand()); + } + + private static void testSecurity(String expectedCount, String setValue, + List cmd) throws Throwable { + System.out.println("Test setting " + + (setValue != null ? setValue : "nothing") + + " as a Security property"); + Path tmpDirPath = Files.createTempDirectory("tmpdir"); + try { + if (setValue != null) { + String javaSecurityPath = tmpDirPath + + File.separator + "java.security"; + writeJavaSecurityProp(javaSecurityPath, setValue); + cmd.add("-Djava.security.properties=" + javaSecurityPath); + } + executeCommand(cmd, expectedCount); + System.out.println("............................."); + } finally { + deleteDir(tmpDirPath); + } + } + + private static void testSystemOverridesSecurity() throws Throwable { + System.out.println("Test that setting a System property overrides" + + " the Security one"); + String systemValue = Integer.toString(30000); + System.out.println("System value: " + systemValue); + List cmd = getBasicCommand(); + cmd.add("-Djdk.jceks.iterationCount=" + systemValue); + testSecurity(systemValue, Integer.toString(40000), cmd); + } + + private static void writeJavaSecurityProp(String javaSecurityPath, + String setValue) throws IOException { + try (FileOutputStream fos = new FileOutputStream( + new File(javaSecurityPath))) { + fos.write(("jdk.jceks.iterationCount=" + setValue).getBytes()); + } + } + + private static int getCurrentIterationCountValue() throws Exception { + Class KeyProtectorClass = + Class.forName("com.sun.crypto.provider.KeyProtector"); + Field iterationCountField = + KeyProtectorClass.getDeclaredField("ITERATION_COUNT"); + iterationCountField.setAccessible(true); + return iterationCountField.getInt(KeyProtectorClass); + } + + private static void deleteDir(Path directory) throws IOException { + Files.walkFileTree(directory, new SimpleFileVisitor() { + + @Override + public FileVisitResult visitFile(Path file, + BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } +}