1 /* 2 * Copyright (c) 2019, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package build.tools.generatecacerts; 27 28 import java.io.DataOutputStream; 29 import java.io.FileOutputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.OutputStream; 33 import java.io.UnsupportedEncodingException; 34 import java.nio.file.Files; 35 import java.nio.file.Path; 36 import java.security.DigestOutputStream; 37 import java.security.MessageDigest; 38 import java.security.NoSuchAlgorithmException; 39 import java.security.cert.CertificateException; 40 import java.security.cert.CertificateFactory; 41 import java.security.cert.X509Certificate; 42 import java.util.Arrays; 43 import java.util.List; 44 import java.util.stream.Collectors; 45 46 /** 47 * Generate cacerts 48 * args[0]: Full path string to the directory that contains CA certs 49 * args[1]: Full path string to the generated cacerts 50 */ 51 public class GenerateCacerts { 52 public static void main(String[] args) throws Exception { 53 try (FileOutputStream fos = new FileOutputStream(args[1])) { 54 store(args[0], fos, "changeit".toCharArray()); 55 } 56 } 57 58 // The following code are copied from JavaKeyStore.java. 59 60 private static final int MAGIC = 0xfeedfeed; 61 private static final int VERSION_2 = 0x02; 62 63 // This method is a simplified version of JavaKeyStore::engineStore. 64 // A new "dir" argument is added. All cert names in "dir" is collected into 65 // a sorted array. Each cert is stored with a creation date set to its 66 // notBefore value. Thus the output is determined as long as the certs 67 // are the same. 68 public static void store(String dir, OutputStream stream, char[] password) 69 throws IOException, NoSuchAlgorithmException, CertificateException 70 { 71 byte[] encoded; // the certificate encoding 72 CertificateFactory cf = CertificateFactory.getInstance("X509"); 73 74 MessageDigest md = getPreKeyedHash(password); 75 DataOutputStream dos 76 = new DataOutputStream(new DigestOutputStream(stream, md)); 77 78 dos.writeInt(MAGIC); 79 // always write the latest version 80 dos.writeInt(VERSION_2); 81 82 // All file names in dir sorted. 83 // README is excluded. Name starting with "." excluded. 84 List<String> entries = Files.list(Path.of(dir)) 85 .map(p -> p.getFileName().toString()) 86 .filter(s -> !s.equals("README") && !s.startsWith(".")) 87 .collect(Collectors.toList()); 88 89 entries.sort(String::compareTo); 90 91 dos.writeInt(entries.size()); 92 93 for (String entry : entries) { 94 95 String alias = entry + " [jdk]"; 96 X509Certificate cert; 97 try (InputStream fis = Files.newInputStream(Path.of(dir, entry))) { 98 cert = (X509Certificate) cf.generateCertificate(fis); 99 } 100 101 dos.writeInt(2); 102 103 // Write the alias 104 dos.writeUTF(alias); 105 106 // Write the (entry creation) date, which is notBefore of the cert 107 dos.writeLong(cert.getNotBefore().getTime()); 108 109 // Write the trusted certificate 110 encoded = cert.getEncoded(); 111 dos.writeUTF(cert.getType()); 112 dos.writeInt(encoded.length); 113 dos.write(encoded); 114 } 115 116 /* 117 * Write the keyed hash which is used to detect tampering with 118 * the keystore (such as deleting or modifying key or 119 * certificate entries). 120 */ 121 byte[] digest = md.digest(); 122 123 dos.write(digest); 124 dos.flush(); 125 } 126 127 private static MessageDigest getPreKeyedHash(char[] password) 128 throws NoSuchAlgorithmException, UnsupportedEncodingException 129 { 130 131 MessageDigest md = MessageDigest.getInstance("SHA"); 132 byte[] passwdBytes = convertToBytes(password); 133 md.update(passwdBytes); 134 Arrays.fill(passwdBytes, (byte) 0x00); 135 md.update("Mighty Aphrodite".getBytes("UTF8")); 136 return md; 137 } 138 139 private static byte[] convertToBytes(char[] password) { 140 int i, j; 141 byte[] passwdBytes = new byte[password.length * 2]; 142 for (i=0, j=0; i<password.length; i++) { 143 passwdBytes[j++] = (byte)(password[i] >> 8); 144 passwdBytes[j++] = (byte)password[i]; 145 } 146 return passwdBytes; 147 } 148 }