Merge "Perf profile collection daemon."
diff --git a/ext4_utils/Android.mk b/ext4_utils/Android.mk
index c5684f9..9f56cca 100644
--- a/ext4_utils/Android.mk
+++ b/ext4_utils/Android.mk
@@ -36,6 +36,7 @@
 LOCAL_SRC_FILES := make_ext4fs_main.c canned_fs_config.c
 LOCAL_MODULE := make_ext4fs
 LOCAL_STATIC_LIBRARIES += \
+    libcutils \
     libext4_utils_host \
     libsparse_host \
     libz
@@ -52,12 +53,19 @@
 # -- All host/targets excluding windows
 #
 
+libext4_utils_src_files += \
+    ext4_crypt.cpp \
+    e4crypt_static.c \
+    unencrypted_properties.cpp
+
 ifneq ($(HOST_OS),windows)
 
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(libext4_utils_src_files)
 LOCAL_MODULE := libext4_utils
+LOCAL_C_INCLUDES += system/core/logwrapper/include
 LOCAL_SHARED_LIBRARIES := \
+    libcutils \
     libselinux \
     libsparse \
     libz
@@ -65,10 +73,10 @@
 
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(libext4_utils_src_files)
+LOCAL_SRC_FILES := $(libext4_utils_src_files) \
+    ext4_crypt_init_extensions.cpp
 LOCAL_MODULE := libext4_utils_static
-LOCAL_STATIC_LIBRARIES += \
-    libselinux \
+LOCAL_STATIC_LIBRARIES := \
     libsparse_static
 include $(BUILD_STATIC_LIBRARY)
 
@@ -77,6 +85,7 @@
 LOCAL_SRC_FILES := make_ext4fs_main.c canned_fs_config.c
 LOCAL_MODULE := make_ext4fs
 LOCAL_SHARED_LIBRARIES := \
+    libcutils \
     libext4_utils \
     libselinux \
     libz
@@ -143,4 +152,3 @@
 include $(BUILD_PREBUILT)
 
 endif
