Merge tag android-5.1.0_r1 into AOSP_5.1_MERGE

Change-Id: I3c99bf4440205917238a64d91d5ae07e558b9eac
diff --git a/ext4_utils/mkuserimg.sh b/ext4_utils/mkuserimg.sh
index be69738..78936e0 100755
--- a/ext4_utils/mkuserimg.sh
+++ b/ext4_utils/mkuserimg.sh
@@ -5,7 +5,7 @@
 function usage() {
 cat<<EOT
 Usage:
-mkuserimg.sh [-s] SRC_DIR OUTPUT_FILE EXT_VARIANT MOUNT_POINT SIZE
+mkuserimg.sh [-s] SRC_DIR OUTPUT_FILE EXT_VARIANT MOUNT_POINT SIZE [-j <journal_size>]
              [-T TIMESTAMP] [-C FS_CONFIG] [-B BLOCK_LIST_FILE] [FILE_CONTEXTS]
 EOT
 }
@@ -33,6 +33,16 @@
 SIZE=$5
 shift; shift; shift; shift; shift
 
+JOURNAL_FLAGS=
+if [ "$1" = "-j" ]; then
+  if [ "$2" = "0" ]; then
+    JOURNAL_FLAGS="-J"
+  else
+    JOURNAL_FLAGS="-j $2"
+  fi
+  shift; shift
+fi
+
 TIMESTAMP=-1
 if [[ "$1" == "-T" ]]; then
   TIMESTAMP=$2
@@ -79,7 +89,7 @@
   OPT="$OPT -B $BLOCK_LIST"
 fi
 
-MAKE_EXT4FS_CMD="make_ext4fs $ENABLE_SPARSE_IMAGE -T $TIMESTAMP $OPT -l $SIZE -a $MOUNT_POINT $OUTPUT_FILE $SRC_DIR"
+MAKE_EXT4FS_CMD="make_ext4fs $ENABLE_SPARSE_IMAGE -T $TIMESTAMP $OPT -l $SIZE $JOURNAL_FLAGS -a $MOUNT_POINT $OUTPUT_FILE $SRC_DIR"
 echo $MAKE_EXT4FS_CMD
 vmstat 1 &
 statpid=$!
diff --git a/verity/Android.mk b/verity/Android.mk
index ab1659b..f166f9e 100644
--- a/verity/Android.mk
+++ b/verity/Android.mk
@@ -1,6 +1,15 @@
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
+LOCAL_MODULE := verify_boot_signature
+LOCAL_SRC_FILES := verify_boot_signature.c
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_SHARED_LIBRARIES := libcrypto-host
+LOCAL_C_INCLUDES += external/openssl/include system/extras/ext4_utils system/core/mkbootimg
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(CLEAR_VARS)
 LOCAL_MODULE := generate_verity_key
 LOCAL_SRC_FILES := generate_verity_key.c
 LOCAL_MODULE_CLASS := EXECUTABLES
@@ -10,6 +19,14 @@
 include $(BUILD_HOST_EXECUTABLE)
 
 include $(CLEAR_VARS)
+LOCAL_SRC_FILES := VerityVerifier.java Utils.java
+LOCAL_MODULE := VerityVerifier
+LOCAL_JAR_MANIFEST := VerityVerifier.mf
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
 LOCAL_SRC_FILES := VeritySigner.java Utils.java
 LOCAL_MODULE := VeritySigner
 LOCAL_JAR_MANIFEST := VeritySigner.mf
@@ -34,6 +51,15 @@
 include $(BUILD_HOST_JAVA_LIBRARY)
 
 include $(CLEAR_VARS)
+LOCAL_SRC_FILES := verity_verifier
+LOCAL_MODULE := verity_verifier
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := VerityVerifier
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
 LOCAL_SRC_FILES := verity_signer
 LOCAL_MODULE := verity_signer
 LOCAL_MODULE_CLASS := EXECUTABLES
diff --git a/verity/BootSignature.java b/verity/BootSignature.java
index f5ceb30..03eb32a 100644
--- a/verity/BootSignature.java
+++ b/verity/BootSignature.java
@@ -16,51 +16,105 @@
 
 package com.android.verity;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateEncodingException;
 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.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1InputStream;
 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;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 /**
  *    AndroidVerifiedBootSignature DEFINITIONS ::=
  *    BEGIN
- *        FormatVersion ::= INTEGER
- *        AlgorithmIdentifier ::=  SEQUENCE {
+ *        formatVersion ::= INTEGER
+ *        certificate ::= Certificate
+ *        algorithmIdentifier ::= SEQUENCE {
  *            algorithm OBJECT IDENTIFIER,
  *            parameters ANY DEFINED BY algorithm OPTIONAL
  *        }
- *        AuthenticatedAttributes ::= SEQUENCE {
+ *        authenticatedAttributes ::= SEQUENCE {
  *            target CHARACTER STRING,
  *            length INTEGER
  *        }
- *        Signature ::= OCTET STRING
+ *        signature ::= OCTET STRING
  *     END
  */
 
 public class BootSignature extends ASN1Object
 {
     private ASN1Integer             formatVersion;
+    private ASN1Encodable           certificate;
     private AlgorithmIdentifier     algorithmIdentifier;
     private DERPrintableString      target;
     private ASN1Integer             length;
     private DEROctetString          signature;
+    private PublicKey               publicKey;
 
+    private static final int FORMAT_VERSION = 1;
+
+    /**
+     * Initializes the object for signing an image file
+     * @param target Target name, included in the signed data
+     * @param length Length of the image, included in the signed data
+     */
     public BootSignature(String target, int length) {
-        this.formatVersion = new ASN1Integer(0);
+        this.formatVersion = new ASN1Integer(FORMAT_VERSION);
         this.target = new DERPrintableString(target);
         this.length = new ASN1Integer(length);
-        this.algorithmIdentifier = new AlgorithmIdentifier(
-                PKCSObjectIdentifiers.sha256WithRSAEncryption);
+    }
+
+    /**
+     * Initializes the object for verifying a signed image file
+     * @param signature Signature footer
+     */
+    public BootSignature(byte[] signature)
+            throws Exception {
+        ASN1InputStream stream = new ASN1InputStream(signature);
+        ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
+
+        formatVersion = (ASN1Integer) sequence.getObjectAt(0);
+        if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
+            throw new IllegalArgumentException("Unsupported format version");
+        }
+
+        certificate = sequence.getObjectAt(1);
+        byte[] encoded = ((ASN1Object) certificate).getEncoded();
+        ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
+
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
+        publicKey = c.getPublicKey();
+
+        ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
+        algorithmIdentifier = new AlgorithmIdentifier(
+            (ASN1ObjectIdentifier) algId.getObjectAt(0));
+
+        ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
+        target = (DERPrintableString) attrs.getObjectAt(0);
+        length = (ASN1Integer) attrs.getObjectAt(1);
+
+        this.signature = (DEROctetString) sequence.getObjectAt(4);
     }
 
     public ASN1Object getAuthenticatedAttributes() {
@@ -74,10 +128,29 @@
         return getAuthenticatedAttributes().getEncoded();
     }
 
