diff --git a/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java b/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java index ec8e0f3757d..ad98653b9c2 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java @@ -661,6 +661,10 @@ public final class JceKeyStore extends KeyStoreSpi { dos.close(); } } + + if (debug != null) { + emitWeakKeyStoreWarning(); + } } } @@ -862,6 +866,10 @@ public final class JceKeyStore extends KeyStoreSpi { secretKeyCount); } + if (debug != null) { + emitWeakKeyStoreWarning(); + } + /* * If a password has been provided, we check the keyed digest * at the end. If this check fails, the store has been tampered @@ -978,4 +986,12 @@ public final class JceKeyStore extends KeyStoreSpi { return Status.UNDECIDED; } } + + private void emitWeakKeyStoreWarning() { + debug.println("WARNING: JCEKS uses outdated cryptographic " + + "algorithms and will be removed in a future " + + "release. Migrate to PKCS12 using:"); + debug.println("keytool -importkeystore -srckeystore " + + "-destkeystore -deststoretype pkcs12"); + } } diff --git a/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java b/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java index 3e771c015f4..73ca0c6bf16 100644 --- a/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java +++ b/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -627,6 +627,10 @@ public abstract sealed class JavaKeyStore extends KeyStoreSpi { dos.write(digest); dos.flush(); + + if (debug != null) { + emitWeakKeyStoreWarning(); + } } } @@ -790,6 +794,10 @@ public abstract sealed class JavaKeyStore extends KeyStoreSpi { privateKeyCount + ". trusted key count: " + trustedKeyCount); } + if (debug != null) { + emitWeakKeyStoreWarning(); + } + /* * If a password has been provided, we check the keyed digest * at the end. If this check fails, the store has been tampered @@ -838,4 +846,16 @@ public abstract sealed class JavaKeyStore extends KeyStoreSpi { } return passwdBytes; } + + private void emitWeakKeyStoreWarning() { + String type = this.getClass().getSimpleName(). + toUpperCase(Locale.ROOT); + if (type.equals("JKS")){ + debug.println("WARNING: JKS uses outdated cryptographic " + + "algorithms and will be removed in a future " + + "release. Migrate to PKCS12 using:"); + debug.println("keytool -importkeystore -srckeystore " + + "-destkeystore -deststoretype pkcs12"); + } + } } diff --git a/src/java.base/share/classes/sun/security/tools/keytool/resources/keytool.properties b/src/java.base/share/classes/sun/security/tools/keytool/resources/keytool.properties index fb03573a7d5..751e3af9c9c 100644 --- a/src/java.base/share/classes/sun/security/tools/keytool/resources/keytool.properties +++ b/src/java.base/share/classes/sun/security/tools/keytool/resources/keytool.properties @@ -321,7 +321,8 @@ Unable.to.parse.denyAfter.string.in.exception.message=Unable to parse denyAfter whose.sigalg.weak=%1$s uses the %2$s signature algorithm which is considered a security risk. whose.key.disabled=%1$s uses a %2$s which is considered a security risk and is disabled. whose.key.weak=%1$s uses a %2$s which is considered a security risk. It will be disabled in a future update. -jks.storetype.warning=The %1$s keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12". +jks.storetype.warning=%1$s uses outdated cryptographic algorithms and will be removed in a future release. Migrate to PKCS12 using:\n\ +keytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12 migrate.keystore.warning=Migrated "%1$s" to %4$s. The %2$s keystore is backed up as "%3$s". backup.keystore.warning=The original keystore "%1$s" is backed up as "%3$s"... importing.keystore.status=Importing keystore %1$s to %2$s... diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java index f2c76058434..12967972a88 100644 --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java @@ -167,6 +167,7 @@ public class Main { char[] storepass; // keystore password boolean protectedPath; // protected authentication path String storetype; // keystore type + String realStoreType; String providerName; // provider name List providers = null; // list of provider names List providerClasses = null; // list of provider classes @@ -240,6 +241,7 @@ public class Main { private boolean signerSelfSigned = false; private boolean allAliasesFound = true; private boolean hasMultipleManifests = false; + private boolean weakKeyStore = false; private Throwable chainNotValidatedReason = null; private Throwable tsaChainNotValidatedReason = null; @@ -1482,6 +1484,12 @@ public class Main { warnings.add(rb.getString("external.file.attributes.detected")); } + if (weakKeyStore) { + warnings.add(String.format(rb.getString( + "jks.storetype.warning"), + realStoreType, keystore)); + } + if ((strict) && (!errors.isEmpty())) { result = isSigning ? rb.getString("jar.signed.with.signer.errors.") @@ -2422,6 +2430,23 @@ public class Main { is.close(); } } + + File storeFile = new File(keyStoreName); + if (storeFile.exists()) { + // Probe for real type. A JKS can be loaded as PKCS12 because + // DualFormat support, vice versa. + try { + KeyStore keyStore = KeyStore.getInstance(storeFile, storepass); + realStoreType = keyStore.getType(); + if (realStoreType.equalsIgnoreCase("JKS") + || realStoreType.equalsIgnoreCase("JCEKS")) { + weakKeyStore = true; + } + } catch (KeyStoreException e) { + // Probing not supported, therefore cannot be JKS or JCEKS. + // Skip the legacy type warning at all. + } + } } Enumeration aliases = store.aliases(); while (aliases.hasMoreElements()) { diff --git a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties index b490d386e59..a16420daf8f 100644 --- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties +++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties @@ -222,3 +222,5 @@ entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile=Entry %s is sig jar.contains.internal.inconsistencies.result.in.different.contents.via.jarfile.and.jarinputstream=This JAR file contains internal inconsistencies that may result in different contents when reading via JarFile and JarInputStream: signature.verification.failed.on.entry.1.when.reading.via.jarinputstream=Signature verification failed on entry %s when reading via JarInputStream signature.verification.failed.on.entry.1.when.reading.via.jarfile=Signature verification failed on entry %s when reading via JarFile +jks.storetype.warning=%1$s uses outdated cryptographic algorithms and will be removed in a future release. Migrate to PKCS12 using:\n\ +keytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12 diff --git a/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java b/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java index 25bb67c230b..92b081b4508 100644 --- a/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java +++ b/test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java @@ -850,6 +850,9 @@ public class Compatibility { if (Test.CERTIFICATE_SELF_SIGNED.equals(line)) continue; if (Test.HAS_EXPIRED_CERT_VERIFYING_WARNING.equals(line) && signItem.certInfo.expired) continue; + + if (line.contains(Test.OUTDATED_KEYSTORE_WARNING1)) continue; + if (line.contains(Test.OUTDATED_KEYSTORE_WARNING2)) continue; System.out.println("verifyingStatus: unexpected line: " + line); return Status.ERROR; // treat unexpected warnings as error } diff --git a/test/jdk/sun/security/tools/jarsigner/warnings/Test.java b/test/jdk/sun/security/tools/jarsigner/warnings/Test.java index 224fa754eec..97c05d7c2cb 100644 --- a/test/jdk/sun/security/tools/jarsigner/warnings/Test.java +++ b/test/jdk/sun/security/tools/jarsigner/warnings/Test.java @@ -151,6 +151,13 @@ public abstract class Test { static final String WEAK_KEY_WARNING = "will be disabled in a future update."; + static final String OUTDATED_KEYSTORE_WARNING1 + = "uses outdated cryptographic algorithms and will be " + + "removed in a future release. Migrate to PKCS12 using:"; + + static final String OUTDATED_KEYSTORE_WARNING2 + = "keytool -importkeystore -srckeystore"; + static final String JAR_SIGNED = "jar signed."; static final String JAR_VERIFIED = "jar verified."; diff --git a/test/jdk/sun/security/tools/keytool/OutdatedKeyStoreWarning.java b/test/jdk/sun/security/tools/keytool/OutdatedKeyStoreWarning.java new file mode 100644 index 00000000000..1506b13c51d --- /dev/null +++ b/test/jdk/sun/security/tools/keytool/OutdatedKeyStoreWarning.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * 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 8353749 + * @summary Validate that keytool and jarsigner emit warnings for + * JKS and JCEKS keystore with java.security.debug=keystore + * @library /test/lib + * @modules java.base/sun.security.tools.keytool + * java.base/sun.security.x509 + * @run main/othervm -Djava.security.debug=keystore OutdatedKeyStoreWarning + */ + +import java.io.FileOutputStream; +import java.nio.file.Path; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Locale; + +import jdk.test.lib.SecurityTools; +import jdk.test.lib.util.JarUtils; + +import sun.security.tools.keytool.CertAndKeyGen; +import sun.security.x509.X500Name; + +public class OutdatedKeyStoreWarning { + + private static final String KS_WARNING1 = + "uses outdated cryptographic algorithms and will be removed " + + "in a future release. Migrate to PKCS12 using:"; + + private static final String KS_WARNING2= + "keytool -importkeystore -srckeystore " + + "-destkeystore -deststoretype pkcs12"; + + public static void main(String[] args) throws Exception { + String[] ksTypes = {"JKS", "JCEKS"}; + + for (String type : ksTypes) { + String ksFile = type.toLowerCase() + ".ks"; + String cmdWarning = type + " " + KS_WARNING1; + + checkWarnings(type, () -> { + SecurityTools.keytool(String.format( + "-genkeypair -keystore %s -storetype %s -storepass changeit " + + "-keypass changeit -keyalg ec -alias a1 -dname CN=me " + + "-J-Djava.security.debug=keystore", + ksFile, type.toLowerCase())) + .shouldContain("Warning:") + .shouldContain(cmdWarning) + .shouldContain(KS_WARNING2) + .shouldHaveExitValue(0); + }); + + JarUtils.createJarFile(Path.of("unsigned.jar"), Path.of("."), Path.of(ksFile)); + checkWarnings(type, () -> { + SecurityTools.jarsigner(String.format( + "-keystore %s -storetype %s -storepass changeit -signedjar signed.jar " + + "unsigned.jar a1 " + + "-J-Djava.security.debug=keystore", + ksFile, type.toLowerCase())) + .shouldContain("Warning:") + .shouldContain(cmdWarning) + .shouldContain(KS_WARNING2) + .shouldHaveExitValue(0); + }); + + checkWarnings(type, () -> { + SecurityTools.jarsigner(String.format( + "-verify -keystore %s -storetype %s -storepass changeit signed.jar " + + "-J-Djava.security.debug=keystore", + ksFile, type.toLowerCase())) + .shouldContain("Warning:") + .shouldContain(cmdWarning) + .shouldContain(KS_WARNING2) + .shouldHaveExitValue(0); + }); + } + + for (String type : ksTypes) { + checkStoreAPIWarning(type); + } + } + + private static void checkWarnings(String type, RunnableWithException r) throws Exception { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PrintStream origErr = System.err; + PrintStream origOut = System.out; + + try { + PrintStream pStream = new PrintStream(bOut); + System.setErr(pStream); + System.setOut(pStream); + r.run(); + } finally { + System.setErr(origErr); + System.setOut(origOut); + } + + String msg = bOut.toString(); + if (!msg.contains("WARNING: " + type.toUpperCase(Locale.ROOT)) || + !msg.contains(KS_WARNING1) || + !msg.contains(KS_WARNING2) || + !msg.contains("Warning:")) { + throw new RuntimeException("Expected warning not found for " + type + ":\n" + msg); + } + } + + // Test case for: KeyStore.getInstance("JKS" or "JCEKS"), load(null, null), and + // store it where warning should be emitted. + private static void checkStoreAPIWarning(String type) throws Exception { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PrintStream origErr = System.err; + PrintStream origOut = System.out; + + try { + PrintStream pStream = new PrintStream(bOut); + System.setErr(pStream); + System.setOut(pStream); + + KeyStore ks = KeyStore.getInstance(type); + ks.load(null, null); + + CertAndKeyGen cag = new CertAndKeyGen("EC", "SHA256withECDSA"); + cag.generate("secp256r1"); + X509Certificate cert = cag.getSelfCertificate(new X500Name("CN=one"), 3600); + ks.setKeyEntry("dummy", cag.getPrivateKey(), "changeit".toCharArray(), + new Certificate[] {cert}); + + try (FileOutputStream fos = new FileOutputStream(type.toLowerCase() + + "_storeAPI.ks")) { + ks.store(fos, "changeit".toCharArray()); + } + } finally { + System.setErr(origErr); + System.setOut(origOut); + } + + String msg = bOut.toString(); + if (!msg.contains("WARNING: " + type.toUpperCase(Locale.ROOT)) || + !msg.contains(KS_WARNING1) || + !msg.contains(KS_WARNING2)) { + throw new RuntimeException("Expected warning not found for KeyStore.store() API (" + + type + "):\n" + msg); + } + } + + @FunctionalInterface + interface RunnableWithException { + void run() throws Exception; + } +} diff --git a/test/jdk/sun/security/tools/keytool/WeakAlg.java b/test/jdk/sun/security/tools/keytool/WeakAlg.java index 13c610cd55f..de2435a4f4b 100644 --- a/test/jdk/sun/security/tools/keytool/WeakAlg.java +++ b/test/jdk/sun/security/tools/keytool/WeakAlg.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8171319 8177569 8182879 8172404 + * @bug 8171319 8177569 8182879 8172404 8353749 * @summary keytool should print out warnings when reading or generating * cert/cert req using weak algorithms * @library /test/lib @@ -226,7 +226,8 @@ public class WeakAlg { // No warning for cacerts, all certs kt0("-cacerts -list -storepass changeit") - .shouldNotContain("proprietary format"); + .shouldNotContain("outdated cryptographic algorithms") + .shouldNotContain("keytool -importkeystore -srckeystore"); rm("ks"); rm("ks2"); @@ -242,7 +243,10 @@ public class WeakAlg { // warn if migrating to JKS importkeystore("ks", "ks2", "-deststoretype jks") - .shouldContain("JKS keystore uses a proprietary format"); + .shouldContain("JKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); rm("ks"); rm("ks2"); @@ -252,17 +256,35 @@ public class WeakAlg { kt("-importcert -alias b -file a.crt -storetype jks -noprompt") .shouldNotContain("Warning:"); kt("-genkeypair -keyalg DSA -alias a -dname CN=A") - .shouldContain("JKS keystore uses a proprietary format"); + .shouldContain("JKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-list") - .shouldContain("JKS keystore uses a proprietary format"); + .shouldContain("JKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-list -storetype pkcs12") // warn if JKS used as PKCS12 - .shouldContain("JKS keystore uses a proprietary format"); + .shouldContain("JKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-exportcert -alias a -file a.crt") - .shouldContain("JKS keystore uses a proprietary format"); + .shouldContain("JKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-printcert -file a.crt") // warning since -keystore option is supported - .shouldContain("JKS keystore uses a proprietary format"); + .shouldContain("JKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-certreq -alias a -file a.req") - .shouldContain("JKS keystore uses a proprietary format"); + .shouldContain("JKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-printcertreq -file a.req") // no warning if keystore not touched .shouldNotContain("Warning:"); @@ -276,21 +298,42 @@ public class WeakAlg { rm("ks"); kt("-genkeypair -keyalg DSA -alias a -dname CN=A -storetype jceks") - .shouldContain("JCEKS keystore uses a proprietary format"); + .shouldContain("JCEKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-list") - .shouldContain("JCEKS keystore uses a proprietary format"); + .shouldContain("JCEKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-importcert -alias b -file a.crt -noprompt") - .shouldContain("JCEKS keystore uses a proprietary format"); + .shouldContain("JCEKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-exportcert -alias a -file a.crt") - .shouldContain("JCEKS keystore uses a proprietary format"); + .shouldContain("JCEKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-printcert -file a.crt") // warning since -keystore option is supported - .shouldContain("JCEKS keystore uses a proprietary format"); + .shouldContain("JCEKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-certreq -alias a -file a.req") - .shouldContain("JCEKS keystore uses a proprietary format"); + .shouldContain("JCEKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); kt("-printcertreq -file a.req") .shouldNotContain("Warning:"); kt("-genseckey -alias c -keyalg AES -keysize 128") - .shouldContain("JCEKS keystore uses a proprietary format"); + .shouldContain("JCEKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12"); } static void checkImportKeyStore() throws Exception { @@ -336,7 +379,10 @@ public class WeakAlg { // Migration importkeystore("ks", "ks", "-deststoretype jks") .shouldContain("Warning:") - .shouldContain("JKS keystore uses a proprietary format") + .shouldContain("JKS uses outdated cryptographic algorithms" + + " and will be removed") + .shouldMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12") .shouldMatch("Migrated.*JKS.*PKCS12.*ks.old5"); Asserts.assertEQ( @@ -346,7 +392,9 @@ public class WeakAlg { importkeystore("ks", "ks", "-srcstoretype PKCS12") .shouldContain("Warning:") - .shouldNotContain("proprietary format") + .shouldNotContain("outdated cryptographic algorithms") + .shouldNotMatch("keytool -importkeystore -srckeystore." + + "*-destkeystore.*-deststoretype pkcs12") .shouldMatch("Migrated.*PKCS12.*JKS.*ks.old6"); Asserts.assertEQ(