8225392: Comparison builds are failing due to cacerts file
Reviewed-by: erikj, martin, mullan
--- a/make/jdk/src/classes/build/tools/generatecacerts/GenerateCacerts.java Fri Jun 14 12:19:14 2019 -0700
+++ b/make/jdk/src/classes/build/tools/generatecacerts/GenerateCacerts.java Sat Jun 15 14:39:04 2019 +0800
@@ -25,12 +25,23 @@
package build.tools.generatecacerts;
+import java.io.DataOutputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.security.KeyStore;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
/**
* Generate cacerts
@@ -39,23 +50,99 @@
*/
public class GenerateCacerts {
public static void main(String[] args) throws Exception {
- KeyStore ks = KeyStore.getInstance("JKS");
- ks.load(null, null);
- CertificateFactory cf = CertificateFactory.getInstance("X509");
- Files.list(Path.of(args[0]))
- .filter(p -> !p.getFileName().toString().equals("README"))
- .forEach(p -> {
- try {
- String alias = p.getFileName().toString() + " [jdk]";
- try (InputStream fis = Files.newInputStream(p)) {
- ks.setCertificateEntry(alias, cf.generateCertificate(fis));
- }
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- });
try (FileOutputStream fos = new FileOutputStream(args[1])) {
- ks.store(fos, "changeit".toCharArray());
+ store(args[0], fos, "changeit".toCharArray());
}
}
+
+ // The following code are copied from JavaKeyStore.java.
+
+ private static final int MAGIC = 0xfeedfeed;
+ private static final int VERSION_2 = 0x02;
+
+ // This method is a simplified version of JavaKeyStore::engineStore.
+ // A new "dir" argument is added. All cert names in "dir" is collected into
+ // a sorted array. Each cert is stored with a creation date set to its
+ // notBefore value. Thus the output is determined as long as the certs
+ // are the same.
+ public static void store(String dir, OutputStream stream, char[] password)
+ throws IOException, NoSuchAlgorithmException, CertificateException
+ {
+ byte[] encoded; // the certificate encoding
+ CertificateFactory cf = CertificateFactory.getInstance("X509");
+
+ MessageDigest md = getPreKeyedHash(password);
+ DataOutputStream dos
+ = new DataOutputStream(new DigestOutputStream(stream, md));
+
+ dos.writeInt(MAGIC);
+ // always write the latest version
+ dos.writeInt(VERSION_2);
+
+ // All file names in dir sorted.
+ // README is excluded. Name starting with "." excluded.
+ List<String> entries = Files.list(Path.of(dir))
+ .map(p -> p.getFileName().toString())
+ .filter(s -> !s.equals("README") && !s.startsWith("."))
+ .collect(Collectors.toList());
+
+ entries.sort(String::compareTo);
+
+ dos.writeInt(entries.size());
+
+ for (String entry : entries) {
+
+ String alias = entry + " [jdk]";
+ X509Certificate cert;
+ try (InputStream fis = Files.newInputStream(Path.of(dir, entry))) {
+ cert = (X509Certificate) cf.generateCertificate(fis);
+ }
+
+ dos.writeInt(2);
+
+ // Write the alias
+ dos.writeUTF(alias);
+
+ // Write the (entry creation) date, which is notBefore of the cert
+ dos.writeLong(cert.getNotBefore().getTime());
+
+ // Write the trusted certificate
+ encoded = cert.getEncoded();
+ dos.writeUTF(cert.getType());
+ dos.writeInt(encoded.length);
+ dos.write(encoded);
+ }
+
+ /*
+ * Write the keyed hash which is used to detect tampering with
+ * the keystore (such as deleting or modifying key or
+ * certificate entries).
+ */
+ byte[] digest = md.digest();
+
+ dos.write(digest);
+ dos.flush();
+ }
+
+ private static MessageDigest getPreKeyedHash(char[] password)
+ throws NoSuchAlgorithmException, UnsupportedEncodingException
+ {
+
+ MessageDigest md = MessageDigest.getInstance("SHA");
+ byte[] passwdBytes = convertToBytes(password);
+ md.update(passwdBytes);
+ Arrays.fill(passwdBytes, (byte) 0x00);
+ md.update("Mighty Aphrodite".getBytes("UTF8"));
+ return md;
+ }
+
+ private static byte[] convertToBytes(char[] password) {
+ int i, j;
+ byte[] passwdBytes = new byte[password.length * 2];
+ for (i=0, j=0; i<password.length; i++) {
+ passwdBytes[j++] = (byte)(password[i] >> 8);
+ passwdBytes[j++] = (byte)password[i];
+ }
+ return passwdBytes;
+ }
}
--- a/src/java.base/share/classes/sun/security/tools/keytool/Main.java Fri Jun 14 12:19:14 2019 -0700
+++ b/src/java.base/share/classes/sun/security/tools/keytool/Main.java Sat Jun 15 14:39:04 2019 +0800
@@ -2418,9 +2418,9 @@
out.println(form.format(source));
out.println();
- for (Enumeration<String> e = keyStore.aliases();
- e.hasMoreElements(); ) {
- String alias = e.nextElement();
+ List<String> aliases = Collections.list(keyStore.aliases());
+ aliases.sort(String::compareTo);
+ for (String alias : aliases) {
doPrintEntry("<" + alias + ">", alias, out);
if (verbose || rfc) {
out.println(rb.getString("NEWLINE"));
--- a/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java Fri Jun 14 12:19:14 2019 -0700
+++ b/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java Sat Jun 15 14:39:04 2019 +0800
@@ -26,11 +26,13 @@
* @test
* @bug 8189131 8198240 8191844 8189949 8191031 8196141 8204923 8195774 8199779
* 8209452 8209506 8210432 8195793 8216577 8222089 8222133 8222137 8222136
- * 8223499
+ * 8223499 8225392
* @summary Check root CA entries in cacerts file
*/
+import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileInputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.Certificate;
@@ -52,6 +54,11 @@
// The numbers of certs now.
private static final int COUNT = 88;
+ // SHA-256 of cacerts, can be generated with
+ // shasum -a 256 cacerts | sed -e 's/../&:/g' | tr '[:lower:]' '[:upper:]' | cut -c1-95
+ private static final String CHECKSUM
+ = "4E:21:94:7C:1D:49:28:BB:34:B0:40:DF:AE:19:B4:41:C6:B5:8A:EE:EB:D5:DE:B4:EF:07:AF:63:18:73:A6:FE";
+
// map of cert alias to SHA-256 fingerprint
@SuppressWarnings("serial")
private static final Map<String, String> FINGERPRINT_MAP = new HashMap<>() {
@@ -255,8 +262,16 @@
public static void main(String[] args) throws Exception {
System.out.println("cacerts file: " + CACERTS);
md = MessageDigest.getInstance("SHA-256");
+
+ byte[] data = Files.readAllBytes(Path.of(CACERTS));
+ String checksum = toHexString(md.digest(data));
+ if (!checksum.equals(CHECKSUM)) {
+ atLeastOneFailed = true;
+ System.err.println("ERROR: wrong checksum\n" + checksum);
+ }
+
KeyStore ks = KeyStore.getInstance("JKS");
- ks.load(new FileInputStream(CACERTS), "changeit".toCharArray());
+ ks.load(new ByteArrayInputStream(data), "changeit".toCharArray());
// check the count of certs inside
if (ks.size() != COUNT) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/tools/keytool/ListOrder.java Sat Jun 15 14:39:04 2019 +0800
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2019, 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 8225392
+ * @summary Comparison builds are failing due to cacerts file
+ * @library /test/lib
+ */
+
+import jdk.test.lib.SecurityTools;
+
+import java.util.Random;
+
+public class ListOrder {
+
+ public static void main(String[] args) throws Throwable {
+
+ Random rand = new Random();
+ for (int i = 0; i < 10; i++) {
+ gen(String.format("a%02d", rand.nextInt(100)));
+ }
+
+ String last = "";
+ for (String line : SecurityTools.keytool(
+ "-keystore ks -storepass changeit -list").asLines()) {
+ if (line.contains("PrivateKeyEntry")) {
+ // This is the line starting with the alias
+ System.out.println(line);
+ if (line.compareTo(last) <= 0) {
+ throw new RuntimeException("Not ordered");
+ } else {
+ last = line;
+ }
+ }
+ }
+ }
+
+ static void gen(String a) throws Exception {
+ // Do not check result, there might be duplicated alias(es).
+ SecurityTools.keytool("-keystore ks -storepass changeit "
+ + "-keyalg ec -genkeypair -alias " + a + " -dname CN=" + a);
+ }
+}