-    public void setSignature(byte[] sig) {
+    public AlgorithmIdentifier getAlgorithmIdentifier() {
+        return algorithmIdentifier;
+    }
+
+    public PublicKey getPublicKey() {
+        return publicKey;
+    }
+
+    public byte[] getSignature() {
+        return signature.getOctets();
+    }
+
+    public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
+        algorithmIdentifier = algId;
         signature = new DEROctetString(sig);
     }
 
+    public void setCertificate(X509Certificate cert)
+            throws Exception, IOException, CertificateEncodingException {
+        ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
+        certificate = s.readObject();
+    }
+
     public byte[] generateSignableImage(byte[] image) throws IOException {
         byte[] attrs = getEncodedAuthenticatedAttributes();
         byte[] signable = Arrays.copyOf(image, image.length + attrs.length);
@@ -89,36 +162,145 @@
 
     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 Utils.sign(key, signable);
+    }
+
+    public boolean verify(byte[] image) throws Exception {
+        if (length.getValue().intValue() != image.length) {
+            throw new IllegalArgumentException("Invalid image length");
         }
-        return signed;
+
+        byte[] signable = generateSignableImage(image);
+        return Utils.verify(publicKey, signable, signature.getOctets(),
+                    algorithmIdentifier);
     }
 
     public ASN1Primitive toASN1Primitive() {
         ASN1EncodableVector v = new ASN1EncodableVector();
         v.add(formatVersion);
+        v.add(certificate);
         v.add(algorithmIdentifier);
         v.add(getAuthenticatedAttributes());
         v.add(signature);
         return new DERSequence(v);
     }
 
+    public static int getSignableImageSize(byte[] data) throws Exception {
+        if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
+                "ANDROID!".getBytes("US-ASCII"))) {
+            throw new IllegalArgumentException("Invalid image header: missing magic");
+        }
+
+        ByteBuffer image = ByteBuffer.wrap(data);
+        image.order(ByteOrder.LITTLE_ENDIAN);
+
+        image.getLong(); // magic
+        int kernelSize = image.getInt();
+        image.getInt(); // kernel_addr
+        int ramdskSize = image.getInt();
+        image.getInt(); // ramdisk_addr
+        int secondSize = image.getInt();
+        image.getLong(); // second_addr + tags_addr
+        int pageSize = image.getInt();
+
+        int length = pageSize // include the page aligned image header
+                + ((kernelSize + pageSize - 1) / pageSize) * pageSize
+                + ((ramdskSize + pageSize - 1) / pageSize) * pageSize
+                + ((secondSize + pageSize - 1) / pageSize) * pageSize;
+
+        length = ((length + pageSize - 1) / pageSize) * pageSize;
+
+        if (length <= 0) {
+            throw new IllegalArgumentException("Invalid image header: invalid length");
+        }
+
+        return length;
+    }
+
     public static void doSignature( String target,
                                     String imagePath,
                                     String keyPath,
+                                    String certPath,
                                     String outPath) throws Exception {
+
         byte[] image = Utils.read(imagePath);
+        int signableSize = getSignableImageSize(image);
+
+        if (signableSize < image.length) {
+            System.err.println("NOTE: truncating file " + imagePath +
+                    " from " + image.length + " to " + signableSize + " bytes");
+            image = Arrays.copyOf(image, signableSize);
+        } else if (signableSize > image.length) {
+            throw new IllegalArgumentException("Invalid image: too short, expected " +
+                    signableSize + " bytes");
+        }
+
         BootSignature bootsig = new BootSignature(target, image.length);
-        PrivateKey key = Utils.loadPEMPrivateKeyFromFile(keyPath);
-        byte[] signature = bootsig.sign(image, key);
-        Utils.write(signature, outPath);
+
+        X509Certificate cert = Utils.loadPEMCertificate(certPath);
+        bootsig.setCertificate(cert);
+
+        PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath);
+        bootsig.setSignature(bootsig.sign(image, key),
+            Utils.getSignatureAlgorithmIdentifier(key));
+
+        byte[] encoded_bootsig = bootsig.getEncoded();
+        byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length);
+
+        System.arraycopy(encoded_bootsig, 0, image_with_metadata,
+                image.length, encoded_bootsig.length);
+
+        Utils.write(image_with_metadata, 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]);
+    public static void verifySignature(String imagePath) throws Exception {
+        byte[] image = Utils.read(imagePath);
+        int signableSize = getSignableImageSize(image);
+
+        if (signableSize >= image.length) {
+            throw new IllegalArgumentException("Invalid image: not signed");
+        }
+
+        byte[] signature = Arrays.copyOfRange(image, signableSize, image.length);
+        BootSignature bootsig = new BootSignature(signature);
+
+        try {
+            if (bootsig.verify(Arrays.copyOf(image, signableSize))) {
+                System.err.println("Signature is VALID");
+                System.exit(0);
+            } else {
+                System.err.println("Signature is INVALID");
+            }
+        } catch (Exception e) {
+            e.printStackTrace(System.err);
+        }
+        System.exit(1);
     }
