Project import
diff --git a/verity/Android.mk b/verity/Android.mk
new file mode 100644
index 0000000..1a7351f
--- /dev/null
+++ b/verity/Android.mk
@@ -0,0 +1,92 @@
+LOCAL_PATH:= $(call my-dir)
+
+ifeq ($(HOST_OS),linux)
+
+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
+LOCAL_C_INCLUDES += external/openssl/include system/extras/ext4_utils system/core/mkbootimg
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # HOST_OS == linux
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := generate_verity_key
+LOCAL_SRC_FILES := generate_verity_key.c
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_SHARED_LIBRARIES := libcrypto_utils libcrypto
+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
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host
+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 := 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
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := VeritySigner
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+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 := BootSignature
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := build_verity_metadata.py
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_SRC_FILES := build_verity_metadata.py
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := build_verity_tree
+LOCAL_SRC_FILES := build_verity_tree.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := libsparse_host libz
+LOCAL_SHARED_LIBRARIES := libcrypto libbase
+LOCAL_CFLAGS += -Wall -Werror
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/verity/BootSignature.java b/verity/BootSignature.java
new file mode 100644
index 0000000..3cf9499
--- /dev/null
+++ b/verity/BootSignature.java
@@ -0,0 +1,319 @@
+/*
+ * 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.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.util.ASN1Dump;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ *    AndroidVerifiedBootSignature DEFINITIONS ::=
+ *    BEGIN
+ *        formatVersion ::= INTEGER
+ *        certificate ::= Certificate
+ *        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 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(FORMAT_VERSION);
+        this.target = new DERPrintableString(target);
+        this.length = new ASN1Integer(length);
+    }
+
+    /**
+     * 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() {
+        ASN1EncodableVector attrs = new ASN1EncodableVector();
+        attrs.add(target);
+        attrs.add(length);
+        return new DERSequence(attrs);
+    }
+
+    public byte[] getEncodedAuthenticatedAttributes() throws IOException {
+        return getAuthenticatedAttributes().getEncoded();
+    }
+
+    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();
+        publicKey = cert.getPublicKey();
+    }
+
+    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);
+        return Utils.sign(key, signable);
+    }
+
+    public boolean verify(byte[] image) throws Exception {
+        if (length.getValue().intValue() != image.length) {
+            throw new IllegalArgumentException("Invalid image length");
+        }
+
+        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);
+
+        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);
+    }
+
+    public static void verifySignature(String imagePath, String certPath) 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);
+
+        if (!certPath.isEmpty()) {
+            System.err.println("NOTE: verifying using public key from " + certPath);
+            bootsig.setCertificate(Utils.loadPEMCertificate(certPath));
+        }
+
+        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);
+    }
+
+    /* 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])) {
+            String certPath = "";
+
+            if (args.length >= 4 && "-certificate".equals(args[2])) {
+                /* args[3] is the path to a public key certificate */
+                certPath = args[3];
+            }
+
+            /* args[1] is the path to a signed boot image */
+            verifySignature(args[1], certPath);
+        } 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/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/NOTICE b/verity/NOTICE
new file mode 100644
index 0000000..9109eed
--- /dev/null
+++ b/verity/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2013-2015, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/verity/Utils.java b/verity/Utils.java
new file mode 100644
index 0000000..6d80276
--- /dev/null
+++ b/verity/Utils.java
@@ -0,0 +1,299 @@
+/*
+ * 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.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.ECPublicKeySpec;
+import java.security.spec.ECPrivateKeySpec;
+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.asn1.x9.X9ObjectIdentifiers;
+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(X9ObjectIdentifiers.ecdsa_with_SHA256.getId(), "SHA256withECDSA");
+        ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA384.getId(), "SHA384withECDSA");
+        ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA512.getId(), "SHA512withECDSA");
+        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("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256.getId());
+        ALG_TO_ID.put("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384.getId());
+        ALG_TO_ID.put("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512.getId());
+        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;
+        }
+
+        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);
+    }
+
+    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 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 {
+        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 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) throws Exception {
+        if ("EC".equals(key.getAlgorithm())) {
+            int curveSize;
+            KeyFactory factory = KeyFactory.getInstance("EC");
+
+            if (key instanceof PublicKey) {
+                ECPublicKeySpec spec = factory.getKeySpec(key, ECPublicKeySpec.class);
+                curveSize = spec.getParams().getCurve().getField().getFieldSize();
+            } else if (key instanceof PrivateKey) {
+                ECPrivateKeySpec spec = factory.getKeySpec(key, ECPrivateKeySpec.class);
+                curveSize = spec.getParams().getCurve().getField().getFieldSize();
+            } else {
+                throw new InvalidKeySpecException();
+            }
+
+            if (curveSize <= 256) {
+                return "SHA256withECDSA";
+            } else if (curveSize <= 384) {
+                return "SHA384withECDSA";
+            } else {
+                return "SHA512withECDSA";
+            }
+        } else if ("RSA".equals(key.getAlgorithm())) {
+            return "SHA256withRSA";
+        } else {
+            throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
+        }
+    }
+
+    static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) throws Exception {
+        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.getAlgorithm().getId());
+
+        if (algName == null) {
+            throw new IllegalArgumentException("Unsupported algorithm " + algId.getAlgorithm());
+        }
+
+        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(getSignatureAlgorithm(privateKey));
+        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();
+    }
+}
diff --git a/verity/VeritySigner.java b/verity/VeritySigner.java
new file mode 100644
index 0000000..9d85747
--- /dev/null
+++ b/verity/VeritySigner.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013 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.security.PublicKey;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class VeritySigner {
+
+    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]);
+
+        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/VeritySigner.mf b/verity/VeritySigner.mf
new file mode 100644
index 0000000..b36c198
--- /dev/null
+++ b/verity/VeritySigner.mf
@@ -0,0 +1 @@
+Main-Class: com.android.verity.VeritySigner
diff --git a/verity/VerityVerifier.java b/verity/VerityVerifier.java
new file mode 100644
index 0000000..6b3f49e
--- /dev/null
+++ b/verity/VerityVerifier.java
@@ -0,0 +1,419 @@
+/*
+ * 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.Math;
+import java.lang.Process;
+import java.lang.Runtime;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.xml.bind.DatatypeConverter;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class VerityVerifier {
+
+    private ArrayList<Integer> hashBlocksLevel;
+    private byte[] hashTree;
+    private byte[] rootHash;
+    private byte[] salt;
+    private byte[] signature;
+    private byte[] table;
+    private File image;
+    private int blockSize;
+    private int hashBlockSize;
+    private int hashOffsetForData;
+    private int hashSize;
+    private int hashTreeSize;
+    private long hashStart;
+    private long imageSize;
+    private MessageDigest digest;
+
+    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 MINCRYPT_OFFSET_MODULUS = 0x8;
+    private static final int MINCRYPT_OFFSET_EXPONENT = 0x208;
+    private static final int MINCRYPT_MODULUS_SIZE = 0x100;
+    private static final int MINCRYPT_EXPONENT_SIZE = 0x4;
+    private static final int VERITY_FIELDS = 10;
+    private static final int VERITY_MAGIC = 0xB001B001;
+    private static final int VERITY_SIGNATURE_SIZE = 256;
+    private static final int VERITY_VERSION = 0;
+
+    public VerityVerifier(String fname) throws Exception {
+        digest = MessageDigest.getInstance("SHA-256");
+        hashSize = digest.getDigestLength();
+        hashBlocksLevel = new ArrayList<Integer>();
+        hashTreeSize = -1;
+        openImage(fname);
+        readVerityData();
+    }
+
+    /**
+     * Reverses the order of bytes in a byte array
+     * @param value Byte array to reverse
+     */
+    private static byte[] reverse(byte[] value) {
+        for (int i = 0; i < value.length / 2; i++) {
+            byte tmp = value[i];
+            value[i] = value[value.length - i - 1];
+            value[value.length - i - 1] = tmp;
+        }
+
+        return value;
+    }
+
+    /**
+     * Converts a 4-byte little endian value to a Java integer
+     * @param value Little endian integer to convert
+     */
+    private 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
+     */
+    private static int fromle(short value) {
+        return fromle(value << 16);
+    }
+
+    /**
+     * Reads a 2048-bit RSA public key saved in mincrypt format, and returns
+     * a Java PublicKey for it.
+     * @param fname Name of the mincrypt public key file
+     */
+    private static PublicKey getMincryptPublicKey(String fname) throws Exception {
+        try (RandomAccessFile key = new RandomAccessFile(fname, "r")) {
+            byte[] binaryMod = new byte[MINCRYPT_MODULUS_SIZE];
+            byte[] binaryExp = new byte[MINCRYPT_EXPONENT_SIZE];
+
+            key.seek(MINCRYPT_OFFSET_MODULUS);
+            key.readFully(binaryMod);
+
+            key.seek(MINCRYPT_OFFSET_EXPONENT);
+            key.readFully(binaryExp);
+
+            BigInteger modulus  = new BigInteger(1, reverse(binaryMod));
+            BigInteger exponent = new BigInteger(1, reverse(binaryExp));
+
+            RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
+            KeyFactory factory = KeyFactory.getInstance("RSA");
+            return factory.generatePublic(spec);
+        }
+    }
+
+    /**
+     * Unsparses a sparse image into a temporary file and returns a
+     * handle to the file
+     * @param fname Path to a sparse image file
+     */
+     private void openImage(String fname) throws Exception {
+        image = File.createTempFile("system", ".raw");
+        image.deleteOnExit();
+
+        Process p = Runtime.getRuntime().exec("simg2img " + fname +
+                            " " + image.getAbsoluteFile());
+
+        p.waitFor();
+        if (p.exitValue() != 0) {
+            throw new IllegalArgumentException("Invalid image: failed to unsparse");
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Calculates the size of the verity hash tree based on the image size
+     */
+    private int calculateHashTreeSize() {
+        if (hashTreeSize > 0) {
+            return hashTreeSize;
+        }
+
+        int totalBlocks = 0;
+        int hashes = (int) (imageSize / blockSize);
+
+        hashBlocksLevel.clear();
+
+        do {
+            hashBlocksLevel.add(0, hashes);
+
+            int hashBlocks =
+                (int) Math.ceil((double) hashes * hashSize / hashBlockSize);
+
+            totalBlocks += hashBlocks;
+
+            hashes = hashBlocks;
+        } while (hashes > 1);
+
+        hashTreeSize = totalBlocks * hashBlockSize;
+        return hashTreeSize;
+    }
+
+    /**
+     * Parses the verity mapping table and reads the hash tree from
+     * the image file
+     * @param img Handle to the image file
+     * @param table Verity mapping table
+     */
+    private void readHashTree(RandomAccessFile img, byte[] table)
+            throws Exception {
+        String tableStr = new String(table);
+        String[] fields = tableStr.split(" ");
+
+        if (fields.length != VERITY_FIELDS) {
+            throw new IllegalArgumentException("Invalid image: unexpected number of fields "
+                    + "in verity mapping table (" + fields.length + ")");
+        }
+
+        String hashVersion = fields[0];
+
+        if (!"1".equals(hashVersion)) {
+            throw new IllegalArgumentException("Invalid image: unsupported hash format");
+        }
+
+        String alg = fields[7];
+
+        if (!"sha256".equals(alg)) {
+            throw new IllegalArgumentException("Invalid image: unsupported hash algorithm");
+        }
+
+        blockSize = Integer.parseInt(fields[3]);
+        hashBlockSize = Integer.parseInt(fields[4]);
+
+        int blocks = Integer.parseInt(fields[5]);
+        int start = Integer.parseInt(fields[6]);
+
+        if (imageSize != (long) blocks * blockSize) {
+            throw new IllegalArgumentException("Invalid image: size mismatch in mapping "
+                    + "table");
+        }
+
+        rootHash = DatatypeConverter.parseHexBinary(fields[8]);
+        salt = DatatypeConverter.parseHexBinary(fields[9]);
+
+        hashStart = (long) start * blockSize;
+        img.seek(hashStart);
+
+        int treeSize = calculateHashTreeSize();
+
+        hashTree = new byte[treeSize];
+        img.readFully(hashTree);
+    }
+
+    /**
+     * Reads verity data from the image file
+     */
+    private void readVerityData() throws Exception {
+        try (RandomAccessFile img = new RandomAccessFile(image, "r")) {
+            imageSize = getMetadataPosition(img);
+            img.seek(imageSize);
+
+            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");
+            }
+
+            signature = new byte[VERITY_SIGNATURE_SIZE];
+            img.readFully(signature);
+
+            int tableSize = fromle(img.readInt());
+
+            table = new byte[tableSize];
+            img.readFully(table);
+
+            readHashTree(img, table);
+        }
+    }
+
+    /**
+     * Reads and validates verity metadata, and checks the signature against the
+     * given public key
+     * @param key Public key to use for signature verification
+     */
+    public boolean verifyMetaData(PublicKey key)
+            throws Exception {
+       return Utils.verify(key, table, signature,
+                   Utils.getSignatureAlgorithmIdentifier(key));
+    }
+
+    /**
+     * Hashes a block of data using a salt and checks of the results are expected
+     * @param hash The expected hash value
+     * @param data The data block to check
+     */
+    private boolean checkBlock(byte[] hash, byte[] data) {
+        digest.reset();
+        digest.update(salt);
+        digest.update(data);
+        return Arrays.equals(hash, digest.digest());
+    }
+
+    /**
+     * Verifies the root hash and the first N-1 levels of the hash tree
+     */
+    private boolean verifyHashTree() throws Exception {
+        int hashOffset = 0;
+        int dataOffset = hashBlockSize;
+
+        if (!checkBlock(rootHash, Arrays.copyOfRange(hashTree, 0, hashBlockSize))) {
+            System.err.println("Root hash mismatch");
+            return false;
+        }
+
+        for (int level = 0; level < hashBlocksLevel.size() - 1; level++) {
+            int blocks = hashBlocksLevel.get(level);
+
+            for (int i = 0; i < blocks; i++) {
+                byte[] hashBlock = Arrays.copyOfRange(hashTree,
+                        hashOffset + i * hashSize,
+                        hashOffset + i * hashSize + hashSize);
+
+                byte[] dataBlock = Arrays.copyOfRange(hashTree,
+                        dataOffset + i * hashBlockSize,
+                        dataOffset + i * hashBlockSize + hashBlockSize);
+
+                if (!checkBlock(hashBlock, dataBlock)) {
+                    System.err.printf("Hash mismatch at tree level %d, block %d\n", level, i);
+                    return false;
+                }
+            }
+
+            hashOffset = dataOffset;
+            hashOffsetForData = dataOffset;
+            dataOffset += blocks * hashBlockSize;
+        }
+
+        return true;
+    }
+
+    /**
+     * Validates the image against the hash tree
+     */
+    public boolean verifyData() throws Exception {
+        if (!verifyHashTree()) {
+            return false;
+        }
+
+        try (RandomAccessFile img = new RandomAccessFile(image, "r")) {
+            byte[] dataBlock = new byte[blockSize];
+            int hashOffset = hashOffsetForData;
+
+            for (int i = 0; (long) i * blockSize < imageSize; i++) {
+                byte[] hashBlock = Arrays.copyOfRange(hashTree,
+                        hashOffset + i * hashSize,
+                        hashOffset + i * hashSize + hashSize);
+
+                img.readFully(dataBlock);
+
+                if (!checkBlock(hashBlock, dataBlock)) {
+                    System.err.printf("Hash mismatch at block %d\n", i);
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Verifies the integrity of the image and the verity metadata
+     * @param key Public key to use for signature verification
+     */
+    public boolean verify(PublicKey key) throws Exception {
+        return (verifyMetaData(key) && verifyData());
+    }
+
+    public static void main(String[] args) throws Exception {
+        Security.addProvider(new BouncyCastleProvider());
+        PublicKey key = null;
+
+        if (args.length == 3 && "-mincrypt".equals(args[1])) {
+            key = getMincryptPublicKey(args[2]);
+        } else if (args.length == 2) {
+            X509Certificate cert = Utils.loadPEMCertificate(args[1]);
+            key = cert.getPublicKey();
+        } else {
+            System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem> | -mincrypt <mincrypt_key>");
+            System.exit(1);
+        }
+
+        VerityVerifier verifier = new VerityVerifier(args[0]);
+
+        try {
+            if (verifier.verify(key)) {
+                System.err.println("Signature is VALID");
+                System.exit(0);
+            }
+        } 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/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/build_verity_metadata.py b/verity/build_verity_metadata.py
new file mode 100755
index 0000000..51e629a
--- /dev/null
+++ b/verity/build_verity_metadata.py
@@ -0,0 +1,78 @@
+#! /usr/bin/env python
+
+import os
+import sys
+import struct
+import tempfile
+import commands
+
+VERSION = 0
+MAGIC_NUMBER = 0xb001b001
+BLOCK_SIZE = 4096
+METADATA_SIZE = BLOCK_SIZE * 8
+
+def run(cmd):
+    status, output = commands.getstatusoutput(cmd)
+    print output
+    if status:
+        exit(-1)
+
+def get_verity_metadata_size(data_size):
+    return METADATA_SIZE
+
+def build_metadata_block(verity_table, signature):
+    table_len = len(verity_table)
+    block = struct.pack("II256sI", MAGIC_NUMBER, VERSION, signature, table_len)
+    block += verity_table
+    block = block.ljust(METADATA_SIZE, '\x00')
+    return block
+
+def sign_verity_table(table, signer_path, key_path):
+    with tempfile.NamedTemporaryFile(suffix='.table') as table_file:
+        with tempfile.NamedTemporaryFile(suffix='.sig') as signature_file:
+            table_file.write(table)
+            table_file.flush()
+            cmd = " ".join((signer_path, table_file.name, key_path, signature_file.name))
+            print cmd
+            run(cmd)
+            return signature_file.read()
+
+def build_verity_table(block_device, data_blocks, root_hash, salt):
+    table = "1 %s %s %s %s %s %s sha256 %s %s"
+    table %= (  block_device,
+                block_device,
+                BLOCK_SIZE,
+                BLOCK_SIZE,
+                data_blocks,
+                data_blocks,
+                root_hash,
+                salt)
+    return table
+
+def build_verity_metadata(data_blocks, metadata_image, root_hash,
+                            salt, block_device, signer_path, signing_key):
+    # build the verity table
+    verity_table = build_verity_table(block_device, data_blocks, root_hash, salt)
+    # build the verity table signature
+    signature = sign_verity_table(verity_table, signer_path, signing_key)
+    # build the metadata block
+    metadata_block = build_metadata_block(verity_table, signature)
+    # write it to the outfile
+    with open(metadata_image, "wb") as f:
+        f.write(metadata_block)
+
+if __name__ == "__main__":
+    if len(sys.argv) == 3 and sys.argv[1] == "-s":
+        print get_verity_metadata_size(int(sys.argv[2]))
+    elif len(sys.argv) == 8:
+        data_image_blocks = int(sys.argv[1]) / 4096
+        metadata_image = sys.argv[2]
+        root_hash = sys.argv[3]
+        salt = sys.argv[4]
+        block_device = sys.argv[5]
+        signer_path = sys.argv[6]
+        signing_key = sys.argv[7]
+        build_verity_metadata(data_image_blocks, metadata_image, root_hash,
+                                salt, block_device, signer_path, signing_key)
+    else:
+        exit(-1)
diff --git a/verity/build_verity_tree.cpp b/verity/build_verity_tree.cpp
new file mode 100644
index 0000000..c50e449
--- /dev/null
+++ b/verity/build_verity_tree.cpp
@@ -0,0 +1,367 @@
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+#include <sparse/sparse.h>
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+
+struct sparse_hash_ctx {
+    unsigned char *hashes;
+    const unsigned char *salt;
+    uint64_t salt_size;
+    uint64_t hash_size;
+    uint64_t block_size;
+    const unsigned char *zero_block_hash;
+    const EVP_MD *md;
+};
+
+#define div_round_up(x,y) (((x) + (y) - 1)/(y))
+
+#define round_up(x,y) (div_round_up(x,y)*(y))
+
+#define FATAL(x...) { \
+    fprintf(stderr, x); \
+    exit(1); \
+}
+
+size_t verity_tree_blocks(uint64_t data_size, size_t block_size, size_t hash_size,
+                          int level)
+{
+    size_t level_blocks = div_round_up(data_size, block_size);
+    int hashes_per_block = div_round_up(block_size, hash_size);
+
+    do {
+        level_blocks = div_round_up(level_blocks, hashes_per_block);
+    } while (level--);
+
+    return level_blocks;
+}
+
+int hash_block(const EVP_MD *md,
+               const unsigned char *block, size_t len,
+               const unsigned char *salt, size_t salt_len,
+               unsigned char *out, size_t *out_size)
+{
+    EVP_MD_CTX *mdctx;
+    unsigned int s;
+    int ret = 1;
+
+    mdctx = EVP_MD_CTX_create();
+    assert(mdctx);
+    ret &= EVP_DigestInit_ex(mdctx, md, NULL);
+    ret &= EVP_DigestUpdate(mdctx, salt, salt_len);
+    ret &= EVP_DigestUpdate(mdctx, block, len);
+    ret &= EVP_DigestFinal_ex(mdctx, out, &s);
+    EVP_MD_CTX_destroy(mdctx);
+    assert(ret == 1);
+    if (out_size) {
+        *out_size = s;
+    }
+    return 0;
+}
+
+int hash_blocks(const EVP_MD *md,
+                const unsigned char *in, size_t in_size,
+                unsigned char *out, size_t *out_size,
+                const unsigned char *salt, size_t salt_size,
+                size_t block_size)
+{
+    size_t s;
+    *out_size = 0;
+    for (size_t i = 0; i < in_size; i += block_size) {
+        hash_block(md, in + i, block_size, salt, salt_size, out, &s);
+        out += s;
+        *out_size += s;
+    }
+
+    return 0;
+}
+
+int hash_chunk(void *priv, const void *data, int len)
+{
+    struct sparse_hash_ctx *ctx = (struct sparse_hash_ctx *)priv;
+    assert(len % ctx->block_size == 0);
+    if (data) {
+        size_t s;
+        hash_blocks(ctx->md, (const unsigned char *)data, len,
+                    ctx->hashes, &s,
+                    ctx->salt, ctx->salt_size, ctx->block_size);
+        ctx->hashes += s;
+    } else {
+        for (size_t i = 0; i < (size_t)len; i += ctx->block_size) {
+            memcpy(ctx->hashes, ctx->zero_block_hash, ctx->hash_size);
+            ctx->hashes += ctx->hash_size;
+        }
+    }
+    return 0;
+}
+
+void usage(void)
+{
+    printf("usage: build_verity_tree [ <options> ] -s <size> | <data> <verity>\n"
+           "options:\n"
+           "  -a,--salt-str=<string>       set salt to <string>\n"
+           "  -A,--salt-hex=<hex digits>   set salt to <hex digits>\n"
+           "  -h                           show this help\n"
+           "  -s,--verity-size=<data size> print the size of the verity tree\n"
+           "  -v,                          enable verbose logging\n"
+           "  -S                           treat <data image> as a sparse file\n"
+        );
+}
+
+int main(int argc, char **argv)
+{
+    char *data_filename;
+    char *verity_filename;
+    unsigned char *salt = NULL;
+    size_t salt_size = 0;
+    bool sparse = false;
+    size_t block_size = 4096;
+    uint64_t calculate_size = 0;
+    bool verbose = false;
+
+    while (1) {
+        const static struct option long_options[] = {
+            {"salt-str", required_argument, 0, 'a'},
+            {"salt-hex", required_argument, 0, 'A'},
+            {"help", no_argument, 0, 'h'},
+            {"sparse", no_argument, 0, 'S'},
+            {"verity-size", required_argument, 0, 's'},
+            {"verbose", no_argument, 0, 'v'},
+            {NULL, 0, 0, 0}
+        };
+        int c = getopt_long(argc, argv, "a:A:hSs:v", long_options, NULL);
+        if (c < 0) {
+            break;
+        }
+
+        switch (c) {
+        case 'a':
+            salt_size = strlen(optarg);
+            salt = new unsigned char[salt_size]();
+            if (salt == NULL) {
+                FATAL("failed to allocate memory for salt\n");
+            }
+            memcpy(salt, optarg, salt_size);
+            break;
+        case 'A': {
+                BIGNUM *bn = NULL;
+                if(!BN_hex2bn(&bn, optarg)) {
+                    FATAL("failed to convert salt from hex\n");
+                }
+                salt_size = BN_num_bytes(bn);
+                salt = new unsigned char[salt_size]();
+                if (salt == NULL) {
+                    FATAL("failed to allocate memory for salt\n");
+                }
+                if((size_t)BN_bn2bin(bn, salt) != salt_size) {
+                    FATAL("failed to convert salt to bytes\n");
+                }
+            }
+            break;
+        case 'h':
+            usage();
+            return 1;
+        case 'S':
+            sparse = true;
+            break;
+        case 's': {
+                char* endptr;
+                errno = 0;
+                unsigned long long int inSize = strtoull(optarg, &endptr, 0);
+                if (optarg[0] == '\0' || *endptr != '\0' ||
+                        (errno == ERANGE && inSize == ULLONG_MAX)) {
+                    FATAL("invalid value of verity-size\n");
+                }
+                if (inSize > UINT64_MAX) {
+                    FATAL("invalid value of verity-size\n");
+                }
+                calculate_size = (uint64_t)inSize;
+            }
+            break;
+        case 'v':
+            verbose = true;
+            break;
+        case '?':
+            usage();
+            return 1;
+        default:
+            abort();
+        }
+    }
+
+    argc -= optind;
+    argv += optind;
+
+    const EVP_MD *md = EVP_sha256();
+    if (!md) {
+        FATAL("failed to get digest\n");
+    }
+
+    size_t hash_size = EVP_MD_size(md);
+    assert(hash_size * 2 < block_size);
+
+    if (!salt || !salt_size) {
+        salt_size = hash_size;
+        salt = new unsigned char[salt_size];
+        if (salt == NULL) {
+            FATAL("failed to allocate memory for salt\n");
+        }
+
+        int random_fd = open("/dev/urandom", O_RDONLY);
+        if (random_fd < 0) {
+            FATAL("failed to open /dev/urandom\n");
+        }
+
+        ssize_t ret = read(random_fd, salt, salt_size);
+        if (ret != (ssize_t)salt_size) {
+            FATAL("failed to read %zu bytes from /dev/urandom: %zd %d\n", salt_size, ret, errno);
+        }
+        close(random_fd);
+    }
+
+    if (calculate_size) {
+        if (argc != 0) {
+            usage();
+            return 1;
+        }
+        size_t verity_blocks = 0;
+        size_t level_blocks;
+        int levels = 0;
+        do {
+            level_blocks = verity_tree_blocks(calculate_size, block_size, hash_size, levels);
+            levels++;
+            verity_blocks += level_blocks;
+        } while (level_blocks > 1);
+
+        printf("%" PRIu64 "\n", (uint64_t)verity_blocks * block_size);
+        return 0;
+    }
+
+    if (argc != 2) {
+        usage();
+        return 1;
+    }
+
+    data_filename = argv[0];
+    verity_filename = argv[1];
+
+    int fd = open(data_filename, O_RDONLY);
+    if (fd < 0) {
+        FATAL("failed to open %s\n", data_filename);
+    }
+
+    struct sparse_file *file;
+    if (sparse) {
+        file = sparse_file_import(fd, false, false);
+    } else {
+        file = sparse_file_import_auto(fd, false, verbose);
+    }
+
+    if (!file) {
+        FATAL("failed to read file %s\n", data_filename);
+    }
+
+    int64_t len = sparse_file_len(file, false, false);
+    if (len % block_size != 0) {
+        FATAL("file size %" PRIu64 " is not a multiple of %zu bytes\n",
+                len, block_size);
+    }
+
+    int levels = 0;
+    size_t verity_blocks = 0;
+    size_t level_blocks;
+
+    do {
+        level_blocks = verity_tree_blocks(len, block_size, hash_size, levels);
+        levels++;
+        verity_blocks += level_blocks;
+    } while (level_blocks > 1);
+
+    unsigned char *verity_tree = new unsigned char[verity_blocks * block_size]();
+    unsigned char **verity_tree_levels = new unsigned char *[levels + 1]();
+    size_t *verity_tree_level_blocks = new size_t[levels]();
+    if (verity_tree == NULL || verity_tree_levels == NULL || verity_tree_level_blocks == NULL) {
+        FATAL("failed to allocate memory for verity tree\n");
+    }
+
+    unsigned char *ptr = verity_tree;
+    for (int i = levels - 1; i >= 0; i--) {
+        verity_tree_levels[i] = ptr;
+        verity_tree_level_blocks[i] = verity_tree_blocks(len, block_size, hash_size, i);
+        ptr += verity_tree_level_blocks[i] * block_size;
+    }
+    assert(ptr == verity_tree + verity_blocks * block_size);
+    assert(verity_tree_level_blocks[levels - 1] == 1);
+
+    unsigned char zero_block_hash[hash_size];
+    unsigned char zero_block[block_size];
+    memset(zero_block, 0, block_size);
+    hash_block(md, zero_block, block_size, salt, salt_size, zero_block_hash, NULL);
+
+    unsigned char root_hash[hash_size];
+    verity_tree_levels[levels] = root_hash;
+
+    struct sparse_hash_ctx ctx;
+    ctx.hashes = verity_tree_levels[0];
+    ctx.salt = salt;
+    ctx.salt_size = salt_size;
+    ctx.hash_size = hash_size;
+    ctx.block_size = block_size;
+    ctx.zero_block_hash = zero_block_hash;
+    ctx.md = md;
+
+    sparse_file_callback(file, false, false, hash_chunk, &ctx);
+
+    sparse_file_destroy(file);
+    close(fd);
+
+    for (int i = 0; i < levels; i++) {
+        size_t out_size;
+        hash_blocks(md,
+                verity_tree_levels[i], verity_tree_level_blocks[i] * block_size,
+                verity_tree_levels[i + 1], &out_size,
+                salt, salt_size, block_size);
+          if (i < levels - 1) {
+              assert(div_round_up(out_size, block_size) == verity_tree_level_blocks[i + 1]);
+          } else {
+              assert(out_size == hash_size);
+          }
+    }
+
+    for (size_t i = 0; i < hash_size; i++) {
+        printf("%02x", root_hash[i]);
+    }
+    printf(" ");
+    for (size_t i = 0; i < salt_size; i++) {
+        printf("%02x", salt[i]);
+    }
+    printf("\n");
+
+    fd = open(verity_filename, O_WRONLY|O_CREAT, 0666);
+    if (fd < 0) {
+        FATAL("failed to open output file '%s'\n", verity_filename);
+    }
+    if (!android::base::WriteFully(fd, verity_tree, verity_blocks * block_size)) {
+        FATAL("failed to write '%s'\n", verity_filename);
+    }
+    close(fd);
+
+    delete[] verity_tree_levels;
+    delete[] verity_tree_level_blocks;
+    delete[] verity_tree;
+    delete[] salt;
+}
diff --git a/verity/fec/Android.mk b/verity/fec/Android.mk
new file mode 100644
index 0000000..33ef8b5
--- /dev/null
+++ b/verity/fec/Android.mk
@@ -0,0 +1,43 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+ifeq ($(HOST_OS),linux)
+LOCAL_SANITIZE := integer
+endif
+LOCAL_MODULE := fec
+LOCAL_SRC_FILES := main.cpp image.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := \
+    libsparse_host \
+    libz \
+    libcrypto_utils \
+    libcrypto \
+    libfec_host \
+    libfec_rs_host \
+    libext4_utils_host \
+    libsquashfs_utils_host
+LOCAL_SHARED_LIBRARIES := libbase
+LOCAL_CFLAGS += -Wall -Werror -O3
+LOCAL_C_INCLUDES += external/fec
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_SANITIZE := integer
+LOCAL_MODULE := fec
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_SRC_FILES := main.cpp image.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := \
+    libcrypto_utils \
+    libcrypto \
+    libfec \
+    libfec_rs \
+    libbase \
+    libext4_utils_static \
+    libsquashfs_utils \
+    libcutils
+LOCAL_CFLAGS += -Wall -Werror -O3 -DIMAGE_NO_SPARSE=1
+LOCAL_C_INCLUDES += external/fec
+include $(BUILD_EXECUTABLE)
diff --git a/verity/fec/image.cpp b/verity/fec/image.cpp
new file mode 100644
index 0000000..610a462
--- /dev/null
+++ b/verity/fec/image.cpp
@@ -0,0 +1,629 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#undef NDEBUG
+#define _LARGEFILE64_SOURCE
+
+extern "C" {
+    #include <fec.h>
+}
+
+#include <assert.h>
+#include <android-base/file.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <openssl/sha.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#ifndef IMAGE_NO_SPARSE
+#include <sparse/sparse.h>
+#endif
+#include "image.h"
+
+#if defined(__linux__)
+    #include <linux/fs.h>
+#elif defined(__APPLE__)
+    #include <sys/disk.h>
+    #define BLKGETSIZE64 DKIOCGETBLOCKCOUNT
+    #define O_LARGEFILE 0
+#endif
+
+void image_init(image *ctx)
+{
+    memset(ctx, 0, sizeof(*ctx));
+}
+
+static void mmap_image_free(image *ctx)
+{
+    if (ctx->input) {
+        munmap(ctx->input, (size_t)ctx->inp_size);
+        close(ctx->inp_fd);
+    }
+
+    if (ctx->fec_mmap_addr) {
+        munmap(ctx->fec_mmap_addr, FEC_BLOCKSIZE + ctx->fec_size);
+        close(ctx->fec_fd);
+    }
+
+    if (!ctx->inplace && ctx->output) {
+        delete[] ctx->output;
+    }
+}
+
+static void file_image_free(image *ctx)
+{
+    assert(ctx->input == ctx->output);
+
+    if (ctx->input) {
+        delete[] ctx->input;
+    }
+
+    if (ctx->fec) {
+        delete[] ctx->fec;
+    }
+}
+
+void image_free(image *ctx)
+{
+    if (ctx->mmap) {
+        mmap_image_free(ctx);
+    } else {
+        file_image_free(ctx);
+    }
+
+    image_init(ctx);
+}
+
+static uint64_t get_size(int fd)
+{
+    struct stat st;
+
+    if (fstat(fd, &st) == -1) {
+        FATAL("failed to fstat: %s\n", strerror(errno));
+    }
+
+    uint64_t size = 0;
+
+    if (S_ISBLK(st.st_mode)) {
+        if (ioctl(fd, BLKGETSIZE64, &size) == -1) {
+            FATAL("failed to ioctl(BLKGETSIZE64): %s\n", strerror(errno));
+        }
+    } else if (S_ISREG(st.st_mode)) {
+        size = st.st_size;
+    } else {
+        FATAL("unknown file mode: %d\n", (int)st.st_mode);
+    }
+
+    return size;
+}
+
+static void calculate_rounds(uint64_t size, image *ctx)
+{
+    if (!size) {
+        FATAL("empty file?\n");
+    } else if (size % FEC_BLOCKSIZE) {
+        FATAL("file size %" PRIu64 " is not a multiple of %u bytes\n",
+            size, FEC_BLOCKSIZE);
+    }
+
+    ctx->inp_size = size;
+    ctx->blocks = fec_div_round_up(ctx->inp_size, FEC_BLOCKSIZE);
+    ctx->rounds = fec_div_round_up(ctx->blocks, ctx->rs_n);
+}
+
+static void mmap_image_load(const std::vector<int>& fds, image *ctx,
+        bool output_needed)
+{
+    if (fds.size() != 1) {
+        FATAL("multiple input files not supported with mmap\n");
+    }
+
+    int fd = fds.front();
+
+    calculate_rounds(get_size(fd), ctx);
+
+    /* check that we can memory map the file; on 32-bit platforms we are
+       limited to encoding at most 4 GiB files */
+    if (ctx->inp_size > SIZE_MAX) {
+        FATAL("cannot mmap %" PRIu64 " bytes\n", ctx->inp_size);
+    }
+
+    if (ctx->verbose) {
+        INFO("memory mapping '%s' (size %" PRIu64 ")\n", ctx->fec_filename,
+            ctx->inp_size);
+    }
+
+    int flags = PROT_READ;
+
+    if (ctx->inplace) {
+        flags |= PROT_WRITE;
+    }
+
+    void *p = mmap(NULL, (size_t)ctx->inp_size, flags, MAP_SHARED, fd, 0);
+
+    if (p == MAP_FAILED) {
+        FATAL("failed to mmap '%s' (size %" PRIu64 "): %s\n",
+            ctx->fec_filename, ctx->inp_size, strerror(errno));
+    }
+
+    ctx->inp_fd = fd;
+    ctx->input = (uint8_t *)p;
+
+    if (ctx->inplace) {
+        ctx->output = ctx->input;
+    } else if (output_needed) {
+        if (ctx->verbose) {
+            INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
+        }
+
+        ctx->output = new uint8_t[ctx->inp_size];
+
+        if (!ctx->output) {
+                FATAL("failed to allocate memory\n");
+        }
+
+        memcpy(ctx->output, ctx->input, ctx->inp_size);
+    }
+
+    /* fd is closed in mmap_image_free */
+}
+
+#ifndef IMAGE_NO_SPARSE
+static int process_chunk(void *priv, const void *data, int len)
+{
+    image *ctx = (image *)priv;
+    assert(len % FEC_BLOCKSIZE == 0);
+
+    if (data) {
+        memcpy(&ctx->input[ctx->pos], data, len);
+    }
+
+    ctx->pos += len;
+    return 0;
+}
+#endif
+
+static void file_image_load(const std::vector<int>& fds, image *ctx)
+{
+    uint64_t size = 0;
+#ifndef IMAGE_NO_SPARSE
+    std::vector<struct sparse_file *> files;
+#endif
+
+    for (auto fd : fds) {
+        uint64_t len = 0;
+
+#ifdef IMAGE_NO_SPARSE
+        if (ctx->sparse) {
+            FATAL("sparse files not supported\n");
+        }
+
+        len = get_size(fd);
+#else
+        struct sparse_file *file;
+
+        if (ctx->sparse) {
+            file = sparse_file_import(fd, false, false);
+        } else {
+            file = sparse_file_import_auto(fd, false, ctx->verbose);
+        }
+
+        if (!file) {
+            FATAL("failed to read file %s\n", ctx->fec_filename);
+        }
+
+        len = sparse_file_len(file, false, false);
+        files.push_back(file);
+#endif /* IMAGE_NO_SPARSE */
+
+        size += len;
+    }
+
+    calculate_rounds(size, ctx);
+
+    if (ctx->verbose) {
+        INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
+    }
+
+    ctx->input = new uint8_t[ctx->inp_size];
+
+    if (!ctx->input) {
+        FATAL("failed to allocate memory\n");
+    }
+
+    memset(ctx->input, 0, ctx->inp_size);
+    ctx->output = ctx->input;
+    ctx->pos = 0;
+
+#ifdef IMAGE_NO_SPARSE
+    for (auto fd : fds) {
+        uint64_t len = get_size(fd);
+
+        if (!android::base::ReadFully(fd, &ctx->input[ctx->pos], len)) {
+            FATAL("failed to read: %s\n", strerror(errno));
+        }
+
+        ctx->pos += len;
+        close(fd);
+    }
+#else
+    for (auto file : files) {
+        sparse_file_callback(file, false, false, process_chunk, ctx);
+        sparse_file_destroy(file);
+    }
+
+    for (auto fd : fds) {
+        close(fd);
+    }
+#endif
+}
+
+bool image_load(const std::vector<std::string>& filenames, image *ctx,
+        bool output_needed)
+{
+    assert(ctx->roots > 0 && ctx->roots < FEC_RSM);
+    ctx->rs_n = FEC_RSM - ctx->roots;
+
+    int flags = O_RDONLY;
+
+    if (ctx->inplace) {
+        flags = O_RDWR;
+    }
+
+    std::vector<int> fds;
+
+    for (const auto& fn : filenames) {
+        int fd = TEMP_FAILURE_RETRY(open(fn.c_str(), flags | O_LARGEFILE));
+
+        if (fd < 0) {
+            FATAL("failed to open file '%s': %s\n", fn.c_str(), strerror(errno));
+        }
+
+        fds.push_back(fd);
+    }
+
+    if (ctx->mmap) {
+        mmap_image_load(fds, ctx, output_needed);
+    } else {
+        file_image_load(fds, ctx);
+    }
+
+    return true;
+}
+
+bool image_save(const std::string& filename, image *ctx)
+{
+    if (ctx->inplace && ctx->mmap) {
+        return true; /* nothing to do */
+    }
+
+    /* TODO: support saving as a sparse file */
+    int fd = TEMP_FAILURE_RETRY(open(filename.c_str(),
+                O_WRONLY | O_CREAT | O_TRUNC, 0666));
+
+    if (fd < 0) {
+        FATAL("failed to open file '%s: %s'\n", filename.c_str(),
+            strerror(errno));
+    }
+
+    if (!android::base::WriteFully(fd, ctx->output, ctx->inp_size)) {
+        FATAL("failed to write to output: %s\n", strerror(errno));
+    }
+
+    close(fd);
+    return true;
+}
+
+static void mmap_image_ecc_new(image *ctx)
+{
+    if (ctx->verbose) {
+        INFO("mmaping '%s' (size %u)\n", ctx->fec_filename, ctx->fec_size);
+    }
+
+    int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
+                O_RDWR | O_CREAT, 0666));
+
+    if (fd < 0) {
+        FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
+            strerror(errno));
+    }
+
+    assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+    size_t fec_size = FEC_BLOCKSIZE + ctx->fec_size;
+
+    if (ftruncate(fd, fec_size) == -1) {
+        FATAL("failed to ftruncate file '%s': %s\n", ctx->fec_filename,
+            strerror(errno));
+    }
+
+    if (ctx->verbose) {
+        INFO("memory mapping '%s' (size %zu)\n", ctx->fec_filename, fec_size);
+    }
+
+    void *p = mmap(NULL, fec_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+    if (p == MAP_FAILED) {
+        FATAL("failed to mmap '%s' (size %zu): %s\n", ctx->fec_filename,
+            fec_size, strerror(errno));
+    }
+
+    ctx->fec_fd = fd;
+    ctx->fec_mmap_addr = (uint8_t *)p;
+    ctx->fec = ctx->fec_mmap_addr;
+}
+
+static void file_image_ecc_new(image *ctx)
+{
+    if (ctx->verbose) {
+        INFO("allocating %u bytes of memory\n", ctx->fec_size);
+    }
+
+    ctx->fec = new uint8_t[ctx->fec_size];
+
+    if (!ctx->fec) {
+        FATAL("failed to allocate %u bytes\n", ctx->fec_size);
+    }
+}
+
+bool image_ecc_new(const std::string& filename, image *ctx)
+{
+    assert(ctx->rounds > 0); /* image_load should be called first */
+
+    ctx->fec_filename = filename.c_str();
+    ctx->fec_size = ctx->rounds * ctx->roots * FEC_BLOCKSIZE;
+
+    if (ctx->mmap) {
+        mmap_image_ecc_new(ctx);
+    } else {
+        file_image_ecc_new(ctx);
+    }
+
+    return true;
+}
+
+bool image_ecc_load(const std::string& filename, image *ctx)
+{
+    int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_RDONLY));
+
+    if (fd < 0) {
+        FATAL("failed to open file '%s': %s\n", filename.c_str(),
+            strerror(errno));
+    }
+
+    if (lseek64(fd, -FEC_BLOCKSIZE, SEEK_END) < 0) {
+        FATAL("failed to seek to header in '%s': %s\n", filename.c_str(),
+            strerror(errno));
+    }
+
+    assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+
+    uint8_t header[FEC_BLOCKSIZE];
+    fec_header *p = (fec_header *)header;
+
+    if (!android::base::ReadFully(fd, header, sizeof(header))) {
+        FATAL("failed to read %zd bytes from '%s': %s\n", sizeof(header),
+            filename.c_str(), strerror(errno));
+    }
+
+    if (p->magic != FEC_MAGIC) {
+        FATAL("invalid magic in '%s': %08x\n", filename.c_str(), p->magic);
+    }
+
+    if (p->version != FEC_VERSION) {
+        FATAL("unsupported version in '%s': %u\n", filename.c_str(),
+            p->version);
+    }
+
+    if (p->size != sizeof(fec_header)) {
+        FATAL("unexpected header size in '%s': %u\n", filename.c_str(),
+            p->size);
+    }
+
+    if (p->roots == 0 || p->roots >= FEC_RSM) {
+        FATAL("invalid roots in '%s': %u\n", filename.c_str(), p->roots);
+    }
+
+    if (p->fec_size % p->roots || p->fec_size % FEC_BLOCKSIZE) {
+        FATAL("invalid length in '%s': %u\n", filename.c_str(), p->fec_size);
+    }
+
+    ctx->roots = (int)p->roots;
+    ctx->rs_n = FEC_RSM - ctx->roots;
+
+    calculate_rounds(p->inp_size, ctx);
+
+    if (!image_ecc_new(filename, ctx)) {
+        FATAL("failed to allocate ecc\n");
+    }
+
+    if (p->fec_size != ctx->fec_size) {
+        FATAL("inconsistent header in '%s'\n", filename.c_str());
+    }
+
+    if (lseek64(fd, 0, SEEK_SET) < 0) {
+        FATAL("failed to rewind '%s': %s", filename.c_str(), strerror(errno));
+    }
+
+    if (!ctx->mmap && !android::base::ReadFully(fd, ctx->fec, ctx->fec_size)) {
+        FATAL("failed to read %u bytes from '%s': %s\n", ctx->fec_size,
+            filename.c_str(), strerror(errno));
+    }
+
+    close(fd);
+
+    uint8_t hash[SHA256_DIGEST_LENGTH];
+    SHA256(ctx->fec, ctx->fec_size, hash);
+
+    if (memcmp(hash, p->hash, SHA256_DIGEST_LENGTH) != 0) {
+        FATAL("invalid ecc data\n");
+    }
+
+    return true;
+}
+
+bool image_ecc_save(image *ctx)
+{
+    assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+
+    uint8_t header[FEC_BLOCKSIZE];
+    uint8_t *p = header;
+
+    if (ctx->mmap) {
+        p = (uint8_t *)&ctx->fec_mmap_addr[ctx->fec_size];
+    }
+
+    memset(p, 0, FEC_BLOCKSIZE);
+
+    fec_header *f = (fec_header *)p;
+
+    f->magic = FEC_MAGIC;
+    f->version = FEC_VERSION;
+    f->size = sizeof(fec_header);
+    f->roots = ctx->roots;
+    f->fec_size = ctx->fec_size;
+    f->inp_size = ctx->inp_size;
+
+    SHA256(ctx->fec, ctx->fec_size, f->hash);
+
+    /* store a copy of the fec_header at the end of the header block */
+    memcpy(&p[sizeof(header) - sizeof(fec_header)], p, sizeof(fec_header));
+
+    if (!ctx->mmap) {
+        assert(ctx->fec_filename);
+
+        int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
+                    O_WRONLY | O_CREAT | O_TRUNC, 0666));
+
+        if (fd < 0) {
+            FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
+                strerror(errno));
+        }
+
+        if (!android::base::WriteFully(fd, ctx->fec, ctx->fec_size) ||
+            !android::base::WriteFully(fd, header, sizeof(header))) {
+            FATAL("failed to write to output: %s\n", strerror(errno));
+        }
+
+        close(fd);
+    }
+
+    return true;
+}
+
+static void * process(void *cookie)
+{
+    image_proc_ctx *ctx = (image_proc_ctx *)cookie;
+    ctx->func(ctx);
+    return NULL;
+}
+
+bool image_process(image_proc_func func, image *ctx)
+{
+    int threads = ctx->threads;
+
+    if (threads < IMAGE_MIN_THREADS) {
+        threads = sysconf(_SC_NPROCESSORS_ONLN);
+
+        if (threads < IMAGE_MIN_THREADS) {
+            threads = IMAGE_MIN_THREADS;
+        }
+    }
+
+    assert(ctx->rounds > 0);
+
+    if ((uint64_t)threads > ctx->rounds) {
+        threads = (int)ctx->rounds;
+    }
+    if (threads > IMAGE_MAX_THREADS) {
+        threads = IMAGE_MAX_THREADS;
+    }
+
+    if (ctx->verbose) {
+        INFO("starting %d threads to compute RS(255, %d)\n", threads,
+            ctx->rs_n);
+    }
+
+    pthread_t pthreads[threads];
+    image_proc_ctx args[threads];
+
+    uint64_t current = 0;
+    uint64_t end = ctx->rounds * ctx->rs_n * FEC_BLOCKSIZE;
+    uint64_t rs_blocks_per_thread =
+        fec_div_round_up(ctx->rounds * FEC_BLOCKSIZE, threads);
+
+    if (ctx->verbose) {
+        INFO("computing %" PRIu64 " codes per thread\n", rs_blocks_per_thread);
+    }
+
+    for (int i = 0; i < threads; ++i) {
+        args[i].func = func;
+        args[i].id = i;
+        args[i].ctx = ctx;
+        args[i].rv = 0;
+        args[i].fec_pos = current * ctx->roots;
+        args[i].start = current * ctx->rs_n;
+        args[i].end = (current + rs_blocks_per_thread) * ctx->rs_n;
+
+        args[i].rs = init_rs_char(FEC_PARAMS(ctx->roots));
+
+        if (!args[i].rs) {
+            FATAL("failed to initialize encoder for thread %d\n", i);
+        }
+
+        if (args[i].end > end) {
+            args[i].end = end;
+        } else if (i == threads && args[i].end + rs_blocks_per_thread *
+                                        ctx->rs_n > end) {
+            args[i].end = end;
+        }
+
+        if (ctx->verbose) {
+            INFO("thread %d: [%" PRIu64 ", %" PRIu64 ")\n",
+                i, args[i].start, args[i].end);
+        }
+
+        assert(args[i].start < args[i].end);
+        assert((args[i].end - args[i].start) % ctx->rs_n == 0);
+
+        if (pthread_create(&pthreads[i], NULL, process, &args[i]) != 0) {
+            FATAL("failed to create thread %d\n", i);
+        }
+
+        current += rs_blocks_per_thread;
+    }
+
+    ctx->rv = 0;
+
+    for (int i = 0; i < threads; ++i) {
+        if (pthread_join(pthreads[i], NULL) != 0) {
+            FATAL("failed to join thread %d: %s\n", i, strerror(errno));
+        }
+
+        ctx->rv += args[i].rv;
+
+        if (args[i].rs) {
+            free_rs_char(args[i].rs);
+            args[i].rs = NULL;
+        }
+    }
+
+    return true;
+}
diff --git a/verity/fec/image.h b/verity/fec/image.h
new file mode 100644
index 0000000..f0211fd
--- /dev/null
+++ b/verity/fec/image.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef __FEC_H__
+#define __FEC_H__
+
+#include <utils/Compat.h>
+#include <string>
+#include <vector>
+#include <fec/io.h>
+#include <fec/ecc.h>
+
+#define IMAGE_MIN_THREADS     1
+#define IMAGE_MAX_THREADS     128
+
+#define INFO(x...) \
+    fprintf(stderr, x);
+#define FATAL(x...) { \
+    fprintf(stderr, x); \
+    exit(1); \
+}
+
+#define unlikely(x)    __builtin_expect(!!(x), 0)
+
+struct image {
+    /* if true, decode file in place instead of creating a new output file */
+    bool inplace;
+    /* if true, use memory mapping instead of copying all input into memory */
+    bool mmap;
+    /* if true, assume input is a sparse file */
+    bool sparse;
+    /* if true, print more verbose information to stderr */
+    bool verbose;
+    const char *fec_filename;
+    int fec_fd;
+    int inp_fd;
+    /* the number of Reed-Solomon generator polynomial roots, also the number
+       of parity bytes generated for each N bytes in RS(M, N) */
+    int roots;
+    /* for RS(M, N), N = M - roots */
+    int rs_n;
+    int threads;
+    uint32_t fec_size;
+    uint64_t blocks;
+    uint64_t inp_size;
+    uint64_t pos;
+    uint64_t rounds;
+    uint64_t rv;
+    uint8_t *fec;
+    uint8_t *fec_mmap_addr;
+    uint8_t *input;
+    uint8_t *output;
+};
+
+struct image_proc_ctx;
+typedef void (*image_proc_func)(image_proc_ctx *);
+
+struct image_proc_ctx {
+    image_proc_func func;
+    int id;
+    image *ctx;
+    uint64_t rv;
+    uint64_t fec_pos;
+    uint64_t start;
+    uint64_t end;
+    void *rs;
+};
+
+extern bool image_load(const std::vector<std::string>& filename, image *ctx,
+        bool output_needed);
+extern bool image_save(const std::string& filename, image *ctx);
+
+extern bool image_ecc_new(const std::string& filename, image *ctx);
+extern bool image_ecc_load(const std::string& filename, image *ctx);
+extern bool image_ecc_save(image *ctx);
+
+extern bool image_process(image_proc_func f, image *ctx);
+
+extern void image_init(image *ctx);
+extern void image_free(image *ctx);
+
+inline uint8_t image_get_interleaved_byte(uint64_t i, image *ctx)
+{
+    uint64_t offset = fec_ecc_interleave(i, ctx->rs_n, ctx->rounds);
+
+    if (unlikely(offset >= ctx->inp_size)) {
+        return 0;
+    }
+
+    return ctx->input[offset];
+}
+
+inline void image_set_interleaved_byte(uint64_t i, image *ctx,
+        uint8_t value)
+{
+    uint64_t offset = fec_ecc_interleave(i, ctx->rs_n, ctx->rounds);
+
+    if (unlikely(offset >= ctx->inp_size)) {
+        assert(value == 0);
+    } else if (ctx->output && ctx->output[offset] != value) {
+        ctx->output[offset] = value;
+    }
+}
+
+#endif // __FEC_H__
diff --git a/verity/fec/main.cpp b/verity/fec/main.cpp
new file mode 100644
index 0000000..0675fd6
--- /dev/null
+++ b/verity/fec/main.cpp
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+extern "C" {
+    #include <fec.h>
+}
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <android-base/file.h>
+#include "image.h"
+
+enum {
+    MODE_ENCODE,
+    MODE_DECODE,
+    MODE_PRINTSIZE,
+    MODE_GETECCSTART,
+    MODE_GETVERITYSTART
+};
+
+static void encode_rs(struct image_proc_ctx *ctx)
+{
+    struct image *fcx = ctx->ctx;
+    int j;
+    uint8_t data[fcx->rs_n];
+    uint64_t i;
+
+    for (i = ctx->start; i < ctx->end; i += fcx->rs_n) {
+        for (j = 0; j < fcx->rs_n; ++j) {
+            data[j] = image_get_interleaved_byte(i + j, fcx);
+        }
+
+        encode_rs_char(ctx->rs, data, &fcx->fec[ctx->fec_pos]);
+        ctx->fec_pos += fcx->roots;
+    }
+}
+
+static void decode_rs(struct image_proc_ctx *ctx)
+{
+    struct image *fcx = ctx->ctx;
+    int j, rv;
+    uint8_t data[fcx->rs_n + fcx->roots];
+    uint64_t i;
+
+    assert(sizeof(data) == FEC_RSM);
+
+    for (i = ctx->start; i < ctx->end; i += fcx->rs_n) {
+        for (j = 0; j < fcx->rs_n; ++j) {
+            data[j] = image_get_interleaved_byte(i + j, fcx);
+        }
+
+        memcpy(&data[fcx->rs_n], &fcx->fec[ctx->fec_pos], fcx->roots);
+        rv = decode_rs_char(ctx->rs, data, NULL, 0);
+
+        if (rv < 0) {
+            FATAL("failed to recover [%" PRIu64 ", %" PRIu64 ")\n",
+                i, i + fcx->rs_n);
+        } else if (rv > 0) {
+            /* copy corrected data to output */
+            for (j = 0; j < fcx->rs_n; ++j) {
+                image_set_interleaved_byte(i + j, fcx, data[j]);
+            }
+
+            ctx->rv += rv;
+        }
+
+        ctx->fec_pos += fcx->roots;
+    }
+}
+
+static int usage()
+{
+    printf("fec: a tool for encoding and decoding files using RS(255, N).\n"
+           "\n"
+           "usage: fec <mode> [ <options> ] [ <data> <fec> [ <output> ] ]\n"
+           "mode:\n"
+           "  -e  --encode                      encode (default)\n"
+           "  -d  --decode                      decode\n"
+           "  -s, --print-fec-size=<data size>  print FEC size\n"
+           "  -E, --get-ecc-start=data          print ECC offset in data\n"
+           "  -V, --get-verity-start=data       print verity offset\n"
+           "options:\n"
+           "  -h                                show this help\n"
+           "  -v                                enable verbose logging\n"
+           "  -r, --roots=<bytes>               number of parity bytes\n"
+           "  -m, --mmap                        use memory mapping\n"
+           "  -j, --threads=<threads>           number of threads to use\n"
+           "  -S                                treat data as a sparse file\n"
+           "decoding options:\n"
+           "  -i, --inplace                     correct <data> in place\n"
+        );
+
+    return 1;
+}
+
+static uint64_t parse_arg(const char *arg, const char *name, uint64_t maxval)
+{
+    char* endptr;
+    errno = 0;
+
+    unsigned long long int value = strtoull(arg, &endptr, 0);
+
+    if (arg[0] == '\0' || *endptr != '\0' ||
+            (errno == ERANGE && value == ULLONG_MAX)) {
+        FATAL("invalid value of %s\n", name);
+    }
+    if (value > maxval) {
+        FATAL("value of roots too large (max. %" PRIu64 ")\n", maxval);
+    }
+
+    return (uint64_t)value;
+}
+
+static int print_size(image& ctx)
+{
+    /* output size including header */
+    printf("%" PRIu64 "\n", fec_ecc_get_size(ctx.inp_size, ctx.roots));
+    return 0;
+}
+
+static int get_start(int mode, const std::string& filename)
+{
+    fec::io fh(filename, O_RDONLY, FEC_VERITY_DISABLE);
+
+    if (!fh) {
+        FATAL("failed to open input\n");
+    }
+
+    if (mode == MODE_GETECCSTART) {
+        fec_ecc_metadata data;
+
+        if (!fh.get_ecc_metadata(data)) {
+            FATAL("no ecc data\n");
+        }
+
+        printf("%" PRIu64 "\n", data.start);
+    } else {
+        fec_verity_metadata data;
+
+        if (!fh.get_verity_metadata(data)) {
+            FATAL("no verity data\n");
+        }
+
+        printf("%" PRIu64 "\n", data.data_size);
+    }
+
+    return 0;
+}
+
+static int encode(image& ctx, const std::vector<std::string>& inp_filenames,
+        const std::string& fec_filename)
+{
+    if (ctx.inplace) {
+        FATAL("invalid parameters: inplace can only used when decoding\n");
+    }
+
+    if (!image_load(inp_filenames, &ctx, false)) {
+        FATAL("failed to read input\n");
+    }
+
+    if (!image_ecc_new(fec_filename, &ctx)) {
+        FATAL("failed to allocate ecc\n");
+    }
+
+    INFO("encoding RS(255, %d) to '%s' for input files:\n", ctx.rs_n,
+        fec_filename.c_str());
+
+    size_t n = 1;
+
+    for (const auto& fn : inp_filenames) {
+        INFO("\t%zu: '%s'\n", n++, fn.c_str());
+    }
+
+    if (ctx.verbose) {
+        INFO("\traw fec size: %u\n", ctx.fec_size);
+        INFO("\tblocks: %" PRIu64 "\n", ctx.blocks);
+        INFO("\trounds: %" PRIu64 "\n", ctx.rounds);
+    }
+
+    if (!image_process(encode_rs, &ctx)) {
+        FATAL("failed to process input\n");
+    }
+
+    if (!image_ecc_save(&ctx)) {
+        FATAL("failed to write output\n");
+    }
+
+    image_free(&ctx);
+    return 0;
+}
+
+static int decode(image& ctx, const std::vector<std::string>& inp_filenames,
+        const std::string& fec_filename, std::string& out_filename)
+{
+    const std::string& inp_filename = inp_filenames.front();
+
+    if (ctx.inplace && ctx.sparse) {
+        FATAL("invalid parameters: inplace cannot be used with sparse "
+            "files\n");
+    }
+
+    if (!image_ecc_load(fec_filename, &ctx) ||
+            !image_load(inp_filenames, &ctx, !out_filename.empty())) {
+        FATAL("failed to read input\n");
+    }
+
+    if (ctx.inplace) {
+        INFO("correcting '%s' using RS(255, %d) from '%s'\n",
+            inp_filename.c_str(), ctx.rs_n, fec_filename.c_str());
+
+        out_filename = inp_filename;
+    } else {
+        INFO("decoding '%s' to '%s' using RS(255, %d) from '%s'\n",
+            inp_filename.c_str(),
+            out_filename.empty() ? out_filename.c_str() : "<none>", ctx.rs_n,
+            fec_filename.c_str());
+    }
+
+    if (ctx.verbose) {
+        INFO("\traw fec size: %u\n", ctx.fec_size);
+        INFO("\tblocks: %" PRIu64 "\n", ctx.blocks);
+        INFO("\trounds: %" PRIu64 "\n", ctx.rounds);
+    }
+
+    if (!image_process(decode_rs, &ctx)) {
+        FATAL("failed to process input\n");
+    }
+
+    if (ctx.rv) {
+        INFO("corrected %" PRIu64 " errors\n", ctx.rv);
+    } else {
+        INFO("no errors found\n");
+    }
+
+    if (!out_filename.empty() && !image_save(out_filename, &ctx)) {
+        FATAL("failed to write output\n");
+    }
+
+    image_free(&ctx);
+    return 0;
+}
+
+int main(int argc, char **argv)
+{
+    std::string fec_filename;
+    std::string out_filename;
+    std::vector<std::string> inp_filenames;
+    int mode = MODE_ENCODE;
+    image ctx;
+
+    image_init(&ctx);
+    ctx.roots = FEC_DEFAULT_ROOTS;
+
+    while (1) {
+        const static struct option long_options[] = {
+            {"help", no_argument, 0, 'h'},
+            {"encode", no_argument, 0, 'e'},
+            {"decode", no_argument, 0, 'd'},
+            {"sparse", no_argument, 0, 'S'},
+            {"roots", required_argument, 0, 'r'},
+            {"inplace", no_argument, 0, 'i'},
+            {"mmap", no_argument, 0, 'm'},
+            {"threads", required_argument, 0, 'j'},
+            {"print-fec-size", required_argument, 0, 's'},
+            {"get-ecc-start", required_argument, 0, 'E'},
+            {"get-verity-start", required_argument, 0, 'V'},
+            {"verbose", no_argument, 0, 'v'},
+            {NULL, 0, 0, 0}
+        };
+        int c = getopt_long(argc, argv, "hedSr:imj:s:E:V:v", long_options, NULL);
+        if (c < 0) {
+            break;
+        }
+
+        switch (c) {
+        case 'h':
+            return usage();
+        case 'S':
+            ctx.sparse = true;
+            break;
+        case 'e':
+            if (mode != MODE_ENCODE) {
+                return usage();
+            }
+            break;
+        case 'd':
+            if (mode != MODE_ENCODE) {
+                return usage();
+            }
+            mode = MODE_DECODE;
+            break;
+        case 'r':
+            ctx.roots = (int)parse_arg(optarg, "roots", FEC_RSM);
+            break;
+        case 'i':
+            ctx.inplace = true;
+            break;
+        case 'm':
+            ctx.mmap = true;
+            break;
+        case 'j':
+            ctx.threads = (int)parse_arg(optarg, "threads", IMAGE_MAX_THREADS);
+            break;
+        case 's':
+            if (mode != MODE_ENCODE) {
+                return usage();
+            }
+            mode = MODE_PRINTSIZE;
+            ctx.inp_size = parse_arg(optarg, "print-fec-size", UINT64_MAX);
+            break;
+        case 'E':
+            if (mode != MODE_ENCODE) {
+                return usage();
+            }
+            mode = MODE_GETECCSTART;
+            inp_filenames.push_back(optarg);
+            break;
+        case 'V':
+            if (mode != MODE_ENCODE) {
+                return usage();
+            }
+            mode = MODE_GETVERITYSTART;
+            inp_filenames.push_back(optarg);
+            break;
+        case 'v':
+            ctx.verbose = true;
+            break;
+        case '?':
+            return usage();
+        default:
+            abort();
+        }
+    }
+
+    argc -= optind;
+    argv += optind;
+
+    assert(ctx.roots > 0 && ctx.roots < FEC_RSM);
+
+    /* check for input / output parameters */
+    if (mode == MODE_ENCODE) {
+        /* allow multiple input files */
+        for (int i = 0; i < (argc - 1); ++i) {
+            inp_filenames.push_back(argv[i]);
+        }
+
+        if (inp_filenames.empty()) {
+            return usage();
+        }
+
+        /* the last one is the output file */
+        fec_filename = argv[argc - 1];
+    } else if (mode == MODE_DECODE) {
+        if (argc < 2 || argc > 3) {
+            return usage();
+        } else if (argc == 3) {
+            if (ctx.inplace) {
+                return usage();
+            }
+            out_filename = argv[2];
+        }
+
+        inp_filenames.push_back(argv[0]);
+        fec_filename = argv[1];
+    }
+
+    switch (mode) {
+    case MODE_PRINTSIZE:
+        return print_size(ctx);
+    case MODE_GETECCSTART:
+    case MODE_GETVERITYSTART:
+        return get_start(mode, inp_filenames.front());
+    case MODE_ENCODE:
+        return encode(ctx, inp_filenames, fec_filename);
+    case MODE_DECODE:
+        return decode(ctx, inp_filenames, fec_filename, out_filename);
+    default:
+        abort();
+    }
+
+    return 1;
+}
diff --git a/verity/fec/tests/fec.py b/verity/fec/tests/fec.py
new file mode 100644
index 0000000..71a1834
--- /dev/null
+++ b/verity/fec/tests/fec.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2015 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.
+
+import math
+import os
+import random
+import shutil
+import subprocess
+import sys
+import tempfile
+
+blocksize = 4096
+roots = 2
+
+def corrupt(image, offset, length):
+    print "corrupting %d bytes at offset %d" % (length, offset)
+    f = os.open(image, os.O_WRONLY)
+    os.lseek(f, offset, os.SEEK_SET)
+    os.write(f, os.urandom(length))
+    os.close(f)
+
+def corruptmax(image, roots):
+    size = os.stat(image).st_size
+
+    blocks = int(math.ceil(float(size) / blocksize))
+    rounds = int(math.ceil(float(blocks) / (255 - roots)))
+
+    max_errors = int(math.floor(rounds * roots / 2)) * blocksize
+    offset = random.randrange(0, size - max_errors)
+
+    corrupt(image, offset, max_errors)
+
+def encode(image, fec, roots):
+    if subprocess.call([ "fec", "--roots= " + str(roots), image, fec ]) != 0:
+        raise Exception("encoding failed")
+
+def decode(image, fec, output):
+    return subprocess.call([ "fec", "--decode", image, fec, output ])
+
+def compare(a, b):
+    return subprocess.call([ "cmp", "-s", a, b ])
+
+def simg2img(image, output):
+    print "creating a non-sparse copy of '%s' to '%s'" % (image, output)
+    if subprocess.call([ "simg2img", image, output]) != 0:
+        raise Exception("simg2img failed")
+
+def main(argv):
+    image = argv[0]
+
+    temp_img = tempfile.NamedTemporaryFile()
+    temp_cor = tempfile.NamedTemporaryFile()
+    temp_fec = tempfile.NamedTemporaryFile()
+    temp_out = tempfile.NamedTemporaryFile()
+
+    simg2img(image, temp_img.name)
+    simg2img(image, temp_cor.name)
+
+    encode(image, temp_fec.name, roots)
+    corruptmax(temp_cor.name, roots)
+
+    if decode(temp_cor.name, temp_fec.name, temp_out.name) != 0:
+        raise Exception("FAILED: failed to correct maximum expected errors")
+
+    if compare(temp_img.name, temp_out.name) != 0:
+        raise Exception("FAILED: corrected file not identical")
+    else:
+        print "corrected content matches original"
+
+    corrupt(temp_cor.name, 0, blocksize)
+
+    if decode(temp_cor.name, temp_fec.name, temp_out.name) == 0:
+        raise Exception("FAILED: corrected more than maximum number of errors?")
+
+    print "PASSED"
+
+if __name__ == '__main__':
+    main(sys.argv[1:])
diff --git a/verity/generate_verity_key.c b/verity/generate_verity_key.c
new file mode 100644
index 0000000..c598afb
--- /dev/null
+++ b/verity/generate_verity_key.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2013 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 _GNU_SOURCE  /* needed for asprintf */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <crypto_utils/android_pubkey.h>
+
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+
+static int write_public_keyfile(RSA *private_key, const char *private_key_path)
+{
+    uint8_t key_data[ANDROID_PUBKEY_ENCODED_SIZE];
+    BIO *bfile = NULL;
+    char *path = NULL;
+    int ret = -1;
+
+    if (asprintf(&path, "%s.pub", private_key_path) < 0)
+        goto out;
+
+    if (!android_pubkey_encode(private_key, key_data, sizeof(key_data)))
+        goto out;
+
+    bfile = BIO_new_file(path, "w");
+    if (!bfile)
+        goto out;
+
+    BIO_write(bfile, key_data, sizeof(key_data));
+    BIO_flush(bfile);
+
+    ret = 0;
+out:
+    BIO_free_all(bfile);
+    free(path);
+    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;
+    FILE *f = NULL;
+    RSA* rsa = RSA_new();
+    BIGNUM* exponent = BN_new();
+    EVP_PKEY* pkey = EVP_PKEY_new();
+
+    if (!pkey || !exponent || !rsa) {
+        printf("Failed to allocate key\n");
+        goto out;
+    }
+
+    BN_set_word(exponent, RSA_F4);
+    RSA_generate_key_ex(rsa, 2048, exponent, NULL);
+    EVP_PKEY_set1_RSA(pkey, rsa);
+
+    f = fopen(file, "w");
+    if (!f) {
+        printf("Failed to open '%s'\n", file);
+        goto out;
+    }
+
+    if (!PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL)) {
+        printf("Failed to write key\n");
+        goto out;
+    }
+
+    if (write_public_keyfile(rsa, file) < 0) {
+        printf("Failed to write public key\n");
+        goto out;
+    }
+
+    ret = 0;
+
+out:
+    if (f)
+        fclose(f);
+    EVP_PKEY_free(pkey);
+    RSA_free(rsa);
+    BN_free(exponent);
+    return ret;
+}
+
+static void usage(){
+    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) {
+        return generate_key(argv[1]);
+    } else if (argc == 4 && !strcmp(argv[1], "-convert")) {
+        return convert_x509(argv[2], argv[3]);
+    } else {
+        usage();
+        exit(-1);
+    }
+}
diff --git a/verity/verify_boot_signature.c b/verity/verify_boot_signature.c
new file mode 100644
index 0000000..b706e3a
--- /dev/null
+++ b/verity/verify_boot_signature.c
@@ -0,0 +1,429 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/crypto.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_signer b/verity/verity_signer
new file mode 100755
index 0000000..a4f337a
--- /dev/null
+++ b/verity/verity_signer
@@ -0,0 +1,8 @@
+#! /bin/sh
+
+# Start-up script for VeritySigner
+
+VERITYSIGNER_HOME=`dirname "$0"`
+VERITYSIGNER_HOME=`dirname "$VERITYSIGNER_HOME"`
+
+java -Xmx512M -jar "$VERITYSIGNER_HOME"/framework/VeritySigner.jar "$@"
\ No newline at end of file
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 "$@"