-
diff --git a/ext4_utils/contents.c b/ext4_utils/contents.c
index 3144de9..8b2b0fd 100644
--- a/ext4_utils/contents.c
+++ b/ext4_utils/contents.c
@@ -267,6 +267,7 @@
  */
 static size_t xattr_free_space(struct ext4_xattr_entry *entry, char *end)
 {
+        end -= sizeof(uint32_t); /* Required four null bytes */
 	while(!IS_LAST_ENTRY(entry) && (((char *) entry) < end)) {
 		end   -= EXT4_XATTR_SIZE(le32_to_cpu(entry->e_value_size));
 		entry  = EXT4_XATTR_NEXT(entry);
diff --git a/ext4_utils/e4crypt_static.c b/ext4_utils/e4crypt_static.c
new file mode 100644
index 0000000..1a62ce4
--- /dev/null
+++ b/ext4_utils/e4crypt_static.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2015 Google, Inc.
+ */
+
+#define TAG "ext4_utils"
+
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/xattr.h>
+#include <sys/syscall.h>
+#include <sys/stat.h>
+
+#include <cutils/klog.h>
+
+#include "ext4_crypt.h"
+
+/* keyring keyctl commands */
+#define KEYCTL_SETPERM        5 /* set permissions for a key in a keyring */
+#define KEYCTL_UNLINK         9 /* unlink a key from a keyring */
+#define KEYCTL_SEARCH        10 /* search for a key in a keyring */
+
+#define XATTR_NAME_ENCRYPTION_POLICY "encryption.policy"
+#define EXT4_KEYREF_DELIMITER ((char)'.')
+
+/* Validate that all path items are available and accessible. */
+static int is_path_valid(const char *path)
+{
+    if (access(path, W_OK)) {
+        KLOG_ERROR(TAG, "Can't access %s: %s\n",strerror(errno), path);
+        return 0;
+    }
+
+    return 1;
+}
+
+/* Checks whether the policy provided is valid */
+static int is_keyref_valid(const char *keyref)
+{
+    char *period = 0;
+    size_t key_location_len = 0;
+
+    /* Key ref must have a key and location delimiter character. */
+    period = strchr(keyref, EXT4_KEYREF_DELIMITER);
+    if (!period) {
+        return 0;
+    }
+
+    /* period must be >= keyref. */
+    key_location_len = period - keyref;
+
+    if (strncmp(keyref, "@t", key_location_len) == 0 ||
+        strncmp(keyref, "@p", key_location_len) == 0 ||
+        strncmp(keyref, "@s", key_location_len) == 0 ||
+        strncmp(keyref, "@u", key_location_len) == 0 ||
+        strncmp(keyref, "@g", key_location_len) == 0 ||
+        strncmp(keyref, "@us", key_location_len) == 0)
+        return 1;
+
+    return 0;
+}
+
+static int is_dir_empty(const char *dirname)
+{
+    int n = 0;
+    struct dirent *d;
+    DIR *dir;
+
+    dir = opendir(dirname);
+    while ((d = readdir(dir)) != NULL) {
+        if (strcmp(d->d_name, "lost+found") == 0) {
+            // Skip lost+found directory
+        } else if (++n > 2) {
+            break;
+        }
+    }
+    closedir(dir);
+    return n <= 2;
+}
+
+int do_policy_set(const char *directory, const char *policy)
+{
+    struct stat st;
+    ssize_t ret;
+
+    if (!is_keyref_valid(policy)) {
+        KLOG_ERROR(TAG, "Policy has invalid format.\n");
+        return -EINVAL;
+    }
+
+    if (!is_path_valid(directory)) {
+        return -EINVAL;
+    }
+
+    stat(directory, &st);
+    if (!S_ISDIR(st.st_mode)) {
+        KLOG_ERROR(TAG, "Can only set policy on a directory (%s)\n", directory);
+        return -EINVAL;
+    }
+
+    if (!is_dir_empty(directory)) {
+        KLOG_ERROR(TAG, "Can only set policy on an empty directory (%s)\n", directory);
+        return -EINVAL;
+    }
+
+    ret = lsetxattr(directory, XATTR_NAME_ENCRYPTION_POLICY, policy,
+                    strlen(policy), 0);
+
+    if (ret) {
+        KLOG_ERROR(TAG, "Failed to set encryption policy for %s: %s\n",
+                   directory, strerror(errno));
+        return -EINVAL;
+    }
+
+    KLOG_INFO(TAG, "Encryption policy for %s is set to %s\n", directory, policy);
+    return 0;
+}
+
+static long keyctl(int cmd, ...)
+{
+    va_list va;
+    unsigned long arg2, arg3, arg4, arg5;
+
+    va_start(va, cmd);
+    arg2 = va_arg(va, unsigned long);
+    arg3 = va_arg(va, unsigned long);
+    arg4 = va_arg(va, unsigned long);
+    arg5 = va_arg(va, unsigned long);
+    va_end(va);
+    return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);
+}
+
+key_serial_t add_key(const char *type,
+                     const char *description,
+                     const void *payload,
+                     size_t plen,
+                     key_serial_t ringid)
+{
+    return syscall(__NR_add_key, type, description, payload, plen, ringid);
+}
+
+long keyctl_setperm(key_serial_t id, int permissions)
+{
+    return keyctl(KEYCTL_SETPERM, id, permissions);
+}
diff --git a/ext4_utils/ext4_crypt.cpp b/ext4_utils/ext4_crypt.cpp
new file mode 100644
index 0000000..bb57332
--- /dev/null
+++ b/ext4_utils/ext4_crypt.cpp
@@ -0,0 +1,120 @@
+#define TAG "ext4_utils"
+
+#include "ext4_crypt.h"
+
+#include <string>
+#include <fstream>
+#include <map>
+
+#include <errno.h>
+#include <sys/mount.h>
+
+#include <cutils/klog.h>
+#include <cutils/properties.h>
+
+#include "unencrypted_properties.h"
+
+namespace {
+    std::map<std::string, std::string> s_password_store;
+}
+
+bool e4crypt_non_default_key(const char* dir)
+{
+    int type = e4crypt_get_password_type(dir);
+
+    // ext4enc:TODO Use consts, not 1 here
+    return type != -1 && type != 1;
+}
+
+int e4crypt_get_password_type(const char* path)
+{
+    UnencryptedProperties props(path);
+    if (props.Get<std::string>(properties::key).empty()) {
+        KLOG_INFO(TAG, "No master key, so not ext4enc\n");
+        return -1;
+    }
+
+    return props.Get<int>(properties::type, 1);
+}
+
+int e4crypt_change_password(const char* path, int crypt_type,
+                            const char* password)
+{
+    // ext4enc:TODO Encrypt master key with password securely. Store hash of
+    // master key for validation
+    UnencryptedProperties props(path);
+    if (   props.Set(properties::password, password)
+        && props.Set(properties::type, crypt_type))
+        return 0;
+    return -1;
+}
+
+int e4crypt_crypto_complete(const char* path)
+{
+    KLOG_INFO(TAG, "ext4 crypto complete called on %s\n", path);
+    if (UnencryptedProperties(path).Get<std::string>(properties::key).empty()) {
+        KLOG_INFO(TAG, "No master key, so not ext4enc\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+int e4crypt_check_passwd(const char* path, const char* password)
+{
+    UnencryptedProperties props(path);
+    if (props.Get<std::string>(properties::key).empty()) {
+        KLOG_INFO(TAG, "No master key, so not ext4enc\n");
+        return -1;
+    }
+
+    auto actual_password = props.Get<std::string>(properties::password);
+
+    if (actual_password == password) {
+        s_password_store[path] = password;
+        return 0;
+    } else {
+        return -1;
+    }
+}
+
+int e4crypt_restart(const char* path)
+{
+    int rc = 0;
+
+    KLOG_INFO(TAG, "ext4 restart called on %s\n", path);
+    property_set("vold.decrypt", "trigger_reset_main");
+    KLOG_INFO(TAG, "Just asked init to shut down class main\n");
+    sleep(2);
+
+    std::string tmp_path = std::string() + path + "/tmp_mnt";
+
+    // ext4enc:TODO add retry logic
+    rc = umount(tmp_path.c_str());
+    if (rc) {
+        KLOG_ERROR(TAG, "umount %s failed with rc %d, msg %s\n",
+                   tmp_path.c_str(), rc, strerror(errno));
+        return rc;
+    }
+
+    // ext4enc:TODO add retry logic
+    rc = umount(path);
+    if (rc) {
+        KLOG_ERROR(TAG, "umount %s failed with rc %d, msg %s\n",
+                   path, rc, strerror(errno));
+        return rc;
+    }
+
+    return 0;
+}
+
+const char* e4crypt_get_password(const char* path)
+{
+    // ext4enc:TODO scrub password after timeout
+    auto i = s_password_store.find(path);
+    if (i == s_password_store.end()) {
+        return 0;
+    } else {
+        return i->second.c_str();
+    }
+}
diff --git a/ext4_utils/ext4_crypt.h b/ext4_utils/ext4_crypt.h
new file mode 100644
index 0000000..cc69273
--- /dev/null
+++ b/ext4_utils/ext4_crypt.h
@@ -0,0 +1,50 @@
+#include <stdbool.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+// These functions assume they are being called from init
+// They will not operate properly outside of init
+int e4crypt_install_keyring();
+int e4crypt_install_key(const char* dir);
+int e4crypt_create_device_key(const char* dir,
+                              int ensure_dir_exists(const char* dir));
+
+// General functions
+bool e4crypt_non_default_key(const char* dir);
+int e4crypt_set_directory_policy(const char* dir);
+int e4crypt_main(int argc, char* argv[]);
+int e4crypt_change_password(const char* path, int crypt_type,
+                            const char* password);
+int e4crypt_get_password_type(const char* path);
+int e4crypt_crypto_complete(const char* dir);
+int e4crypt_check_passwd(const char* dir, const char* password);
+const char* e4crypt_get_password(const char* dir);
+int e4crypt_restart(const char* dir);
+
+// Key functions. ext4enc:TODO Move to own file
+
+// ext4enc:TODO - get these keyring standard definitions from proper system file
+// keyring serial number type
+typedef int32_t key_serial_t;
+
+// special process keyring shortcut IDs
+#define KEY_SPEC_THREAD_KEYRING       -1 // key ID for thread-specific keyring
+#define KEY_SPEC_PROCESS_KEYRING      -2 // key ID for process-specific keyring
+#define KEY_SPEC_SESSION_KEYRING      -3 // key ID for session-specific keyring
+#define KEY_SPEC_USER_KEYRING         -4 // key ID for UID-specific keyring
+#define KEY_SPEC_USER_SESSION_KEYRING -5 // key ID for UID-session keyring
+#define KEY_SPEC_GROUP_KEYRING        -6 // key ID for GID-specific keyring
+
+key_serial_t add_key(const char *type,
+                     const char *description,
+                     const void *payload,
+                     size_t plen,
+                     key_serial_t ringid);
+
+long keyctl_setperm(key_serial_t id, int permissions);
+
+// Set policy on directory
+int do_policy_set(const char *directory, const char *policy);
+
+__END_DECLS
diff --git a/ext4_utils/ext4_crypt_init_extensions.cpp b/ext4_utils/ext4_crypt_init_extensions.cpp
new file mode 100644
index 0000000..1585911
--- /dev/null
+++ b/ext4_utils/ext4_crypt_init_extensions.cpp
@@ -0,0 +1,269 @@
+#define TAG "ext4_utils"
+
+#include "ext4_crypt.h"
+
+#include <string>
+#include <fstream>
+#include <iomanip>
+#include <sstream>
+
+#include <errno.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+
+#include <cutils/klog.h>
+#include <cutils/properties.h>
+#include <cutils/sockets.h>
+
+#include "unencrypted_properties.h"
+
+// ext4enc:TODO Include structure from somewhere sensible
+// MUST be in sync with ext4_crypto.c in kernel
+#define EXT4_MAX_KEY_SIZE 76
+struct ext4_encryption_key {
+        uint32_t mode;
+        char raw[EXT4_MAX_KEY_SIZE];
+        uint32_t size;
+};
+
+static const std::string keyring = "@s";
+static const std::string arbitrary_sequence_number = "42";
+
+static key_serial_t device_keyring = -1;
+
+static std::string vold_command(std::string const& command)
+{
+    KLOG_INFO(TAG, "Running command %s\n", command.c_str());
+    int sock = socket_local_client("vold",
+                                   ANDROID_SOCKET_NAMESPACE_RESERVED,
+                                   SOCK_STREAM);
+
+    if (sock < 0) {
+        KLOG_INFO(TAG, "Cannot open vold, failing command\n");
+        return "";
+    }
+
+    class CloseSocket
+    {
+        int sock_;
+    public:
+        CloseSocket(int sock) : sock_(sock) {}
+        ~CloseSocket() { close(sock_); }
+    };
+
+    CloseSocket cs(sock);
+
+    // Use arbitrary sequence number. This should only be used when the
+    // framework is down, so this is (mostly) OK.
+    std::string actual_command = arbitrary_sequence_number + " " + command;
+    if (write(sock, actual_command.c_str(), actual_command.size() + 1) < 0) {
+        KLOG_ERROR(TAG, "Cannot write command\n");
+        return "";
+    }
+
+    while (1) {
+        struct timeval to;
+        to.tv_sec = 10;
+        to.tv_usec = 0;
+
+        fd_set read_fds;
+        FD_ZERO(&read_fds);
+        FD_SET(sock, &read_fds);
+
+        int rc = select(sock + 1, &read_fds, NULL, NULL, &to);
+        if (rc < 0) {
+            KLOG_ERROR(TAG, "Error in select %s\n", strerror(errno));
+            return "";
+        } else if (!rc) {
+            KLOG_ERROR(TAG, "Timeout\n");
+            return "";
+        } else if (FD_ISSET(sock, &read_fds)) {
+            char buffer[4096];
+            memset(buffer, 0, sizeof(buffer));
+            rc = read(sock, buffer, sizeof(buffer));
+            if (rc <= 0) {
+                if (rc == 0) {
+                    KLOG_ERROR(TAG, "Lost connection to Vold - did it crash?\n");
+                } else {
+                    KLOG_ERROR(TAG, "Error reading data (%s)\n", strerror(errno));
+                }
+                return "";
+            }
+
+            // We don't truly know that this is the correct result. However,
+            // since this will only be used when the framework is down,
+            // it should be OK unless someone is running vdc at the same time.
+            // Worst case we force a reboot in the very rare synchronization
+            // error
+            return std::string(buffer, rc);
+        }
+    }
+}
+
+int e4crypt_create_device_key(const char* dir,
+                              int ensure_dir_exists(const char*))
+{
+    // Make sure folder exists. Use make_dir to set selinux permissions.
+    KLOG_INFO(TAG, "Creating test device key\n");
+    UnencryptedProperties props(dir);
+    if (ensure_dir_exists(props.GetPath().c_str())) {
+        KLOG_ERROR(TAG, "Failed to create %s with error %s\n",
+                   props.GetPath().c_str(), strerror(errno));
+        return -1;
+    }
+
+    if (props.Get<std::string>(properties::key).empty()) {
+        // Create new key since it doesn't already exist
+        std::ifstream urandom("/dev/urandom", std::ifstream::binary);
+        if (!urandom) {
+            KLOG_ERROR(TAG, "Failed to open /dev/urandom\n");
+            return -1;
+        }
+
+        // ext4enc:TODO Don't hardcode 32
+        std::string key_material(32, '\0');
+        urandom.read(&key_material[0], key_material.length());
+        if (!urandom) {
+            KLOG_ERROR(TAG, "Failed to read random bytes\n");
+            return -1;
+        }
+
+        if (!props.Set(properties::key, key_material)) {
+            KLOG_ERROR(TAG, "Failed to write key material\n");
+            return -1;
+        }
+    }
+
+    if (!props.Remove(properties::ref)) {
+        KLOG_ERROR(TAG, "Failed to remove key ref\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+int e4crypt_install_keyring()
+{
+    device_keyring = add_key("keyring",
+                             "e4crypt",
+                             0,
+                             0,
+                             KEY_SPEC_SESSION_KEYRING);
+
+    if (device_keyring == -1) {
+        KLOG_ERROR(TAG, "Failed to create keyring\n");
+        return -1;
+    }
+
+    KLOG_INFO(TAG, "Keyring created wth id %d in process %d\n",
+              device_keyring, getpid());
+
+    // ext4enc:TODO set correct permissions
+    long result = keyctl_setperm(device_keyring, 0x3f3f3f3f);
+    if (result) {
+        KLOG_ERROR(TAG, "KEYCTL_SETPERM failed with error %ld\n", result);
+        return -1;
+    }
+
+    return 0;
+}
+
+int e4crypt_install_key(const char* dir)
+{
+    UnencryptedProperties props(dir);
+    auto key = props.Get<std::string>(properties::key);
+
+    // Get password to decrypt as needed
+    if (e4crypt_non_default_key(dir)) {
+        std::string result = vold_command("cryptfs getpw");
+        // result is either
+        // 200 0 -1
+        // or
+        // 200 0 {{sensitive}} 0001020304
+        // where 0001020304 is hex encoding of password
+        std::istringstream i(result);
+        std::string bit;
+        i >> bit;
+        if (bit != "200") {
+            KLOG_ERROR(TAG, "Expecting 200\n");
+            return -1;
+        }
+
+        i >> bit;
+        if (bit != arbitrary_sequence_number) {
+            KLOG_ERROR(TAG, "Expecting %s\n", arbitrary_sequence_number.c_str());
+            return -1;
+        }
+
+        i >> bit;
+        if (bit != "{{sensitive}}") {
+            KLOG_INFO(TAG, "Not encrypted\n");
+            return -1;
+        }
+
+        i >> bit;
+    }
+
+    // Add key to keyring
+    ext4_encryption_key ext4_key = {0, {0}, 0};
+    if (key.length() > sizeof(ext4_key.raw)) {
+        KLOG_ERROR(TAG, "Key too long\n");
+        return -1;
+    }
+
+    ext4_key.mode = 0;
+    memcpy(ext4_key.raw, &key[0], key.length());
+    ext4_key.size = key.length();
+
+    // ext4enc:TODO Use better reference not 1234567890
+    key_serial_t key_id = add_key("logon", "ext4-key:1234567890",
+                                  (void*)&ext4_key, sizeof(ext4_key),
+                                  device_keyring);
+
+    if (key_id == -1) {
+        KLOG_ERROR(TAG, "Failed to insert key into keyring with error %s\n",
+                   strerror(errno));
+        return -1;
+    }
+
+    KLOG_INFO(TAG, "Added key %d to keyring %d in process %d\n",
+              key_id, device_keyring, getpid());
+
+    // ext4enc:TODO set correct permissions
+    long result = keyctl_setperm(key_id, 0x3f3f3f3f);
+    if (result) {
+        KLOG_ERROR(TAG, "KEYCTL_SETPERM failed with error %ld\n", result);
+        return -1;
+    }
+
+    // Save reference to key so we can set policy later
+    if (!props.Set(properties::ref, "ext4-key:1234567890")) {
+        KLOG_ERROR(TAG, "Cannot save key reference\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+int e4crypt_set_directory_policy(const char* dir)
+{
+    // Only set policy on first level /data directories
+    // To make this less restrictive, consider using a policy file.
+    // However this is overkill for as long as the policy is simply
+    // to apply a global policy to all /data folders created via makedir
+    if (!dir || strncmp(dir, "/data/", 6) || strchr(dir + 6, '/')) {
+        return 0;
+    }
+
+    UnencryptedProperties props("/data");
+    std::string ref = props.Get<std::string>(properties::ref);
+    std::string policy = keyring + "." + ref;
+    KLOG_INFO(TAG, "Setting policy %s\n", policy.c_str());
+    int result = do_policy_set(dir, policy.c_str());
+    if (result) {
+        KLOG_ERROR(TAG, "Setting policy on %s failed!\n", dir);
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/ext4_utils/mkuserimg.sh b/ext4_utils/mkuserimg.sh
index e2bc33f..3a6006e 100755
--- a/ext4_utils/mkuserimg.sh
+++ b/ext4_utils/mkuserimg.sh
@@ -5,7 +5,7 @@
 function usage() {
 cat<<EOT
 Usage:
-mkuserimg.sh [-s] SRC_DIR OUTPUT_FILE EXT_VARIANT MOUNT_POINT SIZE
+mkuserimg.sh [-s] SRC_DIR OUTPUT_FILE EXT_VARIANT MOUNT_POINT SIZE [-j <journal_size>]
              [-T TIMESTAMP] [-C FS_CONFIG] [-B BLOCK_LIST_FILE] [-L LABEL] [FILE_CONTEXTS]
 EOT
 }
@@ -33,6 +33,16 @@
 SIZE=$5
 shift; shift; shift; shift; shift
 
+JOURNAL_FLAGS=
+if [ "$1" = "-j" ]; then
+  if [ "$2" = "0" ]; then
+    JOURNAL_FLAGS="-J"
+  else
+    JOURNAL_FLAGS="-j $2"
+  fi
+  shift; shift
+fi
+
 TIMESTAMP=-1
 if [[ "$1" == "-T" ]]; then
   TIMESTAMP=$2
@@ -88,7 +98,7 @@
   OPT="$OPT -L $LABEL"
 fi
 
-MAKE_EXT4FS_CMD="make_ext4fs $ENABLE_SPARSE_IMAGE -T $TIMESTAMP $OPT -l $SIZE -a $MOUNT_POINT $OUTPUT_FILE $SRC_DIR"
+MAKE_EXT4FS_CMD="make_ext4fs $ENABLE_SPARSE_IMAGE -T $TIMESTAMP $OPT -l $SIZE $JOURNAL_FLAGS -a $MOUNT_POINT $OUTPUT_FILE $SRC_DIR"
 echo $MAKE_EXT4FS_CMD
 $MAKE_EXT4FS_CMD
 if [ $? -ne 0 ]; then
diff --git a/ext4_utils/unencrypted_properties.cpp b/ext4_utils/unencrypted_properties.cpp
new file mode 100644
index 0000000..bef7c57
--- /dev/null
+++ b/ext4_utils/unencrypted_properties.cpp
@@ -0,0 +1,86 @@
+#include "unencrypted_properties.h"
+
+#include <sys/stat.h>
+
+namespace properties {
+    const char* key = "key";
+    const char* ref = "ref";
+    const char* type = "type";
+    const char* password = "password";
+}
+
+namespace
+{
+    const char* unencrypted_folder = "unencrypted";
+}
+
+UnencryptedProperties::UnencryptedProperties(const char* device)
+  : folder_(std::string() + device + "/" + unencrypted_folder)
+{
+}
+
+UnencryptedProperties::UnencryptedProperties()
+{
+}
+
+template<> std::string UnencryptedProperties::Get(const char* name,
+                                      std::string default_value)
+{
+    if (!OK()) return default_value;
+    std::ifstream i(folder_ + "/" + name, std::ios::binary);
+    if (!i) {
+        return default_value;
+    }
+
+    i.seekg(0, std::ios::end);
+    int length = i.tellg();
+    i.seekg(0, std::ios::beg);
+    if (length == -1) {
+        return default_value;
+    }
+
+    std::string s(length, 0);
+    i.read(&s[0], length);
+    if (!i) {
+        return default_value;
+    }
+
+    return s;
+}
+
+template<> bool UnencryptedProperties::Set(const char* name, std::string const& value)
+{
+    if (!OK()) return false;
+    std::ofstream o(folder_ + "/" + name, std::ios::binary);
+    o << value;
+    return !o.fail();
+}
+
+UnencryptedProperties UnencryptedProperties::GetChild(const char* name)
+{
+    UnencryptedProperties e4p;
+    if (!OK()) return e4p;
+
+    std::string directory(folder_ + "/" + name);
+    if (mkdir(directory.c_str(), 700) == -1 && errno != EEXIST) {
+        return e4p;
+    }
+
+    e4p.folder_ = directory;
+    return e4p;
+}
+
+bool UnencryptedProperties::Remove(const char* name)
+{
+    if (remove((folder_ + "/" + name).c_str())
+        && errno != ENOENT) {
+        return false;
+    }
+
+    return true;
+}
+
+bool UnencryptedProperties::OK() const
+{
+    return !folder_.empty();
+}
diff --git a/ext4_utils/unencrypted_properties.h b/ext4_utils/unencrypted_properties.h
new file mode 100644
index 0000000..80f41df
--- /dev/null
+++ b/ext4_utils/unencrypted_properties.h
@@ -0,0 +1,70 @@
+#include <string>
+#include <fstream>
+
+// key names for properties we use
+namespace properties {
+    extern const char* key;
+    extern const char* ref;
+    extern const char* type;
+    extern const char* password;
+}
+
+/**
+ * Class to store data on the unencrypted folder of a device.
+ * Note that the folder must exist before this class is constructed.
+ * All names must be valid single level (no '/') file or directory names
+ * Data is organized hierarchically so we can get a child folder
+ */
+class UnencryptedProperties
+{
+public:
+    // Opens properties folder on named device.
+    // If folder does not exist, construction will succeed, but all
+    // getters will return default properties and setters will fail.
+    UnencryptedProperties(const char* device);
+
+    // Get named object. Return default if object does not exist or error.
+    template<typename t> t Get(const char* name, t default_value = t());
+
+    // Set named object. Return true if success, false otherwise
+    template<typename t> bool Set(const char* name, t const& value);
+
+    // Get child properties
+    UnencryptedProperties GetChild(const char* name);
+
+    // Remove named object
+    bool Remove(const char* name);
+
+    // Get path of folder
+    std::string const& GetPath() const {return folder_;}
+private:
+    UnencryptedProperties();
+    bool OK() const;
+    std::string folder_;
+};
+
+
+template<typename t> t UnencryptedProperties::Get(const char* name,
+                                                  t default_value)
+{
+    if (!OK()) return default_value;
+    t value = default_value;
+    std::ifstream(folder_ + "/" + name) >> value;
+    return value;
+}
+
+template<typename t> bool UnencryptedProperties::Set(const char* name,
+                                                     t const& value)
+{
+    if (!OK()) return false;
+    std::ofstream o(folder_ + "/" + name);
+    o << value;
+    return !o.fail();
+}
+
+// Specialized getters/setters for strings
+template<> std::string UnencryptedProperties::Get(const char* name,
+                                      std::string default_value);
+
+template<> bool UnencryptedProperties::Set(const char* name,
+                                           std::string const& value);
diff --git a/slideshow/slideshow.cpp b/slideshow/slideshow.cpp
index 25a2206..c015d53 100644
--- a/slideshow/slideshow.cpp
+++ b/slideshow/slideshow.cpp
@@ -94,7 +94,7 @@
 
             if (timeout < 0 || timeout >= LONG_MAX) {
                 timeout = NEXT_TIMEOUT_MS;
-                LOGE("invalid timeout %s, defaulting to %u\n", optarg,
+                LOGE("invalid timeout %s, defaulting to %ld\n", optarg,
                     timeout);
             }
             break;
diff --git a/squashfs_utils/Android.mk b/squashfs_utils/Android.mk
index 7808ec0..c3d2f2d 100644
--- a/squashfs_utils/Android.mk
+++ b/squashfs_utils/Android.mk
@@ -2,6 +2,13 @@
 
 LOCAL_PATH:= $(call my-dir)
 
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := squashfs_utils.c
+LOCAL_STATIC_LIBRARIES := libcutils
+LOCAL_C_INCLUDES := external/squashfs-tools/squashfs-tools
+LOCAL_MODULE := libsquashfs_utils
+include $(BUILD_STATIC_LIBRARY)
+
 ifeq ($(HOST_OS),linux)
 
 include $(CLEAR_VARS)
diff --git a/squashfs_utils/squashfs_utils.c b/squashfs_utils/squashfs_utils.c
new file mode 100644
index 0000000..128a3ef
--- /dev/null
+++ b/squashfs_utils/squashfs_utils.c
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#include "squashfs_utils.h"
+
+#include <cutils/klog.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "squashfs_fs.h"
+
+#define ERROR(x...)   KLOG_ERROR("squashfs_utils", x)
+
+int squashfs_parse_sb(char *blk_device, struct squashfs_info *info) {
+    int ret = 0;
+    struct squashfs_super_block sb;
+    int data_device;
+
+    data_device = TEMP_FAILURE_RETRY(open(blk_device, O_RDONLY | O_CLOEXEC));
+    if (data_device == -1) {
+        ERROR("Error opening block device (%s)\n", strerror(errno));
+        return -1;
+    }
+
+    if (TEMP_FAILURE_RETRY(read(data_device, &sb, sizeof(sb)))
+            != sizeof(sb)) {
+        ERROR("Error reading superblock\n");
+        ret = -1;
+        goto cleanup;
+    }
+    if (sb.s_magic != SQUASHFS_MAGIC) {
+        ERROR("Not a valid squashfs filesystem\n");
+        ret = -1;
+        goto cleanup;
+    }
+
+    info->block_size = sb.block_size;
+    info->inodes = sb.inodes;
+    info->bytes_used = sb.bytes_used;
+    // by default mksquashfs pads the filesystem to 4K blocks
+    info->bytes_used_4K_padded =
+        sb.bytes_used + (4096 - (sb.bytes_used & (4096 - 1)));
+
+cleanup:
+    TEMP_FAILURE_RETRY(close(data_device));
+    return ret;
+}
diff --git a/squashfs_utils/squashfs_utils.h b/squashfs_utils/squashfs_utils.h
new file mode 100644
index 0000000..ccad32d
--- /dev/null
+++ b/squashfs_utils/squashfs_utils.h
@@ -0,0 +1,39 @@
+/*
+ * 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 _SQUASHFS_UTILS_H_
+#define _SQUASHFS_UTILS_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct squashfs_info {
+    uint32_t block_size;
+    uint32_t inodes;
+    uint64_t bytes_used;
+    uint64_t bytes_used_4K_padded;
+};
+
+int squashfs_parse_sb(char *blk_device, struct squashfs_info *info);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/su/Android.mk b/su/Android.mk
index 0593cc9..297e0a3 100644
--- a/su/Android.mk
+++ b/su/Android.mk
@@ -1,14 +1,12 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
+LOCAL_CFLAGS := -std=c11 -Wall -Werror
+
 LOCAL_SRC_FILES:= su.c
 
 LOCAL_MODULE:= su
 
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-
-LOCAL_STATIC_LIBRARIES := libc
-
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
 LOCAL_MODULE_TAGS := debug
 
diff --git a/su/su.c b/su/su.c
index 8365379..d932c1b 100644
--- a/su/su.c
+++ b/su/su.c
@@ -1,54 +1,48 @@
 /*
-**
-** Copyright 2008, 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.
-*/
+ * Copyright (C) 2008 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 LOG_TAG "su"
-
+#include <errno.h>
+#include <error.h>
+#include <getopt.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/types.h>
-#include <dirent.h>
-#include <errno.h>
-
 #include <unistd.h>
-#include <time.h>
-
-#include <pwd.h>
 
 #include <private/android_filesystem_config.h>
 
-
-void pwtoid(const char *tok, uid_t *uid, gid_t *gid)
-{
-    struct passwd *pw;
-    pw = getpwnam(tok);
+void pwtoid(const char* tok, uid_t* uid, gid_t* gid) {
+    struct passwd* pw = getpwnam(tok);
     if (pw) {
         if (uid) *uid = pw->pw_uid;
         if (gid) *gid = pw->pw_gid;
     } else {
-        uid_t tmpid = atoi(tok);
+        char* end;
+        errno = 0;
+        uid_t tmpid = strtoul(tok, &end, 10);
+        if (errno != 0 || end == tok) error(1, errno, "invalid uid/gid '%s'", tok);
         if (uid) *uid = tmpid;
         if (gid) *gid = tmpid;
     }
 }
 
-void extract_uidgids(const char *uidgids, uid_t *uid, gid_t *gid, gid_t *gids,
-                     int *gids_count)
-{
+void extract_uidgids(const char* uidgids, uid_t* uid, gid_t* gid, gid_t* gids, int* gids_count) {
     char *clobberablegids;
     char *nexttok;
     char *tok;
@@ -59,6 +53,7 @@
         *gids_count = 0;
         return;
     }
+
     clobberablegids = strdup(uidgids);
     strcpy(clobberablegids, uidgids);
     nexttok = clobberablegids;
@@ -85,75 +80,61 @@
     free(clobberablegids);
 }
 
-/*
- * SU can be given a specific command to exec. UID _must_ be
- * specified for this (ie argc => 3).
- *
- * Usage:
- *   su 1000
- *   su 1000 ls -l
- *  or
- *   su [uid[,gid[,group1]...] [cmd]]
- *  E.g.
- *  su 1000,shell,net_bw_acct,net_bw_stats id
- * will return
- *  uid=1000(system) gid=2000(shell) groups=3006(net_bw_stats),3007(net_bw_acct)
- */
-int main(int argc, char **argv)
-{
-    struct passwd *pw;
-    uid_t uid, myuid;
-    gid_t gid, gids[10];
+int main(int argc, char** argv) {
+    uid_t current_uid = getuid();
+    if (current_uid != AID_ROOT && current_uid != AID_SHELL) error(1, 0, "not allowed");
 
-    /* Until we have something better, only root and the shell can use su. */
-    myuid = getuid();
-    if (myuid != AID_ROOT && myuid != AID_SHELL) {
-        fprintf(stderr,"su: uid %d not allowed to su\n", myuid);
-        return 1;
+    // Handle -h and --help.
+    ++argv;
+    if (*argv && (strcmp(*argv, "--help") == 0 || strcmp(*argv, "-h") == 0)) {
+        fprintf(stderr,
+                "usage: su [UID[,GID[,GID2]...]] [COMMAND [ARG...]]\n"
+                "\n"
+                "Switch to WHO (default 'root') and run the given command (default sh).\n"
+                "\n"
+                "where WHO is a comma-separated list of user, group,\n"
+                "and supplementary groups in that order.\n"
+                "\n");
+        return 0;
     }
 
-    if(argc < 2) {
-        uid = gid = 0;
-    } else {
+    // The default user is root.
+    uid_t uid = 0;
+    gid_t gid = 0;
+
+    // If there are any arguments, the first argument is the uid/gid/supplementary groups.
+    if (*argv) {
+        gid_t gids[10];
         int gids_count = sizeof(gids)/sizeof(gids[0]);
-        extract_uidgids(argv[1], &uid, &gid, gids, &gids_count);
-        if(gids_count) {
-            if(setgroups(gids_count, gids)) {
-                fprintf(stderr, "su: failed to set groups\n");
-                return 1;
+        extract_uidgids(*argv, &uid, &gid, gids, &gids_count);
+        if (gids_count) {
+            if (setgroups(gids_count, gids)) {
+                error(1, errno, "setgroups failed");
             }
         }
+        ++argv;
     }
 
-    if(setgid(gid) || setuid(uid)) {
-        fprintf(stderr,"su: permission denied\n");
-        return 1;
+    if (setgid(gid)) error(1, errno, "setgid failed");
+    if (setuid(uid)) error(1, errno, "setuid failed");
+
+    // Reset parts of the environment.
+    setenv("PATH", _PATH_DEFPATH, 1);
+    unsetenv("IFS");
+    struct passwd* pw = getpwuid(uid);
+    setenv("LOGNAME", pw->pw_name, 1);
+    setenv("USER", pw->pw_name, 1);
+
+    // Set up the arguments for exec.
+    char* exec_args[argc + 1];  // Having too much space is fine.
+    size_t i = 0;
+    for (; *argv != NULL; ++i) {
+      exec_args[i] = *argv++;
     }
+    // Default to the standard shell.
+    if (i == 0) exec_args[i++] = "/system/bin/sh";
+    exec_args[i] = NULL;
 
-    /* User specified command for exec. */
-    if (argc == 3 ) {
-        if (execlp(argv[2], argv[2], NULL) < 0) {
-            int saved_errno = errno;
-            fprintf(stderr, "su: exec failed for %s Error:%s\n", argv[2],
-                    strerror(errno));
-            return -saved_errno;
-        }
-    } else if (argc > 3) {
-        /* Copy the rest of the args from main. */
-        char *exec_args[argc - 1];
-        memset(exec_args, 0, sizeof(exec_args));
-        memcpy(exec_args, &argv[2], sizeof(exec_args));
-        if (execvp(argv[2], exec_args) < 0) {
-            int saved_errno = errno;
-            fprintf(stderr, "su: exec failed for %s Error:%s\n", argv[2],
-                    strerror(errno));
-            return -saved_errno;
-        }
-    }
-
-    /* Default exec shell. */
-    execlp("/system/bin/sh", "sh", NULL);
-
-    fprintf(stderr, "su: exec failed\n");
-    return 1;
+    execvp(exec_args[0], exec_args);
+    error(1, errno, "failed to exec %s", exec_args[0]);
 }
diff --git a/tests/bionic/libc/bionic/test_cond.c b/tests/bionic/libc/bionic/test_cond.c
index 6a85f9b..62d9694 100644
--- a/tests/bionic/libc/bionic/test_cond.c
+++ b/tests/bionic/libc/bionic/test_cond.c
@@ -33,7 +33,7 @@
 #include <string.h>
 #include <unistd.h>
 
-static pthread_mutex_t lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
+static pthread_mutex_t lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
 static pthread_cond_t  wait = PTHREAD_COND_INITIALIZER;
 
 static void* _thread1(void *__u __attribute__((unused)))
diff --git a/tests/fstest/Android.mk b/tests/fstest/Android.mk
index 7f2bdfc..9be7ae4 100644
--- a/tests/fstest/Android.mk
+++ b/tests/fstest/Android.mk
@@ -15,52 +15,12 @@
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := perm_checker.c
-
-LOCAL_SHARED_LIBRARIES := libc
-
-LOCAL_MODULE := perm_checker
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local
-
-include $(BUILD_EXECUTABLE)
-
-####
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := perm_checker.conf
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE_CLASS := DATA
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local
-
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-
-include $(BUILD_PREBUILT)
-
-####
-
-include $(CLEAR_VARS)
-
 LOCAL_MODULE_TAGS := tests
-
 LOCAL_MODULE := recovery_test
-
 LOCAL_SRC_FILES := recovery_test.cpp
-
 LOCAL_SHARED_LIBRARIES += libcutils libutils liblog liblogwrap
-
 LOCAL_STATIC_LIBRARIES += libtestUtil libfs_mgr
-
 LOCAL_C_INCLUDES += system/extras/tests/include \
-                    system/core/fs_mgr/include \
                     system/extras/ext4_utils \
                     system/core/logwrapper/include
-
 include $(BUILD_NATIVE_TEST)
diff --git a/tests/fstest/README b/tests/fstest/README
deleted file mode 100644
index b328100..0000000
--- a/tests/fstest/README
+++ /dev/null
@@ -1,68 +0,0 @@
-All files and directories will be matched against entries taken from 
-/data/local/perm_checker.conf, and any file/directory which fails the ruleset 
-will cause an error message along with a corresponding explicit (fully 
-specified and minimal) rule for that file/directory to be printed on 
-stdout. If only the message "Passed." is printed on stdout, all files are 
-correctly matched by perm_checker.conf.
-
-A file or directory will always fail the ruleset unless there is AT LEAST 
-one matching rule. If there is an explicit (fully specified) <spec> 
-matching the file or directory name, it will fail if and only if that 
-explicit <spec> rule fails (i.e., other matching <spec> rules will be 
-ignored). Otherwise, it will fail if _any_ matching wildcard or recursive 
-<spec> rule fails to hold.
-
-Entries in the perm_checker.conf file are of the following form:
-
-<spec> <min_mode> <max_mode> <min_uid> <max_uid> <min_gid> <max_gid>
-
-Where <spec> is one of the following:
-
-A fully specified path name, which must end in /         ex: /dev/
-A fully specified filename, symlink, device node, etc.   ex: /dev/tty0
-
-A recursive path specification, which ends in /...       ex: /dev/...
-A wildcard file specification, which ends in *           ex: /dev/tty*
-
-By convention /dev/* will include all files directly in /dev/, but not files 
-that are in subdirectories of /dev/, such as /dev/input/, unlike a 
-recursive path specification. The wildcard notation * will never result in 
-a match to a directory name.
-
-NOTE: Symbolic links are treated specially to prevent infinite recursion
-and simplify the ruleset. Symbolic links are ignored unless an explicit
-rule with the same name as the symlink exists, in which case the permissions
-on the rule must match the permissions on the symlink itself, not the target.
-
-<min_mode> is a numeric mode mask, and a mode will match it if and only if 
-(min_mode & mode) == min_mode.
-
-<max_mode> is a numeric mode mask, and a mode will match it if and only if 
-(max_mode | mode) == max_mode.
-
-<min_uid> may be either a numeric user id, or a user name (which must not 
-start with a number). If it is a user name, getpwnam() will be used to 
-translate it to a numeric user id.
-
-<max_uid>, <min_gid>, and <max_gid> have similar syntax to <min_uid>.
-
-
--- Tips --
-
-I recommend to use 19999 as the maximum uid/gid whenever any valid
-application uid/gid is acceptable.
-
-Once the test is installed, it can be executed via:
-
-adb shell perm_checker
-
-To get a list of all failing rules:
-
-adb shell perm_checker | grep "^# INFO #" | sort | uniq
-
-To get a fully specified set of rules for all failing files:
-
-adb shell perm_checker | grep -v "^#"
-
-NOTE: There may be failing files even if no rules have failed, since a 
-file that does not match any rule is a failure.
diff --git a/tests/fstest/mounts-test.sh b/tests/fstest/mounts-test.sh
deleted file mode 100755
index 1919750..0000000
--- a/tests/fstest/mounts-test.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-
-rtrn=0
-
-adb shell mount | grep -q /sdcard
-if [ 0 -ne $? ]
-then
-    echo FAILURE: /sdcard is not mounted
-    exit 1
-fi
-
-for i in nosuid noexec
-do
-    adb shell mount | grep /sdcard | grep -q $i
-    if [ 0 -ne $? ]
-    then
-        echo FAILURE: /sdcard is not mounted $i
-        rtrn=$(expr $rtrn + 1)
-    fi
-done
-
-for i in mem kmem
-do
-    adb shell ls /dev/*mem | grep -q $i
-    if [ 0 -ne $? ]
-    then
-        echo FAILURE: $i is present on system
-        rtrn=$(expr $rtrn + 1)
-    fi
-
-done
-
-exit $rtrn
-
diff --git a/tests/fstest/perm_checker.c b/tests/fstest/perm_checker.c
deleted file mode 100644
index e6b2105..0000000
--- a/tests/fstest/perm_checker.c
+++ /dev/null
@@ -1,434 +0,0 @@
-/*
- * Copyright (C) 2008 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.
- */
-
-// A simple file permissions checker. See associated README.
-
-#define _GNU_SOURCE
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-#include <ctype.h>
-#include <sys/types.h>
-#include <dirent.h>
-#include <errno.h>
-
-#include <sys/stat.h>
-#include <unistd.h>
-#include <time.h>
-
-#include <pwd.h>
-#include <grp.h>
-
-#include <linux/kdev_t.h>
-
-#define DEFAULT_CONFIG_FILE "/data/local/perm_checker.conf"
-
-#define PERMS(M) (M & ~S_IFMT)
-#define MAX_NAME_LEN 4096
-#define MAX_UID_LEN 256
-#define MAX_GID_LEN MAX_UID_LEN
-
-static char *config_file;
-static char *executable_file;
-
-enum perm_rule_type {EXACT_FILE = 0, EXACT_DIR, WILDCARD, RECURSIVE,
-    NUM_PR_TYPES};
-
-struct perm_rule {
-    char *rule_text;
-    int rule_line;
-    char *spec;
-    mode_t min_mode;
-    mode_t max_mode;
-    uid_t min_uid;
-    uid_t max_uid;
-    gid_t min_gid;
-    gid_t max_gid;
-    enum perm_rule_type type;
-    struct perm_rule *next;
-};
-
-typedef struct perm_rule perm_rule_t;
-
-static perm_rule_t *rules[NUM_PR_TYPES];
-
-static uid_t str2uid(char *str, int line_num)
-{
-    struct passwd *pw;
-
-    if (isdigit(str[0]))
-        return (uid_t) atol(str);
-
-    if (!(pw = getpwnam(str))) {
-        printf("# ERROR # Invalid uid '%s' reading line %d\n", str, line_num);
-        exit(255);
-    }
-    return pw->pw_uid;
-}
-
-static gid_t str2gid(char *str, int line_num)
-{
-    struct group *gr;
-
-    if (isdigit(str[0]))
-        return (uid_t) atol(str);
-
-    if (!(gr = getgrnam(str))) {
-        printf("# ERROR # Invalid gid '%s' reading line %d\n", str, line_num);
-        exit(255);
-    }
-    return gr->gr_gid;
-}
-
-static void add_rule(int line_num, char *spec,
-                     unsigned long min_mode, unsigned long max_mode,
-                     char *min_uid_buf, char *max_uid_buf,
-                     char *min_gid_buf, char *max_gid_buf) {
-
-    char rule_text_buf[MAX_NAME_LEN + 2*MAX_UID_LEN + 2*MAX_GID_LEN + 9];
-    perm_rule_t *pr = malloc(sizeof(perm_rule_t));
-    if (!pr) {
-        printf("Out of memory.\n");
-        exit(255);
-    }
-    if (snprintf(rule_text_buf, sizeof(rule_text_buf),
-                 "%s %lo %lo %s %s %s %s", spec, min_mode, max_mode,
-                 min_uid_buf, max_uid_buf, min_gid_buf, max_gid_buf)
-                 >= (long int) sizeof(rule_text_buf)) {
-        // This should never happen, but just in case...
-        printf("# ERROR # Maximum length limits exceeded on line %d\n",
-               line_num);
-        exit(255);
-    }
-    pr->rule_text = strndup(rule_text_buf, sizeof(rule_text_buf));
-    pr->rule_line = line_num;
-    if (strstr(spec, "/...")) {
-        pr->spec = strndup(spec, strlen(spec) - 3);
-        pr->type = RECURSIVE;
-    } else if (spec[strlen(spec) - 1] == '*') {
-        pr->spec = strndup(spec, strlen(spec) - 1);
-        pr->type = WILDCARD;
-    } else if (spec[strlen(spec) - 1] == '/') {
-        pr->spec = strdup(spec);
-        pr->type = EXACT_DIR;
-    } else {
-        pr->spec = strdup(spec);
-        pr->type = EXACT_FILE;
-    }
-    if ((pr->spec == NULL) || (pr->rule_text == NULL)) {
-        printf("Out of memory.\n");
-        exit(255);
-    }
-    pr->min_mode = min_mode;
-    pr->max_mode = max_mode;
-    pr->min_uid = str2uid(min_uid_buf, line_num);
-    pr->max_uid = str2uid(max_uid_buf, line_num);
-    pr->min_gid = str2gid(min_gid_buf, line_num);
-    pr->max_gid = str2gid(max_gid_buf, line_num);
-
-    // Add the rule to the appropriate set
-    pr->next = rules[pr->type];
-    rules[pr->type] = pr;
-#if 0  // Useful for debugging
-    printf("rule #%d: type = %d spec = %s min_mode = %o max_mode = %o "
-           "min_uid = %d max_uid = %d min_gid = %d max_gid = %d\n",
-           num_rules, pr->type, pr->spec, pr->min_mode, pr->max_mode,
-           pr->min_uid, pr->max_uid, pr->min_gid, pr->max_gid);
-#endif
-}
-
-static int read_rules(FILE *fp)
-{
-    char spec[MAX_NAME_LEN + 5];  // Allows for "/..." suffix + terminator
-    char min_uid_buf[MAX_UID_LEN + 1], max_uid_buf[MAX_UID_LEN + 1];
-    char min_gid_buf[MAX_GID_LEN + 1], max_gid_buf[MAX_GID_LEN + 1];
-    unsigned long min_mode, max_mode;
-    int res;
-    int num_rules = 0, num_lines = 0;
-
-    // Note: Use of an unsafe C function here is OK, since this is a test
-    while ((res = fscanf(fp, "%s %lo %lo %s %s %s %s\n", spec,
-                         &min_mode, &max_mode, min_uid_buf, max_uid_buf,
-                         min_gid_buf, max_gid_buf)) != EOF) {
-        num_lines++;
-        if (res < 7) {
-            printf("# WARNING # Invalid rule on line number %d\n", num_lines);
-            continue;
-        }
-        add_rule(num_lines, spec,
-                 min_mode, max_mode,
-                 min_uid_buf, max_uid_buf,
-                 min_gid_buf, max_gid_buf);
-        num_rules++;
-    }
-
-    // Automatically add a rule to match this executable itself
-    add_rule(-1, executable_file,
-             000, 0777,
-             "root", "shell",
-             "root", "shell");
-
-    // Automatically add a rule to match the configuration file
-    add_rule(-1, config_file,
-             000, 0777,
-             "root", "shell",
-             "root", "shell");
-
-    return num_lines - num_rules;
-}
-
-static void print_failed_rule(const perm_rule_t *pr)
-{
-    printf("# INFO # Failed rule #%d: %s\n", pr->rule_line, pr->rule_text);
-}
-
-static void print_new_rule(const char *name, mode_t mode, uid_t uid, gid_t gid)
-{
-    struct passwd *pw;
-    struct group *gr;
-    gr = getgrgid(gid);
-    pw = getpwuid(uid);
-    printf("%s %4o %4o %s %d %s %d\n", name, mode, mode, pw->pw_name, uid,
-           gr->gr_name, gid);
-}
-
-// Returns 1 if the rule passes, prints the failure and returns 0 if not
-static int pass_rule(const perm_rule_t *pr, mode_t mode, uid_t uid, gid_t gid)
-{
-    if (((pr->min_mode & mode) == pr->min_mode) &&
-            ((pr->max_mode | mode) == pr->max_mode) &&
-            (pr->min_gid <= gid) && (pr->max_gid >= gid) &&
-            (pr->min_uid <= uid) && (pr->max_uid >= uid))
-        return 1;
-    print_failed_rule(pr);
-    return 0;
-}
-
-// Returns 0 on success
-static int validate_file(const char *name, mode_t mode, uid_t uid, gid_t gid)
-{
-    perm_rule_t *pr;
-    int rules_matched = 0;
-    int retval = 0;
-
-    pr = rules[EXACT_FILE];
-    while (pr != NULL) {
-        if (strcmp(name, pr->spec) == 0) {
-            if (!pass_rule(pr, mode, uid, gid))
-                retval++;
-            else
-                rules_matched++;  // Exact match found
-        }
-        pr = pr->next;
-    }
-
-    if ((retval + rules_matched) > 1)
-        printf("# WARNING # Multiple exact rules for file: %s\n", name);
-
-    // If any exact rule matched or failed, we are done with this file
-    if (retval)
-        print_new_rule(name, mode, uid, gid);
-    if (rules_matched || retval)
-        return retval;
-
-    pr = rules[WILDCARD];
-    while (pr != NULL) {
-        // Check if the spec is a prefix of the filename, and that the file
-        // is actually in the same directory as the wildcard.
-        if ((strstr(name, pr->spec) == name) &&
-                (!strchr(name + strlen(pr->spec), '/'))) {
-            if (!pass_rule(pr, mode, uid, gid))
-                retval++;
-            else
-                rules_matched++;
-        }
-        pr = pr->next;
-    }
-
-    pr = rules[RECURSIVE];
-    while (pr != NULL) {
-        if (strstr(name, pr->spec) == name) {
-            if (!pass_rule(pr, mode, uid, gid))
-                retval++;
-            else
-                rules_matched++;
-        }
-        pr = pr->next;
-    }
-
-    if (!rules_matched)
-        retval++;  // In case no rules either matched or failed, be sure to fail
-
-    if (retval)
-        print_new_rule(name, mode, uid, gid);
-
-    return retval;
-}
-
-// Returns 0 on success
-static int validate_link(const char *name, mode_t mode, uid_t uid, gid_t gid)
-{
-    perm_rule_t *pr;
-    int rules_matched = 0;
-    int retval = 0;
-
-    // For now, we match links against "exact" file rules only
-    pr = rules[EXACT_FILE];
-    while (pr != NULL) {
-        if (strcmp(name, pr->spec) == 0) {
-            if (!pass_rule(pr, mode, uid, gid))
-                retval++;
-            else
-                rules_matched++;  // Exact match found
-        }
-        pr = pr->next;
-    }
-
-    if ((retval + rules_matched) > 1)
-        printf("# WARNING # Multiple exact rules for link: %s\n", name);
-    if (retval)
-        print_new_rule(name, mode, uid, gid);
-
-    // Note: Unlike files, if no rules matches for links, retval = 0 (success).
-    return retval;
-}
-
-// Returns 0 on success
-static int validate_dir(const char *name, mode_t mode, uid_t uid, gid_t gid)
-{
-    perm_rule_t *pr;
-    int rules_matched = 0;
-    int retval = 0;
-
-    pr = rules[EXACT_DIR];
-    while (pr != NULL) {
-        if (strcmp(name, pr->spec) == 0) {
-            if (!pass_rule(pr, mode, uid, gid))
-                retval++;
-            else
-                rules_matched++;  // Exact match found
-        }
-        pr = pr->next;
-    }
-
-    if ((retval + rules_matched) > 1)
-        printf("# WARNING # Multiple exact rules for directory: %s\n", name);
-
-    // If any exact rule matched or failed, we are done with this directory
-    if (retval)
-        print_new_rule(name, mode, uid, gid);
-    if (rules_matched || retval)
-        return retval;
-
-    pr = rules[RECURSIVE];
-    while (pr != NULL) {
-        if (strstr(name, pr->spec) == name) {
-            if (!pass_rule(pr, mode, uid, gid))
-                retval++;
-            else
-                rules_matched++;
-        }
-        pr = pr->next;
-    }
-
-    if (!rules_matched)
-        retval++;  // In case no rules either matched or failed, be sure to fail
-
-    if (retval)
-        print_new_rule(name, mode, uid, gid);
-
-    return retval;
-}
-
-// Returns 0 on success
-static int check_path(const char *name)
-{
-    char namebuf[MAX_NAME_LEN + 1];
-    char tmp[MAX_NAME_LEN + 1];
-    DIR *d;
-    struct dirent *de;
-    struct stat s;
-    int err;
-    int retval = 0;
-
-    err = lstat(name, &s);
-    if (err < 0) {
-        if (errno != ENOENT)
-        {
-            perror(name);
-            return 1;
-        }
-        return 0;  // File doesn't exist anymore
-    }
-
-    if (S_ISDIR(s.st_mode)) {
-        if (name[strlen(name) - 1] != '/')
-            snprintf(namebuf, sizeof(namebuf), "%s/", name);
-        else
-            snprintf(namebuf, sizeof(namebuf), "%s", name);
-
-        retval |= validate_dir(namebuf, PERMS(s.st_mode), s.st_uid, s.st_gid);
-        d = opendir(namebuf);
-        if(d == 0) {
-            printf("%s : opendir failed: %s\n", namebuf, strerror(errno));
-            return 1;
-        }
-
-        while ((de = readdir(d)) != 0) {
-            if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
-                continue;
-            snprintf(tmp, sizeof(tmp), "%s%s", namebuf, de->d_name);
-            retval |= check_path(tmp);
-        }
-        closedir(d);
-        return retval;
-    } else if (S_ISLNK(s.st_mode)) {
-        return validate_link(name, PERMS(s.st_mode), s.st_uid, s.st_gid);
-    } else {
-        return validate_file(name, PERMS(s.st_mode), s.st_uid, s.st_gid);
-    }
-}
-
-int main(int argc, char **argv)
-{
-    FILE *fp;
-    int i;
-
-    if (argc > 2) {
-      printf("\nSyntax: %s [configfilename]\n", argv[0]);
-    }
-    config_file = (argc == 2) ? argv[1] : DEFAULT_CONFIG_FILE;
-    executable_file = argv[0];
-
-    // Initialize ruleset pointers
-    for (i = 0; i < NUM_PR_TYPES; i++)
-        rules[i] = NULL;
-
-    if (!(fp = fopen(config_file, "r"))) {
-        printf("Error opening %s\n", config_file);
-        exit(255);
-    }
-    read_rules(fp);
-    fclose(fp);
-
-    if (check_path("/"))
-        return 255;
-
-    printf("Passed.\n");
-    return 0;
-}
diff --git a/tests/fstest/perm_checker.conf b/tests/fstest/perm_checker.conf
deleted file mode 100644
index b4dd1e8..0000000
--- a/tests/fstest/perm_checker.conf
+++ /dev/null
@@ -1,163 +0,0 @@
-/ 755 755 root root root root
-/* 400 755 root root root root
-/cache/ 770 770 system system cache cache
-/cache/... 770 770 system system cache cache
-/cache/lost+found/ 700 770 root root root root
-/d/ 755 755 root root root root
-/d/... 000 770 root root root root
-/data/ 771 771 system system system system
-/data/* 000 744 system system system system
-/data/anr/ 000 751 root system log log
-/data/anr/... 000 662 root system log log
-/data/app/ 771 771 system system system system
-/data/app/... 644 664 system system system system
-/data/app-private/ 700 771 system system system system
-/data/dalvik-cache/ 750 771 root system root system
-/data/dalvik-cache/... 400 744 root 19999 root 19999
-/data/data 701 771 system system system system
-/data/data/... 000 775 system 19999 system 19999
-/data/local/ 751 751 root root root root
-/data/local/tmp/ 771 771 shell shell shell shell
-/data/lost+found/ 700 770 root root root root
-/data/misc/ 1711 1771 root system root misc
-/data/misc/akmd_set.txt 600 640 root compass compass compass
-/data/misc/rild* 600 660 root radio root radio
-/data/misc/dhcp/ 700 770 root dhcp dhcp dhcp
-/data/misc/dhcp/... 000 660 root dhcp dhcp dhcp
-/data/misc/hcid/ 700 770 root bluetooth bluetooth bluetooth
-/data/misc/hcid/... 600 770 root bluetooth bluetooth bluetooth
-/data/misc/wifi/ 000 1771 system wifi system wifi
-/data/misc/wifi/... 000 770 root wifi root wifi
-/data/property/ 700 770 root root root root
-/data/property/... 600 660 root root root root
-/data/system/ 000 775 system system system system
-/data/system/... 000 774 system system system system
-/data/testinfo/ 770 771 root system root system
-/data/testinfo/* 000 664 root system root system
-/data/tombstones/ 755 755 system system system system
-/data/tombstones/* 000 600 system 19999 system 19999
-/dev/ 755 755 root root root root
-/dev/alarm 600 664 root radio root radio
-/dev/ashmem 666 666 root root root root
-/dev/android_adb 600 660 root adb root adb
-/dev/android_adb_enable 600 660 root adb root adb
-/dev/android_ums 640 640 mount mount mount mount
-/dev/binder 666 666 root root root root
-/dev/console 600 600 root root root root
-/dev/full 666 666 root root root root
-/dev/hw3d 660 660 system system graphics graphics
-/dev/htc-acoustic 600 640 radio radio radio radio
-/dev/network_throughput 600 660 root system root system
-/dev/network_latency 600 660 root system root system
-/dev/cpu_dma_latency 600 660 root system root system
-/dev/mem 600 600 root root root root
-/dev/msm_mp3 600 660 root system root audio
-/dev/msm_pcm_ctl 660 660 system system audio audio
-/dev/msm_pcm_in 660 660 system system audio audio
-/dev/msm_pcm_out 660 660 system system audio audio
-/dev/msm_perf 600 600 root root root root
-/dev/null 666 666 root root root root
-/dev/pmem 660 660 system system graphics graphics
-/dev/pmem_adsp 660 660 system system audio audio
-/dev/pmem_camera 600 660 root system root camera
-/dev/ppp 660 660 radio radio vpn vpn
-/dev/psaux 600 600 root root root root
-/dev/ptmx 666 666 root root root root
-/dev/random 666 666 root root root root
-/dev/smd0 640 640 radio radio radio radio
-/dev/ttyMSM0 600 600 bluetooth bluetooth bluetooth bluetooth
-/dev/urandom 666 666 root root root root
-/dev/zero 666 666 root root root root
-/dev/akm* 640 640 compass compass system system
-/dev/km* 600 600 root root root root
-/dev/mt9* 600 660 system system system system
-/dev/pmem_gpu* 660 660 system system graphics graphics
-/dev/qmi* 600 640 radio radio radio radio
-/dev/rtc* 600 600 root root root root
-/dev/smd* 600 600 root root root root
-/dev/tty* 600 600 root root root root
-/dev/vc* 600 600 root root root root
-/dev/adsp/ 750 755 root root root root
-/dev/adsp/* 660 660 system system audio audio
-/dev/block/ 750 775 root root root root
-/dev/block/* 600 600 root root root root
-/dev/graphics/ 755 755 root root root root
-/dev/graphics/* 660 660 root root graphics graphics
-/dev/input/ 755 755 root root root root
-/dev/input/* 660 660 root root input input
-/dev/log/ 755 755 root root root root
-/dev/log/* 662 662 root root log log
-/dev/oncrpc/ 755 755 root root root root
-/dev/oncrpc/... 000 660 root camera root camera
-/dev/pts/ 755 755 root root root root
-/dev/pts/* 600 600 root shell root shell
-/dev/mtd/ 750 775 root root root root
-/dev/mtd/mtd0 460 460 radio radio diag diag
-/dev/mtd/* 600 600 root root root root
-/dev/socket/ 750 755 root system root system
-/dev/socket/bluetooth 600 660 root bluetooth bluetooth bluetooth
-/dev/socket/dbus 660 660 root bluetooth bluetooth bluetooth
-/dev/socket/dbus_bluetooth 600 660 root bluetooth bluetooth bluetooth
-/dev/socket/installd 600 660 system system system system
-/dev/socket/logd 666 666 logd logd logd logd
-/dev/socket/logdr 666 666 logd logd logd logd
-/dev/socket/logdw 222 222 logd logd logd logd
-/dev/socket/mountd 660 660 root mount root mount
-/dev/socket/property_service 666 666 root system root system
-/dev/socket/rild 660 660 root radio root radio
-/dev/socket/rild-debug 660 660 root radio root radio
-/dev/socket/usbd 660 660 root mount mount mount
-/dev/socket/wpa_* 600 660 root wifi wifi wifi
-/dev/socket/zygote 666 666 root root root root
-/etc 777 777 root root root root
-/proc/ 555 555 root root root root
-/proc/... 000 777 root 19999 root 19999
-/proc/sys/kernel/sched_nr_migrate 000 1664 root root root root
-/root/ 700 700 root root root root
-/sdcard/ 000 077 system system system system
-/sdcard/... 000 077 system system system system
-/sbin/ 700 770 root root root root
-/sbin/... 700 775 root root root root
-/sys/ 755 775 root system root system
-/sys/... 000 775 root system root system
-/sys/android_power/acquire_full_wake_lock 664 664 radio radio system system
-/sys/android_power/acquire_partial_wake_lock 664 664 radio radio system system
-/sys/android_power/release_wake_lock 664 664 radio radio system system
-/sys/android_power/request_state 664 664 radio radio system system
-/sys/android_power/state 664 664 radio radio system system
-/sys/module/board_trout/parameters/bluetooth_power_on 660 660 root bluetooth bluetooth bluetooth
-/sys/qemu_trace/process_name 000 777 root system root system
-/sys/qemu_trace/state 000 777 root system root system
-/sys/qemu_trace/symbol 000 777 root system root system
-/system/ 755 755 root root root root
-/system/* 000 664 root system root system
-/system/app/ 755 755 root root root root
-/system/app/... 600 644 root root root root
-/system/bin/... 000 755 root shell root shell
-/system/bin/netcfg 000 2750 root root inet inet
-/system/bin/ping 000 2755 root root net_raw net_raw
-/system/etc/ 755 755 root root root root
-/system/etc/... 000 664 root bluetooth root audio
-/system/etc/firmware/ 700 755 root root root root
-/system/etc/init.goldfish.sh 500 550 root root root shell
-/system/etc/init.gprs-pppd 500 550 root root root shell
-/system/etc/ppp/ 755 755 root root root root
-/system/etc/ppp/* 555 555 root root root root
-/system/etc/security/ 755 755 root root root root
-/system/etc/wifi/ 750 755 root system root system
-/system/lib/ 755 755 root root root root
-/system/lib/... 600 644 root root root root
-/system/lib/modules/ 755 755 root root root root
-/system/lost+found/ 700 770 root root root root
-/system/fonts/ 755 755 root root root root
-/system/fonts/... 644 644 root root root root
-/system/framework/ 755 755 root root root root
-/system/framework/... 600 644 root root root root
-/system/media/ 755 755 root root root root
-/system/media/... 644 644 root root root root
-/system/media/audio/ 755 755 root root root root
-/system/media/audio/notifications/ 755 755 root root root root
-/system/media/audio/ringtones/ 755 755 root root root root
-/system/sounds/ 755 755 root root root root
-/system/sounds/... 644 644 root root root root
-/system/usr/... 400 755 root root root root
diff --git a/verity/Android.mk b/verity/Android.mk
index f39c3f4..46396ca 100644
--- a/verity/Android.mk
+++ b/verity/Android.mk
@@ -1,6 +1,15 @@
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
+LOCAL_MODULE := verify_boot_signature
+LOCAL_SRC_FILES := verify_boot_signature.c
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_SHARED_LIBRARIES := libcrypto-host
+LOCAL_C_INCLUDES += external/openssl/include system/extras/ext4_utils system/core/mkbootimg
+include $(BUILD_HOST_EXECUTABLE)
+
+include $(CLEAR_VARS)
 LOCAL_MODULE := generate_verity_key
 LOCAL_SRC_FILES := generate_verity_key.c
 LOCAL_MODULE_CLASS := EXECUTABLES
@@ -9,6 +18,14 @@
 include $(BUILD_HOST_EXECUTABLE)
 
 include $(CLEAR_VARS)
+LOCAL_SRC_FILES := VerityVerifier.java Utils.java
+LOCAL_MODULE := VerityVerifier
+LOCAL_JAR_MANIFEST := VerityVerifier.mf
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
 LOCAL_SRC_FILES := VeritySigner.java Utils.java
 LOCAL_MODULE := VeritySigner
 LOCAL_JAR_MANIFEST := VeritySigner.mf
@@ -33,6 +50,15 @@
 include $(BUILD_HOST_JAVA_LIBRARY)
 
 include $(CLEAR_VARS)
+LOCAL_SRC_FILES := verity_verifier
+LOCAL_MODULE := verity_verifier
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_REQUIRED_MODULES := VerityVerifier
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
 LOCAL_SRC_FILES := verity_signer
 LOCAL_MODULE := verity_signer
 LOCAL_MODULE_CLASS := EXECUTABLES
diff --git a/verity/BootSignature.java b/verity/BootSignature.java
index f5ceb30..03eb32a 100644
--- a/verity/BootSignature.java
+++ b/verity/BootSignature.java
@@ -16,51 +16,105 @@
 
 package com.android.verity;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateEncodingException;
 import java.util.Arrays;
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.util.ASN1Dump;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 /**
  *    AndroidVerifiedBootSignature DEFINITIONS ::=
  *    BEGIN
- *        FormatVersion ::= INTEGER
- *        AlgorithmIdentifier ::=  SEQUENCE {
+ *        formatVersion ::= INTEGER
+ *        certificate ::= Certificate
+ *        algorithmIdentifier ::= SEQUENCE {
  *            algorithm OBJECT IDENTIFIER,
  *            parameters ANY DEFINED BY algorithm OPTIONAL
  *        }
- *        AuthenticatedAttributes ::= SEQUENCE {
+ *        authenticatedAttributes ::= SEQUENCE {
  *            target CHARACTER STRING,
  *            length INTEGER
  *        }
- *        Signature ::= OCTET STRING
+ *        signature ::= OCTET STRING
  *     END
  */
 
 public class BootSignature extends ASN1Object
 {
     private ASN1Integer             formatVersion;
+    private ASN1Encodable           certificate;
     private AlgorithmIdentifier     algorithmIdentifier;
     private DERPrintableString      target;
     private ASN1Integer             length;
     private DEROctetString          signature;
+    private PublicKey               publicKey;
 
+    private static final int FORMAT_VERSION = 1;
+
+    /**
+     * Initializes the object for signing an image file
+     * @param target Target name, included in the signed data
+     * @param length Length of the image, included in the signed data
+     */
     public BootSignature(String target, int length) {
-        this.formatVersion = new ASN1Integer(0);
+        this.formatVersion = new ASN1Integer(FORMAT_VERSION);
         this.target = new DERPrintableString(target);
         this.length = new ASN1Integer(length);
-        this.algorithmIdentifier = new AlgorithmIdentifier(
-                PKCSObjectIdentifiers.sha256WithRSAEncryption);
+    }
+
+    /**
+     * Initializes the object for verifying a signed image file
+     * @param signature Signature footer
+     */
+    public BootSignature(byte[] signature)
+            throws Exception {
+        ASN1InputStream stream = new ASN1InputStream(signature);
+        ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
+
+        formatVersion = (ASN1Integer) sequence.getObjectAt(0);
+        if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
+            throw new IllegalArgumentException("Unsupported format version");
+        }
+
+        certificate = sequence.getObjectAt(1);
+        byte[] encoded = ((ASN1Object) certificate).getEncoded();
+        ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
+
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
+        publicKey = c.getPublicKey();
+
+        ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
+        algorithmIdentifier = new AlgorithmIdentifier(
+            (ASN1ObjectIdentifier) algId.getObjectAt(0));
+
+        ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
+        target = (DERPrintableString) attrs.getObjectAt(0);
+        length = (ASN1Integer) attrs.getObjectAt(1);
+
+        this.signature = (DEROctetString) sequence.getObjectAt(4);
     }
 
     public ASN1Object getAuthenticatedAttributes() {
@@ -74,10 +128,29 @@
         return getAuthenticatedAttributes().getEncoded();
     }
 
-    public void setSignature(byte[] sig) {
+    public AlgorithmIdentifier getAlgorithmIdentifier() {
+        return algorithmIdentifier;
+    }
+
+    public PublicKey getPublicKey() {
+        return publicKey;
+    }
+
+    public byte[] getSignature() {
+        return signature.getOctets();
+    }
+
+    public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
+        algorithmIdentifier = algId;
         signature = new DEROctetString(sig);
     }
 
+    public void setCertificate(X509Certificate cert)
+            throws Exception, IOException, CertificateEncodingException {
+        ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
+        certificate = s.readObject();
+    }
+
     public byte[] generateSignableImage(byte[] image) throws IOException {
         byte[] attrs = getEncodedAuthenticatedAttributes();
         byte[] signable = Arrays.copyOf(image, image.length + attrs.length);
@@ -89,36 +162,145 @@
 
     public byte[] sign(byte[] image, PrivateKey key) throws Exception {
         byte[] signable = generateSignableImage(image);
-        byte[] signature = Utils.sign(key, signable);
-        byte[] signed = Arrays.copyOf(image, image.length + signature.length);
-        for (int i=0; i < signature.length; i++) {
-            signed[i+image.length] = signature[i];
+        return Utils.sign(key, signable);
+    }
+
+    public boolean verify(byte[] image) throws Exception {
+        if (length.getValue().intValue() != image.length) {
+            throw new IllegalArgumentException("Invalid image length");
         }
-        return signed;
+
+        byte[] signable = generateSignableImage(image);
+        return Utils.verify(publicKey, signable, signature.getOctets(),
+                    algorithmIdentifier);
     }
 
     public ASN1Primitive toASN1Primitive() {
         ASN1EncodableVector v = new ASN1EncodableVector();
         v.add(formatVersion);
+        v.add(certificate);
         v.add(algorithmIdentifier);
         v.add(getAuthenticatedAttributes());
         v.add(signature);
         return new DERSequence(v);
     }
 
+    public static int getSignableImageSize(byte[] data) throws Exception {
+        if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
+                "ANDROID!".getBytes("US-ASCII"))) {
+            throw new IllegalArgumentException("Invalid image header: missing magic");
+        }
+
+        ByteBuffer image = ByteBuffer.wrap(data);
+        image.order(ByteOrder.LITTLE_ENDIAN);
+
+        image.getLong(); // magic
+        int kernelSize = image.getInt();
+        image.getInt(); // kernel_addr
+        int ramdskSize = image.getInt();
+        image.getInt(); // ramdisk_addr
+        int secondSize = image.getInt();
+        image.getLong(); // second_addr + tags_addr
+        int pageSize = image.getInt();
+
+        int length = pageSize // include the page aligned image header
+                + ((kernelSize + pageSize - 1) / pageSize) * pageSize
+                + ((ramdskSize + pageSize - 1) / pageSize) * pageSize
+                + ((secondSize + pageSize - 1) / pageSize) * pageSize;
+
+        length = ((length + pageSize - 1) / pageSize) * pageSize;
+
+        if (length <= 0) {
+            throw new IllegalArgumentException("Invalid image header: invalid length");
+        }
+
+        return length;
+    }
+
     public static void doSignature( String target,
                                     String imagePath,
                                     String keyPath,
+                                    String certPath,
                                     String outPath) throws Exception {
+
         byte[] image = Utils.read(imagePath);
+        int signableSize = getSignableImageSize(image);
+
+        if (signableSize < image.length) {
+            System.err.println("NOTE: truncating file " + imagePath +
+                    " from " + image.length + " to " + signableSize + " bytes");
+            image = Arrays.copyOf(image, signableSize);
+        } else if (signableSize > image.length) {
+            throw new IllegalArgumentException("Invalid image: too short, expected " +
+                    signableSize + " bytes");
+        }
+
         BootSignature bootsig = new BootSignature(target, image.length);
-        PrivateKey key = Utils.loadPEMPrivateKeyFromFile(keyPath);
-        byte[] signature = bootsig.sign(image, key);
-        Utils.write(signature, outPath);
+
+        X509Certificate cert = Utils.loadPEMCertificate(certPath);
+        bootsig.setCertificate(cert);
+
+        PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath);
+        bootsig.setSignature(bootsig.sign(image, key),
+            Utils.getSignatureAlgorithmIdentifier(key));
+
+        byte[] encoded_bootsig = bootsig.getEncoded();
+        byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length);
+
+        System.arraycopy(encoded_bootsig, 0, image_with_metadata,
+                image.length, encoded_bootsig.length);
+
+        Utils.write(image_with_metadata, outPath);
     }
 
-    // java -cp ../../../out/host/common/obj/JAVA_LIBRARIES/AndroidVerifiedBootSigner_intermediates/classes/ com.android.verity.AndroidVerifiedBootSigner boot ../../../out/target/product/flounder/boot.img ../../../build/target/product/security/verity_private_dev_key /tmp/boot.img.signed
-    public static void main(String[] args) throws Exception {
-        doSignature(args[0], args[1], args[2], args[3]);
+    public static void verifySignature(String imagePath) throws Exception {
+        byte[] image = Utils.read(imagePath);
+        int signableSize = getSignableImageSize(image);
+
+        if (signableSize >= image.length) {
+            throw new IllegalArgumentException("Invalid image: not signed");
+        }
+
+        byte[] signature = Arrays.copyOfRange(image, signableSize, image.length);
+        BootSignature bootsig = new BootSignature(signature);
+
+        try {
+            if (bootsig.verify(Arrays.copyOf(image, signableSize))) {
+                System.err.println("Signature is VALID");
+                System.exit(0);
+            } else {
+                System.err.println("Signature is INVALID");
+            }
+        } catch (Exception e) {
+            e.printStackTrace(System.err);
+        }
+        System.exit(1);
     }
-}
\ No newline at end of file
+
+    /* Example usage for signing a boot image using dev keys:
+        java -cp \
+            ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/ \
+                classes/com.android.verity.BootSignature \
+            /boot \
+            ../../../out/target/product/$PRODUCT/boot.img \
+            ../../../build/target/product/security/verity.pk8 \
+            ../../../build/target/product/security/verity.x509.pem \
+            /tmp/boot.img.signed
+    */
+    public static void main(String[] args) throws Exception {
+        Security.addProvider(new BouncyCastleProvider());
+
+        if ("-verify".equals(args[0])) {
+            /* args[1] is the path to a signed boot image */
+            verifySignature(args[1]);
+        } else {
+            /* args[0] is the target name, typically /boot
+               args[1] is the path to a boot image to sign
+               args[2] is the path to a private key
+               args[3] is the path to the matching public key certificate
+               args[4] is the path where to output the signed boot image
+            */
+            doSignature(args[0], args[1], args[2], args[3], args[4]);
+        }
+    }
+}
diff --git a/verity/KeystoreSigner.java b/verity/KeystoreSigner.java
index d57f328..0927d54 100644
--- a/verity/KeystoreSigner.java
+++ b/verity/KeystoreSigner.java
@@ -19,12 +19,17 @@
 import java.io.IOException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.security.Security;
 import java.security.Signature;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERPrintableString;
 import org.bouncycastle.asn1.DERSequence;
@@ -32,6 +37,7 @@
 import org.bouncycastle.asn1.pkcs.RSAPublicKey;
 import org.bouncycastle.asn1.util.ASN1Dump;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 /**
  * AndroidVerifiedBootKeystore DEFINITIONS ::=
@@ -61,8 +67,7 @@
         this.keyMaterial = new RSAPublicKey(
                 k.getModulus(),
                 k.getPublicExponent());
-        this.algorithmIdentifier = new AlgorithmIdentifier(
-                PKCSObjectIdentifiers.sha256WithRSAEncryption);
+        this.algorithmIdentifier = Utils.getSignatureAlgorithmIdentifier(key);
     }
 
     public ASN1Primitive toASN1Primitive() {
@@ -79,12 +84,15 @@
 
 class BootKeystore extends ASN1Object
 {
-    private ASN1Integer                     formatVersion;
-    private ASN1EncodableVector             keyBag;
-    private BootSignature    signature;
+    private ASN1Integer             formatVersion;
+    private ASN1EncodableVector     keyBag;
+    private BootSignature           signature;
+    private X509Certificate         certificate;
+
+    private static final int FORMAT_VERSION = 0;
 
     public BootKeystore() {
-        this.formatVersion = new ASN1Integer(0);
+        this.formatVersion = new ASN1Integer(FORMAT_VERSION);
         this.keyBag = new ASN1EncodableVector();
     }
 
@@ -94,6 +102,10 @@
         keyBag.add(k);
     }
 
+    public void setCertificate(X509Certificate cert) {
+        certificate = cert;
+    }
+
     public byte[] getInnerKeystore() throws Exception {
         ASN1EncodableVector v = new ASN1EncodableVector();
         v.add(formatVersion);
@@ -109,29 +121,87 @@
         return new DERSequence(v);
     }
 
+    public void parse(byte[] input) throws Exception {
+        ASN1InputStream stream = new ASN1InputStream(input);
+        ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
+
+        formatVersion = (ASN1Integer) sequence.getObjectAt(0);
+        if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
+            throw new IllegalArgumentException("Unsupported format version");
+        }
+
+        ASN1Sequence keys = (ASN1Sequence) sequence.getObjectAt(1);
+        Enumeration e = keys.getObjects();
+        while (e.hasMoreElements()) {
+            keyBag.add((ASN1Encodable) e.nextElement());
+        }
+
+        ASN1Object sig = sequence.getObjectAt(2).toASN1Primitive();
+        signature = new BootSignature(sig.getEncoded());
+    }
+
+    public boolean verify() throws Exception {
+        byte[] innerKeystore = getInnerKeystore();
+        return Utils.verify(signature.getPublicKey(), innerKeystore,
+                signature.getSignature(), signature.getAlgorithmIdentifier());
+    }
+
     public void sign(PrivateKey privateKey) throws Exception {
         byte[] innerKeystore = getInnerKeystore();
         byte[] rawSignature = Utils.sign(privateKey, innerKeystore);
         signature = new BootSignature("keystore", innerKeystore.length);
-        signature.setSignature(rawSignature);
+        signature.setCertificate(certificate);
+        signature.setSignature(rawSignature,
+                Utils.getSignatureAlgorithmIdentifier(privateKey));
     }
 
     public void dump() throws Exception {
         System.out.println(ASN1Dump.dumpAsString(toASN1Primitive()));
     }
 
-    // USAGE:
-    //      AndroidVerifiedBootKeystoreSigner <privkeyFile> <outfile> <pubkeyFile0> ... <pubkeyFileN-1>
-    // EG:
-    //     java -cp ../../../out/host/common/obj/JAVA_LIBRARIES/AndroidVerifiedBootKeystoreSigner_intermediates/classes/ com.android.verity.AndroidVerifiedBootKeystoreSigner ../../../build/target/product/security/verity_private_dev_key /tmp/keystore.out /tmp/k
-    public static void main(String[] args) throws Exception {
-        String privkeyFname = args[0];
-        String outfileFname = args[1];
-        BootKeystore ks = new BootKeystore();
-        for (int i=2; i < args.length; i++) {
-            ks.addPublicKey(Utils.read(args[i]));
-        }
-        ks.sign(Utils.loadPEMPrivateKeyFromFile(privkeyFname));
-        Utils.write(ks.getEncoded(), outfileFname);
+    private static void usage() {
+        System.err.println("usage: KeystoreSigner <privatekey.pk8> " +
+                "<certificate.x509.pem> <outfile> <publickey0.der> " +
+                "... <publickeyN-1.der> | -verify <keystore>");
+        System.exit(1);
     }
-}
\ No newline at end of file
+
+    public static void main(String[] args) throws Exception {
+        if (args.length < 2) {
+            usage();
+            return;
+        }
+
+        Security.addProvider(new BouncyCastleProvider());
+        BootKeystore ks = new BootKeystore();
+
+        if ("-verify".equals(args[0])) {
+            ks.parse(Utils.read(args[1]));
+
+            try {
+                if (ks.verify()) {
+                    System.err.println("Signature is VALID");
+                    System.exit(0);
+                } else {
+                    System.err.println("Signature is INVALID");
+                }
+            } catch (Exception e) {
+                e.printStackTrace(System.err);
+            }
+            System.exit(1);
+        } else {
+            String privkeyFname = args[0];
+            String certFname = args[1];
+            String outfileFname = args[2];
+
+            ks.setCertificate(Utils.loadPEMCertificate(certFname));
+
+            for (int i = 3; i < args.length; i++) {
+                ks.addPublicKey(Utils.read(args[i]));
+            }
+
+            ks.sign(Utils.loadDERPrivateKeyFromFile(privkeyFname));
+            Utils.write(ks.getEncoded(), outfileFname);
+        }
+    }
+}
diff --git a/verity/KeystoreSigner.mf b/verity/KeystoreSigner.mf
index a4fee27..472b7c4 100644
--- a/verity/KeystoreSigner.mf
+++ b/verity/KeystoreSigner.mf
@@ -1 +1 @@
-Main-Class: com.android.verity.KeystoreSigner
\ No newline at end of file
+Main-Class: com.android.verity.BootKeystore
diff --git a/verity/Utils.java b/verity/Utils.java
index 2c1e7bb..3576e3b 100644
--- a/verity/Utils.java
+++ b/verity/Utils.java
@@ -18,22 +18,60 @@
 
 import java.lang.reflect.Constructor;
 import java.io.File;
+import java.io.ByteArrayInputStream;
+import java.io.Console;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.InputStreamReader;
 import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.KeyFactory;
 import java.security.Provider;
 import java.security.Security;
 import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
 import java.security.spec.X509EncodedKeySpec;
 import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
+import javax.crypto.Cipher;
+import javax.crypto.EncryptedPrivateKeyInfo;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.util.encoders.Base64;
 
 public class Utils {
 
+    private static final Map<String, String> ID_TO_ALG;
+    private static final Map<String, String> ALG_TO_ID;
+
+    static {
+        ID_TO_ALG = new HashMap<String, String>();
+        ALG_TO_ID = new HashMap<String, String>();
+
+        ID_TO_ALG.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1withRSA");
+        ID_TO_ALG.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256withRSA");
+        ID_TO_ALG.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512withRSA");
+
+        ALG_TO_ID.put("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId());
+        ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId());
+        ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId());
+    }
+
     private static void loadProviderIfNecessary(String providerClassName) {
         if (providerClassName == null) {
             return;
@@ -88,10 +126,45 @@
         return Base64.decode(base64_der);
     }
 
+    private static PKCS8EncodedKeySpec decryptPrivateKey(byte[] encryptedPrivateKey)
+        throws GeneralSecurityException {
+        EncryptedPrivateKeyInfo epkInfo;
+        try {
+            epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey);
+        } catch (IOException ex) {
+            // Probably not an encrypted key.
+            return null;
+        }
+
+        char[] password = System.console().readPassword("Password for the private key file: ");
+
+        SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName());
+        Key key = skFactory.generateSecret(new PBEKeySpec(password));
+        Arrays.fill(password, '\0');
+
+        Cipher cipher = Cipher.getInstance(epkInfo.getAlgName());
+        cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters());
+
+        try {
+            return epkInfo.getKeySpec(cipher);
+        } catch (InvalidKeySpecException ex) {
+            System.err.println("Password may be bad.");
+            throw ex;
+        }
+    }
+
     static PrivateKey loadDERPrivateKey(byte[] der) throws Exception {
-        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(der);
-        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-        return (PrivateKey) keyFactory.generatePrivate(keySpec);
+        PKCS8EncodedKeySpec spec = decryptPrivateKey(der);
+
+        if (spec == null) {
+            spec = new PKCS8EncodedKeySpec(der);
+        }
+
+        ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded()));
+        PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject());
+        String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId();
+
+        return KeyFactory.getInstance(algOid).generatePrivate(spec);
     }
 
     static PrivateKey loadPEMPrivateKey(byte[] pem) throws Exception {
@@ -128,8 +201,48 @@
         return loadDERPublicKey(read(keyFname));
     }
 
+    static X509Certificate loadPEMCertificate(String fname) throws Exception {
+        try (FileInputStream fis = new FileInputStream(fname)) {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            return (X509Certificate) cf.generateCertificate(fis);
+        }
+    }
+
+    private static String getSignatureAlgorithm(Key key) {
+        if ("RSA".equals(key.getAlgorithm())) {
+            return "SHA256withRSA";
+        } else {
+            throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
+        }
+    }
+
+    static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) {
+        String id = ALG_TO_ID.get(getSignatureAlgorithm(key));
+
+        if (id == null) {
+            throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm());
+        }
+
+        return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id));
+    }
+
+    static boolean verify(PublicKey key, byte[] input, byte[] signature,
+            AlgorithmIdentifier algId) throws Exception {
+        String algName = ID_TO_ALG.get(algId.getObjectId().getId());
+
+        if (algName == null) {
+            throw new IllegalArgumentException("Unsupported algorithm " + algId.getObjectId());
+        }
+
+        Signature verifier = Signature.getInstance(algName);
+        verifier.initVerify(key);
+        verifier.update(input);
+
+        return verifier.verify(signature);
+    }
+
     static byte[] sign(PrivateKey privateKey, byte[] input) throws Exception {
-        Signature signer = Signature.getInstance("SHA1withRSA");
+        Signature signer = Signature.getInstance(getSignatureAlgorithm(privateKey));
         signer.initSign(privateKey);
         signer.update(input);
         return signer.sign();
@@ -153,4 +266,4 @@
         out.write(data);
         out.close();
     }
-}
\ No newline at end of file
+}
diff --git a/verity/VeritySigner.java b/verity/VeritySigner.java
index 44c5602..9d85747 100644
--- a/verity/VeritySigner.java
+++ b/verity/VeritySigner.java
@@ -16,18 +16,54 @@
 
 package com.android.verity;
 
+import java.security.PublicKey;
 import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
 public class VeritySigner {
 
-    // USAGE:
-    //     VeritySigner <contentfile> <key.pem> <sigfile>
-    // To verify that this has correct output:
-    //     openssl rsautl -raw -inkey <key.pem> -encrypt -in <sigfile> > /tmp/dump
+    private static void usage() {
+        System.err.println("usage: VeritySigner <contentfile> <key.pk8> " +
+                "<sigfile> | <contentfile> <certificate.x509.pem> <sigfile> " +
+                "-verify");
+        System.exit(1);
+    }
+
     public static void main(String[] args) throws Exception {
+        if (args.length < 3) {
+            usage();
+            return;
+        }
+
+        Security.addProvider(new BouncyCastleProvider());
+
         byte[] content = Utils.read(args[0]);
-        PrivateKey privateKey = Utils.loadPEMPrivateKey(Utils.read(args[1]));
-        byte[] signature = Utils.sign(privateKey, content);
-        Utils.write(signature, args[2]);
+
+        if (args.length > 3 && "-verify".equals(args[3])) {
+            X509Certificate cert = Utils.loadPEMCertificate(args[1]);
+            PublicKey publicKey = cert.getPublicKey();
+
+            byte[] signature = Utils.read(args[2]);
+
+            try {
+                if (Utils.verify(publicKey, content, signature,
+                            Utils.getSignatureAlgorithmIdentifier(publicKey))) {
+                    System.err.println("Signature is VALID");
+                    System.exit(0);
+                } else {
+                    System.err.println("Signature is INVALID");
+                }
+            } catch (Exception e) {
+                e.printStackTrace(System.err);
+            }
+
+            System.exit(1);
+        } else {
+            PrivateKey privateKey = Utils.loadDERPrivateKey(Utils.read(args[1]));
+            byte[] signature = Utils.sign(privateKey, content);
+            Utils.write(signature, args[2]);
+        }
     }
 }
diff --git a/verity/VerityVerifier.java b/verity/VerityVerifier.java
new file mode 100644
index 0000000..5c9d7d2
--- /dev/null
+++ b/verity/VerityVerifier.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.verity;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.lang.Process;
+import java.lang.Runtime;
+import java.security.PublicKey;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class VerityVerifier {
+
+    private static final int EXT4_SB_MAGIC = 0xEF53;
+    private static final int EXT4_SB_OFFSET = 0x400;
+    private static final int EXT4_SB_OFFSET_MAGIC = EXT4_SB_OFFSET + 0x38;
+    private static final int EXT4_SB_OFFSET_LOG_BLOCK_SIZE = EXT4_SB_OFFSET + 0x18;
+    private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_LO = EXT4_SB_OFFSET + 0x4;
+    private static final int EXT4_SB_OFFSET_BLOCKS_COUNT_HI = EXT4_SB_OFFSET + 0x150;
+    private static final int VERITY_MAGIC = 0xB001B001;
+    private static final int VERITY_SIGNATURE_SIZE = 256;
+    private static final int VERITY_VERSION = 0;
+
+    /**
+     * Converts a 4-byte little endian value to a Java integer
+     * @param value Little endian integer to convert
+     */
+     public static int fromle(int value) {
+        byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
+        return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
+    }
+
+     /**
+     * Converts a 2-byte little endian value to Java a integer
+     * @param value Little endian short to convert
+     */
+     public static int fromle(short value) {
+        return fromle(value << 16);
+    }
+
+    /**
+     * Unsparses a sparse image into a temporary file and returns a
+     * handle to the file
+     * @param fname Path to a sparse image file
+     */
+     public static RandomAccessFile openImage(String fname) throws Exception {
+        File tmp = File.createTempFile("system", ".raw");
+        tmp.deleteOnExit();
+
+        Process p = Runtime.getRuntime().exec("simg2img " + fname +
+                            " " + tmp.getAbsoluteFile());
+
+        p.waitFor();
+        if (p.exitValue() != 0) {
+            throw new IllegalArgumentException("Invalid image: failed to unsparse");
+        }
+
+        return new RandomAccessFile(tmp, "r");
+    }
+
+    /**
+     * Reads the ext4 superblock and calculates the size of the system image,
+     * after which we should find the verity metadata
+     * @param img File handle to the image file
+     */
+    public static long getMetadataPosition(RandomAccessFile img)
+            throws Exception {
+        img.seek(EXT4_SB_OFFSET_MAGIC);
+        int magic = fromle(img.readShort());
+
+        if (magic != EXT4_SB_MAGIC) {
+            throw new IllegalArgumentException("Invalid image: not a valid ext4 image");
+        }
+
+        img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_LO);
+        long blocksCountLo = fromle(img.readInt());
+
+        img.seek(EXT4_SB_OFFSET_LOG_BLOCK_SIZE);
+        long logBlockSize = fromle(img.readInt());
+
+        img.seek(EXT4_SB_OFFSET_BLOCKS_COUNT_HI);
+        long blocksCountHi = fromle(img.readInt());
+
+        long blockSizeBytes = 1L << (10 + logBlockSize);
+        long blockCount = (blocksCountHi << 32) + blocksCountLo;
+        return blockSizeBytes * blockCount;
+    }
+
+    /**
+     * Reads and validates verity metadata, and check the signature against the
+     * given public key
+     * @param img File handle to the image file
+     * @param key Public key to use for signature verification
+     */
+    public static boolean verifyMetaData(RandomAccessFile img, PublicKey key)
+            throws Exception {
+        img.seek(getMetadataPosition(img));
+        int magic = fromle(img.readInt());
+
+        if (magic != VERITY_MAGIC) {
+            throw new IllegalArgumentException("Invalid image: verity metadata not found");
+        }
+
+        int version = fromle(img.readInt());
+
+        if (version != VERITY_VERSION) {
+            throw new IllegalArgumentException("Invalid image: unknown metadata version");
+        }
+
+        byte[] signature = new byte[VERITY_SIGNATURE_SIZE];
+        img.readFully(signature);
+
+        int tableSize = fromle(img.readInt());
+
+        byte[] table = new byte[tableSize];
+        img.readFully(table);
+
+        return Utils.verify(key, table, signature,
+                   Utils.getSignatureAlgorithmIdentifier(key));
+    }
+
+    public static void main(String[] args) throws Exception {
+        if (args.length != 2) {
+            System.err.println("Usage: VerityVerifier <sparse.img> <certificate.x509.pem>");
+            System.exit(1);
+        }
+
+        Security.addProvider(new BouncyCastleProvider());
+
+        X509Certificate cert = Utils.loadPEMCertificate(args[1]);
+        PublicKey key = cert.getPublicKey();
+        RandomAccessFile img = openImage(args[0]);
+
+        try {
+            if (verifyMetaData(img, key)) {
+                System.err.println("Signature is VALID");
+                System.exit(0);
+            } else {
+                System.err.println("Signature is INVALID");
+            }
+        } catch (Exception e) {
+            e.printStackTrace(System.err);
+        }
+
+        System.exit(1);
+    }
+}
diff --git a/verity/VerityVerifier.mf b/verity/VerityVerifier.mf
new file mode 100644
index 0000000..6118b31
--- /dev/null
+++ b/verity/VerityVerifier.mf
@@ -0,0 +1 @@
+Main-Class: com.android.verity.VerityVerifier
diff --git a/verity/build_verity_tree.cpp b/verity/build_verity_tree.cpp
index 1281c6a..e7bfa40 100644
--- a/verity/build_verity_tree.cpp
+++ b/verity/build_verity_tree.cpp
@@ -9,6 +9,7 @@
 #include <getopt.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <limits.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -114,6 +115,7 @@
            "  -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"
         );
 }
@@ -126,7 +128,8 @@
     size_t salt_size = 0;
     bool sparse = false;
     size_t block_size = 4096;
-    size_t calculate_size = 0;
+    uint64_t calculate_size = 0;
+    bool verbose = false;
 
     while (1) {
         const static struct option long_options[] = {
@@ -135,8 +138,10 @@
             {"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:", long_options, NULL);
+        int c = getopt_long(argc, argv, "a:A:hSs:v", long_options, NULL);
         if (c < 0) {
             break;
         }
@@ -171,8 +176,22 @@
         case 'S':
             sparse = true;
             break;
-        case 's':
-            calculate_size = strtoul(optarg, NULL, 0);
+        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();
@@ -226,7 +245,7 @@
             verity_blocks += level_blocks;
         } while (level_blocks > 1);
 
-        printf("%zu\n", verity_blocks * block_size);
+        printf("%" PRIu64 "\n", (uint64_t)verity_blocks * block_size);
         return 0;
     }
 
@@ -247,7 +266,7 @@
     if (sparse) {
         file = sparse_file_import(fd, false, false);
     } else {
-        file = sparse_file_import_auto(fd, false);
+        file = sparse_file_import_auto(fd, false, verbose);
     }
 
     if (!file) {
diff --git a/verity/generate_verity_key.c b/verity/generate_verity_key.c
index 7414af5..a55600c 100644
--- a/verity/generate_verity_key.c
+++ b/verity/generate_verity_key.c
@@ -108,6 +108,66 @@
     return ret;
 }
 
+static int convert_x509(const char *pem_file, const char *key_file)
+{
+    int ret = -1;
+    FILE *f = NULL;
+    EVP_PKEY *pkey = NULL;
+    RSA *rsa = NULL;
+    X509 *cert = NULL;
+
+    if (!pem_file || !key_file) {
+        goto out;
+    }
+
+    f = fopen(pem_file, "r");
+    if (!f) {
+        printf("Failed to open '%s'\n", pem_file);
+        goto out;
+    }
+
+    cert = PEM_read_X509(f, &cert, NULL, NULL);
+    if (!cert) {
+        printf("Failed to read PEM certificate from file '%s'\n", pem_file);
+        goto out;
+    }
+
+    pkey = X509_get_pubkey(cert);
+    if (!pkey) {
+        printf("Failed to extract public key from certificate '%s'\n", pem_file);
+        goto out;
+    }
+
+    rsa = EVP_PKEY_get1_RSA(pkey);
+    if (!rsa) {
+        printf("Failed to get the RSA public key from '%s'\n", pem_file);
+        goto out;
+    }
+
+    if (write_public_keyfile(rsa, key_file) < 0) {
+        printf("Failed to write public key\n");
+        goto out;
+    }
+
+    ret = 0;
+
+out:
+    if (f) {
+        fclose(f);
+    }
+    if (cert) {
+        X509_free(cert);
+    }
+    if (pkey) {
+        EVP_PKEY_free(pkey);
+    }
+    if (rsa) {
+        RSA_free(rsa);
+    }
+
+    return ret;
+}
+
 static int generate_key(const char *file)
 {
     int ret = -1;
@@ -153,13 +213,16 @@
 }
 
 static void usage(){
-    printf("Usage: generate_verity_key <path-to-key>");
+    printf("Usage: generate_verity_key <path-to-key> | -convert <path-to-x509-pem> <path-to-key>\n");
 }
 
 int main(int argc, char *argv[]) {
-    if (argc != 2) {
+    if (argc == 2) {
+        return generate_key(argv[1]);
+    } else if (argc == 4 && !strcmp(argv[1], "-convert")) {
+        return convert_x509(argv[2], argv[3]);
+    } else {
         usage();
         exit(-1);
     }
-    return generate_key(argv[1]);
-}
\ No newline at end of file
+}
diff --git a/verity/keystore_signer b/verity/keystore_signer
old mode 100644
new mode 100755
index 7619c54..445f0c9
--- a/verity/keystore_signer
+++ b/verity/keystore_signer
@@ -5,4 +5,4 @@
 KEYSTORESIGNER_HOME=`dirname "$0"`
 KEYSTORESIGNER_HOME=`dirname "$KEYSTORESIGNER_HOME"`
 
-java -Xmx512M -jar "$KEYSTORESIGNER_HOME"/framework/KeystoreSigner.jar "$@"
\ No newline at end of file
+java -Xmx512M -jar "$KEYSTORESIGNER_HOME"/framework/BootKeystoreSigner.jar "$@"
diff --git a/verity/verify_boot_signature.c b/verity/verify_boot_signature.c
new file mode 100644
index 0000000..55591aa
--- /dev/null
+++ b/verity/verify_boot_signature.c
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _LARGEFILE64_SOURCE
+
+#include <endian.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+
+#include "bootimg.h"
+
+#define FORMAT_VERSION 1
+#define BUFFER_SIZE (1024 * 1024)
+
+typedef struct {
+    ASN1_STRING *target;
+    ASN1_INTEGER *length;
+} AuthAttrs;
+
+ASN1_SEQUENCE(AuthAttrs) = {
+    ASN1_SIMPLE(AuthAttrs, target, ASN1_PRINTABLE),
+    ASN1_SIMPLE(AuthAttrs, length, ASN1_INTEGER)
+} ASN1_SEQUENCE_END(AuthAttrs)
+
+IMPLEMENT_ASN1_FUNCTIONS(AuthAttrs)
+
+typedef struct {
+    ASN1_INTEGER *formatVersion;
+    X509 *certificate;
+    X509_ALGOR *algorithmIdentifier;
+    AuthAttrs *authenticatedAttributes;
+    ASN1_OCTET_STRING *signature;
+} BootSignature;
+
+ASN1_SEQUENCE(BootSignature) = {
+    ASN1_SIMPLE(BootSignature, formatVersion, ASN1_INTEGER),
+    ASN1_SIMPLE(BootSignature, certificate, X509),
+    ASN1_SIMPLE(BootSignature, algorithmIdentifier, X509_ALGOR),
+    ASN1_SIMPLE(BootSignature, authenticatedAttributes, AuthAttrs),
+    ASN1_SIMPLE(BootSignature, signature, ASN1_OCTET_STRING)
+} ASN1_SEQUENCE_END(BootSignature)
+
+IMPLEMENT_ASN1_FUNCTIONS(BootSignature)
+
+static BIO *g_error = NULL;
+
+#if defined(OPENSSL_IS_BORINGSSL)
+/* In BoringSSL, ERR_print_errors has been moved to the BIO functions in order
+ * to avoid the incorrect dependency of ERR on BIO. */
+static void ERR_print_errors(BIO *bio) {
+    BIO_print_errors(bio);
+}
+#endif
+
+/**
+ * Rounds n up to the nearest multiple of page_size
+ * @param n The value to round
+ * @param page_size Page size
+ */
+static uint64_t page_align(uint64_t n, uint64_t page_size)
+{
+    return (((n + page_size - 1) / page_size) * page_size);
+}
+
+/**
+ * Calculates the offset to the beginning of the BootSignature block
+ * based on the boot image header. The signature will start after the
+ * the boot image contents.
+ * @param fd File descriptor to the boot image
+ * @param offset Receives the offset in bytes
+ */
+static int get_signature_offset(int fd, off64_t *offset)
+{
+    int i;
+    struct boot_img_hdr hdr;
+
+    if (!offset) {
+        return -1;
+    }
+
+    if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+        return -1;
+    }
+
+    if (memcmp(BOOT_MAGIC, hdr.magic, BOOT_MAGIC_SIZE) != 0) {
+        printf("Invalid boot image: missing magic\n");
+        return -1;
+    }
+
+    if (!hdr.page_size) {
+        printf("Invalid boot image: page size must be non-zero\n");
+        return -1;
+    }
+
+    *offset = page_align(hdr.page_size
+                    + page_align(hdr.kernel_size,  hdr.page_size)
+                    + page_align(hdr.ramdisk_size, hdr.page_size)
+                    + page_align(hdr.second_size,  hdr.page_size),
+                hdr.page_size);
+
+    return 0;
+}
+
+/**
+ * Reads and parses the ASN.1 BootSignature block from the given offset
+ * @param fd File descriptor to the boot image
+ * @param offset Offset from the beginning of file to the signature
+ * @param bs Pointer to receive the BootImage structure
+ */
+static int read_signature(int fd, off64_t offset, BootSignature **bs)
+{
+    BIO *in = NULL;
+
+    if (!bs) {
+        return -1;
+    }
+
+    if (lseek64(fd, offset, SEEK_SET) == -1) {
+        return -1;
+    }
+
+    if ((in = BIO_new_fd(fd, BIO_NOCLOSE)) == NULL) {
+        ERR_print_errors(g_error);
+        return -1;
+    }
+
+    if ((*bs = ASN1_item_d2i_bio(ASN1_ITEM_rptr(BootSignature), in, bs)) == NULL) {
+        ERR_print_errors(g_error);
+        BIO_free(in);
+        return -1;
+    }
+
+    BIO_free(in);
+    return 0;
+}
+
+/**
+ * Validates the format of the boot signature block, and checks that
+ * the length in authenticated attributes matches the actual length of
+ * the image.
+ * @param bs The boot signature block to validate
+ * @param length The actual length of the boot image without the signature
+ */
+static int validate_signature_block(const BootSignature *bs, uint64_t length)
+{
+    BIGNUM expected;
+    BIGNUM value;
+    int rc = -1;
+
+    if (!bs) {
+        return -1;
+    }
+
+    BN_init(&expected);
+    BN_init(&value);
+
+    /* Confirm that formatVersion matches our supported version */
+    if (!BN_set_word(&expected, FORMAT_VERSION)) {
+        ERR_print_errors(g_error);
+        goto vsb_done;
+    }
+
+    ASN1_INTEGER_to_BN(bs->formatVersion, &value);
+
+    if (BN_cmp(&expected, &value) != 0) {
+        printf("Unsupported signature version\n");
+        goto vsb_done;
+    }
+
+    BN_clear(&expected);
+    BN_clear(&value);
+
+    /* Confirm that the length of the image matches with the length in
+        the authenticated attributes */
+    length = htobe64(length);
+    BN_bin2bn((const unsigned char *) &length, sizeof(length), &expected);
+
+    ASN1_INTEGER_to_BN(bs->authenticatedAttributes->length, &value);
+
+    if (BN_cmp(&expected, &value) != 0) {
+        printf("Image length doesn't match signature attributes\n");
+        goto vsb_done;
+    }
+
+    rc = 0;
+
+vsb_done:
+    BN_free(&expected);
+    BN_free(&value);
+
+    return rc;
+}
+
+/**
+ * Creates a SHA-256 hash from the boot image contents and the encoded
+ * authenticated attributes.
+ * @param fd File descriptor to the boot image
+ * @param length Length of the boot image without the signature block
+ * @param aa Pointer to AuthAttrs
+ * @param digest Pointer to a buffer where the hash is written
+ */
+static int hash_image(int fd, uint64_t length, const AuthAttrs *aa,
+        unsigned char *digest)
+{
+    EVP_MD_CTX *ctx = NULL;
+    int rc = -1;
+
+    ssize_t bytes = 0;
+    unsigned char *attrs = NULL;
+    unsigned char *buffer = NULL;
+    unsigned char *p = NULL;
+    uint64_t total = 0;
+
+    if (!aa || !digest) {
+        goto hi_done;
+    }
+
+    if ((buffer = malloc(BUFFER_SIZE)) == NULL) {
+        goto hi_done;
+    }
+
+    if (lseek64(fd, 0, SEEK_SET) != 0) {
+        goto hi_done;
+    }
+
+    if ((ctx = EVP_MD_CTX_create()) == NULL) {
+        ERR_print_errors(g_error);
+        goto hi_done;
+    }
+
+    EVP_DigestInit(ctx, EVP_sha256());
+
+    do {
+        bytes = BUFFER_SIZE;
+
+        if ((length - total) < BUFFER_SIZE) {
+            bytes = length - total;
+        }
+
+        if ((bytes = read(fd, buffer, bytes)) == -1) {
+            printf("%s\n", strerror(errno));
+            goto hi_done;
+        }
+
+        EVP_DigestUpdate(ctx, buffer, bytes);
+        total += bytes;
+    } while (total < length);
+
+    if ((bytes = i2d_AuthAttrs((AuthAttrs *) aa, NULL)) < 0) {
+        ERR_print_errors(g_error);
+        goto hi_done;
+    }
+
+    if ((attrs = OPENSSL_malloc(bytes)) == NULL) {
+        ERR_print_errors(g_error);
+        goto hi_done;
+    }
+
+    p = attrs;
+
+    if (i2d_AuthAttrs((AuthAttrs *) aa, &p) < 0) {
+        ERR_print_errors(g_error);
+        goto hi_done;
+    }
+
+    EVP_DigestUpdate(ctx, attrs, bytes);
+    EVP_DigestFinal(ctx, digest, NULL);
+
+    rc = 0;
+
+hi_done:
+    if (buffer) {
+        free(buffer);
+    }
+
+    if (ctx) {
+        EVP_MD_CTX_destroy(ctx);
+    }
+
+    if (attrs) {
+        OPENSSL_free(attrs);
+    }
+
+    return rc;
+}
+
+/**
+ * Verifies the RSA signature
+ * @param fd File descriptor to the boot image
+ * @param length Length of the boot image without the signature block
+ * @param bs The boot signature block
+ */
+static int verify_signature(int fd, uint64_t length, const BootSignature *bs)
+{
+    int rc = -1;
+    EVP_PKEY *pkey = NULL;
+    RSA *rsa = NULL;
+    unsigned char digest[SHA256_DIGEST_LENGTH];
+
+    if (!bs) {
+        goto vs_done;
+    }
+
+    if (hash_image(fd, length, bs->authenticatedAttributes, digest) == -1) {
+        goto vs_done;
+    }
+
+    if ((pkey = X509_get_pubkey(bs->certificate)) == NULL) {
+        ERR_print_errors(g_error);
+        goto vs_done;
+    }
+
+    if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) {
+        ERR_print_errors(g_error);
+        goto vs_done;
+    }
+
+    if (!RSA_verify(NID_sha256, digest, SHA256_DIGEST_LENGTH,
+                bs->signature->data, bs->signature->length, rsa)) {
+        ERR_print_errors(g_error);
+        goto vs_done;
+    }
+
+    rc = 0;
+
+vs_done:
+    if (pkey) {
+        EVP_PKEY_free(pkey);
+    }
+
+    if (rsa) {
+        RSA_free(rsa);
+    }
+
+    return rc;
+}
+
+/**
+ * Given the file name of a signed boot image, verifies the signature
+ * @param image_file Name of the boot image file
+ */
+static int verify(const char *image_file)
+{
+    BootSignature *bs = NULL;
+    int fd = -1;
+    int rc = 1;
+    off64_t offset = 0;
+
+    if (!image_file) {
+        return rc;
+    }
+
+    if ((fd = open(image_file, O_RDONLY | O_LARGEFILE)) == -1) {
+        return rc;
+    }
+
+    if (get_signature_offset(fd, &offset) == -1) {
+        goto out;
+    }
+
+    if (read_signature(fd, offset, &bs) == -1) {
+        goto out;
+    }
+
+    if (validate_signature_block(bs, offset) == -1) {
+        goto out;
+    }
+
+    if (verify_signature(fd, offset, bs) == -1) {
+        goto out;
+    }
+
+    printf("Signature is VALID\n");
+    rc = 0;
+
+out:
+    if (bs) {
+        BootSignature_free(bs);
+    }
+
+    if (fd != -1) {
+        close(fd);
+    }
+
+    return rc;
+}
+
+static void usage()
+{
+    printf("Usage: verify_boot_signature <path-to-boot-image>\n");
+}
+
+int main(int argc, char *argv[])
+{
+    if (argc != 2) {
+        usage();
+        return 1;
+    }
+
+    /* BIO descriptor for logging OpenSSL errors to stderr */
+    if ((g_error = BIO_new_fd(STDERR_FILENO, BIO_NOCLOSE)) == NULL) {
+        printf("Failed to allocate a BIO handle for error output\n");
+        return 1;
+    }
+
+    ERR_load_crypto_strings();
+
+    return verify(argv[1]);
+}
diff --git a/verity/verity_verifier b/verity/verity_verifier
new file mode 100755
index 0000000..f145228
--- /dev/null
+++ b/verity/verity_verifier
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Start-up script for VerityVerifier
+
+VERITYVERIFIER_HOME=`dirname "$0"`
+VERITYVERIFIER_HOME=`dirname "$VERITYVERIFIER_HOME"`
+
+java -Xmx512M -jar "$VERITYVERIFIER_HOME"/framework/VerityVerifier.jar "$@"