-}
\ No newline at end of file
+
+    /* Example usage for signing a boot image using dev keys:
+        java -cp \
+            ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/ \
+                classes/com.android.verity.BootSignature \
+            /boot \
+            ../../../out/target/product/$PRODUCT/boot.img \
+            ../../../build/target/product/security/verity.pk8 \
+            ../../../build/target/product/security/verity.x509.pem \
+            /tmp/boot.img.signed
+    */
+    public static void main(String[] args) throws Exception {
+        Security.addProvider(new BouncyCastleProvider());
+
+        if ("-verify".equals(args[0])) {
+            /* args[1] is the path to a signed boot image */
+            verifySignature(args[1]);
+        } else {
+            /* args[0] is the target name, typically /boot
+               args[1] is the path to a boot image to sign
+               args[2] is the path to a private key
+               args[3] is the path to the matching public key certificate
+               args[4] is the path where to output the signed boot image
+            */
+            doSignature(args[0], args[1], args[2], args[3], args[4]);
+        }
+    }
+}
diff --git a/verity/KeystoreSigner.java b/verity/KeystoreSigner.java
index d57f328..0927d54 100644
--- a/verity/KeystoreSigner.java
+++ b/verity/KeystoreSigner.java
@@ -19,12 +19,17 @@
 import java.io.IOException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.security.Security;
 import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERSequence;
