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 "$@"