verity: Add tools to help OEMs generate signed boot images.

Change-Id: Iea200def2fdd8a0d366888bb7b1ae401297063f1
diff --git a/verity/Android.mk b/verity/Android.mk
index ba98626..5d37f0c 100644
--- a/verity/Android.mk
+++ b/verity/Android.mk
@@ -10,7 +10,7 @@
 include $(BUILD_HOST_EXECUTABLE)
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := VeritySigner.java
+LOCAL_SRC_FILES := VeritySigner.java Utils.java
 LOCAL_MODULE := VeritySigner
 LOCAL_JAR_MANIFEST := VeritySigner.mf
 LOCAL_MODULE_TAGS := optional
@@ -18,6 +18,22 @@
 include $(BUILD_HOST_JAVA_LIBRARY)
 
 include $(CLEAR_VARS)
+LOCAL_SRC_FILES := BootSignature.java VeritySigner.java Utils.java
+LOCAL_MODULE := BootSignature
+LOCAL_JAR_MANIFEST := BootSignature.mf
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := BootSignature.java KeystoreSigner.java Utils.java
+LOCAL_MODULE := BootKeystoreSigner
+LOCAL_JAR_MANIFEST := KeystoreSigner.mf
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
 LOCAL_SRC_FILES := verity_signer
 LOCAL_MODULE := verity_signer
 LOCAL_MODULE_CLASS := EXECUTABLES
@@ -27,12 +43,21 @@
 include $(BUILD_PREBUILT)
 
 include $(CLEAR_VARS)
-LOCAL_MODULE := build_verity_tree.py
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_SRC_FILES := build_verity_tree.py
+LOCAL_SRC_FILES := boot_signer
+LOCAL_MODULE := boot_signer
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_IS_HOST_MODULE := true
 LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := BootSigner
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := keystore_signer
+LOCAL_MODULE := keystore_signer
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := KeystoreSigner
 include $(BUILD_PREBUILT)
 
 include $(CLEAR_VARS)