@@ -32,6 +37,7 @@
 import org.bouncycastle.asn1.pkcs.RSAPublicKey;
 import org.bouncycastle.asn1.util.ASN1Dump;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 /**
  * AndroidVerifiedBootKeystore DEFINITIONS ::=
@@ -61,8 +67,7 @@
         this.keyMaterial = new RSAPublicKey(
                 k.getModulus(),
                 k.getPublicExponent());
-        this.algorithmIdentifier = new AlgorithmIdentifier(
-                PKCSObjectIdentifiers.sha256WithRSAEncryption);
+        this.algorithmIdentifier = Utils.getSignatureAlgorithmIdentifier(key);
     }
 
     public ASN1Primitive toASN1Primitive() {
@@ -79,12 +84,15 @@
 
 class BootKeystore extends ASN1Object
 {
-    private ASN1Integer                     formatVersion;
-    private ASN1EncodableVector             keyBag;
-    private BootSignature    signature;
+    private ASN1Integer             formatVersion;
+    private ASN1EncodableVector     keyBag;
+    private BootSignature           signature;
+    private X509Certificate         certificate;
+
+    private static final int FORMAT_VERSION = 0;
 
     public BootKeystore() {
-        this.formatVersion = new ASN1Integer(0);
+        this.formatVersion = new ASN1Integer(FORMAT_VERSION);
         this.keyBag = new ASN1EncodableVector();
     }
 
@@ -94,6 +102,10 @@
         keyBag.add(k);
     }
 
+    public void setCertificate(X509Certificate cert) {
+        certificate = cert;
+    }
+
     public byte[] getInnerKeystore() throws Exception {
         ASN1EncodableVector v = new ASN1EncodableVector();
         v.add(formatVersion);
@@ -109,29 +121,87 @@
         return new DERSequence(v);
     }
 
+    public void parse(byte[] input) throws Exception {
+        ASN1InputStream stream = new ASN1InputStream(input);
+        ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
+
+        formatVersion = (ASN1Integer) sequence.getObjectAt(0);
+        if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
+            throw new IllegalArgumentException("Unsupported format version");
+        }
+
+        ASN1Sequence keys = (ASN1Sequence) sequence.getObjectAt(1);
+        Enumeration e = keys.getObjects();
+        while (e.hasMoreElements()) {
+            keyBag.add((ASN1Encodable) e.nextElement());
+        }
+
+        ASN1Object sig = sequence.getObjectAt(2).toASN1Primitive();
+        signature = new BootSignature(sig.getEncoded());
+    }
+
+    public boolean verify() throws Exception {
+        byte[] innerKeystore = getInnerKeystore();
+        return Utils.verify(signature.getPublicKey(), innerKeystore,
+                signature.getSignature(), signature.getAlgorithmIdentifier());
+    }
+
     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);
+        signature.setCertificate(certificate);
+        signature.setSignature(rawSignature,
+                Utils.getSignatureAlgorithmIdentifier(privateKey));
     }
 
     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);
+    private static void usage() {
+        System.err.println("usage: KeystoreSigner <privatekey.pk8> " +
+                "<certificate.x509.pem> <outfile> <publickey0.der> " +
+                "... <publickeyN-1.der> | -verify <keystore>");
+        System.exit(1);
     }
-}
\ No newline at end of file
+
+    public static void main(String[] args) throws Exception {
+        if (args.length < 2) {
+            usage();
+            return;
+        }
+
+        Security.addProvider(new BouncyCastleProvider());
+        BootKeystore ks = new BootKeystore();
+
+        if ("-verify".equals(args[0])) {
+            ks.parse(Utils.read(args[1]));
+
+            try {
+                if (ks.verify()) {
+                    System.err.println("Signature is VALID");
+                    System.exit(0);
+                } else {
+                    System.err.println("Signature is INVALID");
+                }
+            } catch (Exception e) {
+                e.printStackTrace(System.err);
+            }
+            System.exit(1);
+        } else {
+            String privkeyFname = args[0];
+            String certFname = args[1];
+            String outfileFname = args[2];
+
+            ks.setCertificate(Utils.loadPEMCertificate(certFname));
+
+            for (int i = 3; i < args.length; i++) {
+                ks.addPublicKey(Utils.read(args[i]));
+            }
+
+            ks.sign(Utils.loadDERPrivateKeyFromFile(privkeyFname));
+            Utils.write(ks.getEncoded(), outfileFname);
+        }
+    }
+}
diff --git a/verity/KeystoreSigner.mf b/verity/KeystoreSigner.mf
index a4fee27..472b7c4 100644
--- a/verity/KeystoreSigner.mf
+++ b/verity/KeystoreSigner.mf
@@ -1 +1 @@
-Main-Class: com.android.verity.KeystoreSigner
\ No newline at end of file
+Main-Class: com.android.verity.BootKeystore
diff --git a/verity/Utils.java b/verity/Utils.java
index 2c1e7bb..3576e3b 100644
--- a/verity/Utils.java
+++ b/verity/Utils.java
@@ -18,22 +18,60 @@
 
 import java.lang.reflect.Constructor;
 import java.io.File;
+import java.io.ByteArrayInputStream;
+import java.io.Console;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.InputStreamReader;
 import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
 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.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
 import java.security.spec.X509EncodedKeySpec;
 import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
+import javax.crypto.Cipher;
+import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.util.encoders.Base64;
 
 public class Utils {
 
+    private static final Map<String, String> ID_TO_ALG;
+    private static final Map<String, String> ALG_TO_ID;
+
+    static {
+        ID_TO_ALG = new HashMap<String, String>();
+        ALG_TO_ID = new HashMap<String, String>();
+
+        ID_TO_ALG.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1withRSA");
+        ID_TO_ALG.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256withRSA");
+        ID_TO_ALG.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512withRSA");
+
+        ALG_TO_ID.put("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId());
+        ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId());
+        ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
+    }
+
     private static void loadProviderIfNecessary(String providerClassName) {
         if (providerClassName == null) {
             return;
@@ -88,10 +126,45 @@
         return Base64.decode(base64_der);
     }
 
+    private static PKCS8EncodedKeySpec decryptPrivateKey(byte[] encryptedPrivateKey)
+        throws GeneralSecurityException {
+        EncryptedPrivateKeyInfo epkInfo;
+        try {
+            epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey);
+        } catch (IOException ex) {
+            // Probably not an encrypted key.
+            return null;
+        }
+
+        char[] password = System.console().readPassword("Password for the private key file: ");
+
+        SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName());
+        Key key = skFactory.generateSecret(new PBEKeySpec(password));
+        Arrays.fill(password, '\0');
+
+        Cipher cipher = Cipher.getInstance(epkInfo.getAlgName());
+        cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters());
+
+        try {
+            return epkInfo.getKeySpec(cipher);
+        } catch (InvalidKeySpecException ex) {
+            System.err.println("Password may be bad.");
+            throw ex;
+        }
+    }
+
     static PrivateKey loadDERPrivateKey(byte[] der) throws Exception {
-        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(der);
-        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-        return (PrivateKey) keyFactory.generatePrivate(keySpec);
+        PKCS8EncodedKeySpec spec = decryptPrivateKey(der);
+
+        if (spec == null) {
+            spec = new PKCS8EncodedKeySpec(der);
+        }
+
+        ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
+        PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
+        String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
+
+        return KeyFactory.getInstance(algOid).generatePrivate(spec);
     }
 
     static PrivateKey loadPEMPrivateKey(byte[] pem) throws Exception {
@@ -128,8 +201,48 @@
         return loadDERPublicKey(read(keyFname));
     }
 
+    static X509Certificate loadPEMCertificate(String fname) throws Exception {
+        try (FileInputStream fis = new FileInputStream(fname)) {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            return (X509Certificate) cf.generateCertificate(fis);
+        }
+    }
+
+    private static String getSignatureAlgorithm(Key key) {
+        if ("RSA".equals(key.getAlgorithm())) {
+            return "SHA256withRSA";
+        } else {
+            throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
+        }
+    }
+
+    static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) {
+        String id = ALG_TO_ID.get(getSignatureAlgorithm(key));
+
+        if (id == null) {
+            throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
+        }
+
+        return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id));
+    }
+
+    static boolean verify(PublicKey key, byte[] input, byte[] signature,
+            AlgorithmIdentifier algId) throws Exception {
+        String algName = ID_TO_ALG.get(algId.getObjectId().getId());
+
+        if (algName == null) {
+            throw new IllegalArgumentException("Unsupported algorithm " + algId.getObjectId());
+        }
+
+        Signature verifier = Signature.getInstance(algName);
+        verifier.initVerify(key);
+        verifier.update(input);
+
+        return verifier.verify(signature);
+    }
+
     static byte[] sign(PrivateKey privateKey, byte[] input) throws Exception {
-        Signature signer = Signature.getInstance("SHA1withRSA");
+        Signature signer = Signature.getInstance(getSignatureAlgorithm(privateKey));
         signer.initSign(privateKey);
         signer.update(input);
         return signer.sign();
@@ -153,4 +266,4 @@
         out.write(data);
         out.close();
     }
-}
\ No newline at end of file
+}
diff --git a/verity/VeritySigner.java b/verity/VeritySigner.java
index 44c5602..9d85747 100644
--- a/verity/VeritySigner.java
+++ b/verity/VeritySigner.java
@@ -16,18 +16,54 @@
 
 package com.android.verity;
 
+import java.security.PublicKey;
 import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 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
+    private static void usage() {
+        System.err.println("usage: VeritySigner <contentfile> <key.pk8> " +
+                "<sigfile> | <contentfile> <certificate.x509.pem> <sigfile> " +
+                "-verify");
+        System.exit(1);
+    }
+
     public static void main(String[] args) throws Exception {
+        if (args.length < 3) {
+            usage();
+            return;
+        }
+
+        Security.addProvider(new BouncyCastleProvider());
+
         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]);
+
+        if (args.length > 3 && "-verify".equals(args[3])) {
+            X509Certificate cert = Utils.loadPEMCertificate(args[1]);
+            PublicKey publicKey = cert.getPublicKey();
+
+            byte[] signature = Utils.read(args[2]);
+
+            try {
+                if (Utils.verify(publicKey, content, signature,
+                            Utils.getSignatureAlgorithmIdentifier(publicKey))) {
+                    System.err.println("Signature is VALID");
+                    System.exit(0);
+                } else {
+                    System.err.println("Signature is INVALID");
+                }
+            } catch (Exception e) {
+                e.printStackTrace(System.err);
+            }
+
+            System.exit(1);
+        } else {
+            PrivateKey privateKey = Utils.loadDERPrivateKey(Utils.read(args[1]));
+            byte[] signature = Utils.sign(privateKey, content);
+            Utils.write(signature, args[2]);
+        }
     }
 }
diff --git a/verity/VerityVerifier.java b/verity/VerityVerifier.java
new file mode 100644
index 0000000..5c9d7d2
--- /dev/null
+++ b/verity/VerityVerifier.java
@@ -0,0 +1,166 @@
+/*
+ * 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.File;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.lang.Process;
+import java.lang.Runtime;
+import java.security.PublicKey;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class VerityVerifier {
+
+    private static final int EXT4_SB_MAGIC = 0xEF53;
+    private static final int EXT4_SB_OFFSET = 0x400;
+    private static final int EXT4_SB_OFFSET_MAGIC = EXT4_SB_OFFSET + 0x38;
+    private static final int EXT4_SB_OFFSET_LOG_BLOCK_SIZE = EXT4_SB_OFFSET + 0x18;
+    private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_LO = EXT4_SB_OFFSET + 0x4;
+    private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_HI = EXT4_SB_OFFSET + 0x150;
+    private static final int VERITY_MAGIC = 0xB001B001;
+    private static final int VERITY_SIGNATURE_SIZE = 256;
+    private static final int VERITY_VERSION = 0;
+
+    /**
+     * Converts a 4-byte little endian value to a Java integer
+     * @param value Little endian integer to convert
+     */
+     public static int fromle(int value) {
+        byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
+        return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
+    }
+
+     /**
+     * Converts a 2-byte little endian value to Java a integer
+     * @param value Little endian short to convert
+     */
+     public static int fromle(short value) {
+        return fromle(value << 16);
+    }
+
+    /**
+     * Unsparses a sparse image into a temporary file and returns a
+     * handle to the file
+     * @param fname Path to a sparse image file
+     */
+     public static RandomAccessFile openImage(String fname) throws Exception {
+        File tmp = File.createTempFile("system", ".raw");
+        tmp.deleteOnExit();
+
+        Process p = Runtime.getRuntime().exec("simg2img " + fname +
+                            " " + tmp.getAbsoluteFile());
+
+        p.waitFor();
+        if (p.exitValue() != 0) {
+            throw new IllegalArgumentException("Invalid image: failed to unsparse");
+        }
+
+        return new RandomAccessFile(tmp, "r");
+    }
+
+    /**
+     * Reads the ext4 superblock and calculates the size of the system image,
+     * after which we should find the verity metadata
+     * @param img File handle to the image file
+     */
+    public static long getMetadataPosition(RandomAccessFile img)
+            throws Exception {
+        img.seek(EXT4_SB_OFFSET_MAGIC);
+        int magic = fromle(img.readShort());
+
+        if (magic != EXT4_SB_MAGIC) {
+            throw new IllegalArgumentException("Invalid image: not a valid ext4 image");
+        }
+
+        img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_LO);
+        long blocksCountLo = fromle(img.readInt());
+
+        img.seek(EXT4_SB_OFFSET_LOG_BLOCK_SIZE);
+        long logBlockSize = fromle(img.readInt());
+
+        img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_HI);
+        long blocksCountHi = fromle(img.readInt());
+
+        long blockSizeBytes = 1L << (10 + logBlockSize);
+        long blockCount = (blocksCountHi << 32) + blocksCountLo;
+        return blockSizeBytes * blockCount;
+    }
+
+    /**
+     * Reads and validates verity metadata, and check the signature against the
+     * given public key
+     * @param img File handle to the image file
+     * @param key Public key to use for signature verification
+     */
+    public static boolean verifyMetaData(RandomAccessFile img, PublicKey key)
+            throws Exception {
+        img.seek(getMetadataPosition(img));
+        int magic = fromle(img.readInt());
+
+        if (magic != VERITY_MAGIC) {
+            throw new IllegalArgumentException("Invalid image: verity metadata not found");
+        }
+
+        int version = fromle(img.readInt());
+
+        if (version != VERITY_VERSION) {
+            throw new IllegalArgumentException("Invalid image: unknown metadata version");
+        }
+
+        byte[] signature = new byte[VERITY_SIGNATURE_SIZE];
+        img.readFully(signature);
+
+        int tableSize = fromle(img.readInt());
+
+        byte[] table = new byte[tableSize];
+        img.readFully(table);
+
+        return Utils.verify(key, table, signature,
+                   Utils.getSignatureAlgorithmIdentifier(key));
+    }
+
+    public static void main(String[] args) throws Exception {
+        if (args.length != 2) {
+            System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem>");
+            System.exit(1);
+        }
+
+        Security.addProvider(new BouncyCastleProvider());
+
+        X509Certificate cert = Utils.loadPEMCertificate(args[1]);
+        PublicKey key = cert.getPublicKey();
+        RandomAccessFile img = openImage(args[0]);
+
+        try {
+            if (verifyMetaData(img, key)) {
+                System.err.println("Signature is VALID");
+                System.exit(0);
+            } else {
+                System.err.println("Signature is INVALID");
+            }
+        } catch (Exception e) {
+            e.printStackTrace(System.err);
+        }
+
+        System.exit(1);
+    }
+}
diff --git a/verity/VerityVerifier.mf b/verity/VerityVerifier.mf
new file mode 100644
index 0000000..6118b31
--- /dev/null
+++ b/verity/VerityVerifier.mf
@@ -0,0 +1 @@
+Main-Class: com.android.verity.VerityVerifier
diff --git a/verity/generate_verity_key.c b/verity/generate_verity_key.c
index 7414af5..a55600c 100644
--- a/verity/generate_verity_key.c
+++ b/verity/generate_verity_key.c
@@ -108,6 +108,66 @@
     return ret;
 }
 
