--- old/make/jdk/src/classes/build/tools/generatecacerts/GenerateCacerts.java 2019-08-14 00:18:04.000000000 +0800 +++ new/make/jdk/src/classes/build/tools/generatecacerts/GenerateCacerts.java 2019-08-14 00:18:03.000000000 +0800 @@ -25,22 +25,15 @@ package build.tools.generatecacerts; -import java.io.DataOutputStream; -import java.io.FileOutputStream; +import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; +import java.io.PrintStream; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -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.function.Predicate; import java.util.stream.Collectors; /** @@ -50,99 +43,22 @@ */ public class GenerateCacerts { public static void main(String[] args) throws Exception { - try (FileOutputStream fos = new FileOutputStream(args[1])) { - store(args[0], fos, "changeit".toCharArray()); + try (PrintStream ps = new PrintStream( + new File(args[1]), StandardCharsets.UTF_8)) { + Files.list(Path.of(args[0])) + .filter(Predicate.not(Files::isDirectory)) + .map(p -> p.getFileName().toString()) + .filter(s -> !s.equals("README") && !s.startsWith(".")) + .sorted() + .forEach(s -> { + try { + ps.println("@alias: " + s + " [jdk]"); + Files.lines(Path.of(args[0], s)) + .forEach(ps::println); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); } } - - // 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 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> 8); - passwdBytes[j++] = (byte)password[i]; - } - return passwdBytes; - } } --- old/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java 2019-08-14 00:18:05.000000000 +0800 +++ new/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java 2019-08-14 00:18:05.000000000 +0800 @@ -63,6 +63,7 @@ import javax.security.auth.DestroyFailedException; import javax.security.auth.x500.X500Principal; +import sun.security.provider.PemKeyStore; import sun.security.tools.KeyStoreUtil; import sun.security.util.Debug; import sun.security.util.DerInputStream; @@ -108,10 +109,12 @@ private static final int DEFAULT_PBE_ITERATION_COUNT = 50000; private static final int DEFAULT_MAC_ITERATION_COUNT = 100000; - // special PKCS12 keystore that supports PKCS12 and JKS file formats - public static final class DualFormatPKCS12 extends KeyStoreDelegator { - public DualFormatPKCS12() { - super("PKCS12", PKCS12KeyStore.class, "JKS", JKS.class); + // special PKCS12 keystore that supports JKS/PKCS12/PEM file formats + public static final class MultipleFormatPKCS12 extends KeyStoreDelegator { + public MultipleFormatPKCS12() { + super("PKCS12", PKCS12KeyStore.class, + List.of("JKS", "PEM"), + List.of(JKS.class, PemKeyStore.class)); } } @@ -2309,6 +2312,8 @@ // The MacData exists. return false; } + } catch (IOException e) { + return false; } return true; } --- old/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java 2019-08-14 00:18:07.000000000 +0800 +++ new/src/java.base/share/classes/sun/security/provider/JavaKeyStore.java 2019-08-14 00:18:06.000000000 +0800 @@ -68,10 +68,12 @@ } } - // special JKS that supports JKS and PKCS12 file formats - public static final class DualFormatJKS extends KeyStoreDelegator { - public DualFormatJKS() { - super("JKS", JKS.class, "PKCS12", PKCS12KeyStore.class); + // special JKS that supports JKS/PKCS12/PEM file formats + public static final class MultipleFormatJKS extends KeyStoreDelegator { + public MultipleFormatJKS() { + super("JKS", JKS.class, + List.of("PKCS12", "PEM"), + List.of(PKCS12KeyStore.class, PemKeyStore.class)); } } --- old/src/java.base/share/classes/sun/security/provider/SunEntries.java 2019-08-14 00:18:10.000000000 +0800 +++ new/src/java.base/share/classes/sun/security/provider/SunEntries.java 2019-08-14 00:18:09.000000000 +0800 @@ -253,16 +253,18 @@ * KeyStore */ add(p, "KeyStore", "PKCS12", - "sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "sun.security.pkcs12.PKCS12KeyStore$MultipleFormatPKCS12", null, null); add(p, "KeyStore", "JKS", - "sun.security.provider.JavaKeyStore$DualFormatJKS", + "sun.security.provider.JavaKeyStore$MultipleFormatJKS", null, attrs); add(p, "KeyStore", "CaseExactJKS", "sun.security.provider.JavaKeyStore$CaseExactJKS", null, attrs); add(p, "KeyStore", "DKS", "sun.security.provider.DomainKeyStore$DKS", null, attrs); + add(p, "KeyStore", "PEM", "sun.security.provider.PemKeyStore$MultipleFormatPEM", + null, attrs); /* --- old/src/java.base/share/classes/sun/security/tools/keytool/Main.java 2019-08-14 00:18:11.000000000 +0800 +++ new/src/java.base/share/classes/sun/security/tools/keytool/Main.java 2019-08-14 00:18:11.000000000 +0800 @@ -936,6 +936,8 @@ storetype = keyStore.getType(); if (storetype.equalsIgnoreCase("pkcs12")) { isPasswordlessKeyStore = PKCS12KeyStore.isPasswordless(ksfile); + } else if (storetype.equalsIgnoreCase("pem")) { + isPasswordlessKeyStore = true; } } else { if (storetype == null) { @@ -2195,6 +2197,8 @@ srcstoretype = store.getType(); if (srcstoretype.equalsIgnoreCase("pkcs12")) { srcIsPasswordless = PKCS12KeyStore.isPasswordless(srcksfile); + } else if (srcstoretype.equalsIgnoreCase("pem")) { + srcIsPasswordless = true; } } else { if (srcstoretype == null) { --- old/src/java.base/share/classes/sun/security/util/KeyStoreDelegator.java 2019-08-14 00:18:13.000000000 +0800 +++ new/src/java.base/share/classes/sun/security/util/KeyStoreDelegator.java 2019-08-14 00:18:13.000000000 +0800 @@ -46,10 +46,10 @@ private static final Debug debug = Debug.getInstance("keystore"); private String primaryType; // the primary keystore's type - private String secondaryType; // the secondary keystore's type + private List secondaryTypes; // the secondary keystore's type private Class primaryKeyStore; // the primary keystore's class - private Class secondaryKeyStore; + private List> secondaryKeyStores; // the secondary keystore's class private String type; // the delegate's type private KeyStoreSpi keystore; // the delegate @@ -58,8 +58,8 @@ public KeyStoreDelegator( String primaryType, Class primaryKeyStore, - String secondaryType, - Class secondaryKeyStore) { + List secondaryTypes, + List> secondaryKeyStores) { // Check whether compatibility mode has been disabled compatModeEnabled = "true".equalsIgnoreCase( @@ -68,18 +68,18 @@ if (compatModeEnabled) { this.primaryType = primaryType; - this.secondaryType = secondaryType; + this.secondaryTypes = secondaryTypes; this.primaryKeyStore = primaryKeyStore; - this.secondaryKeyStore = secondaryKeyStore; + this.secondaryKeyStores = secondaryKeyStores; } else { this.primaryType = primaryType; - this.secondaryType = null; + this.secondaryTypes = Collections.emptyList(); this.primaryKeyStore = primaryKeyStore; - this.secondaryKeyStore = null; + this.secondaryKeyStores = Collections.emptyList(); if (debug != null) { debug.println("WARNING: compatibility mode disabled for " + - primaryType + " and " + secondaryType + " keystore types"); + primaryType + " and " + secondaryTypes + " keystore types"); } } } @@ -229,50 +229,47 @@ throw (IOException)e; } - try { - // Ignore secondary keystore when no compatibility mode - if (!compatModeEnabled) { - throw e; - } - - @SuppressWarnings("deprecation") - KeyStoreSpi tmp= secondaryKeyStore.newInstance(); - keystore = tmp; - type = secondaryType; - bufferedStream.reset(); - keystore.engineLoad(bufferedStream, password); - - if (debug != null) { - debug.println("WARNING: switching from " + - primaryType + " to " + secondaryType + - " keystore file format has altered the " + - "keystore security level"); - } - - } catch (InstantiationException | - IllegalAccessException e2) { - // can safely ignore - - } catch (IOException | - NoSuchAlgorithmException | - CertificateException e3) { - - // incorrect password - if (e3 instanceof IOException && - e3.getCause() instanceof UnrecoverableKeyException) { - throw (IOException)e3; - } - // rethrow the outer exception - if (e instanceof IOException) { - throw (IOException)e; - } else if (e instanceof CertificateException) { - throw (CertificateException)e; - } else if (e instanceof NoSuchAlgorithmException) { - throw (NoSuchAlgorithmException)e; - } else if (e instanceof RuntimeException){ - throw (RuntimeException)e; + if (compatModeEnabled) { + for (int i = 0; i < secondaryTypes.size(); i++) { + try { + @SuppressWarnings("deprecation") + KeyStoreSpi tmp = secondaryKeyStores.get(i).newInstance(); + keystore = tmp; + type = secondaryTypes.get(i); + bufferedStream.reset(); + keystore.engineLoad(bufferedStream, password); + if (debug != null) { + debug.println("WARNING: switching from " + + primaryType + " to " + type + + " keystore file format has altered the " + + "keystore security level"); + } + e = null; + break; + } catch (InstantiationException | + IllegalAccessException e2) { + // can safely ignore + } catch (IOException e3) { + // incorrect password + if (e3.getCause() instanceof UnrecoverableKeyException) { + e = e3; + } + } catch (Exception e2) { + // continue; + } } } + if (e instanceof IOException) { + throw (IOException) e; + } else if (e instanceof CertificateException) { + throw (CertificateException) e; + } else if (e instanceof NoSuchAlgorithmException) { + throw (NoSuchAlgorithmException) e; + } else if (e instanceof RuntimeException) { + throw (RuntimeException)e; + } else if (e != null) { + throw new IOException(e); + } } if (debug != null) { --- old/src/java.base/share/conf/security/java.security 2019-08-14 00:18:15.000000000 +0800 +++ new/src/java.base/share/conf/security/java.security 2019-08-14 00:18:15.000000000 +0800 @@ -297,12 +297,11 @@ keystore.type=pkcs12 # -# Controls compatibility mode for JKS and PKCS12 keystore types. +# Controls compatibility mode for PEM, JKS and PKCS12 keystore types. # -# When set to 'true', both JKS and PKCS12 keystore types support loading -# keystore files in either JKS or PKCS12 format. When set to 'false' the -# JKS keystore type supports loading only JKS keystore files and the PKCS12 -# keystore type supports loading only PKCS12 keystore files. +# When set to 'true', all PEM, JKS and PKCS12 keystore types support loading +# keystore files in either PEM, JKS or PKCS12 format. When set to 'false' the +# each keystore type supports loading only keystore files of its own. # keystore.type.compat=true --- /dev/null 2019-08-14 00:18:16.000000000 +0800 +++ new/src/java.base/share/classes/sun/security/provider/PemKeyStore.java 2019-08-14 00:18:16.000000000 +0800 @@ -0,0 +1,264 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package sun.security.provider; + +import sun.security.pkcs12.PKCS12KeyStore; +import sun.security.util.KeyStoreDelegator; +import sun.security.x509.X509CertImpl; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreSpi; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * This class provides the keystore implementation referred to as "PEM". + * + * @see java.security.KeyStoreSpi + * @since 14 + */ + +public class PemKeyStore extends KeyStoreSpi { + + // special keystore that supports JKS/PKCS12/PEM file formats + public static final class MultipleFormatPEM extends KeyStoreDelegator { + public MultipleFormatPEM() { + super("PEM", PemKeyStore.class, + List.of("JKS", "PKCS12"), + List.of(JavaKeyStore.JKS.class, PKCS12KeyStore.class)); + } + } + + private Map certs = new LinkedHashMap<>(); + private Map> attrs = new LinkedHashMap<>(); + + @Override + public Key engineGetKey(String alias, char[] password) { + return null; + } + + @Override + public Certificate[] engineGetCertificateChain(String alias) { + return null; + } + + @Override + public Certificate engineGetCertificate(String alias) { + return certs.get(alias); + } + + @Override + public Date engineGetCreationDate(String alias) { + Certificate c = certs.get(alias); + if (c instanceof X509Certificate) { + return ((X509Certificate) c).getNotBefore(); + } else { + return null; + } + } + + @Override + public void engineSetKeyEntry( + String alias, Key key, char[] password, Certificate[] chain) { + throw new UnsupportedOperationException("cannot store key"); + } + + @Override + public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) { + throw new UnsupportedOperationException("cannot store key"); + } + + @Override + public void engineSetCertificateEntry(String alias, Certificate cert) { + certs.put(alias, cert); + } + + @Override + public void engineDeleteEntry(String alias) { + certs.remove(alias); + } + + @Override + public Enumeration engineAliases() { + return Collections.enumeration(certs.keySet()); + } + + @Override + public boolean engineContainsAlias(String alias) { + return certs.containsKey(alias); + } + + @Override + public int engineSize() { + return certs.size(); + } + + @Override + public boolean engineIsKeyEntry(String alias) { + return false; + } + + @Override + public boolean engineIsCertificateEntry(String alias) { + return engineContainsAlias(alias); + } + + @Override + public String engineGetCertificateAlias(Certificate cert) { + for (var entry : certs.entrySet()) { + if (entry.getValue().equals(cert)) { + return entry.getKey(); + } + } + return null; + } + + @Override + public void engineStore(OutputStream stream, char[] password) { + throw new UnsupportedOperationException("cannot store keystore"); + } + + @Override + public void engineLoad(InputStream stream, char[] password) + throws IOException, CertificateException { + + String alias = null; + StringBuilder block = new StringBuilder(); + Set attr = new HashSet<>(); + + boolean inPEM = false; + + try (BufferedReader r = new BufferedReader( + new InputStreamReader(stream, StandardCharsets.UTF_8))) { + while (true) { + String s = r.readLine(); + if (s == null) { + break; + } + s = s.trim(); + if (s.equals(X509Factory.BEGIN_CERT)) { + inPEM = true; + } else if (s.equals(X509Factory.END_CERT)) { + byte[] data = Base64.getMimeDecoder().decode(block.toString()); + X509CertImpl c = new X509CertImpl(data); + if (alias == null) { + alias = c.getFingerprint("SHA-256"); + } + certs.put(alias, c); + attrs.put(alias, attr); + alias = null; + block.setLength(0); + attr = new HashSet<>(); // must recreate + inPEM = false; + } else if (inPEM) { + block.append(s); + } else if (s.charAt(0) == '@') { + String[] kv = s.substring(1).split(":\\s*", 2); + if (kv[0].equals("alias") && kv.length == 2) { + alias = kv[1]; + } else { + attr.add(new PemAttribute( + kv[0], kv.length == 2 ? kv[1] : "true")); + } + } + } + } + } + + @Override + public KeyStore.Entry engineGetEntry( + String alias, KeyStore.ProtectionParameter protParam) { + Certificate cert = certs.get(alias); + if (cert == null) { + return null; + } else { + return new KeyStore.TrustedCertificateEntry(cert, attrs.get(alias)); + } + } + + @Override + public boolean engineProbe(InputStream stream) throws IOException { + Objects.requireNonNull(stream); + // Ensure the file starts with 5 readable text chars + return isReadable(stream.read()) + && isReadable(stream.read()) + && isReadable(stream.read()) + && isReadable(stream.read()) + && isReadable(stream.read()); + } + + private static boolean isReadable(int i) { + return i >= 9 && i <= 13 || i > 31 && i < 128; + } + + private static class PemAttribute implements KeyStore.Entry.Attribute { + + private final String name, value; + + public PemAttribute(String name, String value) { + this.name = Objects.requireNonNull(name); + this.value = Objects.requireNonNull(value); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public int hashCode() { + return Objects.hash(name, value); + } + + @Override + public boolean equals(Object obj) { + return Objects.equals(name, value); + } + } +}