diff --git a/verity/BootSignature.java b/verity/BootSignature.java
new file mode 100644
index 0000000..f5ceb30
--- /dev/null
+++ b/verity/BootSignature.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.verity;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.util.Arrays;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERPrintableString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ *    AndroidVerifiedBootSignature DEFINITIONS ::=
+ *    BEGIN
+ *        FormatVersion ::= INTEGER
+ *        AlgorithmIdentifier ::=  SEQUENCE {
+ *            algorithm OBJECT IDENTIFIER,
+ *            parameters ANY DEFINED BY algorithm OPTIONAL
+ *        }
+ *        AuthenticatedAttributes ::= SEQUENCE {
+ *            target CHARACTER STRING,
+ *            length INTEGER
+ *        }
+ *        Signature ::= OCTET STRING
+ *     END
+ */
+
+public class BootSignature extends ASN1Object
+{
+    private ASN1Integer             formatVersion;
+    private AlgorithmIdentifier     algorithmIdentifier;
+    private DERPrintableString      target;
+    private ASN1Integer             length;
+    private DEROctetString          signature;
+
+    public BootSignature(String target, int length) {
+        this.formatVersion = new ASN1Integer(0);
+        this.target = new DERPrintableString(target);
+        this.length = new ASN1Integer(length);
+        this.algorithmIdentifier = new AlgorithmIdentifier(
+                PKCSObjectIdentifiers.sha256WithRSAEncryption);
+    }
+
+    public ASN1Object getAuthenticatedAttributes() {
+        ASN1EncodableVector attrs = new ASN1EncodableVector();
+        attrs.add(target);
+        attrs.add(length);
+        return new DERSequence(attrs);
+    }
+
+    public byte[] getEncodedAuthenticatedAttributes() throws IOException {
+        return getAuthenticatedAttributes().getEncoded();
+    }
+
+    public void setSignature(byte[] sig) {
+        signature = new DEROctetString(sig);
+    }
+
+    public byte[] generateSignableImage(byte[] image) throws IOException {
+        byte[] attrs = getEncodedAuthenticatedAttributes();
+        byte[] signable = Arrays.copyOf(image, image.length + attrs.length);
+        for (int i=0; i < attrs.length; i++) {
+            signable[i+image.length] = attrs[i];
+        }
+        return signable;
+    }
+
+    public byte[] sign(byte[] image, PrivateKey key) throws Exception {
+        byte[] signable = generateSignableImage(image);
+        byte[] signature = Utils.sign(key, signable);
+        byte[] signed = Arrays.copyOf(image, image.length + signature.length);
+        for (int i=0; i < signature.length; i++) {
+            signed[i+image.length] = signature[i];
+        }
+        return signed;
+    }
+
+    public ASN1Primitive toASN1Primitive() {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(formatVersion);
+        v.add(algorithmIdentifier);
+        v.add(getAuthenticatedAttributes());
+        v.add(signature);
+        return new DERSequence(v);
+    }
+
+    public static void doSignature( String target,
+                                    String imagePath,
+                                    String keyPath,
+                                    String outPath) throws Exception {
+        byte[] image = Utils.read(imagePath);
+        BootSignature bootsig = new BootSignature(target, image.length);
+        PrivateKey key = Utils.loadPEMPrivateKeyFromFile(keyPath);
+        byte[] signature = bootsig.sign(image, key);
+        Utils.write(signature, outPath);
+    }
+
+    // java -cp ../../../out/host/common/obj/JAVA_LIBRARIES/AndroidVerifiedBootSigner_intermediates/classes/ com.android.verity.AndroidVerifiedBootSigner boot ../../../out/target/product/flounder/boot.img ../../../build/target/product/security/verity_private_dev_key /tmp/boot.img.signed
+    public static void main(String[] args) throws Exception {
+        doSignature(args[0], args[1], args[2], args[3]);
+    }
+}
\ No newline at end of file
diff --git a/verity/BootSignature.mf b/verity/BootSignature.mf
new file mode 100644
index 0000000..c1868b6
--- /dev/null
+++ b/verity/BootSignature.mf
@@ -0,0 +1 @@
+Main-Class: com.android.verity.BootSignature
diff --git a/verity/KeystoreSigner.java b/verity/KeystoreSigner.java
new file mode 100644
index 0000000..d57f328
--- /dev/null
+++ b/verity/KeystoreSigner.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.verity;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERPrintableString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSAPublicKey;
+import org.bouncycastle.asn1.util.ASN1Dump;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * AndroidVerifiedBootKeystore DEFINITIONS ::=
+ * BEGIN
+ *     FormatVersion ::= INTEGER
+ *     KeyBag ::= SEQUENCE {
+ *         Key  ::= SEQUENCE {
+ *             AlgorithmIdentifier  ::=  SEQUENCE {
+ *                 algorithm OBJECT IDENTIFIER,
+ *                 parameters ANY DEFINED BY algorithm OPTIONAL
+ *             }
+ *             KeyMaterial ::= RSAPublicKey
+ *         }
+ *     }
+ *     Signature ::= AndroidVerifiedBootSignature
+ * END
+ */
+
+class BootKey extends ASN1Object
+{
+    private AlgorithmIdentifier algorithmIdentifier;
+    private RSAPublicKey keyMaterial;
+
+    public BootKey(PublicKey key) throws Exception {
+        java.security.interfaces.RSAPublicKey k =
+                (java.security.interfaces.RSAPublicKey) key;
+        this.keyMaterial = new RSAPublicKey(
+                k.getModulus(),
+                k.getPublicExponent());
+        this.algorithmIdentifier = new AlgorithmIdentifier(
+                PKCSObjectIdentifiers.sha256WithRSAEncryption);
+    }
+
+    public ASN1Primitive toASN1Primitive() {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(algorithmIdentifier);
+        v.add(keyMaterial);
+        return new DERSequence(v);
+    }
+
+    public void dump() throws Exception {
+        System.out.println(ASN1Dump.dumpAsString(toASN1Primitive()));
+    }
+}
+
+class BootKeystore extends ASN1Object
+{
+    private ASN1Integer                     formatVersion;
+    private ASN1EncodableVector             keyBag;
+    private BootSignature    signature;
+
+    public BootKeystore() {
+        this.formatVersion = new ASN1Integer(0);
+        this.keyBag = new ASN1EncodableVector();
+    }
+
+    public void addPublicKey(byte[] der) throws Exception {
+        PublicKey pubkey = Utils.loadDERPublicKey(der);
+        BootKey k = new BootKey(pubkey);
+        keyBag.add(k);
+    }
+
+    public byte[] getInnerKeystore() throws Exception {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(formatVersion);
+        v.add(new DERSequence(keyBag));
+        return new DERSequence(v).getEncoded();
+    }
+
+    public ASN1Primitive toASN1Primitive() {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+        v.add(formatVersion);
+        v.add(new DERSequence(keyBag));
+        v.add(signature);
+        return new DERSequence(v);
+    }
+
+    public void sign(PrivateKey privateKey) throws Exception {
+        byte[] innerKeystore = getInnerKeystore();
+        byte[] rawSignature = Utils.sign(privateKey, innerKeystore);
+        signature = new BootSignature("keystore", innerKeystore.length);
+        signature.setSignature(rawSignature);
+    }
+
+    public void dump() throws Exception {
+        System.out.println(ASN1Dump.dumpAsString(toASN1Primitive()));
+    }
+
+    // USAGE:
+    //      AndroidVerifiedBootKeystoreSigner <privkeyFile> <outfile> <pubkeyFile0> ... <pubkeyFileN-1>
+    // EG:
+    //     java -cp ../../../out/host/common/obj/JAVA_LIBRARIES/AndroidVerifiedBootKeystoreSigner_intermediates/classes/ com.android.verity.AndroidVerifiedBootKeystoreSigner ../../../build/target/product/security/verity_private_dev_key /tmp/keystore.out /tmp/k
+    public static void main(String[] args) throws Exception {
+        String privkeyFname = args[0];
+        String outfileFname = args[1];
+        BootKeystore ks = new BootKeystore();
+        for (int i=2; i < args.length; i++) {
+            ks.addPublicKey(Utils.read(args[i]));
+        }
+        ks.sign(Utils.loadPEMPrivateKeyFromFile(privkeyFname));
+        Utils.write(ks.getEncoded(), outfileFname);
+    }
+}
\ No newline at end of file
diff --git a/verity/KeystoreSigner.mf b/verity/KeystoreSigner.mf
new file mode 100644
index 0000000..a4fee27
--- /dev/null
+++ b/verity/KeystoreSigner.mf
@@ -0,0 +1 @@
+Main-Class: com.android.verity.KeystoreSigner
\ No newline at end of file
diff --git a/verity/Utils.java b/verity/Utils.java
new file mode 100644
index 0000000..2c1e7bb
--- /dev/null
+++ b/verity/Utils.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.verity;
+
+import java.lang.reflect.Constructor;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.KeyFactory;
+import java.security.Provider;
+import java.security.Security;
+import java.security.Signature;
+import java.security.spec.X509EncodedKeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+
+import org.bouncycastle.util.encoders.Base64;
+
+public class Utils {
+
+    private static void loadProviderIfNecessary(String providerClassName) {
+        if (providerClassName == null) {
+            return;
+        }
+
+        final Class<?> klass;
+        try {
+            final ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
+            if (sysLoader != null) {
+                klass = sysLoader.loadClass(providerClassName);
+            } else {
+                klass = Class.forName(providerClassName);
+            }
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+            System.exit(1);
+            return;
+        }
+
+        Constructor<?> constructor = null;
+        for (Constructor<?> c : klass.getConstructors()) {
+            if (c.getParameterTypes().length == 0) {
+                constructor = c;
+                break;
+            }
+        }
+        if (constructor == null) {
+            System.err.println("No zero-arg constructor found for " + providerClassName);
+            System.exit(1);
+            return;
+        }
+
+        final Object o;
+        try {
+            o = constructor.newInstance();
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.exit(1);
+            return;
+        }
+        if (!(o instanceof Provider)) {
+            System.err.println("Not a Provider class: " + providerClassName);
+            System.exit(1);
+        }
+
+        Security.insertProviderAt((Provider) o, 1);
+    }
+
+    static byte[] pemToDer(String pem) throws Exception {
+        pem = pem.replaceAll("^-.*", "");
+        String base64_der = pem.replaceAll("-.*$", "");
+        return Base64.decode(base64_der);
+    }
+
+    static PrivateKey loadDERPrivateKey(byte[] der) throws Exception {
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(der);
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        return (PrivateKey) keyFactory.generatePrivate(keySpec);
+    }
+
+    static PrivateKey loadPEMPrivateKey(byte[] pem) throws Exception {
+        byte[] der = pemToDer(new String(pem));
+        return loadDERPrivateKey(der);
+    }
+
+    static PrivateKey loadPEMPrivateKeyFromFile(String keyFname) throws Exception {
+        return loadPEMPrivateKey(read(keyFname));
+    }
+
+    static PrivateKey loadDERPrivateKeyFromFile(String keyFname) throws Exception {
+        return loadDERPrivateKey(read(keyFname));
+    }
+
+    static PublicKey loadDERPublicKey(byte[] der) throws Exception {
+        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(der);
+        KeyFactory factory = KeyFactory.getInstance("RSA");
+        return factory.generatePublic(publicKeySpec);
+    }
+
+    static PublicKey loadPEMPublicKey(byte[] pem) throws Exception {
+        byte[] der = pemToDer(new String(pem));
+        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(der);
+        KeyFactory factory = KeyFactory.getInstance("RSA");
+        return factory.generatePublic(publicKeySpec);
+    }
+
+    static PublicKey loadPEMPublicKeyFromFile(String keyFname) throws Exception {
+        return loadPEMPublicKey(read(keyFname));
+    }
+
+    static PublicKey loadDERPublicKeyFromFile(String keyFname) throws Exception {
+        return loadDERPublicKey(read(keyFname));
+    }
+
+    static byte[] sign(PrivateKey privateKey, byte[] input) throws Exception {
+        Signature signer = Signature.getInstance("SHA1withRSA");
+        signer.initSign(privateKey);
+        signer.update(input);
+        return signer.sign();
+    }
+
+    static byte[] read(String fname) throws Exception {
+        long offset = 0;
+        File f = new File(fname);
+        long length = f.length();
+        byte[] image = new byte[(int)length];
+        FileInputStream fis = new FileInputStream(f);
+        while (offset < length) {
+            offset += fis.read(image, (int)offset, (int)(length - offset));
+        }
+        fis.close();
+        return image;
+    }
+
+    static void write(byte[] data, String fname) throws Exception{
+        FileOutputStream out = new FileOutputStream(fname);
+        out.write(data);
+        out.close();
+    }
+}
\ No newline at end of file
diff --git a/verity/VeritySigner.java b/verity/VeritySigner.java
index 2ab94cb..44c5602 100644
--- a/verity/VeritySigner.java
+++ b/verity/VeritySigner.java
@@ -16,63 +16,18 @@
 
 package com.android.verity;
 
