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 }