Home | History | Annotate | Download | only in signtos
      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 }
    315