|
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 } |