-import org.bouncycastle.util.encoders.Base64;
-
-import java.io.DataInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.security.KeyFactory;
 import java.security.PrivateKey;
-import java.security.Signature;
-import java.security.spec.PKCS8EncodedKeySpec;
 
-class VeritySigner {
-
-    private static byte[] sign(PrivateKey privateKey, byte[] input) throws Exception {
-        Signature signer = Signature.getInstance("SHA1withRSA");
-        signer.initSign(privateKey);
-        signer.update(input);
-        return signer.sign();
-    }
-
-    private static PKCS8EncodedKeySpec pemToDer(String pem) throws Exception {
-        pem = pem.replaceAll("^-.*", "");
-        String base64_der = pem.replaceAll("-.*$", "");
-        byte[] der = Base64.decode(base64_der);
-        return new PKCS8EncodedKeySpec(der);
-    }
-
-    private static PrivateKey loadPrivateKey(String pem) throws Exception {
-        PKCS8EncodedKeySpec keySpec = pemToDer(pem);
-        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-        return (PrivateKey) keyFactory.generatePrivate(keySpec);
-    }
-
-    private static byte[] read(String path) throws Exception {
-        File contentFile = new File(path);
-        byte[] content = new byte[(int)contentFile.length()];
-        FileInputStream fis = new FileInputStream(contentFile);
-        fis.read(content);
-        fis.close();
-        return content;
-    }
-
-    private static void writeOutput(String path, byte[] output) throws Exception {
-        FileOutputStream fos = new FileOutputStream(path);
-        fos.write(output);
-        fos.close();
-    }
+public class VeritySigner {
 
     // USAGE:
     //     VeritySigner <contentfile> <key.pem> <sigfile>
     // To verify that this has correct output:
     //     openssl rsautl -raw -inkey <key.pem> -encrypt -in <sigfile> > /tmp/dump
     public static void main(String[] args) throws Exception {
-        byte[] content = read(args[0]);
-        PrivateKey privateKey = loadPrivateKey(new String(read(args[1])));
-        byte[] signature = sign(privateKey, content);
-        writeOutput(args[2], signature);
+        byte[] content = Utils.read(args[0]);
+        PrivateKey privateKey = Utils.loadPEMPrivateKey(Utils.read(args[1]));
+        byte[] signature = Utils.sign(privateKey, content);
+        Utils.write(signature, args[2]);
     }
 }
diff --git a/verity/boot_signer b/verity/boot_signer
new file mode 100755
index 0000000..e2ee42b
--- /dev/null
+++ b/verity/boot_signer
@@ -0,0 +1,8 @@
+#! /bin/sh
+
+# Start-up script for BootSigner
+
+BOOTSIGNER_HOME=`dirname "$0"`
+BOOTSIGNER_HOME=`dirname "$BOOTSIGNER_HOME"`
+
+java -Xmx512M -jar "$BOOTSIGNER_HOME"/framework/BootSignature.jar "$@"
\ No newline at end of file
diff --git a/verity/keystore_signer b/verity/keystore_signer
new file mode 100644
index 0000000..7619c54
--- /dev/null
+++ b/verity/keystore_signer
@@ -0,0 +1,8 @@
+#! /bin/sh
+
+# Start-up script for KeystoreSigner
+
+KEYSTORESIGNER_HOME=`dirname "$0"`
+KEYSTORESIGNER_HOME=`dirname "$KEYSTORESIGNER_HOME"`
+
+java -Xmx512M -jar "$KEYSTORESIGNER_HOME"/framework/KeystoreSigner.jar "$@"
\ No newline at end of file