github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/make/tools/signtos/SignTos.java (about)

     1  /*
     2   * Copyright 2014 The Android Open Source Project
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *      http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package com.android.signtos;
    18  
    19  import org.bouncycastle.asn1.ASN1InputStream;
    20  import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
    21  import org.bouncycastle.jce.provider.BouncyCastleProvider;
    22  
    23  import java.io.BufferedInputStream;
    24  import java.io.BufferedOutputStream;
    25  import java.io.BufferedReader;
    26  import java.io.ByteArrayInputStream;
    27  import java.io.DataInputStream;
    28  import java.io.File;
    29  import java.io.FileInputStream;
    30  import java.io.FileOutputStream;
    31  import java.io.IOException;
    32  import java.io.InputStream;
    33  import java.io.InputStreamReader;
    34  import java.io.OutputStream;
    35  import java.lang.reflect.Constructor;
    36  import java.security.GeneralSecurityException;
    37  import java.security.Key;
    38  import java.security.KeyFactory;
    39  import java.security.MessageDigest;
    40  import java.security.PrivateKey;
    41  import java.security.Provider;
    42  import java.security.PublicKey;
    43  import java.security.Security;
    44  import java.security.Signature;
    45  import java.security.interfaces.ECKey;
    46  import java.security.interfaces.ECPublicKey;
    47  import java.security.spec.InvalidKeySpecException;
    48  import java.security.spec.PKCS8EncodedKeySpec;
    49  import java.util.Arrays;
    50  
    51  import javax.crypto.Cipher;
    52  import javax.crypto.EncryptedPrivateKeyInfo;
    53  import javax.crypto.SecretKeyFactory;
    54  import javax.crypto.spec.PBEKeySpec;
    55  
    56  /**
    57   * Signs Trusty images for use with operating systems that support it.
    58   */
    59  public class SignTos {
    60      /** Size of the signature footer in bytes. */
    61      private static final int SIGNATURE_BLOCK_SIZE = 256;
    62  
    63      /** Current signature version code we use. */
    64      private static final int VERSION_CODE = 1;
    65  
    66      /** Size of the header on the file to skip. */
    67      private static final int HEADER_SIZE = 512;
    68  
    69      private static BouncyCastleProvider sBouncyCastleProvider;
    70  
    71      /**
    72       * Reads the password from stdin and returns it as a string.
    73       *
    74       * @param keyFile The file containing the private key.  Used to prompt the user.
    75       */
    76      private static String readPassword(File keyFile) {
    77          // TODO: use Console.readPassword() when it's available.
    78          System.out.print("Enter password for " + keyFile + " (password will not be hidden): ");
    79          System.out.flush();
    80          BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
    81          try {
    82              return stdin.readLine();
    83          } catch (IOException ex) {
    84              return null;
    85          }
    86      }
    87  
    88      /**
    89       * Decrypt an encrypted PKCS#8 format private key.
    90       *
    91       * Based on ghstark's post on Aug 6, 2006 at
    92       * http://forums.sun.com/thread.jspa?threadID=758133&messageID=4330949
    93       *
    94       * @param encryptedPrivateKey The raw data of the private key
    95       * @param keyFile The file containing the private key
    96       */
    97      private static PKCS8EncodedKeySpec decryptPrivateKey(byte[] encryptedPrivateKey, File keyFile)
    98          throws GeneralSecurityException {
    99          EncryptedPrivateKeyInfo epkInfo;
   100          try {
   101              epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey);
   102          } catch (IOException ex) {
   103              // Probably not an encrypted key.
   104              return null;
   105          }
   106  
   107          char[] password = readPassword(keyFile).toCharArray();
   108  
   109          SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName());
   110          Key key = skFactory.generateSecret(new PBEKeySpec(password));
   111  
   112          Cipher cipher = Cipher.getInstance(epkInfo.getAlgName());
   113          cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters());
   114  
   115          try {
   116              return epkInfo.getKeySpec(cipher);
   117          } catch (InvalidKeySpecException ex) {
   118              System.err.println("signapk: Password for " + keyFile + " may be bad.");
   119              throw ex;
   120          }
   121      }
   122  
   123      /** Read a PKCS#8 format private key. */
   124      private static PrivateKey readPrivateKey(File file) throws IOException,
   125              GeneralSecurityException {
   126          DataInputStream input = new DataInputStream(new FileInputStream(file));
   127          try {
   128              byte[] bytes = new byte[(int) file.length()];
   129              input.read(bytes);
   130  
   131              /* Check to see if this is in an EncryptedPrivateKeyInfo structure. */
   132              PKCS8EncodedKeySpec spec = decryptPrivateKey(bytes, file);
   133              if (spec == null) {
   134                  spec = new PKCS8EncodedKeySpec(bytes);
   135              }
   136  
   137              /*
   138               * Now it's in a PKCS#8 PrivateKeyInfo structure. Read its Algorithm
   139               * OID and use that to construct a KeyFactory.
   140               */
   141              ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
   142              PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
   143              String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
   144  
   145              return KeyFactory.getInstance(algOid).generatePrivate(spec);
   146          } finally {
   147              input.close();
   148          }
   149      }
   150  
   151      /**
   152       * Tries to load a JSE Provider by class name. This is for custom PrivateKey
   153       * types that might be stored in PKCS#11-like storage.
   154       */
   155      private static void loadProviderIfNecessary(String providerClassName) {
   156          if (providerClassName == null) {
   157              return;
   158          }
   159  
   160          final Class<?> klass;
   161          try {
   162              final ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
   163              if (sysLoader != null) {
   164                  klass = sysLoader.loadClass(providerClassName);
   165              } else {
   166                  klass = Class.forName(providerClassName);
   167              }
   168          } catch (ClassNotFoundException e) {
   169              e.printStackTrace();
   170              System.exit(1);
   171              return;
   172          }
   173  
   174          Constructor<?> constructor = null;
   175          for (Constructor<?> c : klass.getConstructors()) {
   176              if (c.getParameterTypes().length == 0) {
   177                  constructor = c;
   178                  break;
   179              }
   180          }
   181          if (constructor == null) {
   182              System.err.println("No zero-arg constructor found for " + providerClassName);
   183              System.exit(1);
   184              return;
   185          }
   186  
   187          final Object o;
   188          try {
   189              o = constructor.newInstance();
   190          } catch (Exception e) {
   191              e.printStackTrace();
   192              System.exit(1);
   193              return;
   194          }
   195          if (!(o instanceof Provider)) {
   196              System.err.println("Not a Provider class: " + providerClassName);
   197              System.exit(1);
   198          }
   199  
   200          Security.insertProviderAt((Provider) o, 1);
   201      }
   202  
   203      private static String getSignatureAlgorithm(Key key) {
   204          if ("EC".equals(key.getAlgorithm())) {
   205              ECKey ecKey = (ECKey) key;
   206              int curveSize = ecKey.getParams().getOrder().bitLength();
   207              if (curveSize <= 256) {
   208                  return "SHA256withECDSA";
   209              } else if (curveSize <= 384) {
   210                  return "SHA384withECDSA";
   211              } else {
   212                  return "SHA512withECDSA";
   213              }
   214          } else {
   215              throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
   216          }
   217      }
   218  
   219      /**
   220       * @param inputFilename
   221       * @param outputFilename
   222       */
   223      private static void signWholeFile(InputStream input, OutputStream output, PrivateKey signingKey)
   224              throws Exception {
   225          Signature sig = Signature.getInstance(getSignatureAlgorithm(signingKey));
   226          sig.initSign(signingKey);
   227  
   228          byte[] buffer = new byte[8192];
   229  
   230          /* Skip the header. */
   231          int skippedBytes = 0;
   232          while (skippedBytes != HEADER_SIZE) {
   233              int bytesRead = input.read(buffer, 0, HEADER_SIZE - skippedBytes);
   234              output.write(buffer, 0, bytesRead);
   235              skippedBytes += bytesRead;
   236          }
   237  
   238          int totalBytes = 0;
   239          for (;;) {
   240              int bytesRead = input.read(buffer);
   241              if (bytesRead == -1) {
   242                  break;
   243              }
   244              totalBytes += bytesRead;
   245              sig.update(buffer, 0, bytesRead);
   246              output.write(buffer, 0, bytesRead);
   247          }
   248  
   249          byte[] sigBlock = new byte[SIGNATURE_BLOCK_SIZE];
   250          sigBlock[0] = VERSION_CODE;
   251          sig.sign(sigBlock, 1, sigBlock.length - 1);
   252  
   253          output.write(sigBlock);
   254      }
   255  
   256      private static void usage() {
   257          System.err.println("Usage: signtos " +
   258                             "[-providerClass <className>] " +
   259                             " privatekey.pk8 " +
   260                             "input.img output.img");
   261          System.exit(2);
   262      }
   263  
   264      public static void main(String[] args) throws Exception {
   265          if (args.length < 3) {
   266              usage();
   267          }
   268  
   269          String providerClass = null;
   270          String providerArg = null;
   271  
   272          int argstart = 0;
   273          while (argstart < args.length && args[argstart].startsWith("-")) {
   274              if ("-providerClass".equals(args[argstart])) {
   275                  if (argstart + 1 >= args.length) {
   276                      usage();
   277                  }
   278                  providerClass = args[++argstart];
   279                  ++argstart;
   280              } else {
   281                  usage();
   282              }
   283          }
   284  
   285          /*
   286           * Should only be "<privatekey> <input> <output>" left.
   287           */
   288          if (argstart != args.length - 3) {
   289              usage();
   290          }
   291  
   292          sBouncyCastleProvider = new BouncyCastleProvider();
   293          Security.addProvider(sBouncyCastleProvider);
   294  
   295          loadProviderIfNecessary(providerClass);
   296  
   297          String keyFilename = args[args.length - 3];
   298          String inputFilename = args[args.length - 2];
   299          String outputFilename = args[args.length - 1];
   300  
   301          PrivateKey privateKey = readPrivateKey(new File(keyFilename));
   302  
   303          InputStream input = new BufferedInputStream(new FileInputStream(inputFilename));
   304          OutputStream output = new BufferedOutputStream(new FileOutputStream(outputFilename));
   305          try {
   306              SignTos.signWholeFile(input, output, privateKey);
   307          } finally {
   308              input.close();
   309              output.close();
   310          }
   311  
   312          System.out.println("Successfully signed: " + outputFilename);
   313      }
   314  }