Skip to content

Commit c2ea75b

Browse files
author
Hai-May Chao
committed
8353749: Improve security warning when using JKS or JCEKS keystores
Reviewed-by: weijun
1 parent 2358d40 commit c2ea75b

File tree

9 files changed

+319
-21
lines changed

9 files changed

+319
-21
lines changed

src/java.base/share/classes/com/sun/crypto/provider/JceKeyStore.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,10 @@ public void engineStore(OutputStream stream, char[] password)
661661
dos.close();
662662
}
663663
}
664+
665+
if (debug != null) {
666+
emitWeakKeyStoreWarning();
667+
}
664668
}
665669
}
666670

@@ -862,6 +866,10 @@ public void engineLoad(InputStream stream, char[] password)
862866
secretKeyCount);
863867
}
864868

869+
if (debug != null) {
870+
emitWeakKeyStoreWarning();
871+
}
872+
865873
/*
866874
* If a password has been provided, we check the keyed digest
867875
* at the end. If this check fails, the store has been tampered
@@ -978,4 +986,12 @@ public DeserializationChecker(int fullLength) {
978986
return Status.UNDECIDED;
979987
}
980988
}
989+
990+
private void emitWeakKeyStoreWarning() {
991+
debug.println("WARNING: JCEKS uses outdated cryptographic "
992+
+ "algorithms and will be removed in a future "
993+
+ "release. Migrate to PKCS12 using:");
994+
debug.println("keytool -importkeystore -srckeystore <keystore> "
995+
+ "-destkeystore <keystore> -deststoretype pkcs12");
996+
}
981997
}

src/java.base/share/classes/sun/security/provider/JavaKeyStore.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -627,6 +627,10 @@ public void engineStore(OutputStream stream, char[] password)
627627

628628
dos.write(digest);
629629
dos.flush();
630+
631+
if (debug != null) {
632+
emitWeakKeyStoreWarning();
633+
}
630634
}
631635
}
632636

@@ -790,6 +794,10 @@ public void engineLoad(InputStream stream, char[] password)
790794
privateKeyCount + ". trusted key count: " + trustedKeyCount);
791795
}
792796

797+
if (debug != null) {
798+
emitWeakKeyStoreWarning();
799+
}
800+
793801
/*
794802
* If a password has been provided, we check the keyed digest
795803
* at the end. If this check fails, the store has been tampered
@@ -838,4 +846,16 @@ private byte[] convertToBytes(char[] password) {
838846
}
839847
return passwdBytes;
840848
}
849+
850+
private void emitWeakKeyStoreWarning() {
851+
String type = this.getClass().getSimpleName().
852+
toUpperCase(Locale.ROOT);
853+
if (type.equals("JKS")){
854+
debug.println("WARNING: JKS uses outdated cryptographic "
855+
+ "algorithms and will be removed in a future "
856+
+ "release. Migrate to PKCS12 using:");
857+
debug.println("keytool -importkeystore -srckeystore <keystore> "
858+
+ "-destkeystore <keystore> -deststoretype pkcs12");
859+
}
860+
}
841861
}

src/java.base/share/classes/sun/security/tools/keytool/resources/keytool.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,8 @@ Unable.to.parse.denyAfter.string.in.exception.message=Unable to parse denyAfter
321321
whose.sigalg.weak=%1$s uses the %2$s signature algorithm which is considered a security risk.
322322
whose.key.disabled=%1$s uses a %2$s which is considered a security risk and is disabled.
323323
whose.key.weak=%1$s uses a %2$s which is considered a security risk. It will be disabled in a future update.
324-
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".
324+
jks.storetype.warning=%1$s uses outdated cryptographic algorithms and will be removed in a future release. Migrate to PKCS12 using:\n\
325+
keytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12
325326
migrate.keystore.warning=Migrated "%1$s" to %4$s. The %2$s keystore is backed up as "%3$s".
326327
backup.keystore.warning=The original keystore "%1$s" is backed up as "%3$s"...
327328
importing.keystore.status=Importing keystore %1$s to %2$s...

src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ public ExitException(int errorCode) {
167167
char[] storepass; // keystore password
168168
boolean protectedPath; // protected authentication path
169169
String storetype; // keystore type
170+
String realStoreType;
170171
String providerName; // provider name
171172
List<String> providers = null; // list of provider names
172173
List<String> providerClasses = null; // list of provider classes
@@ -240,6 +241,7 @@ public ExitException(int errorCode) {
240241
private boolean signerSelfSigned = false;
241242
private boolean allAliasesFound = true;
242243
private boolean hasMultipleManifests = false;
244+
private boolean weakKeyStore = false;
243245

244246
private Throwable chainNotValidatedReason = null;
245247
private Throwable tsaChainNotValidatedReason = null;
@@ -1482,6 +1484,12 @@ private void displayMessagesAndResult(boolean isSigning) {
14821484
warnings.add(rb.getString("external.file.attributes.detected"));
14831485
}
14841486

1487+
if (weakKeyStore) {
1488+
warnings.add(String.format(rb.getString(
1489+
"jks.storetype.warning"),
1490+
realStoreType, keystore));
1491+
}
1492+
14851493
if ((strict) && (!errors.isEmpty())) {
14861494
result = isSigning
14871495
? rb.getString("jar.signed.with.signer.errors.")
@@ -2422,6 +2430,23 @@ void loadKeyStore(String keyStoreName, boolean prompt) {
24222430
is.close();
24232431
}
24242432
}
2433+
2434+
File storeFile = new File(keyStoreName);
2435+
if (storeFile.exists()) {
2436+
// Probe for real type. A JKS can be loaded as PKCS12 because
2437+
// DualFormat support, vice versa.
2438+
try {
2439+
KeyStore keyStore = KeyStore.getInstance(storeFile, storepass);
2440+
realStoreType = keyStore.getType();
2441+
if (realStoreType.equalsIgnoreCase("JKS")
2442+
|| realStoreType.equalsIgnoreCase("JCEKS")) {
2443+
weakKeyStore = true;
2444+
}
2445+
} catch (KeyStoreException e) {
2446+
// Probing not supported, therefore cannot be JKS or JCEKS.
2447+
// Skip the legacy type warning at all.
2448+
}
2449+
}
24252450
}
24262451
Enumeration<String> aliases = store.aliases();
24272452
while (aliases.hasMoreElements()) {

src/jdk.jartool/share/classes/sun/security/tools/jarsigner/resources/jarsigner.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,5 @@ entry.1.is.signed.in.jarinputstream.but.is.not.signed.in.jarfile=Entry %s is sig
222222
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:
223223
signature.verification.failed.on.entry.1.when.reading.via.jarinputstream=Signature verification failed on entry %s when reading via JarInputStream
224224
signature.verification.failed.on.entry.1.when.reading.via.jarfile=Signature verification failed on entry %s when reading via JarFile
225+
jks.storetype.warning=%1$s uses outdated cryptographic algorithms and will be removed in a future release. Migrate to PKCS12 using:\n\
226+
keytool -importkeystore -srckeystore %2$s -destkeystore %2$s -deststoretype pkcs12

test/jdk/sun/security/tools/jarsigner/compatibility/Compatibility.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,9 @@ private static Status verifyingStatus(SignItem signItem, VerifyItem
850850
if (Test.CERTIFICATE_SELF_SIGNED.equals(line)) continue;
851851
if (Test.HAS_EXPIRED_CERT_VERIFYING_WARNING.equals(line)
852852
&& signItem.certInfo.expired) continue;
853+
854+
if (line.contains(Test.OUTDATED_KEYSTORE_WARNING1)) continue;
855+
if (line.contains(Test.OUTDATED_KEYSTORE_WARNING2)) continue;
853856
System.out.println("verifyingStatus: unexpected line: " + line);
854857
return Status.ERROR; // treat unexpected warnings as error
855858
}

test/jdk/sun/security/tools/jarsigner/warnings/Test.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,13 @@ public abstract class Test {
151151
static final String WEAK_KEY_WARNING
152152
= "will be disabled in a future update.";
153153

154+
static final String OUTDATED_KEYSTORE_WARNING1
155+
= "uses outdated cryptographic algorithms and will be "
156+
+ "removed in a future release. Migrate to PKCS12 using:";
157+
158+
static final String OUTDATED_KEYSTORE_WARNING2
159+
= "keytool -importkeystore -srckeystore";
160+
154161
static final String JAR_SIGNED = "jar signed.";
155162

156163
static final String JAR_VERIFIED = "jar verified.";
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8353749
27+
* @summary Validate that keytool and jarsigner emit warnings for
28+
* JKS and JCEKS keystore with java.security.debug=keystore
29+
* @library /test/lib
30+
* @modules java.base/sun.security.tools.keytool
31+
* java.base/sun.security.x509
32+
* @run main/othervm -Djava.security.debug=keystore OutdatedKeyStoreWarning
33+
*/
34+
35+
import java.io.FileOutputStream;
36+
import java.nio.file.Path;
37+
import java.io.ByteArrayOutputStream;
38+
import java.io.PrintStream;
39+
import java.security.KeyStore;
40+
import java.security.cert.Certificate;
41+
import java.security.cert.X509Certificate;
42+
import java.util.Locale;
43+
44+
import jdk.test.lib.SecurityTools;
45+
import jdk.test.lib.util.JarUtils;
46+
47+
import sun.security.tools.keytool.CertAndKeyGen;
48+
import sun.security.x509.X500Name;
49+
50+
public class OutdatedKeyStoreWarning {
51+
52+
private static final String KS_WARNING1 =
53+
"uses outdated cryptographic algorithms and will be removed " +
54+
"in a future release. Migrate to PKCS12 using:";
55+
56+
private static final String KS_WARNING2=
57+
"keytool -importkeystore -srckeystore <keystore> " +
58+
"-destkeystore <keystore> -deststoretype pkcs12";
59+
60+
public static void main(String[] args) throws Exception {
61+
String[] ksTypes = {"JKS", "JCEKS"};
62+
63+
for (String type : ksTypes) {
64+
String ksFile = type.toLowerCase() + ".ks";
65+
String cmdWarning = type + " " + KS_WARNING1;
66+
67+
checkWarnings(type, () -> {
68+
SecurityTools.keytool(String.format(
69+
"-genkeypair -keystore %s -storetype %s -storepass changeit " +
70+
"-keypass changeit -keyalg ec -alias a1 -dname CN=me " +
71+
"-J-Djava.security.debug=keystore",
72+
ksFile, type.toLowerCase()))
73+
.shouldContain("Warning:")
74+
.shouldContain(cmdWarning)
75+
.shouldContain(KS_WARNING2)
76+
.shouldHaveExitValue(0);
77+
});
78+
79+
JarUtils.createJarFile(Path.of("unsigned.jar"), Path.of("."), Path.of(ksFile));
80+
checkWarnings(type, () -> {
81+
SecurityTools.jarsigner(String.format(
82+
"-keystore %s -storetype %s -storepass changeit -signedjar signed.jar " +
83+
"unsigned.jar a1 " +
84+
"-J-Djava.security.debug=keystore",
85+
ksFile, type.toLowerCase()))
86+
.shouldContain("Warning:")
87+
.shouldContain(cmdWarning)
88+
.shouldContain(KS_WARNING2)
89+
.shouldHaveExitValue(0);
90+
});
91+
92+
checkWarnings(type, () -> {
93+
SecurityTools.jarsigner(String.format(
94+
"-verify -keystore %s -storetype %s -storepass changeit signed.jar " +
95+
"-J-Djava.security.debug=keystore",
96+
ksFile, type.toLowerCase()))
97+
.shouldContain("Warning:")
98+
.shouldContain(cmdWarning)
99+
.shouldContain(KS_WARNING2)
100+
.shouldHaveExitValue(0);
101+
});
102+
}
103+
104+
for (String type : ksTypes) {
105+
checkStoreAPIWarning(type);
106+
}
107+
}
108+
109+
private static void checkWarnings(String type, RunnableWithException r) throws Exception {
110+
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
111+
PrintStream origErr = System.err;
112+
PrintStream origOut = System.out;
113+
114+
try {
115+
PrintStream pStream = new PrintStream(bOut);
116+
System.setErr(pStream);
117+
System.setOut(pStream);
118+
r.run();
119+
} finally {
120+
System.setErr(origErr);
121+
System.setOut(origOut);
122+
}
123+
124+
String msg = bOut.toString();
125+
if (!msg.contains("WARNING: " + type.toUpperCase(Locale.ROOT)) ||
126+
!msg.contains(KS_WARNING1) ||
127+
!msg.contains(KS_WARNING2) ||
128+
!msg.contains("Warning:")) {
129+
throw new RuntimeException("Expected warning not found for " + type + ":\n" + msg);
130+
}
131+
}
132+
133+
// Test case for: KeyStore.getInstance("JKS" or "JCEKS"), load(null, null), and
134+
// store it where warning should be emitted.
135+
private static void checkStoreAPIWarning(String type) throws Exception {
136+
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
137+
PrintStream origErr = System.err;
138+
PrintStream origOut = System.out;
139+
140+
try {
141+
PrintStream pStream = new PrintStream(bOut);
142+
System.setErr(pStream);
143+
System.setOut(pStream);
144+
145+
KeyStore ks = KeyStore.getInstance(type);
146+
ks.load(null, null);
147+
148+
CertAndKeyGen cag = new CertAndKeyGen("EC", "SHA256withECDSA");
149+
cag.generate("secp256r1");
150+
X509Certificate cert = cag.getSelfCertificate(new X500Name("CN=one"), 3600);
151+
ks.setKeyEntry("dummy", cag.getPrivateKey(), "changeit".toCharArray(),
152+
new Certificate[] {cert});
153+
154+
try (FileOutputStream fos = new FileOutputStream(type.toLowerCase() +
155+
"_storeAPI.ks")) {
156+
ks.store(fos, "changeit".toCharArray());
157+
}
158+
} finally {
159+
System.setErr(origErr);
160+
System.setOut(origOut);
161+
}
162+
163+
String msg = bOut.toString();
164+
if (!msg.contains("WARNING: " + type.toUpperCase(Locale.ROOT)) ||
165+
!msg.contains(KS_WARNING1) ||
166+
!msg.contains(KS_WARNING2)) {
167+
throw new RuntimeException("Expected warning not found for KeyStore.store() API (" +
168+
type + "):\n" + msg);
169+
}
170+
}
171+
172+
@FunctionalInterface
173+
interface RunnableWithException {
174+
void run() throws Exception;
175+
}
176+
}

0 commit comments

Comments
 (0)