+static int convert_x509(const char *pem_file, const char *key_file)
+{
+    int ret = -1;
+    FILE *f = NULL;
+    EVP_PKEY *pkey = NULL;
+    RSA *rsa = NULL;
+    X509 *cert = NULL;
+
+    if (!pem_file || !key_file) {
+        goto out;
+    }
+
+    f = fopen(pem_file, "r");
+    if (!f) {
+        printf("Failed to open '%s'\n", pem_file);
+        goto out;
+    }
+
+    cert = PEM_read_X509(f, &cert, NULL, NULL);
+    if (!cert) {
+        printf("Failed to read PEM certificate from file '%s'\n", pem_file);
+        goto out;
+    }
+
+    pkey = X509_get_pubkey(cert);
+    if (!pkey) {
+        printf("Failed to extract public key from certificate '%s'\n", pem_file);
+        goto out;
+    }
+
+    rsa = EVP_PKEY_get1_RSA(pkey);
+    if (!rsa) {
+        printf("Failed to get the RSA public key from '%s'\n", pem_file);
+        goto out;
+    }
+
+    if (write_public_keyfile(rsa, key_file) < 0) {
+        printf("Failed to write public key\n");
+        goto out;
+    }
+
+    ret = 0;
+
+out:
+    if (f) {
+        fclose(f);
+    }
+    if (cert) {
+        X509_free(cert);
+    }
+    if (pkey) {
+        EVP_PKEY_free(pkey);
+    }
+    if (rsa) {
+        RSA_free(rsa);
+    }
+
+    return ret;
+}
+
 static int generate_key(const char *file)
 {
     int ret = -1;
@@ -153,13 +213,16 @@
 }
 
 static void usage(){
-    printf("Usage: generate_verity_key <path-to-key>");
+    printf("Usage: generate_verity_key <path-to-key> | -convert <path-to-x509-pem> <path-to-key>\n");
 }
 
 int main(int argc, char *argv[]) {
-    if (argc != 2) {
+    if (argc == 2) {
+        return generate_key(argv[1]);
+    } else if (argc == 4 && !strcmp(argv[1], "-convert")) {
+        return convert_x509(argv[2], argv[3]);
+    } else {
         usage();
         exit(-1);
     }
-    return generate_key(argv[1]);
-}
\ No newline at end of file
+}
diff --git a/verity/keystore_signer b/verity/keystore_signer
old mode 100644
new mode 100755
index 7619c54..445f0c9
--- a/verity/keystore_signer
+++ b/verity/keystore_signer
@@ -5,4 +5,4 @@
 KEYSTORESIGNER_HOME=`dirname "$0"`
 KEYSTORESIGNER_HOME=`dirname "$KEYSTORESIGNER_HOME"`
 
-java -Xmx512M -jar "$KEYSTORESIGNER_HOME"/framework/KeystoreSigner.jar "$@"
\ No newline at end of file
+java -Xmx512M -jar "$KEYSTORESIGNER_HOME"/framework/BootKeystoreSigner.jar "$@"
diff --git a/verity/verify_boot_signature.c b/verity/verify_boot_signature.c
new file mode 100644
index 0000000..2274291
--- /dev/null
+++ b/verity/verify_boot_signature.c
@@ -0,0 +1,426 @@
+/*
+ * 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.
+ */
+
+#define _LARGEFILE64_SOURCE
+
+#include <endian.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+
+#include "bootimg.h"
+
+#define FORMAT_VERSION 1
+#define BUFFER_SIZE (1024 * 1024)
+
+typedef struct {
+    ASN1_STRING *target;
+    ASN1_INTEGER *length;
+} AuthAttrs;
+
+ASN1_SEQUENCE(AuthAttrs) = {
+    ASN1_SIMPLE(AuthAttrs, target, ASN1_PRINTABLE),
+    ASN1_SIMPLE(AuthAttrs, length, ASN1_INTEGER)
+} ASN1_SEQUENCE_END(AuthAttrs)
+
+IMPLEMENT_ASN1_FUNCTIONS(AuthAttrs)
+
+typedef struct {
+    ASN1_INTEGER *formatVersion;
+    X509 *certificate;
+    X509_ALGOR *algorithmIdentifier;
+    AuthAttrs *authenticatedAttributes;
+    ASN1_OCTET_STRING *signature;
+} BootSignature;
+
+ASN1_SEQUENCE(BootSignature) = {
+    ASN1_SIMPLE(BootSignature, formatVersion, ASN1_INTEGER),
+    ASN1_SIMPLE(BootSignature, certificate, X509),
+    ASN1_SIMPLE(BootSignature, algorithmIdentifier, X509_ALGOR),
+    ASN1_SIMPLE(BootSignature, authenticatedAttributes, AuthAttrs),
+    ASN1_SIMPLE(BootSignature, signature, ASN1_OCTET_STRING)
+} ASN1_SEQUENCE_END(BootSignature)
+
+IMPLEMENT_ASN1_FUNCTIONS(BootSignature)
+
+static BIO *g_error = NULL;
+
+/**
+ * Rounds n up to the nearest multiple of page_size
+ * @param n The value to round
+ * @param page_size Page size
+ */
+static uint64_t page_align(uint64_t n, uint64_t page_size)
+{
+    return (((n + page_size - 1) / page_size) * page_size);
+}
+
+/**
+ * Calculates the offset to the beginning of the BootSignature block
+ * based on the boot image header. The signature will start after the
+ * the boot image contents.
+ * @param fd File descriptor to the boot image
+ * @param offset Receives the offset in bytes
+ */
+static int get_signature_offset(int fd, off64_t *offset)
+{
+    int i;
+    struct boot_img_hdr hdr;
+
+    if (!offset) {
+        return -1;
+    }
+
+    if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+        return -1;
+    }
+
+    if (memcmp(BOOT_MAGIC, hdr.magic, BOOT_MAGIC_SIZE) != 0) {
+        printf("Invalid boot image: missing magic\n");
+        return -1;
+    }
+
+    if (!hdr.page_size) {
+        printf("Invalid boot image: page size must be non-zero\n");
+        return -1;
+    }
+
+    *offset = page_align(hdr.page_size
+                    + page_align(hdr.kernel_size,  hdr.page_size)
+                    + page_align(hdr.ramdisk_size, hdr.page_size)
+                    + page_align(hdr.second_size,  hdr.page_size),
+                hdr.page_size);
+
+    return 0;
+}
+
+/**
+ * Reads and parses the ASN.1 BootSignature block from the given offset
+ * @param fd File descriptor to the boot image
+ * @param offset Offset from the beginning of file to the signature
+ * @param bs Pointer to receive the BootImage structure
+ */
+static int read_signature(int fd, off64_t offset, BootSignature **bs)
+{
+    BIO *in = NULL;
+
+    if (!bs) {
+        return -1;
+    }
+
+    if (lseek64(fd, offset, SEEK_SET) == -1) {
+        return -1;
+    }
+
+    if ((in = BIO_new_fd(fd, BIO_NOCLOSE)) == NULL) {
+        ERR_print_errors(g_error);
+        return -1;
+    }
+
+    if ((*bs = ASN1_item_d2i_bio(ASN1_ITEM_rptr(BootSignature), in, bs)) == NULL) {
+        ERR_print_errors(g_error);
+        BIO_free(in);
+        return -1;
+    }
+
+    BIO_free(in);
+    return 0;
+}
+
+/**
+ * Validates the format of the boot signature block, and checks that
+ * the length in authenticated attributes matches the actual length of
+ * the image.
+ * @param bs The boot signature block to validate
+ * @param length The actual length of the boot image without the signature
+ */
+static int validate_signature_block(const BootSignature *bs, uint64_t length)
+{
+    BIGNUM expected;
+    BIGNUM value;
+    int rc = -1;
+
+    if (!bs) {
+        return -1;
+    }
+
+    BN_init(&expected);
+    BN_init(&value);
+
+    /* Confirm that formatVersion matches our supported version */
+    if (!BN_set_word(&expected, FORMAT_VERSION)) {
+        ERR_print_errors(g_error);
+        goto vsb_done;
+    }
+
+    ASN1_INTEGER_to_BN(bs->formatVersion, &value);
+
+    if (BN_cmp(&expected, &value) != 0) {
+        printf("Unsupported signature version\n");
+        goto vsb_done;
+    }
+
+    BN_clear(&expected);
+    BN_clear(&value);
+
+    /* Confirm that the length of the image matches with the length in
+        the authenticated attributes */
+    length = htobe64(length);
+    BN_bin2bn((const unsigned char *) &length, sizeof(length), &expected);
+
+    ASN1_INTEGER_to_BN(bs->authenticatedAttributes->length, &value);
+
+    if (BN_cmp(&expected, &value) != 0) {
+        printf("Image length doesn't match signature attributes\n");
+        goto vsb_done;
+    }
+
+    rc = 0;
+
+vsb_done:
+    BN_free(&expected);
+    BN_free(&value);
+
+    return rc;
+}
+
+/**
+ * Creates a SHA-256 hash from the boot image contents and the encoded
+ * authenticated attributes.
+ * @param fd File descriptor to the boot image
+ * @param length Length of the boot image without the signature block
+ * @param aa Pointer to AuthAttrs
+ * @param digest Pointer to a buffer where the hash is written
+ */
+static int hash_image(int fd, uint64_t length, const AuthAttrs *aa,
+        unsigned char *digest)
+{
+    EVP_MD_CTX *ctx = NULL;
+    int rc = -1;
+
+    ssize_t bytes = 0;
+    unsigned char *attrs = NULL;
+    unsigned char *buffer = NULL;
+    unsigned char *p = NULL;
+    uint64_t total = 0;
+
+    if (!aa || !digest) {
+        goto hi_done;
+    }
+
+    if ((buffer = malloc(BUFFER_SIZE)) == NULL) {
+        goto hi_done;
+    }
+
+    if (lseek64(fd, 0, SEEK_SET) != 0) {
+        goto hi_done;
+    }
+
+    if ((ctx = EVP_MD_CTX_create()) == NULL) {
+        ERR_print_errors(g_error);
+        goto hi_done;
+    }
+
+    EVP_DigestInit(ctx, EVP_sha256());
+
+    do {
+        bytes = BUFFER_SIZE;
+
+        if ((length - total) < BUFFER_SIZE) {
+            bytes = length - total;
+        }
+
+        if ((bytes = read(fd, buffer, bytes)) == -1) {
+            printf("%s\n", strerror(errno));
+            goto hi_done;
+        }
+
+        EVP_DigestUpdate(ctx, buffer, bytes);
+        total += bytes;
+    } while (total < length);
+
+    if ((bytes = i2d_AuthAttrs((AuthAttrs *) aa, NULL)) < 0) {
+        ERR_print_errors(g_error);
+        goto hi_done;
+    }
+
+    if ((attrs = OPENSSL_malloc(bytes)) == NULL) {
+        ERR_print_errors(g_error);
+        goto hi_done;
+    }
+
+    p = attrs;
+
+    if (i2d_AuthAttrs((AuthAttrs *) aa, &p) < 0) {
+        ERR_print_errors(g_error);
+        goto hi_done;
+    }
+
+    EVP_DigestUpdate(ctx, attrs, bytes);
+    EVP_DigestFinal(ctx, digest, NULL);
+
+    rc = 0;
+
+hi_done:
+    if (buffer) {
+        free(buffer);
+    }
+
+    if (ctx) {
+        EVP_MD_CTX_destroy(ctx);
+    }
+
+    if (attrs) {
+        OPENSSL_free(attrs);
+    }
+
+    return rc;
+}
+
+/**
+ * Verifies the RSA signature
+ * @param fd File descriptor to the boot image
+ * @param length Length of the boot image without the signature block
+ * @param bs The boot signature block
+ */
+static int verify_signature(int fd, uint64_t length, const BootSignature *bs)
+{
+    int rc = -1;
+    EVP_PKEY *pkey = NULL;
+    RSA *rsa = NULL;
+    unsigned char digest[SHA256_DIGEST_LENGTH];
+
+    if (!bs) {
+        goto vs_done;
+    }
+
+    if (hash_image(fd, length, bs->authenticatedAttributes, digest) == -1) {
+        goto vs_done;
+    }
+
+    if ((pkey = X509_get_pubkey(bs->certificate)) == NULL) {
+        ERR_print_errors(g_error);
+        goto vs_done;
+    }
+
+    if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) {
+        ERR_print_errors(g_error);
+        goto vs_done;
+    }
+
+    if (!RSA_verify(NID_sha256, digest, SHA256_DIGEST_LENGTH,
+                bs->signature->data, bs->signature->length, rsa)) {
+        ERR_print_errors(g_error);
+        goto vs_done;
+    }
+
+    rc = 0;
+
+vs_done:
+    if (pkey) {
+        EVP_PKEY_free(pkey);
+    }
+
+    if (rsa) {
+        RSA_free(rsa);
+    }
+
+    return rc;
+}
+
+/**
+ * Given the file name of a signed boot image, verifies the signature
+ * @param image_file Name of the boot image file
+ */
+static int verify(const char *image_file)
+{
+    BootSignature *bs = NULL;
+    int fd = -1;
+    int rc = 1;
+    off64_t offset = 0;
+
+    if (!image_file) {
+        return rc;
+    }
+
+    if ((fd = open(image_file, O_RDONLY | O_LARGEFILE)) == -1) {
+        return rc;
+    }
+
+    if (get_signature_offset(fd, &offset) == -1) {
+        goto out;
+    }
+
+    if (read_signature(fd, offset, &bs) == -1) {
+        goto out;
+    }
+
+    if (validate_signature_block(bs, offset) == -1) {
+        goto out;
+    }
+
+    if (verify_signature(fd, offset, bs) == -1) {
+        goto out;
+    }
+
+    printf("Signature is VALID\n");
+    rc = 0;
+
+out:
+    if (bs) {
+        BootSignature_free(bs);
+    }
+
+    if (fd != -1) {
+        close(fd);
+    }
+
+    return rc;
+}
+
+static void usage()
+{
+    printf("Usage: verify_boot_signature <path-to-boot-image>\n");
+}
+
+int main(int argc, char *argv[])
+{
+    if (argc != 2) {
+        usage();
+        return 1;
+    }
+
+    /* BIO descriptor for logging OpenSSL errors to stderr */
+    if ((g_error = BIO_new_fd(STDERR_FILENO, BIO_NOCLOSE)) == NULL) {
+        printf("Failed to allocate a BIO handle for error output\n");
+        return 1;
+    }
+
+    ERR_load_crypto_strings();
+
+    return verify(argv[1]);
+}
diff --git a/verity/verity_verifier b/verity/verity_verifier
new file mode 100755
index 0000000..f145228
--- /dev/null
+++ b/verity/verity_verifier
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Start-up script for VerityVerifier
+
+VERITYVERIFIER_HOME=`dirname "$0"`
+VERITYVERIFIER_HOME=`dirname "$VERITYVERIFIER_HOME"`
+
+java -Xmx512M -jar "$VERITYVERIFIER_HOME"/framework/VerityVerifier.jar "$@"